import { EventDispatcher } from '../events/EventDispatcher'; import { TransformEvent } from '../events/TransformEvent'; import { Matrix } from '../geom/Matrix'; import { Matrix3D } from '../geom/Matrix3D'; import { Rectangle } from '../geom/Rectangle'; import { Vector3D } from '../geom/Vector3D'; import { ColorTransform } from './ColorTransform'; /** * The Transform class provides access to color adjustment properties and two- * or three-dimensional transformation objects that can be applied to a display * object. During the transformation, the color or the orientation and position * of a display object is adjusted(offset) from the current values or * coordinates to new values or coordinates. The Transform class also collects * data about color and two-dimensional matrix transformations that are applied * to a display object and all of its parent objects. You can access these * combined transformations through the `concatenatedColorTransform` and * `concatenatedMatrix` properties. * * To apply color transformations: create a ColorTransform object, set the color * adjustments using the object's methods and properties, and then assign the * `colorTransformation` property of the `transform` property of the display * object to the new ColorTransformation object. * * To apply two-dimensional transformations: create a Matrix object, set the * matrix's two-dimensional transformation, and then assign the * `transform.matrix` property of the display object to the new Matrix object. * * To apply three-dimensional transformations: start with a three-dimensional * display object. A three-dimensional display object has a `z` property value * other than zero. You do not need to create the Matrix3D object. For all * three-dimensional objects, a Matrix3D object is created automatically when * you assign a `z` value to a display object. You can access the display * object's Matrix3D object through the display object's `transform` property. * Using the methods of the Matrix3D class, you can add to or modify the * existing transformation settings. Also, you can create a custom Matrix3D * object, set the custom Matrix3D object's transformation elements, and then * assign the new Matrix3D object to the display object using the * `transform.matrix` property. * * To modify a perspective projection of the stage or root object: use the * `transform.matrix` property of the root display object to gain access to the * PerspectiveProjection object. Or, apply different perspective projection * properties to a display object by setting the perspective projection * properties of the display object's parent. The child display object inherits * the new properties. Specifically, create a PerspectiveProjection object and * set its properties, then assign the PerspectiveProjection object to the * `perspectiveProjection` property of the parent display object's `transform` * property. The specified projection transformation then applies to all the * display object's three-dimensional children. * * Since both PerspectiveProjection and Matrix3D objects perform perspective * transformations, do not assign both to a display object at the same time. Use * the PerspectiveProjection object for focal length and projection center * changes. For more control over the perspective transformation, create a * perspective projection Matrix3D object. */ export class Transform extends EventDispatcher { private static _tempVector: Vector3D = new Vector3D(); private _backVector: Vector3D; private _colorTransform: ColorTransform; private _components: Array; private _componentsDirty: boolean = true; private _downVector: Vector3D; private _forwardVector: Vector3D; private _invalidateColorTransform: TransformEvent; private _invalidateMatrix3D: TransformEvent; private _inverseMatrix3D: Matrix3D; private _inverseMatrix3DDirty: boolean = true; private _leftVector: Vector3D; private _matrix: Matrix; private _matrix3D: Matrix3D; private _matrix3DDirty: boolean; private _pixelBounds: Rectangle; private _rawData: Float32Array; private _rawDataComponents: Float32Array; private _rightVector: Vector3D; private _rotation: Vector3D; private _scale: Vector3D; private _skew: Vector3D; private _upVector: Vector3D; /** * */ public get backVector(): Vector3D { if (!this._backVector) this._backVector = new Vector3D(); this._matrix3D.copyColumnTo(2, this._backVector, true); return this._backVector; } /** * A ColorTransform object containing values that universally adjust the * colors in the display object. * * @throws TypeError The colorTransform is null when being set */ public get colorTransform(): ColorTransform { return this._colorTransform; } public set colorTransform(val: ColorTransform) { if (val) { this._rawData.set(val._rawData, 16); } else { this._colorTransform.clear(); } this.invalidateColorTransform(); } /** * */ public get inverseMatrix3D(): Matrix3D { if (!this._inverseMatrix3D) this._inverseMatrix3D = new Matrix3D(); if (this._inverseMatrix3DDirty) { this._inverseMatrix3DDirty = false; this._inverseMatrix3D.copyFrom(this.matrix3D); this._inverseMatrix3D.invert(); } return this._inverseMatrix3D; } /** * */ public get downVector(): Vector3D { if (!this._downVector) this._downVector = new Vector3D(); this._matrix3D.copyColumnTo(1, this._downVector, true); return this._downVector; } /** * */ public get forwardVector(): Vector3D { if (!this._forwardVector) this._forwardVector = new Vector3D(); this._matrix3D.copyColumnTo(2, this._forwardVector); return this._forwardVector; } /** * */ public get leftVector(): Vector3D { if (!this._leftVector) this._leftVector = new Vector3D(); this._matrix3D.copyColumnTo(0, this._backVector, true); return this._leftVector; } /** * A Matrix object containing values that alter the scaling, rotation, and * translation of the display object. If the `matrix` property is set to a * value (not `null`), the `matrix3D` property is `null`. And if the * `matrix3D` property is set to a value(not `null`), the `matrix` property * is `null`. * * @throws TypeError The matrix is null when being set */ public get matrix(): Matrix { if (!this._matrix) this._matrix = new Matrix(); if (this._matrix3DDirty) this.updateMatrix3D(); this._matrix.a = this._matrix3D._rawData[0]; this._matrix.b = this._matrix3D._rawData[1]; this._matrix.c = this._matrix3D._rawData[4]; this._matrix.d = this._matrix3D._rawData[5]; this._matrix.tx = this._matrix3D._rawData[12]; this._matrix.ty = this._matrix3D._rawData[13]; return this._matrix; } public set matrix(value: Matrix) { if (!value) return; this._matrix3D.identity(); this._matrix3D._rawData[0] = value.a; this._matrix3D._rawData[1] = value.b; this._matrix3D._rawData[4] = value.c; this._matrix3D._rawData[5] = value.d; this._matrix3D._rawData[12] = value.tx; this._matrix3D._rawData[13] = value.ty; this.invalidateComponents(); this._matrix3DDirty = false; this._inverseMatrix3DDirty = true; } /** * Provides access to the Matrix3D object of a three-dimensional display * object. The Matrix3D object represents a transformation matrix that * determines the display object's position and orientation. A Matrix3D * object can also perform perspective projection. * * If the `matrix` property is set to a value(not `null`), the `matrix3D` * property is `null`. And if the `matrix3D` property is set to a value(not * `null`), the `matrix` property is `null`. */ public get matrix3D(): Matrix3D { if (this._matrix3DDirty) this.updateMatrix3D(); return this._matrix3D; } public set matrix3D(val: Matrix3D) { if (!val) { this._matrix3D.identity(); } else { this._rawData.set(val._rawData); } this.invalidateComponents(); this._matrix3DDirty = false; this._inverseMatrix3DDirty = true; } /** * A Rectangle object that defines the bounding rectangle of the display * object on the stage. */ public get pixelBounds(): Rectangle { return this._pixelBounds; } /** * Defines the position of the 3d object, relative to the local coordinates * of the parent `ObjectContainer3D`. */ public get position(): Vector3D { return this._matrix3D.position; } /** * */ public get rightVector(): Vector3D { if (!this._rightVector) this._rightVector = new Vector3D(); this._matrix3D.copyColumnTo(0, this._rightVector); return this._rightVector; } /** * Defines the rotation of the 3d object, relative to the local coordinates * of the parent `ObjectContainer3D` . */ public get rotation(): Vector3D { if (this._componentsDirty) this._updateComponents(); return this._rotation; } /** * Defines the scale of the 3d object, relative to the local coordinates of * the parent `ObjectContainer3D` . */ public get scale(): Vector3D { if (this._componentsDirty) this._updateComponents(); return this._scale; } /** * Defines the scale of the 3d object, relative to the local coordinates of * the parent `ObjectContainer3D` . */ public get skew(): Vector3D { if (this._componentsDirty) this._updateComponents(); return this._skew; } /** * */ public get upVector(): Vector3D { if (!this._upVector) this._upVector = new Vector3D(); this._matrix3D.copyColumnTo(1, this._upVector); return this._upVector; } constructor(rawData: Float32Array = null) { super(); this._rawData = rawData || new Float32Array(24); //create the view for matrix3D this._matrix3D = new Matrix3D(new Float32Array(this._rawData.buffer, 0, 16)); //create the view for colorTransform this._colorTransform = new ColorTransform(new Float32Array(this._rawData.buffer, 64, 8)); if (rawData == null) { this._matrix3D.identity(); this._colorTransform.clear(); } } /** * * @param value */ public append(value: Matrix3D) { console.log(`append(${value}) is not implemented yet in core/Transform`); } /** * */ public clearColorTransform(): void { if (!this._colorTransform) return; this._colorTransform.clear(); this.invalidateColorTransform(); } /** * */ public clearMatrix3D(): void { this._matrix3D.identity(); this.invalidateComponents(); } /** * */ public clone(): Transform { const transform: Transform = new Transform(); this.copyRawDataTo(transform); return transform; } /** * * @param transform */ public copyRawDataTo(transform: Transform) { if (this._matrix3DDirty) this.updateMatrix3D(); transform._rawData.set(this._rawData); this.invalidateComponents(); this.invalidateColorTransform(); } /** * */ public dispose(): void { } public invalidateColorTransform(): void { if (!this._invalidateColorTransform) { this._invalidateColorTransform = new TransformEvent(TransformEvent.INVALIDATE_COLOR_TRANSFORM, this); } this.dispatchEvent(this._invalidateColorTransform); } public invalidateComponents(): void { this.invalidatePosition(); this._componentsDirty = true; } /** * Invalidates the 3D transformation matrix, causing it to be updated upon the next request * * @private */ public invalidateMatrix3D(): void { this._inverseMatrix3DDirty = true; if (this._matrix3DDirty) return; this._matrix3DDirty = true; if (!this._invalidateMatrix3D) this._invalidateMatrix3D = new TransformEvent(TransformEvent.INVALIDATE_MATRIX3D, this); this.dispatchEvent(this._invalidateMatrix3D); } /** * */ public invalidatePosition(): void { this._inverseMatrix3DDirty = true; if (!this._invalidateMatrix3D) this._invalidateMatrix3D = new TransformEvent(TransformEvent.INVALIDATE_MATRIX3D, this); this.dispatchEvent(this._invalidateMatrix3D); } /** * Rotates the 3d object around to face a point defined relative to the * local coordinates of the parent `ObjectContainer3D` . * * @param target The vector defining the point to be looked at. * @param upAxis An optional vector used to define the desired up * orientation of the 3d object after rotation has occurred. */ public lookAt(position: Vector3D, upAxis: Vector3D = null): void { if (upAxis == null) upAxis = Vector3D.Y_AXIS; const vec: Vector3D = Matrix3D.getPointAtMatrix( new Vector3D(), position.subtract(this._matrix3D.position), upAxis, Matrix3D.CALCULATION_MATRIX) .decompose()[1]; this.rotateTo(vec.x, vec.y, vec.z); } /** * Moves the 3d object backwards along it's local z axis * * @param distance The length of the movement */ public moveBackward(distance: number): void { this.translateLocal(Vector3D.Z_AXIS, -distance); } /** * Moves the 3d object backwards along it's local y axis * * @param distance The length of the movement */ public moveDown(distance: number): void { this.translateLocal(Vector3D.Y_AXIS, -distance); } /** * Moves the 3d object forwards along it's local z axis * * @param distance The length of the movement */ public moveForward(distance: number): void { this.translateLocal(Vector3D.Z_AXIS, distance); } /** * Moves the 3d object backwards along it's local x axis * * @param distance The length of the movement */ public moveLeft(distance: number): void { this.translateLocal(Vector3D.X_AXIS, -distance); } /** * Moves the 3d object forwards along it's local x axis * * @param distance The length of the movement */ public moveRight(distance: number): void { this.translateLocal(Vector3D.X_AXIS, distance); } /** * Moves the 3d object directly to a point in space * * @param dx The amount of movement along the local x axis. * @param dy The amount of movement along the local y axis. * @param dz The amount of movement along the local z axis. */ public moveTo(dx: number, dy: number, dz: number): void { this._matrix3D._rawData[12] = dx; this._matrix3D._rawData[13] = dy; this._matrix3D._rawData[14] = dz; this.invalidatePosition(); } /** * Moves the 3d object forwards along it's local y axis * * @param distance The length of the movement */ public moveUp(distance: number): void { this.translateLocal(Vector3D.Y_AXIS, distance); } /** * Rotates the 3d object around it's local x-axis * * @param angle The amount of rotation in degrees */ public pitch(angle: number): void { this.rotate(Vector3D.X_AXIS, angle); } /** * Rotates the 3d object around it's local z-axis * * @param angle The amount of rotation in degrees */ public roll(angle: number): void { this.rotate(Vector3D.Z_AXIS, angle); } /** * Rotates the 3d object around an axis by a defined angle * * @param axis The vector defining the axis of rotation * @param angle The amount of rotation in degrees */ public rotate(axis: Vector3D, angle: number): void { this.matrix3D.prependRotation(angle, axis); this.invalidateComponents(); } /** * Rotates the 3d object directly to a euler angle * * @param ax The angle in degrees of the rotation around the x axis. * @param ay The angle in degrees of the rotation around the y axis. * @param az The angle in degrees of the rotation around the z axis. */ public rotateTo(ax: number, ay: number, az: number): void { if (this._componentsDirty) this._updateComponents(); this._rotation.x = ax; this._rotation.y = ay; this._rotation.z = az; this.invalidateMatrix3D(); } /** * * @param sx * @param sy * @param sz */ public scaleTo(sx: number, sy: number, sz: number): void { if (this._componentsDirty) this._updateComponents(); this._scale.x = sx; this._scale.y = sy; this._scale.z = sz; this.invalidateMatrix3D(); } /** * * @param sx * @param sy * @param sz */ public skewTo(sx: number, sy: number, sz: number): void { if (this._componentsDirty) this._updateComponents(); this._skew.x = sx; this._skew.y = sy; this._skew.z = sz; this.invalidateMatrix3D(); } /** * Moves the 3d object along a vector by a defined length. * * @param axis The vector defining the axis of movement. * @param distance The length of the movement. */ public translate(axis: Vector3D, distance: number): void { const x: number = axis.x, y: number = axis.y, z: number = axis.z; const len: number = distance / Math.sqrt(x * x + y * y + z * z); this.matrix3D.appendTranslation(x * len, y * len, z * len); this.invalidatePosition(); } /** * Moves the 3d object along a vector by a defined length. * * @param axis The vector defining the axis of movement. * @param distance The length of the movement. */ public translateLocal(axis: Vector3D, distance: number): void { const x: number = axis.x, y: number = axis.y, z: number = axis.z; const len: number = distance / Math.sqrt(x * x + y * y + z * z); this.matrix3D.prependTranslation(x * len, y * len, z * len); this.invalidatePosition(); } /** * */ public updateMatrix3D(): void { Transform._tempVector.copyFrom(this._matrix3D.position); this._matrix3D.recompose(this._components); this._matrix3DDirty = false; } /** * Rotates the 3d object around it's local y-axis * * @param angle The amount of rotation in degrees */ public yaw(angle: number): void { this.rotate(Vector3D.Y_AXIS, angle); } private _updateComponents(): void { if (!this._components) { // Cached vector of transformation components used when // recomposing the transform matrix in updateTransform() this._components = new Array(4); this._rawDataComponents = new Float32Array(12); this._rawDataComponents[3] = 1; this._rawDataComponents[7] = 1; this._rawDataComponents[11] = 1; this._rotation = new Vector3D(new Float32Array(this._rawDataComponents.buffer, 0, 4)); this._skew = new Vector3D(new Float32Array(this._rawDataComponents.buffer, 16, 4)); this._scale = new Vector3D(new Float32Array(this._rawDataComponents.buffer, 32, 4)); this._components[0] = Transform._tempVector; this._components[1] = this._rotation; this._components[2] = this._scale; this._components[3] = this._skew; } const elements: Array = this._matrix3D.decompose(); let vec: Vector3D; vec = elements[1]; this._rotation.x = vec.x; this._rotation.y = vec.y; this._rotation.z = vec.z; vec = elements[2]; this._scale.x = vec.x; this._scale.y = vec.y; this._scale.z = vec.z; vec = elements[3]; this._skew.x = vec.x; this._skew.y = vec.y; this._skew.z = vec.z; this._componentsDirty = false; } }