UNPKG

@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
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. */ @serializable(Object3D) target?: Object3D; /** * Inverts the forward direction. */ @serializable() invertForward: boolean = false; /** * Keep the up direction. */ @serializable() keepUpDirection: boolean = true; /** * Copy the target rotation. */ @serializable() 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); } } }