
# cubes.py
# Andrew Davison, ad@coe.psu.ac.th, April 2025
'''
  Recursive cubes drawing. Draw a large cube centered
  at the origin and then smaller cubes spread out along
  the three axes in =/- directions. Repeat this for each
  new cube several times (2, 3, or 4 is recommended)
'''

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from HC3Mat import *


AXIS_SIZE = 15
SIZE = 5  # of cube


# unit cube vertices
verts = [
  [0, 0, 0],  # 0
  [0, 0, 1],  # 1
  [0, 1, 0],  # 2
  [0, 1, 1],  # 3
  [1, 0, 0],  # 4
  [1, 0, 1],  # 5
  [1, 1, 0],  # 6
  [1, 1, 1]   # 7
]

# cube faces using vertex indicies
faceIndices = [
  [0, 1, 3, 2],  # Left
  [4, 5, 7, 6],  # Right  (green)
  [0, 1, 5, 4],  # Front  (blue)
  [2, 3, 7, 6],  # Back
  [0, 2, 6, 4],  # Bottom
  [1, 3, 7, 5],  # Top  (magenta)
]

faceColors = ['red', 'green', 'blue', 'yellow', 
              'cyan', 'magenta']

dirs = [(1,0,0), (-1,0,0), (0,1,0), (0,-1,0),
        (0,0,1), (0,0,-1)]  # offsets from current pos;
                            # along the axes


def drawCubes(ax, verts, depth):
  # draw a cube and then recurse in every direction
  if depth > 0:
    drawCube(ax, verts)
    for dir in dirs:
      drawCubes(ax, transformCube(verts, dir), depth-1)


def drawCube(ax, verts):
  # draw a cube using verts and color its faces;
  # edge it in black and make slightly transparent
  faces = [[verts[i] for i in face] 
                for face in faceIndices]
  cube = Poly3DCollection(faces, facecolors=faceColors, 
              linewidths=1, edgecolors='black', alpha=0.7)
  ax.add_collection3d(cube)



def transformCube(verts, dir):
  '''
    Using the cube's center as origin, scale by 0.75 and
    translate by the distance step*dir. step is 1.4 times the
    side length of the cube before scaling
  '''
  center = calcCenter(verts)
  side = verts[4][0] - verts[0][0]   # x-axis length
  step = side*1.4
  m = multMatsList([
         transMat(center[0], center[1], center[2]),
         transMat(step*dir[0], step*dir[1], step*dir[2]), 
               # translate by step*dir
         scaleMat(0.75, 0.75, 0.75),
         transMat(-center[0], -center[1], -center[2])
      ])
  return [applyMat(m, vert) for vert in verts]


def calcCenter(verts):
  # center coordinate of cube
  xc = (verts[0][0] + verts[4][0])/2
  yc = (verts[0][1] + verts[2][1])/2
  zc = (verts[0][2] + verts[1][2])/2
  return (xc, yc, zc)


# -------------------------------------------

fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(111, projection='3d')

# axis labels and limits
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim([-AXIS_SIZE, AXIS_SIZE])
ax.set_ylim([-AXIS_SIZE, AXIS_SIZE])
ax.set_zlim([-AXIS_SIZE, AXIS_SIZE])
ax.set_box_aspect([1,1,1])

# center and resize the initial cube
side = verts[1][2] - verts[0][2]   # x-axis length
m = multMatsList([
             scaleMat(SIZE,SIZE,SIZE),
             transMat(-side/2, -side/2, -side/2)
        ])
verts = [applyMat(m, v) for v in verts]

drawCubes(ax, verts, 3)  # use 2, 3 or 4

plt.show()
