@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.
143 lines • 6.35 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { Object3D, Quaternion, Vector3 } from "three";
import { Mathf } from "../engine/engine_math.js";
import { Axes } from "../engine/engine_physics.types.js";
import { serializable } from "../engine/engine_serialization_decorator.js";
import { getWorldPosition, getWorldQuaternion } from "../engine/engine_three_utils.js";
import { Behaviour } from "./Component.js";
/**
* [SmoothFollow](https://engine.needle.tools/docs/api/SmoothFollow) makes this GameObject smoothly follow another target object's position and/or rotation.
*
* **Position Following:**
* When enabled (`followFactor > 0`), this object will move towards the target's world position.
* The object interpolates from its current position to the target's position each frame.
* Use `positionAxes` to restrict following to specific axes (e.g., only horizontal movement).
*
* **Rotation Following:**
* When enabled (`rotateFactor > 0`), this object will rotate to match the target's world rotation.
* The object smoothly interpolates from its current rotation to the target's rotation each frame.
* This makes the object face the same direction as the target, not look at it (use {@link LookAt} for that).
*
* **Smoothing:**
* Both position and rotation use time-based interpolation (lerp/slerp).
* Higher factor values = faster following (less lag), lower values = slower following (more lag).
* Set a factor to 0 to disable that type of following entirely.
*
* **Common Use Cases:**
* - Camera following a player character
* - UI elements tracking world objects
* - Delayed motion effects (ghost trails, spring arms)
* - Smoothed object attachment
*
* @example Follow a target with smooth position
* ```ts
* const follower = myObject.addComponent(SmoothFollow);
* follower.target = playerObject;
* follower.followFactor = 5; // Higher = faster following
* follower.rotateFactor = 0; // Don't follow rotation
* ```
*
* @example Follow only on horizontal plane
* ```ts
* follower.positionAxes = Axes.X | Axes.Z; // Follow X and Z only (no vertical)
* ```
*
* @example Follow both position and rotation
* ```ts
* follower.target = targetObject;
* follower.followFactor = 3; // Smooth position following
* follower.rotateFactor = 2; // Smooth rotation following
* ```
*
* @summary Smoothly follows a target object's position and/or rotation
* @category Interactivity
* @group Components
* @see {@link LookAtConstraint} for making an object look at a target (different from rotation following)
* @see {@link Mathf} for the interpolation used
*/
export class SmoothFollow extends Behaviour {
/**
* The target to follow. If null, the GameObject will not move.
*/
target = null;
/**
* Speed factor for position following.
* Controls how quickly this object moves to match the target's position.
* Higher values = faster/tighter following (less lag), lower = slower/looser (more lag).
* Set to 0 to disable position following entirely.
* @default 0.1
*/
followFactor = .1;
/**
* Speed factor for rotation following.
* Controls how quickly this object rotates to match the target's rotation.
* Higher values = faster/tighter following (less lag), lower = slower/looser (more lag).
* Set to 0 to disable rotation following entirely.
* @default 0.1
*/
rotateFactor = .1;
/**
* Which position axes to follow. Use bitwise OR to combine:
* `Axes.X | Axes.Y` follows only X and Y axes.
* @default Axes.All
*/
positionAxes = Axes.All;
/** When true, rotates 180° around Y axis (useful for mirrored setups) */
flipForward = false;
static _invertForward = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
_firstUpdate = true;
/**
* Update the position and rotation of the GameObject to follow the target.
*/
onBeforeRender() {
this.updateNow(false);
}
/**
* Manually update the position/rotation to follow the target.
* @param hard If true, snaps instantly to target without smoothing
*/
updateNow(hard) {
if (!this.target || this.target === this.gameObject)
return;
if (this.followFactor > 0) {
const wp = getWorldPosition(this.target);
const fpos = this._firstUpdate || hard ? 1 : Mathf.clamp01(this.context.time.deltaTime * this.followFactor);
const currentPosition = this.worldPosition;
if (this.positionAxes & Axes.X)
currentPosition.x = Mathf.lerp(currentPosition.x, wp.x, fpos);
if (this.positionAxes & Axes.Y)
currentPosition.y = Mathf.lerp(currentPosition.y, wp.y, fpos);
if (this.positionAxes & Axes.Z)
currentPosition.z = Mathf.lerp(currentPosition.z, wp.z, fpos);
// TODO lerp distance from target as well
this.worldPosition = currentPosition;
}
if (this.rotateFactor > 0) {
const wr = getWorldQuaternion(this.target);
if (this.flipForward) {
wr.premultiply(SmoothFollow._invertForward);
}
const frot = this._firstUpdate || hard ? 1 : Mathf.clamp01(this.context.time.deltaTime * this.rotateFactor);
this.worldQuaternion = this.worldQuaternion.slerp(wr, frot);
}
this._firstUpdate = false;
}
}
__decorate([
serializable(Object3D)
], SmoothFollow.prototype, "target", void 0);
__decorate([
serializable()
], SmoothFollow.prototype, "followFactor", void 0);
__decorate([
serializable()
], SmoothFollow.prototype, "rotateFactor", void 0);
__decorate([
serializable()
], SmoothFollow.prototype, "positionAxes", void 0);
//# sourceMappingURL=SmoothFollow.js.map