myou-engine/src/objects/gameobject.nim

708 lines
29 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.
import ../types
import vmath except Quat, quat
import ../quat
when defined(nimdoc):
type TYPES* = ObjectType | GameObject
# Forward declarations and ob type methods
proc initGameObject*(self: GameObject, engine: MyouEngine, name: string="", scene: Scene=nil): GameObject
proc newGameObject*(engine: MyouEngine, name: string="", scene: Scene=nil): GameObject
proc show*(self: GameObject, recursive: static[bool] = true)
proc hide*(self: GameObject, recursive: static[bool] = true)
proc set_visibility*(self: GameObject, visible: bool, recursive: static[bool] = true)
proc update_matrices*(self: GameObject)
proc update_matrices_recursive(self: GameObject)
proc set_rotation_order*(self: GameObject, order: RotationOrder)
proc get_world_matrix*(self: GameObject): Mat4
proc get_local_matrix*(self: GameObject): Mat4
proc get_world_position*(self: GameObject): Vec3
proc get_world_rotation*(self: GameObject): Quat
proc get_world_position_rotation*(self: GameObject): (Vec3, Quat)
proc translate*(self: GameObject, vector: Vec3, relative_object: GameObject=nil): GameObject {.discardable.}
proc translate_x*(self: GameObject, x: SomeFloat, relative_object: GameObject=nil): GameObject {.discardable.}
proc translate_y*(self: GameObject, y: SomeFloat, relative_object: GameObject=nil): GameObject {.discardable.}
proc translate_z*(self: GameObject, z: SomeFloat, relative_object: GameObject=nil): GameObject {.discardable.}
proc rotate_quat*(self: GameObject, q: Quat, relative_object: GameObject=nil): GameObject {.discardable.}
proc rotate_x*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil)
proc rotate_y*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil)
proc rotate_z*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil)
proc rotate_x_deg*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil)
proc rotate_y_deg*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil)
proc rotate_z_deg*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil)
proc set_world_position_rotation*(
self: GameObject,
position: Vec3,
rotation: Quat)
proc set_world_position*(self: GameObject, position: Vec3)
proc set_world_rotation*(self: GameObject, rotation: Quat)
proc clone*(self: GameObject,
recursive: bool=true,
with_behaviours: bool=true,
name: string=self.name,
new_parent: GameObject=self.parent,
scene: Scene=self.scene,
instance_body: bool=true
): GameObject
proc clone_impl*(self: GameObject, n: var GameObject, recursive: bool,
with_behaviours: bool, name: string, new_parent: GameObject,
scene: Scene, instance_body: bool): GameObject
proc set_parent*(self: GameObject, parent: GameObject, keep_transform: bool=true)
proc parent_to*(self: GameObject, parent: GameObject, keep_transform: bool=true)
proc clear_parent*(self: GameObject, keep_transform: bool=true)
proc get_top_ancestor*(self: GameObject, top_level_parents: seq[GameObject] = @[]): GameObject
proc remove*(self: GameObject, recursive: bool = true)
proc destroy*(self: GameObject, recursive: bool = true)
proc convert_bone_child_to_bone_parent*(self: GameObject)
proc get_local_X_vector*(self: GameObject): Vec3
proc get_local_Y_vector*(self: GameObject): Vec3
proc get_local_Z_vector*(self: GameObject): Vec3
proc get_world_X_vector*(self: GameObject): Vec3
proc get_world_Y_vector*(self: GameObject): Vec3
proc get_world_Z_vector*(self: GameObject): Vec3
proc get_local_dimensions*(self: GameObject): Vec3
proc get_world_dimensions*(self: GameObject): Vec3
proc get_world_scale*(self: GameObject): float
proc get_world_scale_vector*(self: GameObject): Vec3
proc get_local_size*(self: GameObject): float
proc set_local_size*(self: GameObject, size: SomeFloat)
proc get_world_size*(self: GameObject): float
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
# End forward declarations
import json
import std/math
import std/tables
import ../incomplete
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()
self.engine = engine
self.name = name
self.rotation.quat = quat()
self.rotation_order = EulerXYZ
self.scale = vec3(1, 1, 1)
self.object_color = vec4(1, 1, 1, 1)
# self.alpha = 1
self.visible = true
self.auto_update_matrix = true
self.world_matrix = mat4()
self.bone_matrix_inverse = mat4()
self.parent_bone_index = -1
self.zindex = 1 # ??
self.world_center = vec4(0, 0, 0, 1)
self.sqscale = 1
self.object_render_ubo = self.engine.newUBO("ObjectRenderUniform", ObjectRenderUniform, 1)
self.object_ubo = self.engine.newUBO("ObjectUniform", ObjectUniform, 1)
# self.camera_render_ubo = self.engine.newUBO("CameraRenderUniform", CameraRenderUniform, 1)
if scene != nil:
scene.add_object(self, name=name)
return self
proc newGameObject*(engine: MyouEngine, name: string="", scene: Scene=nil): GameObject =
## Create a new GameObject. If you supply `scene` it will be added to that
## scene.
new(result)
return initGameObject(result, engine, name, scene)
proc show*(self: GameObject, recursive: static[bool] = true) =
## Enable visibility of the object, its children, their children and so on.
## If you set `recursive = false`, only the visibility of the object will be
## changed.
self.visible = true
when recursive:
for c in self.children:
c.show(recursive)
proc hide*(self: GameObject, recursive: static[bool] = true) =
## Disable visibility of the object, its children, their children and so on.
## If you set `recursive = false`, only the visibility of the object will be
## changed.
self.visible = false
when recursive:
for c in self.children:
c.hide(recursive)
proc set_visibility*(self: GameObject, visible: bool, recursive: static[bool] = true) =
## Set the visibility of the object and its descendants to the value of the
## argument `visible`. If you set `recursive = false`, only the visibility of
## the object will be changed.
if visible: self.show(recursive)
else: self.hide(recursive)
proc update_matrices*(self: GameObject) =
## Calculate the matrices of the object from its position, rotation, scale,
## and parent matrix. It assumes the parent object and/or bone has its world
## matrix already up to date.
var q = if self.rotation_order == Quaternion:
self.rotation.quat
else:
to_quat(self.rotation.euler, self.rotation_order)
# var q = self.rotation
q = normalize(q)
let (x,y,z,w) = q.toTuple
let scl = self.scale
self.flip = false
self.sqscale = length_sq(scl)
if self.parent != nil:
self.flip = self.parent.flip
self.sqscale *= self.parent.sqscale
if scl.x * scl.y * scl.z < 0:
self.flip = not self.flip
let pos = self.position
var wm = mat4(
(w * w + x * x - y * y - z * z) * scl.x,
(2 * (x * y + z * w)) * scl.x,
(2 * (x * z - y * w)) * scl.x,
0.0,
(2 * (x * y - z * w)) * scl.y,
(w * w - x * x + y * y - z * z) * scl.y,
(2 * (z * y + x * w)) * scl.y,
0.0,
(2 * (x * z + y * w)) * scl.z,
(2 * (y * z - x * w)) * scl.z,
(w * w - x * x - y * y + z * z) * scl.z,
0.0,
pos.x,
pos.y,
pos.z,
1.0
)
if self.parent != nil:
wm = self.matrix_parent_inverse * wm
var bi = self.parent_bone_index
if bi >= 0:
let bone = self.parent.get_armature.bone_list[bi]
# In Blender, matrix_parent_inverse is relative to the
# TIP of the bone, while bone.matrix is relative to the BASE
# so we need to add the bone length
wm[3,1] = wm[3,1] + bone.blength
self.world_matrix = bone.matrix * self.world_matrix
wm = self.parent.world_matrix * wm
self.world_matrix = wm
self.world_center.xyz = wm * self.center
return
proc update_matrices_recursive(self: GameObject) =
## Calculate the matrices of the object from its position, rotation, scale,
## and parent. It will update the parent matrices first if it has a parent.
if self != nil:
self.parent.update_matrices_recursive()
self.update_matrices()
proc set_rotation_order*(self: GameObject, order: RotationOrder) =
## Change the rotation mode and order of the object.
if order == self.rotation_order:
return
var q = if self.rotation_order != Quaternion:
self.rotation.euler.to_quat(self.rotation_order)
else:
self.rotation.quat
self.rotation = case order:
of Quaternion: Rotation(quat: q)
of EulerXYZ: Rotation(euler: q.to_euler_XYZ)
of EulerXZY: Rotation(euler: q.to_euler_XZY)
of EulerYXZ: Rotation(euler: q.to_euler_YXZ)
of EulerYZX: Rotation(euler: q.to_euler_YZX)
of EulerZXY: Rotation(euler: q.to_euler_ZXY)
of EulerZYX: Rotation(euler: q.to_euler_ZYX)
of AxisAngle:
raise newException(ValueError, "axis angle not supported yet")
self.rotation_order = order
proc get_world_matrix*(self: GameObject): Mat4 =
## Calculates and returns the world matrix.
# TODO: use dirty flag
self.update_matrices_recursive()
return self.world_matrix
proc get_local_matrix*(self: GameObject): Mat4 =
## Calculates and returns the transformation matrix in local space. If the
## object has no parent, this is equivalent to the world matrix.
var q = if self.rotation_order == Quaternion:
self.rotation.quat
else:
to_quat(self.rotation.euler, self.rotation_order)
q = normalize(q)
var (x,y,z,w) = q.toTuple
let scl = self.scale
# TODO: test negative scales
let pos = self.position
return mat4(
(w * w + x * x - y * y - z * z) * scl.x,
(2 * (x * y + z * w)) * scl.x,
(2 * (x * z - y * w)) * scl.x,
0,
(2 * (x * y - z * w)) * scl.y,
(w * w - x * x + y * y - z * z) * scl.y,
(2 * (z * y + x * w)) * scl.y,
0,
(2 * (x * z + y * w)) * scl.z,
(2 * (y * z - x * w)) * scl.z,
(w * w - x * x - y * y + z * z) * scl.z,
0,
pos.x,
pos.y,
pos.z,
1
)
proc get_world_position*(self: GameObject): Vec3 =
## Calculates and returns the position in world space.
if self.parent == nil:
return self.position
return self.get_world_matrix[3].xyz
proc get_world_rotation*(self: GameObject): Quat =
## Calculates and returns the rotation in world space as a quaternion.
let wm = self.get_world_matrix
# TODO: Calculate rotation matrix more efficiently (dirty flag?)
return wm.to_mat3_rotation.to_quat
proc get_world_position_rotation*(self: GameObject): (Vec3, Quat) =
## Calculates and returns the position and rotation in world space.
let wm = self.get_world_matrix
let position: Vec3 = wm[3].xyz
# TODO: Calculate rotation matrix more efficiently (dirty flag?)
# TODO: handle negative scales
let rotation: Quat = wm.to_mat3_rotation.to_quat
return (position, rotation)
proc translate*(self: GameObject, vector: Vec3, relative_object: GameObject=nil): GameObject {.discardable.} =
## Translate (move) the object by adding the vector `vector` to its position,
## either relative to the scene, or relative to the specified object in
## `relative_object`
var vector = vector
if relative_object != nil:
vector = relative_object.get_world_rotation() * vector
if self.parent != nil:
let m = self.parent.get_world_matrix * self.matrix_parent_inverse
vector = m.to_mat3_rotation.transpose * vector
self.position = self.position + vector
return self
proc translate_x*(self: GameObject, x: SomeFloat, relative_object: GameObject=nil): GameObject {.discardable.} =
## Translate (move) the object along the X axis of the scene, or the X axis
## of the specified object in `relative_object`
return self.translate(vec3(x, 0, 0), relative_object)
proc translate_y*(self: GameObject, y: SomeFloat, relative_object: GameObject=nil): GameObject {.discardable.} =
## Translate (move) the object along the Y axis of the scene, or the Y axis
## of the specified object in `relative_object`
return self.translate(vec3(0, y, 0), relative_object)
proc translate_z*(self: GameObject, z: SomeFloat, relative_object: GameObject=nil): GameObject {.discardable.} =
## Translate (move) the object along the Z axis of the scene, or the Z axis
## of the specified object in `relative_object`
return self.translate(vec3(0, 0, z), relative_object)
# proc rotate_euler*(self: GameObject, vector: Vec3, relative_object: GameObject=nil): GameObject =
# let q = fromEulerOrder(vector, order)
# self.rotate_quat(q, relative_object)
# proc rotate_euler_deg*(self: GameObject, vector: Vec3, relative_object: GameObject=nil): GameObject =
# let v = vector * 0.017453292519943295 # PI*2 / 360
# let q = fromEulerOrder(v, order)
# self.rotate_quat(q, relative_object)
proc rotate_quat*(self: GameObject, q: Quat, relative_object: GameObject=nil): GameObject {.discardable.} =
# Rotate the object by applying (multiplying) the quaternion `q`, either
# relative to the scene, or relative to the specified object in
# `relative_object`
# TODO: optimize (dirty flag?)
var rel = quat()
var inv_rel = quat()
var par = quat()
var inv_par = quat()
if relative_object != nil:
rel = relative_object.get_world_rotation
inv_rel = inverse(rel)
if self.parent != nil:
var m = self.parent.get_world_matrix * self.matrix_parent_inverse
par = m.to_mat3_rotation.to_quat
inv_par = inverse(par)
let rotation_order = self.rotation_order
if rotation_order != Quaternion:
self.set_rotation_order(Quaternion)
self.rotation.quat = inv_par * rel * q * inv_rel * par * self.rotation.quat
if rotation_order != Quaternion:
self.set_rotation_order(rotation_order)
return self
proc rotate_x*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil) =
## Rotate the object around the X axis of the scene, or the X axis of the
## specified object in `relative_object`. The angle is in radians.
self.rotate_quat(rotateX(angle.float32).to_mat3.to_quat, relative_object)
proc rotate_y*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil) =
## Rotate the object around the Y axis of the scene, or the Y axis of the
## specified object in `relative_object`. The angle is in radians.
self.rotate_quat(rotateY(angle.float32).to_mat3.to_quat, relative_object)
proc rotate_z*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil) =
## Rotate the object around the Z axis of the scene, or the Z axis of the
## specified object in `relative_object`. The angle is in radians.
self.rotate_quat(rotateZ(angle.float32).to_mat3.to_quat, relative_object)
proc rotate_x_deg*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil) =
## Rotate the object around the X axis of the scene, or the X axis of the
## specified object in `relative_object`. The angle is in degrees.
self.rotate_quat(rotateX(angle.float32.to_radians).to_mat3.to_quat, relative_object)
proc rotate_y_deg*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil) =
## Rotate the object around the Y axis of the scene, or the Y axis of the
## specified object in `relative_object`. The angle is in degrees.
self.rotate_quat(rotateY(angle.float32.to_radians).to_mat3.to_quat, relative_object)
proc rotate_z_deg*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil) =
## Rotate the object around the Z axis of the scene, or the Z axis of the
## specified object in `relative_object`. The angle is in degrees.
self.rotate_quat(rotateZ(angle.float32.to_radians).to_mat3.to_quat, relative_object)
proc set_world_position_rotation*(
self: GameObject,
position: Vec3,
rotation: Quat) =
## Changes the position and the rotation of the object to match the supplied
## position and rotation in world space.
if self.parent != nil:
# TODO: optimize and use less memory?
# TODO: does it work well with parents?
let wm = inverse(self.parent.get_world_matrix * self.matrix_parent_inverse)
self.position = wm * position
self.rotation.quat = wm.to_mat3_rotation.to_quat * rotation
else:
self.position = position
self.rotation.quat = rotation
self.rotation_order = Quaternion
proc set_world_position*(self: GameObject, position: Vec3) =
## Changes the position of the object to match the supplied position in world
## space.
self.set_world_position_rotation(position, self.get_world_rotation())
proc set_world_rotation*(self: GameObject, rotation: Quat) =
## Changes the rotation of the object to match the supplied rotation in world
## space.
self.set_world_position_rotation(self.get_world_position(), rotation)
type LookAtAxis* = enum
AxisX = 0
AxisY
AxisZ
AxisMinusX
AxisMinusY
AxisMinusZ
proc clone*(self: GameObject,
recursive: bool=true,
with_behaviours: bool=true,
name: string=self.name,
new_parent: GameObject=self.parent,
scene: Scene=self.scene,
instance_body: bool=true
): GameObject =
## Makes a clone of the object and its children (unless `recursive` is
## false).
case self.otype:
of TGameObject:
var n = new GameObject
n[] = self[]
return self.clone_impl(n, recursive, with_behaviours, name, new_parent, scene, instance_body)
of TMesh:
var n = new Mesh
var ob = cast[Mesh](self)
n[] = ob[]
discard self.clone_impl(n.GameObject, recursive, with_behaviours, name, new_parent, scene, instance_body)
return ob.clone_impl(n)
of TCamera:
discard
of TLight:
discard
of TArmature:
discard
proc clone_impl*(self: GameObject, n: var GameObject, recursive: bool,
with_behaviours: bool, name: string, new_parent: GameObject,
scene: Scene, instance_body: bool): GameObject =
n.children = @[]
for k,v in n.properties.pairs:
n.properties[k] = v.copy
# n.behaviours = @[]
# TODO: lods
# TODO: physics version of a mesh
if new_parent != nil:
n.parent = new_parent
n.parent.children.add(n)
# self.body = self.body.clone # TODO
n.scene = nil
if scene != nil:
scene.add_object(n, name=name)
# Adding children afterwards ensures objects don't need to be sorted
if recursive:
for i,child in self.children:
self.children[i] = child.clone(recursive, with_behaviours, name, new_parent=n, scene, instance_body=false)
# TODO: instance children bodies
return n
proc set_parent*(self: GameObject, parent: GameObject, keep_transform: bool=true) =
## Sets the parent of the object, while keeping the same transform in world
## space (unless you set `keep_transform` to false).
# TODO: there should be no distinction between this and parent_to
self.scene.make_parent(parent, self, keep_transform=keep_transform)
proc parent_to*(self: GameObject, parent: GameObject, keep_transform: bool=true) =
## Sets the parent of the object, while adding it to the same scene if
## needed, as well as keeping the same transform in world space (unless you
## set `keep_transform` to false).
# TODO move this to make_parent
if self.scene == nil:
parent.scene.add_object(self)
self.scene.make_parent(parent, self, keep_transform=keep_transform)
proc clear_parent*(self: GameObject, keep_transform: bool=true) =
self.scene.clear_parent(self, keep_transform=keep_transform)
proc get_top_ancestor*(self: GameObject, top_level_parents: seq[GameObject] = @[]): GameObject =
## Find the topmost ancestor (e.g. parent of parent of parent...) or an
## object contained in `top_level_parents`, whatever happens first.
var ob = self
while ob.parent != nil and ob.parent notin top_level_parents:
ob = ob.parent
return ob
iterator children_recursive*(self: GameObject, include_self: static[bool] = false): GameObject =
## Iterate all the descendants of the object, i.e. its children, the
## children's children an so on.
# NOTE: This was written before the knowledge of closure iterators
# (by default iterators are inline and don't allow recursion)
when include_self:
yield self
var ob = self
var stack: seq[int]
var pos = 0
var children = ob.children
while true:
while pos < children.len:
let c = children[pos]
yield c
inc(pos)
if c.children.len != 0:
ob = c
stack.add pos
pos = 0
children = c.children
if stack.len != 0:
ob = ob.parent
pos = stack.pop()
children = ob.children
else:
break
template children_recursive_and_self*(self: GameObject): GameObject =
## Iterate all the descendants of the object, and include itself in the
## iteration.
children_recursive(self, include_self = true)
proc remove*(self: GameObject, recursive: bool = true) =
## Remove the object from the scene it is in, without destroying it.
## It will also remove its children unless you set `recursive` to false.
if self.scene.nonNil:
self.scene.remove_object(self, recursive)
proc destroy*(self: GameObject, recursive: bool = true) =
## Destroy the object and free all unused resources previously used by it.
## It will also destroy its children unless you set `recursive` to false.
if self.is_mesh and self.get_mesh.data.nonNil:
self.get_mesh.data.gpu_buffers_delete()
self.get_mesh.materials.setLen 0
self.body.destroy()
self.remove(recursive)
if recursive:
for child in self.children:
var child = child
child.destroy(true)
self.engine.objects.del(self.name)
self.object_render_ubo.destroy()
self.object_ubo.destroy()
proc convert_bone_child_to_bone_parent*(self: GameObject) =
## Turns an object that is parented to a bone, into the parent of the same
## bone. It disconnects the bone from its former parent, if any. Used
## internally to create ragdolls.
assert self.parent == nil or self.parent.is_armature, "An armature parent is required"
var parent = self.parent.get_armature
if not (parent != nil and (self.parent_bone_index) >= 0):
return
var bone = parent.bone_list[self.parent_bone_index]
self.parent = nil
var m = self.matrix_parent_inverse * self.get_world_matrix
m[3,1] = m[3,1] + bone.blength
self.bone_matrix_inverse = inverse(m)
self.parent = parent
self.clear_parent()
bone.parent_object = self
proc get_local_X_vector*(self: GameObject): Vec3 =
## Get the vector of the X axis of the object in local space.
return self.get_local_matrix[0].xyz
proc get_local_Y_vector*(self: GameObject): Vec3 =
## Get the vector of the Y axis of the object in local space.
return self.get_local_matrix[1].xyz
proc get_local_Z_vector*(self: GameObject): Vec3 =
## Get the vector of the Z axis of the object in local space.
return self.get_local_matrix[2].xyz
proc get_world_X_vector*(self: GameObject): Vec3 =
## Get the vector of the X axis of the object in world space.
return self.get_world_matrix[0].xyz
proc get_world_Y_vector*(self: GameObject): Vec3 =
## Get the vector of the Y axis of the object in world space.
return self.get_world_matrix[1].xyz
proc get_world_Z_vector*(self: GameObject): Vec3 =
## Get the vector of the Z axis of the object in world space.
return self.get_world_matrix[2].xyz
proc get_local_dimensions*(self: GameObject): Vec3 =
## Calculate the local dimensions of the object, i.e. the size of the
## bounding box with the scale of the object.
let (a,b) = self.bound_box
return (b-a) * self.scale
proc get_world_dimensions*(self: GameObject): Vec3 =
## Calculate the world dimensions of the object, i.e. the size of the
## bounding box in world scale.
let (a,b) = self.bound_box
return (b-a) * self.get_world_scale_vector
proc get_world_scale*(self: GameObject): float =
# TODO: use the diagonal of a sphere instead?
# i.e. length((wm*vec3(1).normalize,0).xyz)
var s = self.scale.z
var p = self.parent
while p != nil:
s *= p.scale.z
p = p.parent
return s
proc get_world_scale_vector*(self: GameObject): Vec3 =
## Gets the world scale vector in local coordinates (i.e. relative to the
## current object orientation).
let rotation = self.get_world_rotation
let wm = self.world_matrix
let rot_inv = inverse(rotation)
var wm3 = wm.to_mat3
let wm3_b = rot_inv.to_mat3
wm3 = wm3_b * wm3
return vec3(
copy_sign(wm[0].xyz.length, wm3[0,0]),
copy_sign(wm[1].xyz.length, wm3[1,1]),
copy_sign(wm[2].xyz.length, wm3[2,2]),
)
proc get_local_size*(self: GameObject): float =
## Calculates the size of the largest dimension of the object in local
## coordinates.
let d = self.get_local_dimensions()
return max(max(d.x, d.y), d.z)
proc set_local_size*(self: GameObject, size: SomeFloat) =
## Sets the size of the object in local cordinates, given by the desired
## largest dimension of the object. The other two dimensions are scaled
## proportionally. If the object dimensions are zero, no action is taken.
let current_size = self.get_local_size
if abs(current_size) > 1e-8:
self.scale = self.scale * size / current_size
return
proc get_world_size*(self: GameObject): float =
## Calculates the size of the largest dimension of the object in world
## coordinates.
let d = self.get_world_dimensions()
return max(max(d.x, d.y), d.z)
proc set_world_size*(self: GameObject, size: SomeFloat) =
## Sets the size of the object in world cordinates, given by the desired
## largest dimension of the object. The other two dimensions are scaled
## proportionally. If the object dimensions are zero, no action is taken.
let current_size = self.get_world_size
if abs(current_size) > 1e-8:
self.scale = self.scale * size / current_size
return
proc set_name*(self: GameObject, name: string) =
## Rename the object and update the tables that use it.
assert self.scene != nil, "Object has no scene"
self.engine.objects.del(self.name)
self.scene.objects.del(self.name)
self.name = name
self.scene.set_ob_name(self, name)
proc local_to_world*(self: GameObject, point: Vec3): Vec3 =
## Transforms a point in local coordinates to world coordinates.
return self.get_world_matrix * point
proc world_to_local*(self: GameObject, point: Vec3): Vec3 =
## Transforms a point in world coordinates to local coordinates. If the
## object has a scale of 0, the point is not transformed.
let wm = self.get_world_matrix
if wm.determinant != 0.0:
return wm.inverse * point
return point