expo-three
Version:
Utilities for using THREE.js on Expo
147 lines (123 loc) • 4.82 kB
text/typescript
import { AR } from 'expo';
import THREE from '../Three';
import { positionFromTransform, worldPositionFromScreenPosition } from './calculations';
import ARCamera from './Camera';
// TODO: Bacon: Vertical support
class MagneticObject extends THREE.Object3D {
// use average of recent positions to avoid jitter
recentMagneticPositions: any[] = [];
anchorsOfVisitedPlanes: any[] = [];
maintainScale = true;
maintainRotation = true;
constructor() {
super();
}
updateForAnchor = (
position: THREE.Vector3,
planeAnchor: AR.PlaneAnchor | null,
camera: THREE.Camera
) => {
if (planeAnchor != null) {
// const index = this.anchorsOfVisitedPlanes.indexOf(planeAnchor);
this.anchorsOfVisitedPlanes.unshift(planeAnchor);
// TODO: Move in direction
// this.visible = false;
} else {
// this.visible = true;
}
this.updateTransform(position, camera);
};
update = (camera: ARCamera, screenPosition): void => {
const data = worldPositionFromScreenPosition(camera, screenPosition, this.position);
if (data && data.worldPosition) {
this.updateForAnchor(data.worldPosition, data.planeAnchor, camera);
}
};
isValidVector = vector => vector && !isNaN(vector.x) && !isNaN(vector.y) && !isNaN(vector.z);
updateTransform = (position, camera) => {
if (!this.isValidVector(position)) {
return;
}
// add to list of recent positions
this.recentMagneticPositions.push(position);
// remove anything older than the last 8
while (this.recentMagneticPositions.length > 8) {
this.recentMagneticPositions.shift();
}
// move to average of recent positions to avoid jitter
if (this.recentMagneticPositions.length > 4) {
const { length } = this.recentMagneticPositions;
let average = new THREE.Vector3();
for (let position of this.recentMagneticPositions) {
average.add(position);
}
average.divide(new THREE.Vector3(length, length, length));
this.position.set(average.x, average.y, average.z);
if (this.maintainScale) {
const scale = this.scaleBasedOnDistance(camera);
this.scale.set(scale, scale, scale);
} else {
this.scale.set(1, 1, 1);
}
}
if (this.maintainRotation) {
// Correct y rotation of camera square
if (camera) {
let cameraQuaternion = new THREE.Quaternion();
camera.getWorldQuaternion(cameraQuaternion);
let cameraEuler = new THREE.Euler();
cameraEuler.setFromQuaternion(cameraQuaternion, 'YZX');
const tilt = Math.abs(cameraEuler.x);
const threshold1 = (Math.PI / 2) * 0.65;
const threshold2 = (Math.PI / 2) * 0.75;
const yaw = Math.atan2(camera.matrixWorld.elements[0], camera.matrixWorld.elements[1]);
let angle = 0;
if (tilt >= 0 || tilt < threshold1) {
angle = cameraEuler.y;
} else if (tilt >= threshold1 || tilt < threshold2) {
const relativeInRange = Math.abs((tilt - threshold1) / (threshold2 - threshold1));
const normalizedY = this.normalize(cameraEuler.y, yaw);
angle = normalizedY * (1 - relativeInRange) + yaw * relativeInRange;
} else {
angle = yaw;
}
this.setRotationFromAxisAngle(new THREE.Vector3(0, 1, 0), angle);
}
}
};
normalize = (angle: number, ref: number) => {
// Normalize angle in steps of 90 degrees such that the rotation to the other angle is minimal
let normalized = angle;
while (Math.abs(normalized - ref) > Math.PI / 4) {
if (angle > ref) {
normalized -= Math.PI / 2;
} else {
normalized += Math.PI / 2;
}
}
return normalized;
};
get worldPosition(): THREE.Vector3 {
let worldPosition = new THREE.Vector3();
this.getWorldPosition(worldPosition);
return worldPosition;
}
scaleBasedOnDistance = (camera): number => {
if (camera) {
const cameraPosition = positionFromTransform(camera.matrixWorld);
const delta = this.position.clone().sub(cameraPosition);
let distanceFromCamera = delta.length();
// console.log('distanceFromCamera', cameraPosition, this.position, distanceFromCamera);
// From Apple:
// Reduce size changes of the node based on the distance by scaling it up if it is far away,
// and down if it is very close.
// The values are adjusted such that scale will be 1 in 0.7 m distance (estimated distance when looking at a table),
// and 1.2 in 1.5 m distance (estimated distance when looking at the floor).
const newScale =
distanceFromCamera < 0.7 ? distanceFromCamera / 0.7 : 0.25 * distanceFromCamera + 0.825;
return newScale;
}
return 1.0;
};
}
export default MagneticObject;