274 lines
5.7 KiB
Go
274 lines
5.7 KiB
Go
package transform
|
|
|
|
import (
|
|
"zworld/plugins/math/mat4"
|
|
"zworld/plugins/math/quat"
|
|
"zworld/plugins/math/vec3"
|
|
"zworld/plugins/system/events"
|
|
)
|
|
|
|
type T interface {
|
|
Forward() vec3.T
|
|
Right() vec3.T
|
|
Up() vec3.T
|
|
|
|
Position() vec3.T
|
|
SetPosition(vec3.T)
|
|
|
|
Rotation() quat.T
|
|
SetRotation(quat.T)
|
|
|
|
Scale() vec3.T
|
|
SetScale(vec3.T)
|
|
|
|
Matrix() mat4.T
|
|
|
|
ProjectDir(dir vec3.T) vec3.T
|
|
|
|
// Project local coordinates to world coordinates
|
|
Project(point vec3.T) vec3.T
|
|
|
|
// Unproject world coordinates to local coordinates
|
|
Unproject(point vec3.T) vec3.T
|
|
|
|
UnprojectDir(dir vec3.T) vec3.T
|
|
|
|
WorldPosition() vec3.T
|
|
SetWorldPosition(vec3.T)
|
|
|
|
WorldScale() vec3.T
|
|
SetWorldScale(vec3.T)
|
|
|
|
WorldRotation() quat.T
|
|
SetWorldRotation(quat.T)
|
|
|
|
Parent() T
|
|
SetParent(T)
|
|
OnChange() *events.Event[T]
|
|
}
|
|
|
|
// Transform represents a 3D transformation
|
|
type transform struct {
|
|
position vec3.T
|
|
scale vec3.T
|
|
rotation quat.T
|
|
|
|
wposition vec3.T
|
|
wscale vec3.T
|
|
wrotation quat.T
|
|
|
|
matrix mat4.T
|
|
right vec3.T
|
|
up vec3.T
|
|
forward vec3.T
|
|
|
|
inv *mat4.T
|
|
dirty bool
|
|
parent T
|
|
changed events.Event[T]
|
|
unsub func()
|
|
}
|
|
|
|
// NewTransform creates a new 3D transform
|
|
func New(position vec3.T, rotation quat.T, scale vec3.T) T {
|
|
t := &transform{
|
|
matrix: mat4.Ident(),
|
|
position: position,
|
|
rotation: rotation,
|
|
scale: scale,
|
|
dirty: true,
|
|
}
|
|
t.refresh()
|
|
return t
|
|
}
|
|
|
|
// Identity returns a new transform that does nothing.
|
|
func Identity() T {
|
|
return New(vec3.Zero, quat.Ident(), vec3.One)
|
|
}
|
|
|
|
func (t *transform) Parent() T { return t.parent }
|
|
func (t *transform) SetParent(parent T) {
|
|
// check for cycles
|
|
ancestor := parent
|
|
for ancestor != nil {
|
|
if ancestor.(T) == t {
|
|
panic("cyclical transform hierarchies are not allowed")
|
|
}
|
|
ancestor = ancestor.Parent()
|
|
}
|
|
|
|
// todo: we might want to maintain world transform when attaching/detaching
|
|
|
|
// detach from previous parent (if any)
|
|
if t.unsub != nil {
|
|
// unsub
|
|
t.unsub()
|
|
t.unsub = nil
|
|
}
|
|
|
|
t.parent = parent
|
|
|
|
// attach to new parent (if any)
|
|
if t.parent != nil {
|
|
t.unsub = t.parent.OnChange().Subscribe(func(parent T) {
|
|
// mark as dirty on parent change
|
|
t.invalidate()
|
|
})
|
|
}
|
|
|
|
t.invalidate()
|
|
}
|
|
|
|
func (t *transform) OnChange() *events.Event[T] {
|
|
return &t.changed
|
|
}
|
|
|
|
func (t *transform) invalidate() {
|
|
t.dirty = true
|
|
t.changed.Emit(t)
|
|
}
|
|
|
|
// Update transform matrix and its right/up/forward vectors
|
|
func (t *transform) refresh() {
|
|
if !t.dirty {
|
|
return
|
|
}
|
|
|
|
position := t.position
|
|
rotation := t.rotation
|
|
scale := t.scale
|
|
|
|
if t.parent != nil {
|
|
scale = scale.Mul(t.parent.WorldScale())
|
|
|
|
rotation = rotation.Mul(t.parent.WorldRotation())
|
|
|
|
position = t.parent.WorldRotation().Rotate(t.parent.WorldScale().Mul(position))
|
|
position = t.parent.WorldPosition().Add(position)
|
|
}
|
|
|
|
// calculate basis vectors
|
|
t.right = rotation.Rotate(vec3.Right)
|
|
t.up = rotation.Rotate(vec3.Up)
|
|
t.forward = rotation.Rotate(vec3.Forward)
|
|
|
|
// apply scaling
|
|
x := t.right.Scaled(scale.X)
|
|
y := t.up.Scaled(scale.Y)
|
|
z := t.forward.Scaled(scale.Z)
|
|
|
|
// create transformation matrix
|
|
p := position
|
|
t.matrix = mat4.T{
|
|
x.X, x.Y, x.Z, 0,
|
|
y.X, y.Y, y.Z, 0,
|
|
z.X, z.Y, z.Z, 0,
|
|
p.X, p.Y, p.Z, 1,
|
|
}
|
|
|
|
// save world transforms
|
|
t.wposition = position
|
|
t.wscale = scale
|
|
t.wrotation = rotation
|
|
|
|
// mark as clean
|
|
t.dirty = false
|
|
|
|
// clear inversion cache
|
|
t.inv = nil
|
|
}
|
|
|
|
func (t *transform) inverse() *mat4.T {
|
|
t.refresh()
|
|
if t.inv == nil {
|
|
inv := t.matrix.Invert()
|
|
t.inv = &inv
|
|
}
|
|
return t.inv
|
|
}
|
|
|
|
func (t *transform) Project(point vec3.T) vec3.T {
|
|
t.refresh()
|
|
return t.matrix.TransformPoint(point)
|
|
}
|
|
|
|
func (t *transform) Unproject(point vec3.T) vec3.T {
|
|
return t.inverse().TransformPoint(point)
|
|
}
|
|
|
|
func (t *transform) ProjectDir(dir vec3.T) vec3.T {
|
|
t.refresh()
|
|
return t.matrix.TransformDir(dir)
|
|
}
|
|
|
|
func (t *transform) UnprojectDir(dir vec3.T) vec3.T {
|
|
return t.inverse().TransformDir(dir)
|
|
}
|
|
|
|
func (t *transform) WorldPosition() vec3.T { t.refresh(); return t.wposition }
|
|
func (t *transform) WorldScale() vec3.T { t.refresh(); return t.wscale }
|
|
func (t *transform) WorldRotation() quat.T { t.refresh(); return t.wrotation }
|
|
|
|
func (t *transform) SetWorldPosition(wp vec3.T) {
|
|
t.refresh()
|
|
if t.parent != nil {
|
|
t.position = t.parent.Unproject(wp)
|
|
} else {
|
|
t.position = wp
|
|
}
|
|
t.invalidate()
|
|
}
|
|
|
|
func (t *transform) SetWorldScale(wscale vec3.T) {
|
|
t.refresh()
|
|
if t.parent != nil {
|
|
// world_scale = parent_scale * local_scale
|
|
// local_scale = world_scale / parent_scale
|
|
t.scale = wscale.Div(t.parent.WorldScale())
|
|
} else {
|
|
t.scale = wscale
|
|
}
|
|
t.invalidate()
|
|
}
|
|
|
|
func (t *transform) SetWorldRotation(rot quat.T) {
|
|
t.refresh()
|
|
if t.parent != nil {
|
|
t.rotation = rot.Mul(t.parent.WorldRotation().Inverse())
|
|
} else {
|
|
t.rotation = rot
|
|
}
|
|
t.invalidate()
|
|
}
|
|
|
|
func (t *transform) Matrix() mat4.T { t.refresh(); return t.matrix }
|
|
func (t *transform) Right() vec3.T { t.refresh(); return t.right }
|
|
func (t *transform) Up() vec3.T { t.refresh(); return t.up }
|
|
func (t *transform) Forward() vec3.T { t.refresh(); return t.forward }
|
|
|
|
func (t *transform) Position() vec3.T { t.refresh(); return t.position }
|
|
func (t *transform) Rotation() quat.T { t.refresh(); return t.rotation }
|
|
func (t *transform) Scale() vec3.T { t.refresh(); return t.scale }
|
|
func (t *transform) SetPosition(p vec3.T) { t.position = p; t.invalidate() }
|
|
func (t *transform) SetRotation(r quat.T) { t.rotation = r; t.invalidate() }
|
|
func (t *transform) SetScale(s vec3.T) { t.scale = s; t.invalidate() }
|
|
|
|
func Matrix(position vec3.T, rotation quat.T, scale vec3.T) mat4.T {
|
|
x := rotation.Rotate(vec3.Right)
|
|
y := rotation.Rotate(vec3.Up)
|
|
z := rotation.Rotate(vec3.Forward)
|
|
|
|
x.Scale(scale.X)
|
|
y.Scale(scale.Y)
|
|
z.Scale(scale.Z)
|
|
|
|
p := position
|
|
return mat4.T{
|
|
x.X, x.Y, x.Z, 0,
|
|
y.X, y.Y, y.Z, 0,
|
|
z.X, z.Y, z.Z, 0,
|
|
p.X, p.Y, p.Z, 1,
|
|
}
|
|
}
|