* Incomplete port of myou-engine-js to nimskull, after many months of work, and a few extra features that weren't exactly necessary for a "first commit" to work. Excuse the lack of commit history up to this point. * Bare bones structure of the documentation and the process to update it. * Restructure of the whole project to have a more sensible organization. * Making submodules of forks of larger libraries. * README, licenses, AUTHORS.md.
1064 lines
41 KiB
Nim
1064 lines
41 KiB
Nim
# 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]
|