Armature: Add initial Armature object, Bones, armature deform vertex modifier.
This commit is contained in:
parent
ba70b1addb
commit
e169723b7b
10 changed files with 320 additions and 27 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
81
src/modifiers/armature_deform.nim
Normal file
81
src/modifiers/armature_deform.nim
Normal file
|
@ -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()
|
||||
|
189
src/objects/armature.nim
Normal file
189
src/objects/armature.nim
Normal file
|
@ -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
|
|
@ -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()
|
||||
|
|
13
src/quat.nim
13
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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue