tetra3d/node.go

1243 lines
44 KiB
Go

package tetra3d
import (
"fmt"
"strconv"
"strings"
)
type SectorType int
const (
SectorTypeObject SectorType = iota
SectorTypeSector
SectorTypeStandalone
)
// NodeType represents a Node's type. Node types are categorized, and can be said to extend or "be of" more general types.
// For example, a BoundingSphere has a type of NodeTypeBoundingSphere. That type can also be said to be NodeTypeBoundingObject
// (because it is a bounding object). However, it is not of type NodeTypeBoundingTriangles, as that is a different category.
type NodeType string
const (
NodeTypeNode NodeType = "NodeNode" // NodeTypeNode represents specifically a node
NodeTypeModel NodeType = "NodeModel" // NodeTypeModel represents specifically a Model
NodeTypeCamera NodeType = "NodeCamera" // NodeTypeCamera represents specifically a Camera
NodeTypePath NodeType = "NodePath" // NodeTypePath represents specifically a Path
NodeTypeGrid NodeType = "NodeGrid" // NodeTypeGrid represents specifically a Grid
NodeTypeGridPoint NodeType = "Node_GridPoint" // NodeTypeGrid represents specifically a GridPoint (note the extra underscore to ensure !NodeTypeGridPoint.Is(NodeTypeGrid))
NodeTypeBoundingObject NodeType = "NodeBounding" // NodeTypeBoundingObject represents any generic bounding object
NodeTypeBoundingAABB NodeType = "NodeBoundingAABB" // NodeTypeBoundingAABB represents specifically a BoundingAABB
NodeTypeBoundingCapsule NodeType = "NodeBoundingCapsule" // NodeTypeBoundingCapsule represents specifically a BoundingCapsule
NodeTypeBoundingTriangles NodeType = "NodeBoundingTriangles" // NodeTypeBoundingTriangles represents specifically a BoundingTriangles object
NodeTypeBoundingSphere NodeType = "NodeBoundingSphere" // NodeTypeBoundingSphere represents specifically a BoundingSphere BoundingObject
NodeTypeLight NodeType = "NodeLight" // NodeTypeLight represents any generic light
NodeTypeAmbientLight NodeType = "NodeLightAmbient" // NodeTypeAmbientLight represents specifically an ambient light
NodeTypePointLight NodeType = "NodeLightPoint" // NodeTypePointLight represents specifically a point light
NodeTypeDirectionalLight NodeType = "NodeLightDirectional" // NodeTypeDirectionalLight represents specifically a directional (sun) light
NodeTypeCubeLight NodeType = "NodeLightCube" // NodeTypeCubeLight represents, specifically, a cube light
)
// Is returns true if a NodeType satisfies another NodeType category. A specific node type can be said to
// contain a more general one, but not vice-versa. For example, a Model (which has type NodeTypeModel) can be
// said to be a Node (NodeTypeNode), but the reverse is not true (a NodeTypeNode is not a NodeTypeModel).
func (nt NodeType) Is(other NodeType) bool {
if nt == other {
return true
}
return strings.Contains(string(nt), string(other))
}
// INode represents an object that exists in 3D space and can be positioned relative to an origin point.
// By default, this origin point is {0, 0, 0} (or world origin), but Nodes can be parented
// to other Nodes to change this origin (making their movements relative and their transforms
// successive). Models and Cameras are two examples of objects that fully implement the INode interface
// by means of embedding Node.
type INode interface {
// Name returns the object's name.
Name() string
// ID returns the object's unique ID.
ID() uint64
// SetName sets the object's name.
SetName(name string)
// Clone returns a clone of the specified INode implementer.
Clone() INode
// SetData sets user-customizeable data that could be usefully stored on this node.
SetData(data interface{})
// Data returns a pointer to user-customizeable data that could be usefully stored on this node.
Data() interface{}
// Type returns the NodeType for this object.
Type() NodeType
setLibrary(lib *Library)
// Library returns the source Library from which this Node was instantiated. If it was created through code, this will be nil.
Library() *Library
setParent(INode)
// Parent returns the Node's parent. If the Node has no parent, this will return nil.
Parent() INode
// Unparent unparents the Node from its parent, removing it from the scenegraph.
Unparent()
// Scene looks for the Node's parents recursively to return what scene it exists in.
// If the node is not within a tree (i.e. unparented), this will return nil.
Scene() *Scene
// Root returns the root node in this tree by recursively traversing this node's hierarchy of
// parents upwards.
Root() *Node
InSceneTree() bool // Returns if this node is in the scene tree.
setCachedSceneRoot(root *Node)
cachedSceneRoot() *Node
// ReindexChild moves the child in the calling Node's children slice to the specified newPosition.
// The function returns the old index where the child Node was, or -1 if it wasn't a child of the calling Node.
// The newPosition is clamped to the size of the node's children slice.
ReindexChild(child INode, newPosition int) int
// Index returns the index of the Node in its parent's children list.
// If the node doesn't have a parent, its index will be -1.
Index() int
// Children() returns the Node's children as an INode.
Children() []INode
// SearchTree() returns a NodeFilter to search the given Node's hierarchical tree.
SearchTree() *NodeFilter
// AddChildren parents the provided children Nodes to the passed parent Node, inheriting its transformations and being under it in the scenegraph
// hierarchy. If the children are already parented to other Nodes, they are unparented before doing so.
AddChildren(...INode)
// RemoveChildren removes the provided children from this object.
RemoveChildren(...INode)
// updateLocalTransform(newParent INode)
dirtyTransform()
// ClearLocalTransform clears the local transform properties (position, scale, and rotation) for the Node, reverting it to essentially an
// identity matrix (0, 0, 0 for position, 1, 1, 1 for scale, and an identity Matrix4 for rotation, indicating no rotation).
// This can be useful because by default, when you parent one Node to another, the local transform properties (position,
// scale, and rotation) are altered to keep the object in the same absolute location, even though the origin changes.
ClearLocalTransform()
// ResetWorldTransform resets the local transform properties (position, scale, and rotation) for the Node to the original transform when
// the Node was first created / cloned / instantiated in the Scene.
ResetWorldTransform()
// ResetWorldPosition resets the Node's local position to the value the Node had when
// it was first instantiated in the Scene or cloned.
ResetWorldPosition()
// ResetWorldScale resets the Node's local scale to the value the Node had when
// it was first instantiated in the Scene or cloned.
ResetWorldScale()
// ResetWorldRotation resets the Node's local rotation to the value the Node had when
// it was first instantiated in the Scene or cloned.
ResetWorldRotation()
setOriginalTransform()
// SetWorldTransform sets the Node's global (world) transform to the full 4x4 transformation matrix provided.
SetWorldTransform(transform Matrix4)
// LocalRotation returns the object's local rotation Matrix4.
LocalRotation() Matrix4
// SetLocalRotation sets the object's local rotation Matrix4 (relative to any parent).
SetLocalRotation(rotation Matrix4)
// LocalPosition returns the object's local position as a Vector.
LocalPosition() Vector
// SetLocalPosition sets the object's local position (position relative to its parent). If this object has no parent, the position should be
// relative to world origin (0, 0, 0).
SetLocalPositionVec(position Vector)
// SetLocalPosition sets the object's local position (position relative to its parent). If this object has no parent, the position should be
// relative to world origin (0, 0, 0).
SetLocalPosition(x, y, z float64)
// LocalScale returns the object's local scale (scale relative to its parent). If this object has no parent, the scale will be absolute.
LocalScale() Vector
// SetLocalScale sets the object's local scale (scale relative to its parent). If this object has no parent, the scale would be absolute.
// scale should be a 3D vector (i.e. X, Y, and Z components).
SetLocalScaleVec(scale Vector)
SetLocalScale(w, h, d float64)
// WorldRotation returns an absolute rotation Matrix4 representing the object's rotation.
WorldRotation() Matrix4
// SetWorldRotation sets an object's global, world rotation to the provided rotation Matrix4.
SetWorldRotation(rotation Matrix4)
// WorldPosition returns the node's world position, taking into account its parenting hierarchy.
WorldPosition() Vector
// SetWorldPositionVec sets the world position of the given object using the provided position vector.
// Note that this isn't as performant as setting the position locally.
SetWorldPositionVec(position Vector)
// SetWorldPosition sets the world position of the given object using the provided position arguments.
// Note that this isn't as performant as setting the position locally.
SetWorldPosition(x, y, z float64)
// SetWorldX sets the x component of the Node's world position.
SetWorldX(x float64)
// SetWorldY sets the y component of the Node's world position.
SetWorldY(x float64)
// SetWorldZ sets the z component of the Node's world position.
SetWorldZ(x float64)
// WorldScale returns the object's absolute world scale as a 3D vector (i.e. X, Y, and Z components).
WorldScale() Vector
// SetWorldScaleVec sets the object's absolute world scale. scale should be a 3D vector (i.e. X, Y, and Z components).
SetWorldScaleVec(scale Vector)
// Move moves a Node in local space by the x, y, and z values provided.
Move(x, y, z float64)
// MoveVec moves a Node in local space using the vector provided.
MoveVec(moveVec Vector)
// Rotate rotates a Node on its local orientation on a vector composed of the given x, y, and z values, by the angle provided in radians.
Rotate(x, y, z, angle float64)
// RotateVec rotates a Node on its local orientation on the given vector, by the angle provided in radians.
RotateVec(vec Vector, angle float64)
// Grow scales the object additively (i.e. calling Node.Grow(1, 0, 0) will scale it +1 on the X-axis).
Grow(x, y, z float64)
// GrowVec scales the object additively (i.e. calling Node.Grow(1, 0, 0) will scale it +1 on the X-axis).
GrowVec(vec Vector)
// Transform returns a Matrix4 indicating the global position, rotation, and scale of the object, transforming it by any parents'.
// If there's no change between the previous Transform() call and this one, Transform() will return a cached version of the
// transform for efficiency.
Transform() Matrix4
// Visible returns whether the Object is visible.
Visible() bool
// SetVisible sets the object's visibility. If recursive is true, all recursive children of this Node will have their visibility set the same way.
SetVisible(visible, recursive bool)
// Get searches a node's hierarchy using a string to find a specified node. The path is in the format of names of nodes, separated by forward
// slashes ('/'), and is relative to the node you use to call Get. As an example of Get, if you had a cup parented to a desk, which was
// parented to a room, that was finally parented to the root of the scene, it would be found at "Room/Desk/Cup". Note also that you can use "../" to
// "go up one" in the hierarchy (so cup.Get("../") would return the Desk node).
// Since Get uses forward slashes as path separation, it would be good to avoid using forward slashes in your Node names. Also note that Get()
// trims the extra spaces from the beginning and end of Node Names, so avoid using spaces at the beginning or end of your Nodes' names.
Get(path string) INode
// FindNode searches a node's hierarchy using a string to find the specified Node.
FindNode(nodeName string) INode
// HierarchyAsString returns a string displaying the hierarchy of this Node, and all recursive children.
// This is a useful function to debug the layout of a node tree, for example.
HierarchyAsString() string
// Path returns a string indicating the hierarchical path to get this Node from the root. The path returned will be absolute, such that
// passing it to Get() called on the scene root node will return this node. The path returned will not contain the root node's name ("Root").
Path() string
// Properties returns this object's game Properties struct.
Properties() Properties
// IsBone returns if the Node is a "bone" (a node that was a part of an armature and so can play animations back to influence a skinned mesh).
IsBone() bool
// IsRootBone() bool
// AnimationPlayer returns the object's animation player - every object has an AnimationPlayer by default.
AnimationPlayer() *AnimationPlayer
// Sector returns the Sector this Node is in.
Sector() *Sector
sectorHierarchy() *Sector
isInVisibleSector(sectorsModels []*Model) bool
SetSectorType(sectorType SectorType)
SectorType() SectorType
// DistanceTo returns the distance between the given Nodes' centers.
// Quick syntactic sugar for Node.WorldPosition().Distance(otherNode.WorldPosition()).
DistanceTo(otherNode INode) float64
// DistanceSquared returns the squared distance between the given Nodes' centers.
// Quick syntactic sugar for Node.WorldPosition().DistanceSquared(otherNode.WorldPosition()).
DistanceSquaredTo(otherNode INode) float64
}
var nodeID uint64 = 0
// Node represents a minimal struct that fully implements the Node interface. Model and Camera embed Node
// into their structs to automatically easily implement Node.
type Node struct {
id uint64 // Unique ID for this node
name string
position Vector
scale Vector
rotation Matrix4
originalTransform Matrix4
visible bool
data interface{} // A place to store a pointer to something if you need it
children []INode
parent INode
cachedTransform Matrix4
isTransformDirty bool
props Properties // Properties is an unordered set of properties, representing a means of identifying and setting game properties on Nodes.
animationPlayer *AnimationPlayer
inverseBindMatrix Matrix4 // Specifically for bones in an armature used for animating skinned meshes
isBone bool
collectionObjects []INode // Returns if the node is a collection instance
boneInfluence Matrix4
library *Library // The Library this Node was instantiated from (nil if it wasn't instantiated with a library at all)
scene *Scene
onTransformUpdate func()
sectorType SectorType
cachedSceneRootNode *Node
cachedSector *Sector
}
// NewNode returns a new Node.
func NewNode(name string) *Node {
nb := &Node{
id: nodeID,
name: name,
// position: NewVectorZero(),
scale: Vector{1, 1, 1, 0},
rotation: NewMatrix4(),
children: []INode{},
visible: true,
isTransformDirty: true,
props: NewProperties(),
// We set this just in case we call a transform property getter before setting it and caching anything
cachedTransform: NewMatrix4(),
// originalLocalPosition: NewVectorZero(),
// sectorType: NewBitMask(0 + 1 + 2 + 3 + 4 + 5 + 6 + 7),
}
nodeID++
nb.animationPlayer = NewAnimationPlayer(nb)
return nb
}
// ID returns the object's unique ID.
func (node *Node) ID() uint64 {
return node.id
}
// Name returns the object's name.
func (node *Node) Name() string {
return node.name
}
// SetName sets the object's name.
func (node *Node) SetName(name string) {
node.name = name
}
// Type returns the NodeType for this object.
func (node *Node) Type() NodeType {
return NodeTypeNode
}
// Library returns the Library from which this Node was instantiated. If it was created through code, this will be nil.
func (node *Node) Library() *Library {
return node.library
}
func (node *Node) setLibrary(library *Library) {
node.library = library
}
// Clone returns a new Node.
func (node *Node) Clone() INode {
newNode := NewNode(node.name)
newNode.position = node.position
newNode.scale = node.scale
newNode.rotation = node.rotation.Clone()
newNode.visible = node.visible
newNode.data = node.data
newNode.sectorType = node.sectorType
newNode.setOriginalTransform()
newNode.cachedSector = node.cachedSector
newNode.props = node.props.Clone()
newNode.animationPlayer = node.animationPlayer.Clone()
newNode.library = node.library
if node.animationPlayer.RootNode == node {
newNode.animationPlayer.SetRoot(newNode)
}
for _, child := range node.children {
childClone := child.Clone()
childClone.setParent(newNode)
newNode.children = append(newNode.children, childClone)
}
for _, child := range newNode.children {
if model, isModel := child.(*Model); isModel && model.SkinRoot == node {
model.ReassignBones(newNode)
}
}
newNode.dirtyTransform()
newNode.isBone = node.isBone
if newNode.isBone {
newNode.inverseBindMatrix = node.inverseBindMatrix.Clone()
}
return newNode
}
// SetData sets user-customizeable data that could be usefully stored on this node.
func (node *Node) SetData(data interface{}) {
node.data = data
}
// Data returns a pointer to user-customizeable data that could be usefully stored on this node.
func (node *Node) Data() interface{} {
return node.data
}
// Transform returns a Matrix4 indicating the global position, rotation, and scale of the object, transforming it by any parents'.
// If there's no change between the previous Transform() call and this one, Transform() will return a cached version of the
// transform for efficiency.
func (node *Node) Transform() Matrix4 {
// T * R * S * O
if !node.isTransformDirty {
return node.cachedTransform
}
// TODO: I think I could speed up this area considerably.
transform := NewMatrix4Scale(node.scale.X, node.scale.Y, node.scale.Z)
transform = transform.Mult(node.rotation)
transform = transform.Mult(NewMatrix4Translate(node.position.X, node.position.Y, node.position.Z))
if node.parent != nil {
transform = transform.Mult(node.parent.Transform())
}
node.cachedTransform = transform
node.isTransformDirty = false
if node.isBone {
node.boneInfluence = node.inverseBindMatrix.Mult(transform)
}
if node.onTransformUpdate != nil {
node.onTransformUpdate()
}
// We want to call child.Transform() here to ensure the children also rebuild their transforms as necessary; otherwise,
// children (i.e. BoundingAABBs) may not be rotating along with their owning Nodes (as they don't get rendered).
for _, child := range node.children {
child.Transform()
}
return transform
}
// SetWorldTransform sets the Node's global (world) transform to the full 4x4 transformation matrix provided.
func (node *Node) SetWorldTransform(transform Matrix4) {
position, scale, rotationMatrix := transform.Decompose()
node.SetWorldPositionVec(position)
node.SetWorldScaleVec(scale)
node.SetWorldRotation(rotationMatrix)
}
// dirtyTransform sets this Node and all recursive children's isTransformDirty flags to be true, indicating that they need to be
// rebuilt. This should be called when modifying the transformation properties (position, scale, rotation) of the Node.
func (node *Node) dirtyTransform() {
for _, child := range node.SearchTree().INodes() {
child.dirtyTransform()
}
node.isTransformDirty = true
node.cachedSector = nil
}
// updateLocalTransform updates the local transform properties for a Node given a change in parenting. This is done so that, for example,
// parenting an object with a given postiion, scale, and rotation keeps those visual properties when parenting (by updating them to take into
// account the parent's transforms as well).
// func (node *Node) updateLocalTransform(newParent INode) {
// if newParent != nil {
// parentTransform := newParent.Transform()
// parentPos, parentScale, parentRot := parentTransform.Decompose()
// diff := node.position.Sub(parentPos)
// diff[0] /= parentScale[0]
// diff[1] /= parentScale[1]
// diff[2] /= parentScale[2]
// node.position = parentRot.Transposed().MultVec(diff)
// node.rotation = node.rotation.Mult(parentRot.Transposed())
// node.scale[0] /= parentScale[0]
// node.scale[1] /= parentScale[1]
// node.scale[2] /= parentScale[2]
// } else {
// // Reverse
// parentTransform := node.Parent().Transform()
// parentPos, parentScale, parentRot := parentTransform.Decompose()
// pr := parentRot.MultVec(node.position)
// pr[0] *= parentScale[0]
// pr[1] *= parentScale[1]
// pr[2] *= parentScale[2]
// node.position = parentPos.Add(pr)
// node.rotation = node.rotation.Mult(parentRot)
// node.scale[0] *= parentScale[0]
// node.scale[1] *= parentScale[1]
// node.scale[2] *= parentScale[2]
// }
// node.dirtyTransform()
// }
// LocalPosition returns a 3D Vector consisting of the object's local position (position relative to its parent). If this object has no parent, the position will be
// relative to world origin (0, 0, 0).
func (node *Node) LocalPosition() Vector {
return node.position
}
// ClearLocalTransform clears the local transform properties (position, scale, and rotation) for the Node, reverting it to essentially an
// identity matrix (0, 0, 0 for position, 1, 1, 1 for scale, and an identity Matrix4 for rotation, indicating no rotation).
// This can be useful because by default, when you parent one Node to another, the local transform properties (position,
// scale, and rotation) are altered to keep the object in the same absolute location, even though the origin changes.
func (node *Node) ClearLocalTransform() {
node.position.X = 0
node.position.Y = 0
node.position.Z = 0
node.scale.X = 1
node.scale.Y = 1
node.scale.Z = 1
node.rotation = NewMatrix4()
node.dirtyTransform()
}
// ResetWorldTransform resets the Node's world transform properties (position, scale, and rotation) for the Node to the original
// values when the Node was first instantiated in the Scene or cloned.
func (node *Node) ResetWorldTransform() {
node.SetWorldTransform(node.originalTransform)
node.dirtyTransform()
}
// ResetWorldPosition resets the Node's local position to the value the Node had when
// it was first instantiated in the Scene or cloned.
func (node *Node) ResetWorldPosition() {
p, _, _ := node.originalTransform.Decompose()
node.SetWorldPositionVec(p)
node.dirtyTransform()
}
// ResetWorldScale resets the Node's local scale to the value the Node had when
// it was first instantiated in the Scene or cloned.
func (node *Node) ResetWorldScale() {
_, s, _ := node.originalTransform.Decompose()
node.SetWorldScaleVec(s)
node.dirtyTransform()
}
// ResetWorldRotation resets the Node's local rotation to the value the Node had when
// it was first instantiated in the Scene or cloned.
func (node *Node) ResetWorldRotation() {
_, _, r := node.originalTransform.Decompose()
node.SetWorldRotation(r)
node.dirtyTransform()
}
func (node *Node) setOriginalTransform() {
node.originalTransform = node.Transform()
}
// WorldPosition returns a 3D Vector consisting of the object's world position (position relative to the world origin point of {0, 0, 0}).
func (node *Node) WorldPosition() Vector {
position := node.Transform().Row(3) // We don't want to have to decompose if we don't have to
return position
}
// SetLocalPosition sets the object's local position (position relative to its parent). If this object has no parent, the position should be
// relative to world origin (0, 0, 0). position should be a 3D vector (i.e. X, Y, and Z components).
func (node *Node) SetLocalPosition(x, y, z float64) {
node.position.X = x
node.position.Y = y
node.position.Z = z
node.dirtyTransform()
}
// SetLocalPositionVec sets the object's local position (position relative to its parent). If this object has no parent, the position should be
// relative to world origin (0, 0, 0). position should be a 3D vector (i.e. X, Y, and Z components).
func (node *Node) SetLocalPositionVec(position Vector) {
node.SetLocalPosition(position.X, position.Y, position.Z)
}
// SetWorldPositionVec sets the object's world position (position relative to the world origin point of {0, 0, 0}).
// position needs to be a 3D vector (i.e. X, Y, and Z components).
func (node *Node) SetWorldPositionVec(position Vector) {
if node.parent != nil {
parentTransform := node.parent.Transform()
parentPos, parentScale, parentRot := parentTransform.Decompose()
pr := parentRot.Transposed().MultVec(position.Sub(parentPos))
pr.X /= parentScale.X
pr.Y /= parentScale.Y
pr.Z /= parentScale.Z
node.position = pr
} else {
node.position.X = position.X
node.position.Y = position.Y
node.position.Z = position.Z
}
node.dirtyTransform()
}
// SetWorldPosition sets the object's world position (position relative to the world origin point of {0, 0, 0}).
func (node *Node) SetWorldPosition(x, y, z float64) {
node.SetWorldPositionVec(Vector{x, y, z, 0})
}
// SetWorldX sets the X component of the object's world position.
func (node *Node) SetWorldX(x float64) {
v := node.WorldPosition()
v.X = x
node.SetWorldPositionVec(v)
}
// SetWorldY sets the Y component of the object's world position.
func (node *Node) SetWorldY(y float64) {
v := node.WorldPosition()
v.Y = y
node.SetWorldPositionVec(v)
}
// SetWorldZ sets the Z component of the object's world position.
func (node *Node) SetWorldZ(z float64) {
v := node.WorldPosition()
v.Z = z
node.SetWorldPositionVec(v)
}
// LocalScale returns the object's local scale (scale relative to its parent). If this object has no parent, the scale will be absolute.
func (node *Node) LocalScale() Vector {
return node.scale
}
// SetLocalScaleVec sets the object's local scale (scale relative to its parent). If this object has no parent, the scale would be absolute.
// scale should be a 3D vector (i.e. X, Y, and Z components).
func (node *Node) SetLocalScaleVec(scale Vector) {
node.SetLocalScale(scale.X, scale.Y, scale.Z)
}
// SetLocalScale sets the object's local scale (scale relative to its parent). If this object has no parent, the scale would be absolute.
func (node *Node) SetLocalScale(w, h, d float64) {
node.scale.X = w
node.scale.Y = h
node.scale.Z = d
node.dirtyTransform()
}
// WorldScale returns the object's absolute world scale as a 3D vector (i.e. X, Y, and Z components). Note that this is a bit slow as it
// requires decomposing the node's world transform, so you want to use node.LocalScale() if you can and performacne is a concern.
func (node *Node) WorldScale() Vector {
_, scale, _ := node.Transform().Decompose()
return scale
}
// SetWorldScaleVec sets the object's absolute world scale. scale should be a 3D vector (i.e. X, Y, and Z components).
func (node *Node) SetWorldScaleVec(scale Vector) {
node.SetWorldScale(scale.X, scale.Y, scale.Z)
}
// SetWorldScale sets the object's absolute world scale.
func (node *Node) SetWorldScale(w, h, d float64) {
if node.parent != nil {
parentTransform := node.parent.Transform()
_, parentScale, _ := parentTransform.Decompose()
node.scale = Vector{
w / parentScale.X,
h / parentScale.Y,
d / parentScale.Z,
0,
}
} else {
node.scale.X = w
node.scale.Y = h
node.scale.Z = d
}
node.dirtyTransform()
}
// LocalRotation returns the object's local rotation Matrix4.
func (node *Node) LocalRotation() Matrix4 {
return node.rotation.Clone()
}
// SetLocalRotation sets the object's local rotation Matrix4 (relative to any parent).
func (node *Node) SetLocalRotation(rotation Matrix4) {
if rotation.IsZero() {
return
}
node.rotation.Set(rotation)
node.dirtyTransform()
}
// WorldRotation returns an absolute rotation Matrix4 representing the object's rotation. Note that this is a bit slow as it
// requires decomposing the node's world transform, so you want to use node.LocalRotation() if you can and performacne is a concern.
func (node *Node) WorldRotation() Matrix4 {
_, _, rotation := node.Transform().Decompose()
return rotation
}
// SetWorldRotation sets an object's rotation to the provided rotation Matrix4.
func (node *Node) SetWorldRotation(rotation Matrix4) {
if node.parent != nil {
parentTransform := node.parent.Transform()
_, _, parentRot := parentTransform.Decompose()
node.rotation.Set(parentRot.Transposed().Mult(rotation))
} else {
node.rotation.Set(rotation)
}
node.dirtyTransform()
}
// Move moves a Node in local space by the x, y, and z values provided.
func (node *Node) Move(x, y, z float64) {
if x == 0 && y == 0 && z == 0 {
return
}
node.position.X += x
node.position.Y += y
node.position.Z += z
node.dirtyTransform()
}
// MoveVec moves a Node in local space using the vector provided.
func (node *Node) MoveVec(vec Vector) {
node.Move(vec.X, vec.Y, vec.Z)
}
// Rotate rotates a Node on its local orientation on a vector composed of the given x, y, and z values, by the angle provided in radians.
func (node *Node) Rotate(x, y, z, angle float64) {
if x == 0 && y == 0 && z == 0 {
return
}
localRot := node.LocalRotation()
localRot = localRot.Rotated(x, y, z, angle)
node.SetLocalRotation(localRot)
}
// RotateVec rotates a Node on its local orientation on the given vector, by the angle provided in radians.
func (node *Node) RotateVec(vec Vector, angle float64) {
node.Rotate(vec.X, vec.Y, vec.Z, angle)
}
// Grow scales the object additively using the x, y, and z arguments provided (i.e. calling
// Node.Grow(1, 0, 0) will scale it +1 on the X-axis).
func (node *Node) Grow(x, y, z float64) {
if x == 0 && y == 0 && z == 0 {
return
}
scale := node.LocalScale()
scale.X += x
scale.Y += y
scale.Z += z
node.SetLocalScale(scale.X, scale.Y, scale.Z)
}
// GrowVec scales the object additively using the scaling vector provided (i.e. calling
// Node.GrowVec(Vector{1, 0, 0}) will scale it +1 on the X-axis).
func (node *Node) GrowVec(vec Vector) {
node.Grow(vec.X, vec.Y, vec.Z)
}
// Parent returns the Node's parent. If the Node has no parent, this will return nil.
func (node *Node) Parent() INode {
return node.parent
}
// setParent sets the Node's parent.
func (node *Node) setParent(parent INode) {
node.parent = parent
// fmt.Println(node, "parent:", parent, parent != nil)
if parent != nil {
node.cachedSceneRootNode = parent.Root()
} else {
node.cachedSceneRootNode = nil
}
node.SearchTree().ForEach(func(child INode) bool { child.setCachedSceneRoot(node.cachedSceneRootNode); return true })
}
func (node *Node) setCachedSceneRoot(root *Node) {
node.cachedSceneRootNode = root
}
func (node *Node) cachedSceneRoot() *Node {
return node.cachedSceneRootNode
}
// Scene looks for the Node's parents recursively to return what scene it exists in.
// If the node is not within a tree (i.e. unparented), this will return nil.
func (node *Node) Scene() *Scene {
root := node.Root()
if root != nil {
return root.scene
}
return nil
}
// addChildren adds the children to the parent node, but sets their parent to be the parent node passed. This is done so children have the
// correct, specific Node as parent (because I can't really think of a better way to do this rn). Basically, without this approach,
// after parent.AddChildren(child), child.Parent() wouldn't be parent, but rather parent.Node, which is no good.
func (node *Node) addChildren(parent INode, children ...INode) {
for _, child := range children {
// child.updateLocalTransform(parent)
if child.Parent() != nil {
child.Parent().RemoveChildren(child)
}
child.setParent(parent)
child.dirtyTransform()
node.children = append(node.children, child)
}
}
// AddChildren parents the provided children Nodes to the passed parent Node, inheriting its transformations and being under it in the scenegraph
// hierarchy. If the children are already parented to other Nodes, they are unparented before doing so.
func (node *Node) AddChildren(children ...INode) {
node.addChildren(node, children...)
}
// RemoveChildren removes the provided children from this object.
func (node *Node) RemoveChildren(children ...INode) {
for _, child := range children {
for i, c := range node.children {
if c == child {
// child.updateLocalTransform(nil)
child.setParent(nil)
child.dirtyTransform()
node.children[i] = nil
node.children = append(node.children[:i], node.children[i+1:]...)
break
}
}
}
}
// Unparent unparents the Node from its parent, removing it from the scenegraph. Note that this needs to be overridden for objects that embed Node.
func (node *Node) Unparent() {
if node.parent != nil {
node.parent.RemoveChildren(node)
}
}
// ReindexChild moves the child in the calling Node's children slice to the specified newPosition.
// The function returns the old index where the child Node was, or -1 if it wasn't a child of the calling Node.
// The newPosition is clamped to the size of the node's children slice.
func (node *Node) ReindexChild(child INode, newPosition int) int {
if oldIndex := child.Index(); oldIndex >= 0 {
if newPosition < 0 {
newPosition = 0
} else if newPosition > len(node.children)-1 {
newPosition = len(node.children) - 1
}
node.children = append(node.children[:oldIndex], node.children[oldIndex+1:]...)
node.children = append(node.children, nil)
copy(node.children[newPosition+1:], node.children[newPosition:])
node.children[newPosition] = child
return oldIndex
}
return -1
}
// Index returns the index of the Node in its parent's children list.
// If the node doesn't have a parent, its index will be -1.
func (node *Node) Index() int {
if node.parent != nil {
for i, c := range node.parent.Children() {
if c == node {
return i
}
}
}
return -1
}
// Children returns the Node's children as a slice of INodes.
func (node *Node) Children() []INode {
return append(make([]INode, 0, len(node.children)), node.children...)
}
// SearchTree returns a NodeFilter to search through and filter a Node's hierarchy.
func (node *Node) SearchTree() *NodeFilter {
return newNodeFilter(node)
}
// FindNode searches through a Node's tree for the node by name. This is mostly syntactic sugar for
// Node.SearchTree().ByName(nodeName).First().
func (node *Node) FindNode(nodeName string) INode {
return node.SearchTree().ByName(nodeName).First()
}
// Visible returns whether the Object is visible.
func (node *Node) Visible() bool {
return node.visible
}
// SetVisible sets the object's visibility. If recursive is true, all recursive children of this Node will have their visibility set the same way.
func (node *Node) SetVisible(visible bool, recursive bool) {
if recursive {
for _, child := range node.SearchTree().INodes() {
child.SetVisible(visible, true)
}
}
node.visible = visible
}
// Properties represents an unordered set of game properties that can be used to identify this object.
func (node *Node) Properties() Properties {
return node.props
}
// HierarchyAsString returns a string displaying the hierarchy of this Node, and all recursive children.
// This is a useful function to debug the layout of a node tree, for example.
// All Nodes except for the top-level Node will show their type by means of a prefix ("MODEL" for Models, for example).
// All Nodes listed in the hierarchy will also show their world positions, truncated to the first 2 decimals.
func (node *Node) HierarchyAsString() string {
var printNode func(node INode, level int) string
printNode = func(node INode, level int) string {
prefix := ""
// We do this because calling Node.HierarchyAsString() will always return the base Node's
// type, which is NodeTypeNode, unless we override this function, essentially, for each
// "more specific type". To avoid doing this, I'm just going to have the first level node
// look like : [-] .
if level == 0 {
prefix = "ROOT"
} else {
nodeType := node.Type()
if nodeType.Is(NodeTypeCamera) {
prefix = "CAM"
} else if nodeType.Is(NodeTypePath) {
prefix = "PATH"
} else if nodeType.Is(NodeTypeGrid) {
prefix = "GRID"
} else if nodeType.Is(NodeTypeGridPoint) {
prefix = "GPOINT"
} else if nodeType.Is(NodeTypeAmbientLight) {
prefix = "AMB"
} else if nodeType.Is(NodeTypeDirectionalLight) {
prefix = "DIR"
} else if nodeType.Is(NodeTypePointLight) {
prefix = "POINT"
} else if nodeType.Is(NodeTypeCubeLight) {
prefix = "CUBE"
} else if nodeType.Is(NodeTypeBoundingSphere) {
prefix = "BS"
} else if nodeType.Is(NodeTypeBoundingAABB) {
prefix = "AABB"
} else if nodeType.Is(NodeTypeBoundingCapsule) {
prefix = "CAP"
} else if nodeType.Is(NodeTypeBoundingTriangles) {
prefix = "TRI"
} else if nodeType.Is(NodeTypeModel) {
prefix = "MODEL"
} else {
prefix = "NODE"
}
}
str := ""
if node.Parent() != nil {
for i := 0; i < level; i++ {
str += " |"
}
str += "\n"
}
for i := 0; i < level; i++ {
str += " |"
}
wp := node.WorldPosition()
floatTruncation := 2
wpStr := "[" + strconv.FormatFloat(wp.X, 'f', floatTruncation, 64) + ", " + strconv.FormatFloat(wp.Y, 'f', floatTruncation, 64) + ", " + strconv.FormatFloat(wp.Z, 'f', floatTruncation, 64) + "]"
if level > 0 {
str += "-"
}
str += " [" + prefix + "] " + node.Name() + " : " + wpStr + "\n"
for _, child := range node.Children() {
str += printNode(child, level+1)
}
return str
}
return printNode(node, 0)
}
// If enabled, Nodes, Materials, and other object types will be represented by their names when
// printed directly. Otherwise, they will be represented by their pointer locations, like default.
var ReadableReferences = true
func (node *Node) String() string {
if ReadableReferences {
path := node.Path()
if path == "" {
path = "{ no path }"
}
return "< " + node.Name() + " : " + path + " : " + fmt.Sprintf("%d", node.id) + " >"
} else {
return fmt.Sprintf("%p", node)
}
}
// Get searches a node's hierarchy using a string to find a specified node. The path is in the format of names of nodes, separated by forward
// slashes ('/'), and is relative to the node you use to call Get. As an example of Get, if you had a cup parented to a desk, which was
// parented to a room, that was finally parented to the root of the scene, it would be found at "Room/Desk/Cup". Note also that you can use "../" to
// "go up one" in the hierarchy (so cup.Get("../") would return the Desk node).
// Since Get uses forward slashes as path separation, it would be good to avoid using forward slashes in your Node names. Also note that Get()
// trims the extra spaces from the beginning and end of Node Names, so avoid using spaces at the beginning or end of your Nodes' names.
func (node *Node) Get(path string) INode {
var search func(node INode) INode
split := []string{}
for _, s := range strings.Split(path, `/`) {
if len(strings.TrimSpace(s)) > 0 {
split = append(split, s)
}
}
search = func(node INode) INode {
if node == nil {
return nil
} else if len(split) == 0 {
return node
}
if split[0] == ".." {
split = split[1:]
return search(node.Parent())
}
for _, child := range node.Children() {
if child.Name() == split[0] {
if len(split) <= 1 {
return child
} else {
split = split[1:]
return search(child)
}
}
}
return nil
}
return search(node)
}
// Path returns a string indicating the hierarchical path to get this Node from the root. The path returned will be absolute, such that
// passing it to Get() called on the scene root node will return this node. The path returned will not contain the root node's name ("Root").
// If the Node is not in a scene tree (i.e. does not have a path to a root node), Path() will return a blank string.
func (node *Node) Path() string {
root := node.Root()
if root == nil {
return ""
}
parent := node.Parent()
path := node.Name()
for parent != nil && parent != root {
path = parent.Name() + "/" + path
parent = parent.Parent()
}
return path
}
// Root returns the root node in the scene by recursively traversing this node's hierarchy of
// parents upwards. If, instead, the node this is called on is the root (and so has no parents), this function
// returns the node itself. If the node has no path to the scene root, this function returns nil.
func (node *Node) Root() *Node {
if node.cachedSceneRootNode != nil {
return node.cachedSceneRootNode
}
if node.parent != nil {
parent := node.parent
for parent != nil {
if parent == parent.cachedSceneRoot() {
node.cachedSceneRootNode = parent.(*Node)
return node.cachedSceneRootNode
}
parent = parent.Parent()
}
}
return nil
}
func (node *Node) InSceneTree() bool {
return node.Root() != nil
}
// IsBone returns if the Node is a "bone" (a node that was a part of an armature and so can play animations back to influence a skinned mesh).
func (node *Node) IsBone() bool {
return node.isBone
}
// // IsRootBone returns if the Node SHOULD be the root of an Armature (a Node that was the base of an armature).
// func (node *Node) IsRootBone() bool {
// return node.IsBone() && (node.parent == nil || !node.parent.IsBone())
// }
// AnimationPlayer returns the object's animation player - every object has an AnimationPlayer by default.
func (node *Node) AnimationPlayer() *AnimationPlayer {
return node.animationPlayer
}
func (node *Node) sectorHierarchy() *Sector {
if node.parent != nil {
model, ok := node.parent.(*Model)
if ok && model.sector != nil {
return model.sector
} else {
parentSector := node.parent.sectorHierarchy()
if parentSector != nil {
return parentSector
}
}
}
return nil
}
// Sector returns the Sector this node is in hierarchically. If that fails, then
// Sector() will search the scene tree spatially to see which of the sectors the
// calling Node lies in.
func (node *Node) Sector() *Sector {
if node.cachedSector != nil {
return node.cachedSector
}
if sectorHierarchy := node.sectorHierarchy(); sectorHierarchy != nil {
node.cachedSector = sectorHierarchy
} else {
root := node.Root()
if root == nil {
node.cachedSector = nil
} else {
pos := node.WorldPosition()
root.SearchTree().bySectors().ForEach(func(child INode) bool {
sectorModel := child.(*Model)
if sectorModel.sector.AABB.PointInside(pos) {
node.cachedSector = sectorModel.sector
return false
}
return true
})
}
}
return node.cachedSector
}
func (node *Node) isInVisibleSector(sectorModels []*Model) bool {
for _, model := range sectorModels {
if model.sector.rendering && model.sector.AABB.PointInside(node.WorldPosition()) {
return true
}
}
return false
}
// SectorType returns the current SectorType of the Node.
func (node *Node) SectorType() SectorType {
return node.sectorType
}
// SetSectorType sets the current SectorType of the Node. Note that setting this to Sector won't work.
func (node *Node) SetSectorType(sectorType SectorType) {
node.sectorType = sectorType
}
// DistanceTo returns the distance between the given Nodes' centers.
// Quick syntactic sugar for Node.WorldPosition().Distance(otherNode.WorldPosition()).
func (node *Node) DistanceTo(other INode) float64 {
return node.WorldPosition().Distance(other.WorldPosition())
}
// DistanceSquaredTo returns the squared distance between the given Nodes' centers.
// Quick syntactic sugar for Node.WorldPosition().DistanceSquared(otherNode.WorldPosition()).
func (node *Node) DistanceSquaredTo(other INode) float64 {
return node.WorldPosition().DistanceSquared(other.WorldPosition())
}