diff --git a/src/bundle.nim b/src/bundle.nim index f973f26..833cabc 100644 --- a/src/bundle.nim +++ b/src/bundle.nim @@ -47,8 +47,10 @@ import ./incomplete import ./input import ./loaders/blend import ./loaders/loader_base +import ./modifiers/armature_deform import ./modifiers/shape_keys import ./myou_engine +import ./objects/armature import ./objects/camera import ./objects/cubemap_probe import ./objects/gameobject @@ -64,6 +66,8 @@ import ./util import std/tables import vmath except Quat, quat +export armature +export armature_deform export attributes export blend export camera diff --git a/src/graphics/render.nim b/src/graphics/render.nim index 8e8e8d5..bbc7110 100644 --- a/src/graphics/render.nim +++ b/src/graphics/render.nim @@ -62,6 +62,7 @@ import ../objects/cubemap_probe import ../objects/gameobject # import ../objects/light import ../objects/mesh +import ../objects/armature import ../platform/platform import ../shadows/shadow_common import ../scene @@ -255,7 +256,7 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren if mesh.sqscale < 0.000001: mesh.culled_in_last_frame = true return - if self.use_frustum_culling and mesh.parent.is_armature: + if self.use_frustum_culling and not (mesh.parent.nonNil and mesh.parent.is_armature): # Cull object if it's outside camera frustum let pos4 = mesh.world_center # TODO: USE SCALE diff --git a/src/incomplete.nim b/src/incomplete.nim index 3bfe7e0..0574826 100644 --- a/src/incomplete.nim +++ b/src/incomplete.nim @@ -10,12 +10,4 @@ proc newWorld*(scene: Scene): World = discard proc destroy*(this: Body) = discard proc destroy*(this: World) = discard -proc recalculate_bone_matrices*(this: Armature) = discard - proc render*(this: PlanarProbe, v: Viewport) = discard - -proc newArmature*(engine: MyouEngine, name: string="", scene: Scene=nil): Armature = - result = new Armature - result.engine = engine - result.name = name - diff --git a/src/loaders/blend.nim b/src/loaders/blend.nim index 3d8384e..5da3fc7 100644 --- a/src/loaders/blend.nim +++ b/src/loaders/blend.nim @@ -56,6 +56,7 @@ import ../graphics/render import ../graphics/texture import ../incomplete import ../myou_engine +import ../objects/armature import ../objects/camera import ../objects/cubemap_probe import ../objects/gameobject diff --git a/src/modifiers/armature_deform.nim b/src/modifiers/armature_deform.nim new file mode 100644 index 0000000..f12686b --- /dev/null +++ b/src/modifiers/armature_deform.nim @@ -0,0 +1,81 @@ +# 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. + +import ../types +import ../graphics/ubo +import std/strformat +import vmath except Quat, quat +import ../quat + +proc newArmatureModifier*(engine: MyouEngine, num_bones: int): VertexModifier = + doAssert num_bones <= 256, "More than 256 bones not yet supported" + + result.get_code = proc(v: seq[Varying]): VertexModifierCodeLines = + result.uniform_lines = @[ + "layout(std140) uniform ArmatureBones {", + &" mat4 bones[{num_bones}];", + "};", + ] + result.body_lines = @[ + "vec4 blendco = vec4(0.0);", + "vec3 blendnor = vec3(0.0);", + "mat4 m; float w;", + "ivec4 inds = ivec4(b_indices);", + "for(int i=0;i<4;i++){", + " m = bones[inds[i]];", + &" w = weights[i] * {1/255};", + " blendco += m * co * w;", + " blendnor += mat3(m) * normal * w;", + "}", + "co = blendco; normal = blendnor;", + ] + + + let ubo = engine.renderer.newUBO("ArmatureBones", Mat4, num_bones) + result.ubo = ubo + + result.update = proc(mesh: Mesh) = + doAssert mesh.armature != nil, "Mesh has no armature assigned" + doAssert mesh.armature.deform_bones.len <= num_bones, + "Armature has more deform bones than specified in vertex modifier" + var data = ubo.storage(Mat4) + + # TODO: apply armature in world space or camera space + # instead of converting back and forth several times + # TODO: apply mesh transform and detect when it's the case + # TODO: 3x4 matrices, dual quaternions + let rel = mesh.armature.world_matrix.inverse * mesh.world_matrix + let rel_inv = rel.inverse + for i,bone in mesh.armature.deform_bones: + data[i] = rel_inv * bone.ol_matrix * rel + ubo.update() + diff --git a/src/objects/armature.nim b/src/objects/armature.nim new file mode 100644 index 0000000..8e2774f --- /dev/null +++ b/src/objects/armature.nim @@ -0,0 +1,189 @@ +# 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. + +import ../types +import std/options +import std/tables +import vmath except Quat, quat +import ../quat +import ../util + +when defined(nimdoc): + type TYPES* = Armature | Bone | Constraint + +# Forward declarations +proc newArmature*(engine: MyouEngine, name: string="armature", scene: Scene = nil): Armature +proc add_bone*(self: Armature, name: string, position: Vec3, rotation: Quat, deform_id: int, parent_name: string, blength = 0.0): Bone {.discardable.} +proc add_constraint*(self: Armature; constraint: Constraint) +proc update_rest_matrices*(self: Armature) +proc recalculate_bone_matrices*(self: Armature, use_constraints: bool = true) +# End forward declarations + +method is_armature*(self: GameObject): bool {.base.} = + ## Return whether a GameObject is an armature + return false +method get_armature*(self: GameObject): Armature {.base.} = + ## Get the Armature object of a GameObject, or nil if it's not an armature + return nil +method is_armature*(self: Armature): bool = + ## Return whether a GameObject is an armature + return true +method get_armature*(self: Armature): Armature = + ## Get the Armature object of a GameObject, or nil if it's not an armature + return self + +import ./gameobject + +proc newBone(): Bone = + let self = new Bone + self.base_rotation = quat() + self.rotation = quat() + self.scale = vec3(1, 1, 1) + self.final_scale = vec3(1, 1, 1) + self.matrix = mat4() + self.ol_matrix = mat4() + self.inv_rest_matrix = mat4() + self.deform_id = -1 + self.blength = 1 + return self + +proc clone_to(self: Bone, new_armature: Armature): Bone = + var n = newBone() + n[] = self[] + if self.parent != nil: + n.parent = new_armature.bone_list[self.parent.index] + n.object_children.setLen 0 + return n + +proc head*(self: Bone): Vec3 = + # NOTE: assuming matrix is already calculated + self.matrix[3].xyz + +proc tail*(self: Bone): Vec3 = + # NOTE: assuming matrix is already calculated + self.matrix * vec3(0, self.blength, 0) + +proc center*(self: Bone): Vec3 = + # NOTE: assuming matrix is already calculated + self.matrix * vec3(0, self.blength/2, 0) + +proc dist_to_sphere*(self: Bone, point: Vec3): float32 = + # NOTE: assuming matrix is already calculated + dist(self.matrix * vec3(0, self.blength/2, 0), point) - self.blength/2 + +proc newArmature*(engine: MyouEngine, name: string="armature", scene: Scene = nil): Armature = + ## Create a new Armature object. If you supply `scene` it will be added to that + ## scene. + var self = new Armature + discard procCall(self.GameObject.initGameObject(engine, name, scene)) + return self + + +# for now we will add bones, then update inv_rest_matrix of all +var dedup = 0 + +proc add_bone*(self: Armature, + name: string, + position: Vec3, + rotation: Quat, + deform_id: int, + parent_name: string, + blength = 0.0, + ): Bone {.discardable.} = + var bone = newBone() + var n = name + while n in self.bones: + dedup.inc + n = name & "$" & $dedup + bone.base_position = position + bone.base_rotation = rotation + if deform_id != -1: + bone.deform_id = deform_id + if self.deform_bones.len <= deform_id: + self.deform_bones.setLen 1 shl ceil(log2(float(deform_id+1))).int + self.deform_bones[deform_id] = bone + if parent_name != "": + bone.parent = self.bones[parent_name] + #bone.parent_matrix = bone.parent.matrix + bone.blength = blength + bone.index = self.bone_list.len + self.bone_list.add(bone) + self.bones[n] = bone + return bone + +proc add_constraint*(self: Armature; constraint: Constraint) = + discard + # self.bones[constraint.owner].constraints.add constraint + +proc update_rest_matrices*(self: Armature) = + # TODO: set rest pose first + self.recalculate_bone_matrices(use_constraints = false) + for bone in self.bone_list: + bone.inv_rest_matrix = inverse(bone.matrix) + +proc recalculate_bone_matrices*(self: Armature, use_constraints: bool = true) = + let inv = self.get_world_matrix.inverse + # update final position/rotation/scale + for bone in self.bone_list: + if bone.parent_object != nil: + continue + var pos = bone.base_position + bone.base_rotation * bone.position + var rot = bone.base_rotation * bone.rotation.normalize + var scl = bone.scale + let parent = bone.parent + if parent != nil: + scl = parent.final_scale * scl + rot = parent.final_rotation * rot + pos = pos * parent.final_scale + pos = parent.final_rotation * pos + pos = pos + parent.final_position + bone.final_position = pos + bone.final_rotation = rot + bone.final_scale = scl + if use_constraints: + for con in bone.constraints: + con.apply(con) + # update matrix + for bone in self.bone_list: + let ob = bone.parent_object + if ob == nil: + bone.matrix = scale(bone.final_scale) * + translate(bone.final_position) * + bone.final_rotation.to_mat4 + else: + bone.matrix = inv * ob.world_matrix * ob.bone_matrix_inverse + # TODO: scale? + # TODO: this is probably what it's messinig with the rotation + # if the scale is not taken in account, rotation is messed up and we have to normalize + bone.final_position = bone.matrix[3].xyz + bone.final_rotation = bone.matrix.to_quat.normalize + bone.ol_matrix = bone.matrix * bone.inv_rest_matrix diff --git a/src/objects/gameobject.nim b/src/objects/gameobject.nim index d10fae9..3eaacda 100644 --- a/src/objects/gameobject.nim +++ b/src/objects/gameobject.nim @@ -103,12 +103,7 @@ proc set_world_size*(self: GameObject, size: SomeFloat) proc set_name*(self: GameObject, name: string) proc local_to_world*(self: GameObject, point: Vec3): Vec3 proc world_to_local*(self: GameObject, point: Vec3): Vec3 - -method is_armature*(self: GameObject): bool {.base.} = - return false -method get_armature*(self: GameObject): Armature {.base.} = - return nil -# End forward declarations and ob type methods +# End forward declarations import json import std/math @@ -118,6 +113,7 @@ import ../util import ../graphics/ubo import ../scene import ./mesh +import ./armature proc initGameObject*(self: GameObject, engine: MyouEngine, name: string="", scene: Scene=nil): GameObject = # Remember to add any new mutable reference to clone() diff --git a/src/quat.nim b/src/quat.nim index 772554b..619bb79 100644 --- a/src/quat.nim +++ b/src/quat.nim @@ -52,7 +52,7 @@ func swap_quat_handness*[T](q: GQuat[T]): GQuat[T] = # TODO: rotate 90 to not change up-ness? GQuat[T](x: -q.x, y: -q.z, z: -q.y, w: q.w) -func to_quat*[T](m: GMat3[T]): GQuat[T] = +func to_quat*[T](m: GMat3[T]|GMat4[T]): GQuat[T] = # http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm let trace = m[0,0] + m[1,1] + m[2,2] if trace > 0: @@ -92,7 +92,7 @@ proc normalize*[T](q: GQuat[T]): GQuat[T] = return q return gquat[T](q.x / length , q.y / length, q.z / length, q.w / length) -func to_mat3*[T](q: GQuat[T]): GMat3[T] = +func to_mat[T,U](q: GQuat[T]): U = let xx = q.x * q.x * 2 let yx = q.y * q.x * 2 let yy = q.y * q.y * 2 @@ -114,6 +114,14 @@ func to_mat3*[T](q: GQuat[T]): GMat3[T] = result[0,2] = zx - wy result[1,2] = zy + wx result[2,2] = 1 - xx - yy + when U is GMat4[T]: + result[3,3] = 1 + +template to_mat3*[T](q: GQuat[T]): GMat3[T] = + to_mat[T,GMat3[T]](q) + +template to_mat4*[T](q: GQuat[T]): GMat4[T] = + to_mat[T,GMat4[T]](q) func `*`*[T](q: GQuat[T], a: GVec3[T]): GVec3[T] = @@ -267,4 +275,3 @@ proc rotationTo*(a,b: Vec3): Quat = else: let v = cross(a, b) return quat(v.x, v.y, v.z, 1.0+dot).normalize - diff --git a/src/scene.nim b/src/scene.nim index 378c565..187e893 100644 --- a/src/scene.nim +++ b/src/scene.nim @@ -76,6 +76,7 @@ import ./graphics/material import ./graphics/texture import ./graphics/ubo import ./incomplete +import ./objects/armature import ./objects/camera import ./objects/cubemap_probe import ./objects/gameobject diff --git a/src/types.nim b/src/types.nim index 75558a4..2663c74 100644 --- a/src/types.nim +++ b/src/types.nim @@ -321,6 +321,36 @@ type tex_size*: float32 bias*: float32 pad0, pad1: float32 + + # armature.nim + + Armature* = ref object of GameObject + bone_list*: seq[Bone] + deform_bones*: seq[Bone] + bones*: Table[string, Bone] + + Bone* = ref object + base_position*: Vec3 + base_rotation*: Quat + position*: Vec3 + rotation*: Quat + scale*: Vec3 + final_position*: Vec3 + final_rotation*: Quat + final_scale*: Vec3 + matrix*: Mat4 + ol_matrix*: Mat4 + parent*: Bone + index*: int + inv_rest_matrix*: Mat4 + deform_id*: int + blength*: float32 + constraints*: seq[Constraint] + object_children*: seq[GameObject] + parent_object*: GameObject + + Constraint* = ref object + apply*: proc(self: Constraint) # scene.nim @@ -873,15 +903,6 @@ type # INCOMPLETE - Armature* = ref object of GameObject - bone_list*: seq[Bone] - bones*: Table[string, Bone] - Bone = ref object - blength*: float - object_children*: seq[GameObject] - matrix*: Mat4 - parent_object*: GameObject - World* = ref object of RootObj enabled*: bool