js/Mobilizing/renderer/3D/three/scene/Transform.js
import { Raycaster, Object3D } from 'three';
import Mesh from '../shape/Mesh';
import Quaternion from '../types/Quaternion';
import Matrix4 from '../types/Matrix4';
import Vector3 from '../types/Vector3';
import Vector2 from '../types/Vector2';
import * as _Math from '../../../../core/util/Math';
export default class Transform {
/**
Transform class is meant to be aggregated to any object that needs to be transformed in space.
{@link Mesh}, {@link Light} or {@link Camera} contains a transform instance.
@class Transform
@constructor
@uses {Math}
*/
constructor(obj) {
this.mobObject = obj;//used in children array to avoid confusion between three and mob objects
if (obj.getNativeObject()) {
this.nativeObject = obj.getNativeObject();
//add a custom prop to three meshes
this.nativeObject.mobObject = obj;
}
else {
console.error("No object was assigned to this Transform, aborting.")
return;
}
this.children = [];
this.recursiveChildren = [];
}
/**
* @returns the Three.js native object used in this class
*/
getNativeObject() {
return this.nativeObject;
}
/**
* Geneates a transform to point at a certain coordinates, makes the object "look at" this point.
* @param {Vector} vector
*/
lookAt(vector) {
this.nativeObject.lookAt(vector);
}
/**
Set the object rotation with a quarternion {x,y,z,w}.
@param {Quaternion} quarternion The quaternion to apply
*/
setLocalQuaternion(q) {
if (q instanceof Quaternion) {
this.nativeObject.quaternion.set(q.x, q.y, q.z, q.w);
}
}
/**
Get the object rotation as a quarternion.
@return {Quaternion} quarternion
*/
getLocalQuaternion() {
const temp = new Quaternion(this.nativeObject.quaternion.x, this.nativeObject.quaternion.y, this.nativeObject.quaternion.z, this.nativeObject.quaternion.w);
return temp;
}
setUpDirection(vector){
this.nativeObject.up.set(vector.x, vector.y, vector.z);
}
/**
* Get the 3 direction vectors of this transform
* @return {Object} object with all 3 Vector3 as {upVector: upVector, forwardVector: forwardVector,leftVector: leftVector};
*/
getDirections() {
const quaternion = this.nativeObject.quaternion;
const upVector = new Vector3();
upVector.x = 2 * (quaternion.x * quaternion.y - quaternion.w * quaternion.z);
upVector.y = 1 - 2 * (quaternion.x * quaternion.x + quaternion.z * quaternion.z);
upVector.z = 2 * (quaternion.y * quaternion.z + quaternion.w * quaternion.x);
const forwardVector = new Vector3();
forwardVector.x = 2 * (quaternion.x * quaternion.z + quaternion.w * quaternion.y);
forwardVector.y = 2 * (quaternion.y * quaternion.z - quaternion.w * quaternion.x);
forwardVector.z = 1 - 2 * (quaternion.x * quaternion.x + quaternion.y * quaternion.y);
const leftVector = new Vector3();
leftVector.x = 1 - 2 * (quaternion.y * quaternion.y + quaternion.z * quaternion.z);
leftVector.y = 2 * (quaternion.x * quaternion.y + quaternion.w * quaternion.z);
leftVector.z = 2 * (quaternion.x * quaternion.z - quaternion.w * quaternion.y);
const result = {
upVector,
forwardVector,
leftVector
};
return result;
}
setLocalMatrix(m) {
this.nativeObject.matrixAutoUpdate = false;
const mat = new Matrix4().multiplyMatrices(this.nativeObject.matrix, m);
this.nativeObject.applyMatrix(mat);
this.nativeObject.updateMatrix();
}
//not documented - use setRotation
setLocalEulerAngles(arg1, arg2, arg3) {
//x,y,z float
if (arguments.length === 3) {
this.nativeObject.rotation.x = _Math.degToRad(arg1);
this.nativeObject.rotation.y = _Math.degToRad(arg2);
this.nativeObject.rotation.z = _Math.degToRad(arg3);
}
else if (arguments.length === 2) {
if (typeof arg1 === "number" && typeof arg2 === "number") {
this.nativeObject.rotation.x = _Math.degToRad(arg1);
this.nativeObject.rotation.y = _Math.degToRad(arg2);
}
}
//direct vector or single float
else if (arguments.length === 1) {
if (arg1 instanceof Vector3) {
this.nativeObject.rotation.x = _Math.degToRad(arg1.x);
this.nativeObject.rotation.y = _Math.degToRad(arg1.y);
this.nativeObject.rotation.z = _Math.degToRad(arg1.z);
}
if (arg1 instanceof Vector2) {
this.nativeObject.rotation.x = _Math.degToRad(arg1.x);
this.nativeObject.rotation.y = _Math.degToRad(arg1.y);
}
else if (typeof arg1 === "number") {
this.nativeObject.rotation.z = _Math.degToRad(arg1);
}
}
}
//not documented - use getRotation
getLocalEulerAngles() {
return new Vector3(_Math.radToDeg(this.nativeObject.rotation.x) % 360, _Math.radToDeg(this.nativeObject.rotation.y) % 360, _Math.radToDeg(this.nativeObject.rotation.z) % 360);
}
/**
* Defines the oreder of application for rotation axis.
* Default is 'XYZ', others are'YZX', 'ZXY', 'XZY', 'YXZ' and 'ZYX'
* @param {string} order one of 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ' and 'ZYX'
*/
setLocalRotationOrder(val) {
this.nativeObject.rotation.order = val;
}
/**
Set the euler angles rotation in degrees.
Polymorphic : can take various agruments of various types. Possible arguments number is 1, 2 or 3.
@param {float|Vector3|Vector2} number|Vector3|Vector2 Value for the new rotation of the transform. If a Vector3 is given, its x, y, z will be used for the rotation x, y, z. If a Vector2 is given, its x, y will be used for the rotation x and y, but z will be unchanged. If a number is given, it will be the position x.
@param {float} Number Value for the new y rotation of the transform.
@param {float} Number Value for the new z rotation of the transform.
*/
setLocalRotation() {
this.setLocalEulerAngles.apply(this, arguments);
}
/**
* Sets this transform's x rotation
* @param {Number} arg
*/
setLocalRotationX(arg) {
if (typeof arg === "number") {
this.nativeObject.rotation.x = _Math.degToRad(arg);
}
}
/**
* Sets this transform's y rotation
* @param {Number} arg
*/
setLocalRotationY(arg) {
if (typeof arg === "number") {
this.nativeObject.rotation.y = _Math.degToRad(arg);
}
}
/**
* Sets this transform's z rotation
* @param {Number} arg
*/
setLocalRotationZ(arg) {
if (typeof arg === "number") {
this.nativeObject.rotation.z = _Math.degToRad(arg);
}
}
/**
* Get the current local rotation of this transform
* @return {Vector3} localRotation vector
*/
getLocalRotation() {
return this.getLocalEulerAngles();
}
/**
* Get the current x local rotation of this transform
* @return {Number} localRotation x value
*/
getLocalRotationX() {
return this.getLocalEulerAngles().x;
}
/**
* Get the current y local rotation of this transform
* @return {Number} localRotation y value
*/
getLocalRotationY() {
return this.getLocalEulerAngles().y;
}
/**
* Get the current z local rotation of this transform
* @return {Number} localRotation z value
*/
getLocalRotationZ() {
return this.getLocalEulerAngles().z;
}
/**
Set the local position of the transform (and to the object attach to it).
Polymorphic : can take various agruments of various types. Possible arguments number is 1, 2 or 3.
@param {float|Vector3|Vector2} number|Vector3|Vector2 Value for the new position of the transform. If a Vector3 is given, its x, y, z will be used for the position x, y, z. If a Vector2 is given, its x, y will be used for the position x and y, but z will be unchanged. If a number is given, it will be the position x.
@param {float} Number Value for the new y position of the transform.
@param {float} Number Value for the new z position of the transform.
*/
setLocalPosition(arg1, arg2, arg3) {
//x,y,z float
if (arguments.length === 3) {
this.nativeObject.position.x = arg1;
this.nativeObject.position.y = arg2;
this.nativeObject.position.z = arg3;
}
else if (arguments.length === 2) {
if (typeof arg1 === "number" && typeof arg2 === "number") {
this.nativeObject.position.x = arg1;
this.nativeObject.position.y = arg2;
}
}
//direct vector
else if (arguments.length === 1) {
if (arg1 instanceof Vector3) {
this.nativeObject.position.x = arg1.x;
this.nativeObject.position.y = arg1.y;
this.nativeObject.position.z = arg1.z;
}
else if (arg1 instanceof Vector2) {
this.nativeObject.position.x = arg1.x;
this.nativeObject.position.y = arg1.y;
}
else if (typeof arg1 === "number") {
this.nativeObject.position.x = arg1;
this.nativeObject.position.y = arg1;
this.nativeObject.position.z = arg1;
}
}
}
/**
* Sets this transform's x position
* @param {Number} arg
*/
setLocalPositionX(arg) {
if (typeof arg === "number") {
this.nativeObject.position.x = arg;
}
}
/**
* Sets this transform's y position
* @param {Number} arg
*/
setLocalPositionY(arg) {
if (typeof arg === "number") {
this.nativeObject.position.y = arg;
}
}
/**
* Sets this transform's z position
* @param {Number} arg
*/
setLocalPositionZ(arg) {
if (typeof arg === "number") {
this.nativeObject.position.z = arg;
}
}
/**
Gets the local position of this transform
@return {Vector3} localPosition vector
*/
getLocalPosition() {
const tempVec = new Vector3(this.nativeObject.position.x, this.nativeObject.position.y, this.nativeObject.position.z);
return tempVec;
}
/**
Gets the local x position of this transform
@return {Number} localPosition x
*/
getLocalPositionX() {
const tempVec = this.nativeObject.position.x;
return tempVec;
}
/**
Gets the local x position of this transform
@return {Number} localPosition y
*/
getLocalPositionY() {
const tempVec = this.nativeObject.position.y;
return tempVec;
}
/**
Gets the local z position of this transform
@return {Number} localPosition z
*/
getLocalPositionZ() {
const tempVec = this.nativeObject.position.z;
return tempVec;
}
/**
@returns {Vector3} world Position vector of the object
*/
getWorldPosition() {
const vector = new Vector3();
vector.setFromMatrixPosition(this.nativeObject.matrixWorld);
return vector;
}
/**
* get the world matrix of the object
* @returns world matrix of the object
*/
getWorldMatrix() {
return this.nativeObject.matrixWorld;
}
/**
* get the world quaternion of the object
* @returns world quaternion of the object
*/
getWorldQuaternion() {
const q = new Quaternion().setFromRotationMatrix(this.getWorldMatrix());
return q;
}
/**
@return {Matrix4} local matrix of the object
*/
getLocalMatrix() {
return this.nativeObject.matrix;
}
/**
Set the scale of the transform.
Polymorphic : can take various agruments of various types. Possible arguments number is 1, 2 or 3.
@param {float|Vector3|Vector2} number|Vector3|Vector2 Value for the new scale. If a Vector3 is given, its x, y, z will be used for the scale x, y, z. If a Vector2 is given, its x, y will be used for the scale x and y, but z will be 1. If a number is given, it will be the scale x.
@param {float} Number Value for the new y scale.
@param {float} Number Value for the new z scale.
*/
setLocalScale(arg1, arg2, arg3) {
if (arguments.length === 3) {
this.nativeObject.scale.x = arg1;
this.nativeObject.scale.y = arg2;
this.nativeObject.scale.z = arg3;
}
else if (arguments.length === 2) {
if (typeof arg1 === "number" && typeof arg2 === "number") {
this.nativeObject.scale.x = arg1;
this.nativeObject.scale.y = arg2;
}
}
else if (arguments.length === 1) {
if (arg1 instanceof Vector3) {
this.nativeObject.scale.x = arg1.x;
this.nativeObject.scale.y = arg1.y;
this.nativeObject.scale.z = arg1.z;
}
else if (arg1 instanceof Vector2) {
this.nativeObject.scale.x = arg1.x;
this.nativeObject.scale.y = arg1.y;
}
else if (typeof arg1 === "number") {
this.nativeObject.scale.x = arg1;
this.nativeObject.scale.y = arg1;
this.nativeObject.scale.z = arg1;
}
}
}
/**
* Sets this transform's x scale
* @param {Number} arg
*/
setLocalScaleX(arg) {
if (typeof arg === "number") {
this.nativeObject.scale.x = arg;
}
}
/**
* Sets this transform's y scale
* @param {Number} arg
*/
setLocalScaleY(arg) {
if (typeof arg === "number") {
this.nativeObject.scale.y = arg;
}
}
/**
* Sets this transform's z scale
* @param {Number} arg
*/
setLocalScaleZ(arg) {
if (typeof arg === "number") {
this.nativeObject.scale.z = arg;
}
}
/**
@return {Vector3} localScale vector
*/
getLocalScale() {
const temp = new Vector3(this.nativeObject.scale.x, this.nativeObject.scale.y, this.nativeObject.scale.z);
return temp;
}
/**
@return {Number} localScale x
*/
getLocalScaleX() {
const temp = this.nativeObject.scale.x;
return temp;
}
/**
@return {Number} localScale y
*/
getLocalScaleY() {
const temp = this.nativeObject.scale.y;
return temp;
}
/**
@return {Number} localScale z
*/
getLocalScaleZ() {
const temp = this.nativeObject.scale.z;
return temp;
}
/**
* Perform a RayCast picking
*
* @param {Camera} cam The camera to use as a ray caster.
* @param {number} x The x coordinate in screen space for the ray casting
* @param {number} y The y coordinate in screen space for the ray casting
* @return {Object} if picking success, gives an object as {point:{x,y,z} in **world coordinates**, uv:{u,v}, distance:dist}, null otherwise.
*/
pick(cam, x, y, recursive) {
//FIXME : will bug with non fullscreen configuration!!!
//IDEA : use the context referenced in the cam to grab the canvas!
let nx = 0;
let ny = 0;
if (cam.context) {
const canvas = cam.context.getCanvasSize();
nx = (x / canvas.width) * 2 - 1;
ny = - (y / canvas.height) * 2 + 1;
}
else {
nx = (x / window.innerWidth) * 2 - 1;
ny = - (y / window.innerHeight) * 2 + 1;
}
// create a Ray with origin at the mouse position
//and direction into the scene (camera direction)
const raycaster = new Raycaster(); // create once
const vector = new Vector3(nx, ny, 1);
const camera = cam.transform.nativeObject;
raycaster.setFromCamera(vector, camera);
//const intersects = raycaster.intersectObjects([this.nativeObject]);
const intersects = raycaster.intersectObject(this.nativeObject, recursive);
/* if (intersects && intersects.length > 0) {
console.log("intersects", intersects);
} */
//TODO switch to array return
// if there is one (or more) intersections
if (intersects.length > 0) {
if (recursive) {
const result = [];
for (let i = 0; i < intersects.length; i++) {
const temp = {};
temp.point = intersects[i].point;
temp.uv = intersects[i].uv;
temp.distance = intersects[i].distance;
temp.mobObject = intersects[i].object.mobObject;
result.push(temp);
}
return result;
}
if (!recursive) {
const temp = {};
temp.point = intersects[0].point;
temp.uv = intersects[0].uv;
temp.distance = intersects[0].distance;
temp.mobObject = intersects[0].object.mobObject;
return temp;
}
}
// there are no intersections
return null;
}
/**
* Return a Vector2 giving the screen coordinates of the object
* @param {Context} context the current Mobilizing context
* @param {Camera} camera the Camera to use for the projection
* @return {Vector2} the screen coordinates of the object
*/
getScreenCoordinates(context, camera) {
if (camera.type === "perspective") {
const vector = new Vector3();
// TODO: need to update this when resize window
const widthHalf = 0.5 * context.getCanvasSize().width * camera.viewport.width;
const heightHalf = 0.5 * context.getCanvasSize().height * camera.viewport.height;
this.nativeObject.updateMatrixWorld();
vector.setFromMatrixPosition(this.nativeObject.matrixWorld);
vector.project(camera.camera);
vector.x = (vector.x * widthHalf) + widthHalf;
vector.y = -(vector.y * heightHalf) + heightHalf;
return {
x: vector.x,
y: vector.y
}
}
return null;
}
/**
* Adds a child to this transform (argument must be a transform too)
* @param {Transform Mesh} child
*/
addChild(child) {
if (child instanceof Transform) {
child.parent = this.mobObject;
this.children.push(child.mobObject);
this.nativeObject.add(child.nativeObject);
}
else if (child instanceof Mesh) {
child.transform.parent = this.mobObject;
this.children.push(child.transform.mobObject);
this.nativeObject.add(child.transform.nativeObject);
}
else if (child instanceof Object3D){
console.log("Three object3D added");
}
}
/**
* Adds a children array to this transform (argument must be a transform too)
* @param {Array} child
*/
addChildren(array) {
array.forEach((child) => {
this.addChild(child);
});
}
/**
* Removes a child from the children chain
* @param {Transform} child
*/
removeChild(child) {
this.children.splice(this.children.indexOf(child), 1);
this.nativeObject.remove(child.nativeObject);
}
/**
* Gets an array containning all the children objects of this transform
* @return {Array}
*/
getChildren(recursive) {
if (!recursive) {
return this.children;
}
this.getRecurseChildren(this);
return this.recursiveChildren;
}
getRecurseChildren(transform) {
if (transform.children.length > 0) {
transform.children.forEach((child) => {
this.recursiveChildren.push(child);
if (child.transform.getChildren().length > 0) {
transform.getRecurseChildren(child.transform);
}
});
}
}
/**
* Gets one of all the children objects of this transform
*
* @param {Number} index Index of the child to get
* @return {Object}
*/
getChild(index) {
return this.children[index];
}
getParent() {
return this.parent;
}
/**
* Sets the render order of this object. The sortObjects of the renderer should be true for this property to have any effect.
@param {Number} the render order index
*/
setRenderOrder(val) {
this.nativeObject.renderOrder = val;
}
/**
* Set this object visibility
* @param {Boolean} val
*/
setVisible(val) {
this.nativeObject = val;
}
/**
* @returns the visibility of this object
*/
getVisible() {
return this.nativeObject.visible;
}
}