@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.
89 lines (75 loc) • 3.61 kB
text/typescript
import { Matrix4, Object3D, Quaternion, Vector3 } from "three";
import { serializable } from "../../engine/engine_serialization.js";
import { lookAtObject } from "../../engine/engine_three_utils.js";
import { type UsdzBehaviour } from "../../engine-components/export/usdz/extensions/behavior/Behaviour.js";
import { ActionBuilder, BehaviorModel, TriggerBuilder, USDVec3 } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js";
import { USDObject } from "../../engine-components/export/usdz/ThreeUSDZExporter.js";
import { Behaviour } from "../Component.js";
/**
* LookAt behaviour makes the object look at a target object or the camera.
* It can also invert the forward direction and keep the up direction.
*/
export class LookAt extends Behaviour implements UsdzBehaviour {
/**
* The target object to look at. If not set, the main camera will be used.
*/
(Object3D)
target?: Object3D;
/**
* Inverts the forward direction.
*/
()
invertForward: boolean = false;
/**
* Keep the up direction.
*/
()
keepUpDirection: boolean = true;
/**
* Copy the target rotation.
*/
()
copyTargetRotation: boolean = false;
private static flipYQuat: Quaternion = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
/** @internal */
onBeforeRender(): void {
let target: Object3D | null | undefined = this.target;
if (!target) target = this.context.mainCamera;
if (!target) return;
let copyTargetRotation = this.copyTargetRotation;
// Disable copy target rotation during VR/AR session: https://linear.app/needle/issue/NE-5634
if (this.context.isInVR || this.context.isInPassThrough) {
copyTargetRotation = false;
}
lookAtObject(this.gameObject, target, this.keepUpDirection, copyTargetRotation);
if (this.invertForward)
this.gameObject.quaternion.multiply(LookAt.flipYQuat);
}
/** @internal */
createBehaviours(ext, model: USDObject, _context) {
if (model.uuid === this.gameObject.uuid) {
let alignmentTarget = model;
// not entirely sure why we need to do this - looks like LookAt with up vector doesn't work properly in
// QuickLook, so we need to introduce an empty parent and rotate the model by 90° around Y
if (this.keepUpDirection) {
const parent = USDObject.createEmptyParent(model);
alignmentTarget = parent;
// rotate by 90° - counter-rotation on the parent makes sure
// that without Preliminary Behaviours it still looks right
const flip = this.invertForward ? -1 : 1;
parent.setMatrix(parent.getMatrix().multiply(new Matrix4().makeRotationZ(Math.PI / 2 * flip)));
model.setMatrix(model.getMatrix().multiply(new Matrix4().makeRotationZ(-Math.PI / 2 * flip)));
}
const lookAt = new BehaviorModel("lookat " + this.name,
TriggerBuilder.sceneStartTrigger(),
ActionBuilder.lookAtCameraAction(
alignmentTarget,
undefined,
this.invertForward ? USDVec3.back : USDVec3.forward,
this.keepUpDirection ? USDVec3.up : USDVec3.zero
),
);
ext.addBehavior(lookAt);
}
}
}