const Faces = preload("./Faces.gd") const VoxData = preload("./VoxFormat/VoxData.gd") const vox_to_godot = Basis(Vector3.RIGHT, Vector3.FORWARD, Vector3.UP) # Names for the faces by orientation enum FaceOrientation { Top = 0, Bottom = 1, Left = 2, Right = 3, Front = 4, Back = 5, } # An Array(FaceOrientation) of all possible face orientations const face_orientations :Array = [ FaceOrientation.Top, FaceOrientation.Bottom, FaceOrientation.Left, FaceOrientation.Right, FaceOrientation.Front, FaceOrientation.Back ] # An Array(int) of the depth axis by orientation const depth_axis :Array = [ Vector3.AXIS_Z, Vector3.AXIS_Z, Vector3.AXIS_X, Vector3.AXIS_X, Vector3.AXIS_Y, Vector3.AXIS_Y, ] # An Array(int) of the width axis by orientation const width_axis :Array = [ Vector3.AXIS_Y, Vector3.AXIS_Y, Vector3.AXIS_Z, Vector3.AXIS_Z, Vector3.AXIS_X, Vector3.AXIS_X, ] # An Array(int) of height axis by orientation const height_axis :Array = [ Vector3.AXIS_X, Vector3.AXIS_X, Vector3.AXIS_Y, Vector3.AXIS_Y, Vector3.AXIS_Z, Vector3.AXIS_Z, ] # An Array(Vector3) describing what vectors to use to check for face occlusion # by orientation const face_checks :Array = [ Vector3(0, 0, 1), Vector3(0, 0, -1), Vector3(-1, 0, 0), Vector3(1, 0, 0), Vector3(0, -1, 0), Vector3(0, 1, 0), ] # An array of the face meshes by orientation const face_meshes :Array = [ Faces.Front, Faces.Back, Faces.Left, Faces.Right, Faces.Bottom, Faces.Top, ] # An Array(Vector3) describing what normals to use by orientation const normals :Array = [ Vector3(0, 1, 0), Vector3(0, -1, 0), Vector3(-1, 0, 0), Vector3(1, 0, 0), Vector3(0, 0, 1), Vector3(0, 0, -1), ] # The SurfaceTool the object will use to generate the mesh var st :SurfaceTool = SurfaceTool.new() # A Dictonary[Vector3]int of the voxel data for the visible faces of the # current slice var faces :Dictionary # Minimum extends of the volume var mins :Vector3 = Vector3(1000000, 1000000, 1000000) # Maximum extends of the volume var maxs :Vector3 = Vector3(-1000000,-1000000,-1000000) # Generate a mesh for the given voxel_data with single-pass greedy face merging # Primary RefCounted: https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/ # Secondary RefCounted: https://www.gedge.ca/dev/2014/08/17/greedy-voxel-meshing # voxel_data is a dict[Vector3]int func generate(vox :VoxData, voxel_data :Dictionary, scale :float, snaptoground : bool): # Remeber, MagicaVoxel thinks Y is the depth axis. We convert to the correct # coordinate space when we generate the faces. st.begin(Mesh.PRIMITIVE_TRIANGLES) # Short-circut empty models if voxel_data.size() == 0: return st.commit() # Convert voxel data to raw color values for v in voxel_data: voxel_data[v] = vox.colors[voxel_data[v]] # Find bounds for v in voxel_data: mins.x = min(mins.x, v.x) mins.y = min(mins.y, v.y) mins.z = min(mins.z, v.z) maxs.x = max(maxs.x, v.x) maxs.y = max(maxs.y, v.y) maxs.z = max(maxs.z, v.z) # Itterate over all face orientations to reduce problem to 3 dimensions for o in face_orientations: generate_geometry_for_orientation(voxel_data, o, scale, snaptoground) # Finish the mesh and material and return var material = StandardMaterial3D.new() material.vertex_color_is_srgb = true material.vertex_color_use_as_albedo = true material.roughness = 1 st.set_material(material) return st.commit() # Generates all of the geometry for a given face orientation func generate_geometry_for_orientation(voxel_data :Dictionary, o :int, scale :float, snaptoground :bool) -> void: # Sweep through the volume along the depth reducing the problem to 2 dimensional var da :int = depth_axis[o] for slice in range(mins[da], maxs[da]+1): var faces :Dictionary = query_slice_faces(voxel_data, o, slice) if faces.size() > 0: generate_geometry(faces, o, slice, scale, snaptoground) # Returns the voxels in the set voxel_data with a visible face along the slice # for the given orientation func query_slice_faces(voxel_data :Dictionary, o :int, slice :float) -> Dictionary: var ret :Dictionary = Dictionary() var da = depth_axis[o] for v in voxel_data: if v[da] == slice and voxel_data.has(v + face_checks[o]) == false: ret[v] = voxel_data[v] return ret # Generates geometry for the given orientation for the set of faces func generate_geometry(faces :Dictionary, o :int, slice :float, scale :float, snaptoground :bool) -> void: var da :int = depth_axis[o] var wa :int = width_axis[o] var ha :int = height_axis[o] var v :Vector3 = Vector3() v[da] = slice # Itterate the rows of the sparse volume v[ha] = mins[ha] while v[ha] <= maxs[ha]: # Itterate over the voxels of the row v[wa] = mins[wa] while v[wa] <= maxs[wa]: if faces.has(v): generate_geometry_for_face(faces, v, o, scale, snaptoground) v[wa] += 1.0 v[ha] += 1.0 # Generates the geometry for the given face and orientation and scale and returns # the set of remaining faces func generate_geometry_for_face(faces :Dictionary, face :Vector3, o :int, scale :float, snaptoground :bool) -> Dictionary: var da :int = depth_axis[o] var wa :int = width_axis[o] var ha :int = height_axis[o] # Greedy face merging var width :int = width_query(faces, face, o) var height :int = height_query(faces, face, o, width) var grow :Vector3 = Vector3(1, 1, 1) grow[wa] *= width grow[ha] *= height # Generate geometry var yoffset = Vector3(0,0,0); if snaptoground : yoffset = Vector3(0, -mins.z * scale, 0); st.set_color(faces[face]) st.set_normal(normals[o]) for vert in face_meshes[o]: st.add_vertex(yoffset + vox_to_godot * ((vert * grow) + face) * scale) # Remove these faces from the pool var v :Vector3 = Vector3() v[da] = face[da] for iy in range(height): v[ha] = face[ha] + float(iy) for ix in range(width): v[wa] = face[wa] + float(ix) faces.erase(v) return faces # Returns the number of voxels wide the run starting at face is with respect to # the set of faces and orientation func width_query(faces :Dictionary, face :Vector3, o :int) -> int: var wd :int = width_axis[o] var v :Vector3 = face while faces.has(v) and faces[v] == faces[face]: v[wd] += 1.0 return int(v[wd] - face[wd]) # Returns the number of voxels high the run starting at face is with respect to # the set of faces and orientation, with the given width func height_query(faces :Dictionary, face :Vector3, o :int, width :int) -> int: var hd :int = height_axis[o] var c :Color = faces[face] var v :Vector3 = face v[hd] += 1.0 while faces.has(v) and faces[v] == c and width_query(faces, v, o) >= width: v[hd] += 1.0 return int(v[hd] - face[hd])