Toggle Navigation
TiDAL Hatchery
Eggs
TiDAL 3D
object.py
Users
Badges
Login
Register
object.py
raw
Content
from array import array from tidal3d import * class Mesh: def __init__(self, filename): # A face is made of 3 vertices, a normal vector, and a material self.vertices = [] self.normals = [] self.colours = [] # To prevent duplication of data (and therefore saving on expensive memory and calculation # time) we store each unique vertex, normal and material once and instead keep per-face # indices into the above lists self.vert_indices = [] self.norm_indices = [] self.col_indices = [] # A face-oriented view into the above index data for convenience # It is a list of (vert_index, norm_index, col_index) tuples self.faces = [] # Pre-allocated space for face index/depth pairs for depth-sorting faces self.depth_map = None # Pre-allocated space for transformed vertices and normals self.vertices_trans = None self.normals_trans = None # Load mesh and material data self._load(filename) # Position and linear velocity self.position = array('f', [0, 0, 0]) self.velocity = array('f', [0, 0, 0]) self.delta_v = array('f', [0, 0, 0]) # Orientation and angular velocity self.orientation = array('f', [1, 0, 0, 0]) self.angular = array('f', [0, 0, 0]) self.axis = array('f', [0, 0, 0]) def rotate_y(self, val): self.angular[1] = val def rotate_x(self, val): self.angular[0] = val def _load(self, filename): # Parse the geometry file op = ObjectParser() op.parse("apps/tidal_3d/" + filename) self.vertices = op.vertices self.vert_indices = [f['indices'] for f in op.faces] # Pre-calculate face normal vectors, a normal is the direction exactly perpendicular to # the plane of the face, the direction the front of the face is pointing a = array('f', [0, 0, 0]) b = array('f', [0, 0, 0]) for face in self.vert_indices: v_subtract(self.vertices[face[0]], self.vertices[face[1]], a) v_subtract(self.vertices[face[1]], self.vertices[face[2]], b) normal = array('f', [0, 0, 0]) v_cross(a, b, normal) v_normalise(normal) # TODO normal deduplication -- "item in list" is not implemented in micropython for lists of arrays self.normals.append(normal) self.norm_indices.append(len(self.normals) - 1) # If the geometry has materials, let's also parse the accompanying material library file if op.mat_lib: mp = MaterialParser() mp.parse("apps/tidal_3d/" + op.mat_lib) # Use the material's diffuse colour for the colour of the faces self.col_indices = [0] * len(self.vert_indices) for material in mp.materials: self.colours.append(array('f', material['diffuse'])) for i in range(len(op.faces)): if op.faces[i]['material'] == material['name']: self.col_indices[i] = len(self.colours) - 1 else: # Just default to all white faces if no materials specified self.colours.append(array('f', [255, 255, 255])) self.col_indices = [0] * len(self.vert_indices) # Create a face-oriented view of index data for i in range(len(self.vert_indices)): self.faces.append([self.vert_indices[i], self.norm_indices[i], self.col_indices[i]]) # Pre-allocate some working space for face index/depth pairs for depth-sorting faces self.depth_map = array('f', [0] * (len(self.faces) * 2)) # Pre-allocate some working space for transforming vertices and normals self.vertices_trans = [None] * len(self.vertices) for i in range(len(self.vertices)): self.vertices_trans[i] = array('f', [0, 0, 0]) self.normals_trans = [None] * len(self.normals) for i in range(len(self.normals)): self.normals_trans[i] = array('f', [0, 0, 0]) def update(self, delta_t): # Move our position by our velocity v_scale(self.velocity, delta_t, self.delta_v) v_add(self.position, self.delta_v) # Rotate ourselves around the axis degrees = v_magnitude(self.angular) v_normalise(self.angular, self.axis) q_rotate(self.orientation, degrees * delta_t, self.axis) class ParserInterface: """ General interface for Wavefront geometry style file parsers, each non-comment line is tokenised and then passed into the parameter method for decoding; sub-classes implement the parameter method according to the specific file type they want to parse; the finish method will be called when the end of the file is reached """ def parameter(self, name, values): pass def finish(self): pass def parse(self, file): with open(file) as f: while line := f.readline(): # Ignore comments and empty lines line = line.strip() if not line or line.startswith("#"): continue tokens = line.split() # Decode the parameter self.parameter(tokens[0], tokens[1:]) # Do any tidy up the parser needs to do self.finish() class ObjectParser(ParserInterface): """ A parser for Wavefront object geometry files (*.obj) """ def __init__(self): self.mat_lib = None self.current_mat = None self.vertices = [] self.faces = [] def parameter(self, name, values): # This gives the file containing the material library if name == 'mtllib': self.mat_lib = values[0] # Set the active material for the following faces if name == 'usemtl': self.current_mat = values[0] # Extract a vertex if name == 'v': self.vertices.append(array('f', [float(v) for v in values])) # Extract a face if name == 'f': # Faces are given as "vert_index/uv_index/normal_index" triplets but we don't support # texturing, so ignore the uv part, and we don't support anything other than flat shading, # so ignore the vertex normals part -- since we know face vertices have anti-clockwise # winding, we can just calculate face normals ourselves vert_indices = [int(a.split('/')[0]) - 1 for a in values] face = {'material':self.current_mat, 'indices':vert_indices} self.faces.append(face) class MaterialParser(ParserInterface): """ A parser for Wavefront material library files (*.mtl) """ def __init__(self): self.materials = [] self.current = None def parameter(self, name, values): # A new material is being defined if name == 'newmtl': if self.current: self.materials.append(self.current) self.current = {'name' : values[0]} # Extract the diffuse colour (base colour in Blender) if name == 'Kd': # RGB values are given as floating point values between 0 and 1, so convert them here # to byte values between 0 and 255 self.current['diffuse'] = [int(255 if float(f) >= 1 else float(f) * 256) for f in values] def finish(self): self.materials.append(self.current) self.current = None