# The contents of this file are subject to the Common Public Attribution License # Version 1.0 (the “License”); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # https://myou.dev/licenses/LICENSE-CPAL. The License is based on the Mozilla # Public License Version 1.1 but Sections 14 and 15 have been added to cover use # of software over a computer network and provide for limited attribution for # the Original Developer. In addition, Exhibit A has been modified to be # consistent with Exhibit B. # # Software distributed under the License is distributed on an “AS IS” basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for # the specific language governing rights and limitations under the License. # # The Original Code is Myou Engine. # # the Original Developer is the Initial Developer. # # The Initial Developer of the Original Code is the Myou Engine developers. # All portions of the code written by the Myou Engine developers are Copyright # (c) 2024. All Rights Reserved. # # Alternatively, the contents of this file may be used under the terms of the # GNU Affero General Public License version 3 (the [AGPL-3] License), in which # case the provisions of [AGPL-3] License are applicable instead of those above. # # If you wish to allow use of your version of this file only under the terms of # the [AGPL-3] License and not to allow others to use your version of this file # under the CPAL, indicate your decision by deleting the provisions above and # replace them with the notice and other provisions required by the [AGPL-3] # License. If you do not delete the provisions above, a recipient may use your # version of this file under either the CPAL or the [AGPL-3] License. ## # TODO: Split mesh object and mesh data import ../types import vmath except Quat import arr_ref when defined(nimdoc): type TYPES* = Mesh | MeshData | MeshDrawMethod | SortSign # Forward declarations and ob type methods proc load_from_va_ia*(self: Mesh, varrays: seq[ArrRef[float32]], iarrays: seq[ArrRef[uint16]] | seq[ArrRef[int32]] = newSeq[ArrRef[uint16]](), layout: AttributeList = @[], index_types: seq[DataType] = @[]) proc generate_tangents*(self: MeshData, uv_layer_name, tangent_layer_name: string) proc ensure_capacity*(self: Mesh, extra_elements: int) proc write_vaos*(self: MeshData) # End forward declarations and ob type methods import elvis import ../quat import std/algorithm # import std/sequtils import std/math import std/tables import std/strformat import std/strutils import ./gameobject import ../scene import ../util import ../attributes export arr_ref export tables # TODO: move MeshData elsewhere import ../platform/gl method is_mesh*(self: GameObject): bool {.base.} = return false method get_mesh*(self: GameObject): Mesh {.base.} = return nil method is_mesh*(self: Mesh): bool = return true method get_mesh*(self: Mesh): Mesh = return self proc newMeshData(engine: MyouEngine): MeshData = result = new MeshData result.draw_method = Triangles result.engine = engine proc remove*(self: MeshData, ob: Mesh, delete_buffers: bool = true) = var idx = self.users.find(ob) while idx != -1: self.users.delete(idx) idx = self.users.find(ob) if self.users.len == 0: # if delete_buffers: # glBindBuffer(GL_ARRAY_BUFFER, 0) # for buf in self.vertex_buffers: # glDeleteBuffers(1, addr buf) # for buf in self.index_buffers: # glDeleteBuffers(1, addr buf) # glBindVertexArray(0) # for vao in self.vaos: # glDeleteVertexArrays(1, addr vao) self.engine.mesh_datas.del(self.hash) ob.data = nil proc write_vaos*(self: MeshData) = for i,vao in self.vaos: if vao == 0: continue glBindVertexArray(vao) for vb_attrs in self.vao_specs[i].vbs.mitems: glBindBuffer(GL_ARRAY_BUFFER, vb_attrs.vb) for a in vb_attrs.attribs: glEnableVertexAttribArray(a.location.GLuint) glVertexAttribPointer(a.location.GLuint, a.count.GLint, a.dtype.GLenum, false, self.stride.GLsizei, cast[pointer](cast[int](a.offset))) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.vao_specs[i].ib) when defined(myouUseRenderdoc): let name = &"{self.name} {i}" glObjectLabel(GL_VERTEX_ARRAY, vao, GLsizei(name.len), name.cstring) glBindVertexArray(0) proc gpu_buffers_upload*(self: MeshData) = var num_submeshes = self.varrays.len self.vertex_starts.setLen(0) let had_indices = self.num_indices.len != 0 if had_indices: self.num_indices.setLen num_submeshes self.index_types.setLen num_submeshes for idx,va in self.varrays: if va.len == 0: self.num_indices.add(0) self.vertex_buffers.add(0) self.index_buffers.add(0) self.vaos.add(0) self.vao_specs.add VaoSpec() continue var vb: GLuint var tf: array[2, GLuint] glGenBuffers(1, addr vb) glBindBuffer(GL_ARRAY_BUFFER, vb) let buffer_size = va.bytelen # TODO: initialize without uploading when we know that the array is just zeroes glBufferData(GL_ARRAY_BUFFER, buffer_size.GLsizeiptr, addr va[0], GL_DYNAMIC_DRAW) glBindBuffer(GL_ARRAY_BUFFER, 0) self.vertex_buffers.add(vb) var ib: GLuint = 0 var num_indices: Natural if idx < self.iarrays.len: let ia = self.iarrays[idx] num_indices = ia.len if self.index_types[idx] == Unknown: # deduce index type from vertex buffer length # TODO: move this logic to loader? self.index_types[idx] = if num_indices >= 65536: UInt else: UShort if self.index_types[idx] == UInt: num_indices = num_indices div 2 else: assert self.index_types[idx] == UShort, "Index type must be UInt or UShort" glGenBuffers(1, addr ib) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib) let buffer_size = ia.bytelen glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer_size.GLsizeiptr, nil, GL_STATIC_DRAW) glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer_size.GLsizeiptr, addr ia[0], GL_STATIC_DRAW) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0) if not had_indices: self.num_indices.add(num_indices.int32) elif not had_indices: self.num_indices.add((buffer_size div (self.stride ?: 0)).int32) self.index_buffers.add(ib) if self.use_tf: self.tf_vbos.setLen 2 glGenBuffers(2, addr tf[0]) let tf_buffer_size = if self.draw_method == Points or num_indices == 0: (buffer_size div self.stride) * self.tf_layout.stride else: self.iarrays[idx].len * self.tf_layout.stride glBindBuffer(GL_ARRAY_BUFFER, tf[0]) glBufferData(GL_ARRAY_BUFFER, tf_buffer_size.GLsizeiptr, nil, GL_DYNAMIC_DRAW) glBindBuffer(GL_ARRAY_BUFFER, tf[1]) glBufferData(GL_ARRAY_BUFFER, tf_buffer_size.GLsizeiptr, nil, GL_DYNAMIC_DRAW) glBindBuffer(GL_ARRAY_BUFFER, 0) self.tf_vbos[idx].add tf[0] self.tf_vbos[idx].add tf[1] # If transform feedback is enabled, we create two VAOs that have two # parts: # * The regular mesh, which has the same buffers and attributes in each # * The TF buffer (with tf_layout), for storing data that will be # available next time it's rendered. This one is unique in each, to be # able to write in one while we read from the other. # IMPORTANT! This is ONLY for *loopback* use of transform feedback. # If we don't want to read the same data we write, we need a different # API. We should probably rename this one. # # TODO: Document this well enough. # # TODO: A way to distinguish between execution in the same frame or a # different one. Probably with a #define in the shader. So e.g. # particles are not calculated twice in VR with different positions. var vao_spec = VaoSpec( vbs: @[VertexBufferAttribs(vb: vb, attribs: self.layout)], ib: ib ) var vao: GLuint glGenVertexArrays(1, addr vao) self.vaos.add(vao) if self.use_tf: var vao_tf = vao_spec vao_spec.vbs.add VertexBufferAttribs(vb: tf[0], attribs: self.tf_layout) vao_tf.vbs.add VertexBufferAttribs(vb: tf[0], attribs: self.tf_layout) self.tf_vao_specs.add vao_tf self.vao_specs.add vao_spec self.write_vaos() self.loaded = true # TODO: do this but only in WebGL # This forces the mesh to be uploaded # draw_mesh(mesh, mat4(0), RenderCameraData(), ...) # set loaded to true after that proc gpu_buffers_delete*(self: MeshData) = self.vertex_buffers = @[] self.index_buffers = @[] self.loaded = false glBindVertexArray(0) for i in 0 ..< self.vaos.len: glDeleteVertexArrays(1, addr self.vaos[i]) # TODO! delete individual buffers? self.vaos = @[] if self.users.len == 0: self.engine.mesh_datas.del(self.hash) proc update_varray*(self: MeshData) = if self.vertex_buffers.len == 0: return for i,va in self.varrays: glBindBuffer(GL_ARRAY_BUFFER, self.vertex_buffers[i]) glBufferSubData(GL_ARRAY_BUFFER, 0.GLintptr, cast[GLsizeiptr](va.bytelen), addr va[0]) proc update_iarray*(self: MeshData) = if self.index_buffers.len == 0: return for i,ia in self.iarrays: glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.index_buffers[i]) glBufferData(GL_ELEMENT_ARRAY_BUFFER, cast[GLsizeiptr](ia.bytelen), addr ia[0], GL_STATIC_DRAW) proc clone*(self: MeshData): MeshData = var d = new MeshData d[] = self[] d.users = @[] return d proc initMesh*(self: Mesh, engine: MyouEngine, name: string, scene: Scene = nil, draw_method: MeshDrawMethod = Triangles, common_attributes: CommonMeshAttributes = {vertex, color}, layout: AttributeList = @[], # stride: int32 = layout.stride, skip_upload: bool = false, vertex_count: int32 = 0, vertex_array: seq[float32] = @[], index_array: seq[uint16] = @[], pass: int32 = 0, ): Mesh = discard procCall(self.GameObject.initGameObject(engine, name)) self.otype = TMesh self.passes = @[0'i32] self.sort_sign = BackToFront self.draw_method = draw_method self.layout = layout if layout.len == 0: if vertex in common_attributes: self.layout.add Attribute(name: "vertex", dtype: Float, count: 3) if color in common_attributes: self.layout.add Attribute(name: "vc_color", dtype: UByte, count: 4) if normal in common_attributes: self.layout.add Attribute(name: "normal", dtype: Byte, count: 4) if uv in common_attributes: self.layout.add Attribute(name: "uv_0", dtype: Float, count: 2) # self.stride = if stride != 0: stride else: self.layout.stride if vertex_count != 0: self.ensure_capacity(vertex_count) # var va = newArrRef(vertex_array) # if vertex_count != 0: # va.set_len max(va.len, vertex_count * self.stride div 4) # if va.len != 0: # var ia = newArrRef(index_array) # self.skip_upload = skip_upload # self.load_from_va_ia(@[va], @[ia]) # self.data.num_indices ?= @[0.int32] # if ?vertex_array: # if not ?ia: # self.data.num_indices[0] = vertex_count # assert((self.data.varray.bytelen div self.data.stride) >= vertex_count, # &"Array is {self.data.varray.bytelen} bytes long but expected at least {vertex_count * self.data.stride}") # else: # self.data.num_indices[0] = 0 if scene != nil: scene.add_object(self, name=name) return self proc newMesh*(engine: MyouEngine, name: string="mesh", scene: Scene = nil, draw_method: MeshDrawMethod = Triangles, common_attributes: CommonMeshAttributes = {vertex, color}, layout: AttributeList = @[], # stride: int32 = layout.stride, skip_upload: bool = false, vertex_count: int32 = 0, vertex_array: seq[float32] = @[], index_array: seq[uint16] = @[], pass: int32 = 0, ): Mesh = result = new Mesh result.otype = TMesh return initMesh(result, engine, name, scene, draw_method, common_attributes, layout, skip_upload, vertex_count, vertex_array, index_array, pass) proc add_vertex*(self: Mesh, x, y, z: float32, r, g, b, a: uint8): int {.discardable.} = # TODO: Check it actually has a color attribute!! var varray = self.data.varrays[0] var varray_byte = varray.to(uint8) let stride = self.data.stride let index = self.data.num_indices[0] var fpos = (stride div 4) * index let bpos = stride * index # dump (fpos, varray.len) if fpos >= varray.len: # not adding anything more return -1 varray[fpos] = x varray[fpos + 1] = y varray[fpos + 2] = z varray_byte[bpos + 12] = r varray_byte[bpos + 13] = g varray_byte[bpos + 14] = b varray_byte[bpos + 15] = a self.data.num_indices[0] += 1 return index+1 proc add_vertex*(self: Mesh, x, y, z, r, g, b, a: SomeFloat): int {.discardable.} = return self.add_vertex(x, y, z, (r*255).uint8, (g*255).uint8, (b*255).uint8, (a*255).uint8) proc add_vertex*(self: Mesh, position: Vec3, color: Vec4): int {.discardable.} = let (x,y,z) = position.to_tuple let (r,g,b,a) = color.to_tuple return self.add_vertex(x, y, z, r, g, b, a) proc clear_vertices*(self: Mesh) = self.data.num_indices[0] = 0 proc remove_vertex*(self: Mesh, index: int) = if not (index >= 0): return # -1 or undefined let num_indices = self.data.num_indices let stride = self.data.stride # move the last vertex to the one in index let pos0 = num_indices[0] * stride let pos1 = index * stride var bytes = self.data.varrays[0].to(int8) bytes.copyWithin(pos1, pos0, pos0 + stride) proc ensure_capacity*(self: Mesh, extra_elements: int) = if self.data == nil: let stride = self.layout.stride assert stride != 0 self.load_from_va_ia @[newArrRef[float32](4*stride*extra_elements)] self.data.num_indices[0] = 0 return let varray = self.data.varrays[0] let bytelen = varray.byteLen let current_index = self.data.num_indices[0] let stride = self.data.stride let index = current_index + extra_elements let fpos = (stride div 4) * index if fpos >= bytelen: var cap = bytelen * 2 while fpos >= cap: cap *= 2 var va = newArrRef[float32](cap) varray.copy_bytes_to va self.load_from_va_ia @[va] self.data.num_indices[0] = current_index # proc load_from_arraybuffer*(self: Mesh, data: unknown, buffer_offset: int = 0) = # # ASSUMING LITTLE ENDIAN # let vlen = self.offsets[self.offsets.len - 2] # let ilen = self.offsets[self.offsets.len - 1] # let offset = (self.pack_offset or 0) + buffer_offset # try: # let va = makeSeq[float32](data, offset, vlen) # let ia = makeSeq[uint16](data, offset + vlen * 4, ilen) # except Exception as e: # let e = Error(&"Mesh {self.name} is corrupt") # raise e # self.load_from_va_ia(va, ia) proc load_from_va_ia*(self: Mesh, varrays: seq[ArrRef[float32]], iarrays: seq[ArrRef[uint16]] | seq[ArrRef[int32]] = newSeq[ArrRef[uint16]](), layout: AttributeList = @[], index_types: seq[DataType] = @[]) = if self.data != nil: self.data.remove(self) # if @hash and (@data = @engine.mesh_datas[@hash])? # @data.users.push @ # @engine.main_loop?.reset_timeout() # return var data = newMeshData(self.engine) when defined(myouUseRenderdoc): data.name = self.name self.data = data self.engine.mesh_datas[self.hash] = data data.users.add(self) data.draw_method = self.draw_method data.hash = self.hash data.varrays = varrays data.iarrays.setLen 0 for ia in iarrays: data.iarrays.add ia.to(uint16) while data.iarrays.len != 0 and data.iarrays[^1].len == 0: discard data.iarrays.pop() data.index_types = index_types if layout.len != 0: self.layout = layout data.stride = self.layout.stride assert data.stride != 0, "Invalid stride" # TODO: Will there be meshes with same hash but different layout? data.layout = self.layout if self.generate_tangents: for a in self.layout: if not a.name.startsWith("tg_"): continue self.data.generate_tangents("uv_" & a.name[3 ..< ^0], a.name) # Apply modifiers let skip_upload = self.skip_upload var vertex_modifiers = self.vertex_modifiers if vertex_modifiers.len != 0: self.skip_upload = true self.vertex_modifiers = @[] # for modifier in vertex_modifiers: # if modifier.prepare_mesh != nil: # modifier.prepare_mesh(self) self.skip_upload = skip_upload self.vertex_modifiers = vertex_modifiers data = self.data if not self.skip_upload: self.engine.renderer.enqueue proc()= data.gpu_buffers_upload() proc load_from_va_ia*(self: Mesh, varray: seq[float32], iarray: seq[uint16] = @[], layout: AttributeList = @[]) = self.load_from_va_ia(@[newArrRef varray], @[newArrRef iarray], layout) proc add_modifier*(self: Mesh, modifier: VertexModifier) = self.vertex_modifiers.add(modifier) if ?modifier.prepare_mesh and ?self.data: echo &"applying modifiers of {self.name} after it was already loaded" modifier.prepare_mesh(self) # self.update_signature() proc insert_modifier*(self: Mesh, index: int, modifier: VertexModifier) = self.vertex_modifiers.insert(modifier, index) if ?modifier.prepare_mesh and ?self.data: echo &"applying modifiers of {self.name} after it was already loaded" modifier.prepare_mesh(self) # self.update_signature() proc remove_modifier*(self: Mesh, index: int) = self.vertex_modifiers.delete(index) # self.update_signature() proc remove_modifier*(self: Mesh, modifier: VertexModifier) = let index = self.vertex_modifiers.find(modifier) if index != -1: self.remove_modifier index # proc force_lod_level*(self: Mesh, level: unknown) = # if not level != nil: # for cam, data in pairs(self.last_lod): # data.render_tick = -1 # return # let mesh = self.lod_objects[level].?object ? self # for cam, data in pairs(self.last_lod): # data.mesh = mesh # data.render_tick = nil # return # proc get_lod_mesh*(self: Mesh, viewport: unknown, min_length_px: unknown, render_tick: int): Mesh = # # TODO: put min_length_px and render_tick in camera? # var amesh = self # if self.lod_objects.len != 0: # var camera = viewport.camera # camera = viewport.debug_camera ? camera # # remember previous mesh and # # avoid doing the same calculation several times # var last_lod_data = self.last_lod[camera.name] # if not last_lod_data != nil: # self.last_lod[camera.name] = last_lod_data = {mesh: nil, render_tick: -1}.toTable # let previous_mesh = last_lod_data.mesh # if last_lod_data.render_tick >= render_tick: # return previous_mesh # last_lod_data.render_tick = render_tick # let cwm = camera.world_matrix # let cam_pos = vec3(cwm.m12, cwm.m13, cwm.m14) # # Approximation to nearest point to the surface: # # We clamp the camera position to the object's bounding box # # we transform the point with the inverse matrix # # we clamp with dimensions # # we clamp with radius # # we transform back with matrix # # that's the approximate near distance # # TODO: Optimize # let inv = invert(self.world_matrix) # if not inv != nil or not self.bound_box: # if self.data != nil: # # Return highest loaded LoD # return self # for {object}.toTable in self.lod_objects: # if not object.data != nil: # continue # return object # return self # var p = transformMat4(cam_pos, inv) # p = max(p, self.bound_box[0]) # p = min(p, self.bound_box[1]) # p = transformMat4(p, self.world_matrix) # let distance_to_camera = dist(p, cam_pos) # # world scale: assuming all three axes have same scale as X # let m00 = self.world_matrix.m00 # let m01 = self.world_matrix.m01 # let m02 = self.world_matrix.m02 # let world_scale = sqrt(m00 * m00 + m01 * m01 + m02 * m02) # # number that converts a length to screen pixels # let poly_length_to_visual_size = (viewport.units_to_pixels / distance_to_camera) * world_scale # # we'll going to find the biggest length # # that is small enough on screen # var biggest_length = self.avg_poly_length # amesh = self # # from highest to lowest # for lod in reversed(self.lod_objects): # var h_ratio = 1 # if amesh == previous_mesh: # # hysteresis: ratio of distance the mesh can go # # further away before popping back to a lower LoD # h_ratio += lod.hysteresis # let ob = lod.object # let ob_apl = ob.avg_poly_length # let visual_size_px = ob_apl * poly_length_to_visual_size * h_ratio # if not amesh.data != nil or (ob_apl > biggest_length and visual_size_px < min_length_px): # biggest_length = ob_apl # amesh = ob # last_lod_data.mesh = amesh # return amesh proc clone_impl*(self: Mesh, clone: Mesh): Mesh = clone.last_lod.clear() if ?clone.data: clone.data.users.add(clone) return clone proc clone*(self: Mesh, recursive: bool=true, with_behaviours: bool=true, name: string=self.name, new_parent: GameObject=self.parent, scene: Scene=self.scene, instance_body: bool=true ): Mesh = var n = new Mesh n[] = self[] discard procCall(self.GameObject.clone_impl(n.GameObject, recursive, with_behaviours, name, new_parent, scene, instance_body)) return self.clone_impl(n) proc sort_faces*(self: Mesh, camera_position: Vec3) = # if self.data?.draw_method != GL_TRIANGLES: # return # let BIG_ENDIAN = 0 # let offsets = self.offsets # let num_submeshes = (offsets.len / 2) - 1 # let varray = self.data.varray # let iarray = self.data.iarray # var stride = self.data.stride # stride >>= 2 # let v = vec3() # let vt = vec3() # # we scale this vector so we avoid dividing triangle positions by 3 # var camera_position3 = camera_position # var m4 = self.world_matrix # m4 = invert(m4) # camera_position3 = transformMat4(camera_position3, m4) # camera_position3 = camera_position3 * 3 # let cp3x = camera_position3.x # let cp3y = camera_position3.y # let cp3z = camera_position3.z # let sign = self.sort_sign # # # Find out the furthest possible distance we can find, so that's 2**16 # # vec3 # for i in 0 ..< num_submeshes: # let i2 = i << 1 # let va = varray.subarray(offsets[i2], offsets[i2 + 2]) # var ia = iarray.subarray(offsets[i2 + 1], offsets[i2 + 3]) # let num_triangles = (ia.len * 0.3333333333333333) or 0 # if face_sort_array.len < num_triangles: # face_sort_array = makeSeq[float64](num_triangles) # face_sort_array32 = makeSeq[uint32](face_sort_array.buffer) # iarray_temporary = makeSeq[uint32](ia.len) # var j2 = var j3 = 0 # for j in 0 ..< num_triangles: # let v0 = ia[j3] * stride # let v1 = ia[j3 + 1] * stride # let v2 = ia[j3 + 2] * stride # # vec3.set v, va[v0], va[v0+1], va[v0+2] # # vec3.set vt, va[v1], va[v1+1], va[v1+2] # # vec3.add v, v, vt # # vec3.set vt, va[v2], va[v2+1], va[v2+2] # # vec3.add v, v, vt # # sqr_dist = vec3.sqrDist v, camera_position3 # var x = va[v0] # var y = va[v0 + 1] # var z = va[v0 + 2] # x += va[v1] # y += va[v1 + 1] # z += va[v1 + 2] # x += va[v2] - cp3x # y += va[v2 + 1] - cp3y # z += va[v2 + 2] - cp3z # let sqr_dist = x * x + y * y + z * z # face_sort_array[j] = sqr_dist * sign # face_sort_array32[j2 + BIG_ENDIAN] = j # j2 += 2 # j3 += 3 # face_sort_array.subarray(0, num_triangles).sort() # iarray_temporary.set(ia) # j3 = 0 # j2 = BIG_ENDIAN # while j2 < num_triangles * 2: # let t = face_sort_array32[j2] # let t3 = t + (t << 1) # ia[j3] = iarray_temporary[t3] # ia[j3 + 1] = iarray_temporary[t3 + 1] # ia[j3 + 2] = iarray_temporary[t3 + 2] # j3 += 3 # j2 += 2 # self.update_iarray() return # proc generate_normals*(self: Mesh, normal_offset: float = 4 * 3) = # # dbg?.clear_vertices() # let offsets = self.offsets # let num_submeshes = (offsets.len / 2) - 1 # let varray = self.data.varray # let varray_byte = self.data.varray_byte # let iarray = self.data.iarray # var stride = self.data.stride # let varray_int8 = makeSeq[int8](varray.buffer, varray.byteOffset, varray.bytelength) # let iarray_u32 = makeSeq[uint32](iarray.buffer, iarray.byteOffset, iarray.bytelength >> 2) # stride = stride # let stride_f = stride >> 2 # # TODO: have the size be of the largest submesh, not the whole thing # var normals = makeSeq[float32](varray.len * 3 / stride_f) # var a = vec3() # var b = vec3() # let c = vec3() # for i in 0 ..< num_submeshes: # let i2 = i << 1 # let va = varray.subarray(offsets[i2], offsets[i2 + 2]) # # pre-adding normal offset to slice instead of adding it each time # var va_n = varray_int8.subarray((offsets[i2] << 2) + normal_offset, (offsets[i2 + 2] << 2) + normal_offset) # if va.bytelength / stride >= 65536: # var ia = iarray_u32.subarray(offsets[i2 + 1] >> 1, offsets[i2 + 3] >> 1) # else: # ia = iarray.subarray(offsets[i2 + 1], offsets[i2 + 3]) # j = 0 # while j < ia.len: # # get vertex indices, multiply by stride_f # var v0 = var v0_3 = ia[j] # var v1 = var v1_3 = ia[j + 1] # var v2 = var v2_3 = ia[j + 2] # v0 *= stride_f # v1 *= stride_f # v2 *= stride_f # v0_3 *= 3 # v1_3 *= 3 # v2_3 *= 3 # # get delta position of v1 v2 # var x = va[v0] # var y = va[v0 + 1] # var z = va[v0 + 2] # let v1x = va[v1] - x # let v1y = va[v1 + 1] - y # let v1z = va[v1 + 2] - z # let v2x = va[v2] - x # let v2y = va[v2 + 1] - y # let v2z = va[v2 + 2] - z # a = vec3(v1x, v1y, v1z) # b = vec3(v2x, v2y, v2z) # a = cross(a, b) # a = normalize(a) # # if dbg? # # vec3.set b, x,y,z # # vec3.add b, b, {x: v1x/3, y: v1y/3, z: v1z/3} # # vec3.add b, b, {x: v2x/3, y: v2y/3, z: v2z/3} # # vec3.transformMat4 c, b, @world_matrix # # dbg.add_vertex c, {r:1,g:0,b:0,a:1} # # vec3.scale a, a, 0.01 # # vec3.add b, b, a # # vec3.scale a, a, 100 # # vec3.transformMat4 c, b, @world_matrix # # dbg.add_vertex c, {r:1,g:0,b:0,a:1} # # add to all vertices # # TODO: add normal count # # to divide at the ends instead of normalizing # let (x,y,z) = a.toTuple # normals[v0_3] += x # normals[v0_3 + 1] += y # normals[v0_3 + 2] += z # normals[v1_3] += x # normals[v1_3 + 1] += y # normals[v1_3 + 2] += z # normals[v2_3] += x # normals[v2_3 + 1] += y # normals[v2_3 + 2] += z # j += 3 # # copy normals normalized to bytes # var i_b = 0 # i3 = 0 # while i3 < normals.len: # a = vec3(normals[i3], normals[i3 + 1], normals[i3 + 2]) # a = normalize(a) # # console.log a # va_n[i_b] = a.x * 127 # va_n[i_b + 1] = a.y * 127 # va_n[i_b + 2] = a.z * 127 # i_b += stride # i3 += 3 # return # TODO: support multiple index types with a generic? # NOTE: doing it using the triangles and not the original polygons might be # slightly inaccurate, unless mesh is trianglulated when baking proc generate_tangents*(self: MeshData, uv_layer_name, tangent_layer_name: string) = if self.varrays.len != self.iarrays.len: raise newException(ValueError, "Function generate_tangents() doesn't support meshes without indices at the moment") var uv_offset, tangent_offset: int32 for attr in self.layout: if attr.name == uv_layer_name: uv_offset = (attr.offset div 4).int32 elif attr.name == tangent_layer_name: tangent_offset = attr.offset.int32 if uv_offset == 0 or tangent_offset == 0: raise newException(KeyError, "Cound't find attributes") let winding_offset = tangent_offset + 3 let stride = (self.stride).int32 let stride_f = (stride div 4).int32 var max_len = 0 for va in self.varrays: max_len = max(max_len, va.len) for ia in self.iarrays: var maxi = 0 for idx in ia.to int32: maxi = max(maxi, idx) # dump (max_len, maxi) var tangents = makeSeq[Vec3](max_len div stride_f.int) # for va, ia in zip(self.varrays, self.iarrays): for i in 0 ..< self.varrays.len: let va = self.varrays[i] var ia = self.iarrays[i].to int32 if va.len == 0 or ia.len == 0: continue # let va_i8 = va.to int8 # TODO: why is this not working? let va_i8 = cast[ArrRef[int8]](va) var j = 0 var ialen = ia.len while j < ialen: # get vertex indices var v0 = ia[j] var v1 = ia[j + 1] var v2 = ia[j + 2] var v0s = v0*stride_f var v1s = v1*stride_f var v2s = v2*stride_f # get delta position of v1 v2 var x = va[v0s] var y = va[v0s + 1] var z = va[v0s + 2] var v1x = va[v1s] - x var v1y = va[v1s + 1] - y var v1z = va[v1s + 2] - z var v2x = va[v2s] - x var v2y = va[v2s + 1] - y var v2z = va[v2s + 2] - z # get delta position of uv1 uv2 x = va[v0s + uv_offset] y = va[v0s + uv_offset + 1] let uv1x = va[v1s + uv_offset] - x let uv1y = va[v1s + uv_offset + 1] - y let uv2x = va[v2s + uv_offset] - x let uv2y = va[v2s + uv_offset + 1] - y # get UV winding direction let w = sgn(uv1x * uv2y - uv2x * uv1y).int8 # multiply v deltas by uv.y (squish to X) v1x *= uv2y v1y *= uv2y v1z *= uv2y v2x *= uv1y v2y *= uv1y v2z *= uv1y var v = vec3(v2x - v1x, v2y - v1y, v2z - v1z) v = v * -w.float32 v = normalize(v) # add to all vertices # dump (v0, v1, v2, tangents.len) tangents[v0] += v tangents[v1] += v tangents[v2] += v # write winding va_i8[(v0 * stride + winding_offset).int] = w va_i8[(v1 * stride + winding_offset).int] = w va_i8[(v2 * stride + winding_offset).int] = w j += 3 # copy tangents normalized to bytes var i_b = tangent_offset let va_i8_len = va_i8.len for v in tangents: let vn = normalize(v) va_i8[i_b] = (vn.x * 127).int8 va_i8[i_b + 1] = (vn.y * 127).int8 va_i8[i_b + 2] = (vn.z * 127).int8 i_b += stride if i_b >= va_i8_len: break # proc displace_vertices*(self: Mesh, offset: Vec3) = # if not self.data: # return # let (x,y,z) = offset.toTuple # var varray = self.data.varray # var stride = self.data.stride # stride >>= 2 # i = 0 # while i < varray.len: # varray[i] += x # varray[i + 1] += y # varray[i + 2] += z # i += stride # for bb in self.bound_box: # let bb = bb + offset # if self.data.vertex_buffers.len != 0: # self.update_varray() # return # proc has_attribute*(self: Mesh, attribute_name: unknown): bool = # for {name}.toTable in self.layout: # if not attribute_name == name: # continue # return true # return false # proc insert_attribute*(self: Mesh, attribute: unknown) = # # {name: 'xxxx', type: 'f', count: 3, offset: 0, location: 0} # let varray = self.data.varray # let iarray = self.data.iarray # let stride = self.data.stride # self.data?.remove(self) # attribute.offset = stride # attribute.location = self.layout[self.layout.len - 1].location + 1 # self.layout.add(attribute) # let stride_f = stride >> 2 # assuming it's always aligned to 4 bytes # var stride_f_out = stride_f # let attr_size = attribute.dtype.size * attribute.count # stride_f_out += ceil(attr_size / 4) # let varray2 = makeSeq[float32]((varray.len / stride_f) * stride_f_out) # var pos_in = var pos_out = 0 # let len = varray2.len # let uarray = makeSeq[uint32](varray.buffer, varray.byteOffset, varray.len) # var uarray2 = makeSeq[uint32](varray2.buffer, varray2.byteOffset, varray2.len) # while pos_out < len: # for i in 0 ..< stride_f: # uarray2[pos_out + i] = uarray[pos_in + i] # pos_in += stride_f # pos_out += stride_f_out # self.stride = stride_f_out << 2 # i = 2 # while i < self.offsets.len: # self.offsets[i] = (self.offsets[i] / stride_f) * stride_f_out # i += 2 # self.load_from_va_ia(varray2, iarray) # proc split_triangles*(self: Mesh) = # var varray_byte = self.data.varray_byte # var iarray = self.data.iarray # let stride = self.data.stride # let vertices = @[] # let indices = @[] # let idx_out = 0 # for idx_in in iarray: # let i = idx_in * stride # vertices.add(varray_byte.subarray(i, i + stride)...) # indices.add(++idx_out) # varray_byte = makeSeq[uint8](vertices) # let varray = makeSeq[float32](varray_byte.buffer) # if idx_out > (1 << 16): # iarray = makeSeq[uint32](indices) # iarray = makeSeq[uint16](iarray.buffer) # else: # iarray = makeSeq[uint16](indices) # self.offsets = @[0, 0, varray.len, iarray.bytelength >> 1] # self.load_from_va_ia(varray, iarray) # return # proc origin_to_bounding_box_center*(self: Mesh, options: Table[string, unknown] = {:}.toTable) = # let preserve_visual_position = options.preserve_visual_position ?: true # let (bb0,bb1) = self.bound_box.toTuple # var v = vec3() # v = lerp(bb0, bb1, 0.5) # v = -v # self.displace_vertices(v) # v = v * self.scale # v = -v # if preserve_visual_position: # self.translate(v, self) # return # proc update_BB*(self: Mesh) = # return self.update_bounding_box() proc update_bounding_box*(self: Mesh) = if self.data == nil or self.layout.len == 0: return # TODO: byte vertices if self.layout[0].dtype != Float: raise newException(Defect, "not implemented") let varray = self.data.varrays[0] let stride = self.data.stride div 4 var minx, miny, minz = Inf.float32 var maxx, maxy, maxz = -Inf.float32 var i = 0 while i < varray.len: var v = varray[i] minx = min(minx, v) maxx = max(maxx, v) v = varray[i + 1] miny = min(miny, v) maxy = max(maxy, v) v = varray[i + 2] minz = min(minz, v) maxz = max(maxz, v) i += stride self.bound_box[0] = vec3(minx, miny, minz) self.bound_box[1] = vec3(maxx, maxy, maxz) # proc unload*(self: Mesh) = # self.data?.remove(self) # # @data = null # for tex in self.related_textures: # var idx = tex.mesh_users.find(self) # if idx == -1: # raise Error("this shouldn't happen") # var popped = tex.mesh_users.pop() # if popped != self: # tex.mesh_users[idx] = popped # if tex.mesh_users.len == 0: # console.log(&"Texture {tex.name} has no more users, unloading") # tex.unload() # self.related_textures = @[] # return # proc destroy*(self: Mesh, recursive: bool = true) = # self.data?.remove(self) # procCall(self.GameObject.destroy(recursive)) proc debug_print_vertices*(self: Mesh, starts: int = 0, ends: int = 10) = if self.data.varrays[0].len == 0: return let varray = cast[ptr UncheckedArray[int8]](self.data.varrays[0][0].addr) let stride = self.data.stride for i in starts ..< ends: echo &"index {i}" for attr in self.layout: let offset = i * stride + attr.offset let data = case attr.dtype: of Float: ($cast[ptr array[16, float32]](varray[offset].addr)[]).split(',')[0 ..< attr.count].join(",") & "]" of Byte: ($cast[ptr array[16, int8]](varray[offset].addr)[]).split(',')[0 ..< attr.count].join(",") & "]" of UByte: ($cast[ptr array[16, uint8]](varray[offset].addr)[]).split(',')[0 ..< attr.count].join(",") & "]" of Short: ($cast[ptr array[16, uint16]](varray[offset].addr)[]).split(',')[0 ..< attr.count].join(",") & "]" # of HalfFloat: # let a = makeSeq[uint16](varray.buffer, offset, attr.count) # @[read_f16(a[0]), read_f16(a[1]), read_f16(a[2])] else: "" echo &"{attr.name} {data}" proc add_polygonal_line*(self: Mesh, orig, dest: Vec3, width: float) = let last = self.last_polyline_point let last_left = self.last_polyline_left let has_last = self.data.num_indices[0] != 0 var left = ((dest - orig).normalize * (width/2)).rotate_ccw if has_last and dist(orig, dest)*2 < width: left = last_left var inleft = left if has_last and (last ~= orig): inleft = mix(left, last_left, 0.5).normalize * (width/2) let angle = (left.xy.angle - last_left.xy.angle).fixAngle inleft *= 1/cos(angle/2) if angle > 2.5: # may be too big, ignore it inleft = left if has_last and not (last ~= orig): ## NaN polygons self.add_vertex(vec3(), vec4(0,0,0,1)) self.add_vertex(vec3(), vec4(0,0,0,1)) # ## degen tris # self.add_vertex(last, vec4(0,0,0,1)) # self.add_vertex(last, vec4(0,0,0,1)) # self.add_vertex(orig, vec4(0,0,0,1)) # self.add_vertex(orig, vec4(0,0,0,1)) # self.data.num_indices[0] -= 2 self.add_vertex(orig + inleft, vec4(0,0,0,1)) self.add_vertex(orig - inleft, vec4(0,0,0,1)) self.add_vertex(dest + left, vec4(0,0,0,1)) self.add_vertex(dest - left, vec4(0,0,0,1)) self.last_polyline_point = dest self.last_polyline_left = left self.data.update_varray() # var fl32 = makeSeq[float32](1) # var ui32 = makeSeq[uint32](fl32.buffer) # let rounder = (1 << 12) # proc write_f16(float: unknown): int = # fl32[0] = float # var fltInt32 = ui32[0] # fltInt32 += fltInt32 and rounder # var fltInt16 = (fltInt32 >> 31) << 5 # var tmp = (fltInt32 >> 23) and 255 # tmp = (tmp - 112) and (((112 - tmp) >> 4) >> 27) # fltInt16 = (fltInt16 or tmp) << 10 # fltInt16 |= (fltInt32 >> 13) and 1023 # return fltInt16 # ui32[0] = (254 - 15) << 23 # let magic = fl32[0] # ui32[0] = (127 + 16) << 23 # let was_inf_nan = fl32[0] # proc read_f16(short: unknown) = # ui32[0] = (short and 32767) << 13 # fl32[0] *= magic # if fl32[0] >= was_inf_nan: # ui32[0] |= 255 << 23 # ui32[0] |= (short and 32768) << 16 # return fl32[0]