# 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