diff --git a/src/objects/gameobject.nim b/src/objects/gameobject.nim index 503fc20..20788e9 100644 --- a/src/objects/gameobject.nim +++ b/src/objects/gameobject.nim @@ -92,7 +92,8 @@ 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_dimensions*(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 @@ -392,6 +393,9 @@ proc clone*(self: GameObject, 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 @@ -440,9 +444,17 @@ proc clone_impl*(self: GameObject, n: var GameObject, recursive: bool, 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) @@ -452,12 +464,17 @@ 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: @@ -483,11 +500,20 @@ iterator children_recursive*(self: GameObject, include_self: static[bool] = fals 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 @@ -518,27 +544,41 @@ proc convert_bone_child_to_bone_parent*(self: GameObject) = 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_dimensions*(self: GameObject): Vec3 = +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) @@ -550,46 +590,46 @@ proc get_world_scale*(self: GameObject): float = 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 - let scale_x_sign = copy_sign(1.0, wm3[0,0]) - let scale_y_sign = copy_sign(1.0, wm3[1,1]) - let scale_z_sign = copy_sign(1.0, wm3[2,2]) return vec3( - wm[0].xyz.length * scale_x_sign, - wm[1].xyz.length * scale_y_sign, - wm[2].xyz.length * scale_z_sign, + 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 = - let d = self.get_dimensions() + ## 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 = - let d = self.get_dimensions() - let size = max(max(d.x, d.y), d.z) - if self.parent != nil: - let parent_z_scale = self.parent.get_world_Z_vector.length - return size * parent_z_scale - return size + ## 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) = - var size = size - if self.parent != nil: - let parent_z_scale = self.parent.get_world_Z_vector.length - if abs(parent_z_scale) > 1e-8: - size /= parent_z_scale - let current_size = self.get_local_size + ## 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 @@ -602,11 +642,14 @@ proc set_name*(self: GameObject, name: string) = 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 = - # TODO: check that determinant is not zero? - # does the compiler optimize multiple calls to .determinant? - let wm = self.get_world_matrix.inverse - return wm * point + ## 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