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.

91 lines (76 loc) 3.61 kB
import { Euler, Plane,Quaternion, Vector3 } from "three"; import { serializable } from "../engine/engine_serialization_decorator.js"; import * as utils from "./../engine/engine_three_utils.js"; import { Behaviour, GameObject } from "./Component.js"; /** * The [OffsetConstraint](https://engine.needle.tools/docs/api/OffsetConstraint) maintains a fixed positional and rotational offset relative to a target object. * Useful for attaching objects to moving targets while preserving a specific spatial relationship. * * **Use cases:** * - Camera following a player with offset * - UI elements attached to characters * - Weapons attached to hands * - Objects orbiting around a target * * **Options:** * - `affectPosition` - Apply position offset * - `affectRotation` - Apply rotation offset * - `alignLookDirection` - Make object face same direction as target * - `levelLookDirection` - Keep look direction horizontal (ignore pitch) * - `levelPosition` - Project position onto horizontal plane * - `referenceSpace` - Transform offset in this object's coordinate space * * @example Attach camera offset to player * ```ts * const constraint = camera.addComponent(OffsetConstraint); * // Configure via serialized properties in editor * ``` * * @summary Maintains positional/rotational offset relative to target * @category Constraints * @group Components * @see {@link SmoothFollow} for smoothed following * @see {@link AlignmentConstraint} for alignment between two objects */ export class OffsetConstraint extends Behaviour { @serializable(GameObject) private referenceSpace: GameObject | undefined; @serializable(GameObject) private from: GameObject | undefined; private affectPosition: boolean = false; private affectRotation: boolean = false; private alignLookDirection: boolean = false; private levelLookDirection: boolean = false; private levelPosition: boolean = false; @serializable(Vector3) private positionOffset: Vector3 = new Vector3(0,0,0); @serializable(Vector3) private rotationOffset: Vector3 = new Vector3(0,0,0); private offset: Vector3 = new Vector3(0,0,0); update() { if (!this.from) return; var pos = utils.getWorldPosition(this.from); var rot: Quaternion = utils.getWorldQuaternion(this.from); this.offset.copy(this.positionOffset); const l = this.offset.length(); if (this.referenceSpace) this.offset.transformDirection(this.referenceSpace.matrixWorld).multiplyScalar(l); pos.add(this.offset); if (this.levelPosition && this.referenceSpace) { const plane = new Plane(this.gameObject.up, 0); const refSpacePoint = utils.getWorldPosition(this.referenceSpace); plane.setFromNormalAndCoplanarPoint(this.gameObject.up, refSpacePoint); const v2 = new Vector3(0,0,0); plane.projectPoint(pos, v2); pos.copy(v2); } if (this.affectPosition) utils.setWorldPosition(this.gameObject, pos); const euler = new Euler(this.rotationOffset.x, this.rotationOffset.y, this.rotationOffset.z); const quat = new Quaternion().setFromEuler(euler); if(this.affectRotation) utils.setWorldQuaternion(this.gameObject, rot.multiply(quat)); const lookDirection = new Vector3(); this.from.getWorldDirection(lookDirection).multiplyScalar(50); if (this.levelLookDirection) lookDirection.y = 0; if (this.alignLookDirection) this.gameObject.lookAt(lookDirection); } }