2025-01-22 16:18:30 +01:00

365 lines
8.5 KiB
Haxe

package iron.object;
import iron.math.Mat4;
import iron.math.Vec4;
import iron.math.Quat;
class Transform {
/**
The world matrix (read-only).
**/
public var world: Mat4;
/**
Prevent applying parent matrix.
**/
public var localOnly = false;
/**
The local matrix. If you modify this, call `decompose()` to update the
`loc`, `rot` and `scale` fields, or `buildMatrix()` to update
everything.
**/
public var local: Mat4;
/**
The local translation. Changes to this field should be applied by
calling `buildMatrix()`.
**/
public var loc: Vec4;
/**
The local rotation. Changes to this field should be applied by
calling `buildMatrix()`.
**/
public var rot: Quat;
/**
The local scale. Changes to this field should be applied by
calling `buildMatrix()`.
**/
public var scale: Vec4;
/**
Uniform scale factor for `world` matrix.
**/
public var scaleWorld: kha.FastFloat = 1.0;
/**
The world matrix with `scaleWorld` applied (read-only).
**/
public var worldUnpack: Mat4;
/**
Flag to rebuild the `world` matrix on next update.
**/
public var dirty: Bool;
/**
The object that is effected by this transform.
**/
public var object: Object;
/**
The dimensions of the object in local space (without parent, prepended
or appended matrices applied).
**/
public var dim: Vec4;
/**
The radius of the smallest sphere that encompasses the object in local
space.
**/
public var radius: kha.FastFloat;
static var temp = Mat4.identity();
static var q = new Quat();
var boneParent: Mat4 = null;
var lastWorld: Mat4 = null;
// Wrong order returned from getEuler(), store last state for animation
var _eulerX: kha.FastFloat;
var _eulerY: kha.FastFloat;
var _eulerZ: kha.FastFloat;
// Animated delta transform
var dloc: Vec4 = null;
var drot: Quat = null;
var dscale: Vec4 = null;
var _deulerX: kha.FastFloat;
var _deulerY: kha.FastFloat;
var _deulerZ: kha.FastFloat;
public function new(object: Object) {
this.object = object;
reset();
}
/**
Reset to a null transform: zero location and rotation, and a uniform
scale of one. Other fields such as prepended matrices and bone parents
will not be changed.
**/
public function reset() {
world = Mat4.identity();
worldUnpack = Mat4.identity();
local = Mat4.identity();
loc = new Vec4();
rot = new Quat();
scale = new Vec4(1.0, 1.0, 1.0);
dim = new Vec4(2.0, 2.0, 2.0);
radius = 1.0;
dirty = true;
}
/**
Rebuild the matrices, if needed.
**/
public function update() {
if (dirty) buildMatrix();
}
/**
Clear delta transforms. `dloc`, `drot` and `dscale` are set to `null`
**/
public function clearDelta() {
dloc = null;
drot = null;
dscale = null;
}
/**
Reset delta transforms. `dloc`, `drot` and `dscale`
are set to `Vec4(0, 0, 0)`, `Quat(0, 0, 0, 0)` and `Vec4(1, 1, 1)` respectively
**/
public function resetDelta() {
dloc = new Vec4();
drot = new Quat();
_deulerX = _deulerY = _deulerZ = 0.0;
dscale = new Vec4().set(1, 1, 1);
}
function composeDelta() {
// Delta transform
var dl = new Vec4().addvecs(loc, dloc);
var ds = new Vec4().setFrom(scale);
ds.x *= dscale.x;
ds.y *= dscale.y;
ds.z *= dscale.z;
var dr = new Quat().fromEuler(_deulerX, _deulerY, _deulerZ);
dr.multquats(dr, rot);
dr.multquats(drot, dr);
local.compose(dl, dr, ds);
}
/**
Update the transform matrix based on `loc`, `rot`, and `scale`. If any
change is made to `loc`, `rot`, or `scale` `buildMatrix()` must be
called to update the objects transform.
**/
public function buildMatrix() {
dloc == null ? local.compose(loc, rot, scale) : composeDelta();
if (boneParent != null) local.multmats(boneParent, local);
if (object.parent != null && !localOnly) {
world.multmats3x4(local, object.parent.transform.world);
}
else {
world.setFrom(local);
}
worldUnpack.setFrom(world);
if (scaleWorld != 1.0) {
worldUnpack._00 *= scaleWorld;
worldUnpack._01 *= scaleWorld;
worldUnpack._02 *= scaleWorld;
worldUnpack._03 *= scaleWorld;
worldUnpack._10 *= scaleWorld;
worldUnpack._11 *= scaleWorld;
worldUnpack._12 *= scaleWorld;
worldUnpack._13 *= scaleWorld;
worldUnpack._20 *= scaleWorld;
worldUnpack._21 *= scaleWorld;
worldUnpack._22 *= scaleWorld;
worldUnpack._23 *= scaleWorld;
}
// Constraints
if (object.constraints != null) for (c in object.constraints) c.apply(this);
computeDim();
// Update children
for (n in object.children) {
n.transform.buildMatrix();
}
dirty = false;
}
/**
Move the game Object by the defined amount relative to its current location.
@param x Amount to move on the local x axis.
@param y Amount to move on the local y axis.
@param z Amount to move on the local z axis.
**/
public function translate(x: kha.FastFloat, y: kha.FastFloat, z: kha.FastFloat) {
loc.x += x;
loc.y += y;
loc.z += z;
buildMatrix();
}
/**
Set the local matrix and update `loc`, `rot`, `scale` and `world`.
@param mat The new local matrix.
**/
public function setMatrix(mat: Mat4) {
local.setFrom(mat);
decompose();
buildMatrix();
}
/**
Apply another transform to this one, i.e. multiply this transform's
local matrix by another.
@param mat The other transform to apply.
**/
public function multMatrix(mat: Mat4) {
local.multmat(mat);
decompose();
buildMatrix();
}
/**
Update the `loc`, `rot` and `scale` fields according to the local
matrix. You may need to call this after directly mutating the local
matrix.
**/
public function decompose() {
local.decompose(loc, rot, scale);
}
/**
Rotate around an axis.
@param axis The axis to rotate around.
@param f The magnitude of the rotation in radians.
**/
public function rotate(axis: Vec4, f: kha.FastFloat) {
q.fromAxisAngle(axis, f);
rot.multquats(q, rot);
buildMatrix();
}
/**
Apply a scaled translation in local space.
@param axis The direction to move.
@param f A multiplier for the movement. If `axis` is a unit
vector, then this is the distance to move.
**/
public function move(axis: Vec4, f = 1.0) {
loc.addf(axis.x * f, axis.y * f, axis.z * f);
buildMatrix();
}
/**
Set the rotation of the object in radians.
@param x Set the x axis rotation in radians.
@param y Set the y axis rotation in radians.
@param z Set the z axis rotation in radians.
**/
public function setRotation(x: kha.FastFloat, y: kha.FastFloat, z: kha.FastFloat) {
rot.fromEuler(x, y, z);
_eulerX = x;
_eulerY = y;
_eulerZ = z;
dirty = true;
}
function computeRadius() {
radius = Math.sqrt(dim.x * dim.x + dim.y * dim.y + dim.z * dim.z);
}
function computeDim() {
if (object.raw == null) {
computeRadius();
return;
}
var d = object.raw.dimensions;
if (d == null) dim.set(2 * scale.x, 2 * scale.y, 2 * scale.z);
else dim.set(d[0] * scale.x, d[1] * scale.y, d[2] * scale.z);
computeRadius();
}
public function applyParentInverse() {
var pt = object.parent.transform;
pt.buildMatrix();
temp.getInverse(pt.world);
this.local.multmat(temp);
this.decompose();
this.buildMatrix();
}
public function applyParent() {
var pt = object.parent.transform;
pt.buildMatrix();
this.local.multmat(pt.world);
this.decompose();
this.buildMatrix();
}
/**
Check whether the transform has changed at all since the last time
this function was called.
@return `true` if the transform has changed.
**/
public function diff(): Bool {
if (lastWorld == null) {
lastWorld = Mat4.identity().setFrom(world);
return false;
}
var a = world;
var b = lastWorld;
var r = a._00 != b._00 || a._01 != b._01 || a._02 != b._02 || a._03 != b._03 ||
a._10 != b._10 || a._11 != b._11 || a._12 != b._12 || a._13 != b._13 ||
a._20 != b._20 || a._21 != b._21 || a._22 != b._22 || a._23 != b._23 ||
a._30 != b._30 || a._31 != b._31 || a._32 != b._32 || a._33 != b._33;
if (r) lastWorld.setFrom(world);
return r;
}
/**
@return The look vector (positive local y axis) in world space.
**/
public inline function look(): Vec4 {
return world.look();
}
/**
@return The right vector (positive local x axis) in world space.
**/
public inline function right(): Vec4 {
return world.right();
}
/**
@return The up vector (positive local z axis) in world space.
**/
public inline function up(): Vec4 {
return world.up();
}
/**
@return The world x location.
**/
public inline function worldx(): kha.FastFloat {
return world._30;
}
/**
@return The world y location.
**/
public inline function worldy(): kha.FastFloat {
return world._31;
}
/**
@return The world z location.
**/
public inline function worldz(): kha.FastFloat {
return world._32;
}
}