@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.
164 lines • 6.18 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 { SkinnedMesh } from "three";
import { RaycastOptions } from "../../engine/engine_physics.js";
import { serializable } from "../../engine/engine_serialization.js";
import { NeedleXRSession } from "../../engine/engine_xr.js";
import { Behaviour } from "../Component.js";
import { EventSystem } from "./EventSystem.js";
/**
* [ObjectRaycaster](https://engine.needle.tools/docs/api/ObjectRaycaster) Base class for raycasters that detect pointer interactions.
* Derive from this class to create custom raycasting logic.
*
* **Built-in raycasters:**
* - {@link ObjectRaycaster} - Raycasts against 3D objects
* - {@link GraphicRaycaster} - Raycasts against UI elements
* - {@link SpatialGrabRaycaster} - Sphere overlap for XR grab
*
* **Important:** If you override `awake`, `onEnable`, or `onDisable`,
* call the base class methods to ensure proper registration with {@link EventSystem}.
*
* @category Interactivity
* @group Components
* @see {@link EventSystem} for the event dispatch system
*/
export class Raycaster extends Behaviour {
awake() {
EventSystem.createIfNoneExists(this.context);
}
onEnable() {
EventSystem.get(this.context)?.register(this);
}
onDisable() {
EventSystem.get(this.context)?.unregister(this);
}
}
/**
* ObjectRaycaster enables pointer interactions with 3D objects.
* Add this component to any object that needs click/hover detection.
*
* **Usage:**
* Objects with ObjectRaycaster will receive pointer events when
* they implement interfaces like {@link IPointerClickHandler}.
*
* **Note:**
* In older Needle Engine versions the ObjectRaycaster was required to be added to the Scene.
* This is no longer the case - the EventSystem will automatically handle raycasts.
*
*
* @category Interactivity
* @group Components
* @see {@link IPointerClickHandler} for click events
* @see {@link DragControls} for drag interactions
*/
export class ObjectRaycaster extends Raycaster {
targets = null;
raycastHits = [];
ignoreSkinnedMeshes = false;
start() {
this.targets = [this.gameObject];
}
performRaycast(opts = null) {
if (!this.targets)
return null;
opts ??= new RaycastOptions();
opts.targets = this.targets;
opts.results = this.raycastHits;
opts.useAcceleratedRaycast = true;
const orig = opts.testObject;
if (this.ignoreSkinnedMeshes) {
opts.testObject = obj => {
// if we are set to ignore skinned meshes, we return false for them
if (obj instanceof SkinnedMesh) {
return "continue in children";
}
// call the original testObject function
if (orig)
return orig(obj);
// otherwise allow raycasting
return true;
};
}
const hits = this.context.physics.raycast(opts);
opts.testObject = orig;
return hits;
}
}
__decorate([
serializable()
], ObjectRaycaster.prototype, "ignoreSkinnedMeshes", void 0);
/**
* GraphicRaycaster enables pointer interactions with UI elements.
* Add this to a {@link Canvas} or UI hierarchy to enable button clicks,
* hover effects, and other UI interactions.
*
* **Requirements:**
* - Must be on the same object as a Canvas or on a parent
* - UI elements need proper RectTransform setup
*
* @example Enable UI interaction
* ```ts
* // Add to Canvas object
* canvas.addComponent(GraphicRaycaster);
* // Now buttons and other UI elements will respond to clicks
* ```
*
* @summary Raycaster for UI elements
* @category User Interface
* @group Components
* @see {@link Canvas} for UI root
* @see {@link Button} for clickable UI
* @see {@link EventSystem} for event handling
*/
export class GraphicRaycaster extends ObjectRaycaster {
// eventCamera: Camera | null = null;
// ignoreReversedGraphics: boolean = false;
// rootRaycaster: GraphicRaycaster | null = null;
constructor() {
super();
this.ignoreSkinnedMeshes = true;
}
}
/**
* SpatialGrabRaycaster enables direct grab interactions in VR/AR.
* Uses sphere overlap detection around the controller/hand position
* to allow grabbing objects by reaching into them.
*
* **Features:**
* - Active only during XR sessions
* - Can be globally disabled via `SpatialGrabRaycaster.allow`
* - Works alongside ray-based interaction
*
* @category XR
* @group Components
* @see {@link WebXR} for XR session management
* @see {@link DragControls} for object manipulation
*/
export class SpatialGrabRaycaster extends Raycaster {
/**
* Use to disable SpatialGrabRaycaster globally
*/
static allow = true;
performRaycast(_opts) {
// ensure we're in XR, otherwise return
if (!NeedleXRSession.active)
return null;
if (!SpatialGrabRaycaster.allow)
return null;
if (!_opts?.ray)
return null;
// TODO this raycast should actually start from gripWorldPosition, not the ray origin, for
// cases like transient-pointer on VisionOS where the ray starts at the head and not the hand
const rayOrigin = _opts.ray.origin;
const radius = 0.015;
// TODO if needed, check if the input source is a XR controller or hand
// draw gizmo around ray origin
// Gizmos.DrawSphere(rayOrigin, radius, 0x00ff0022);
return this.context.physics.sphereOverlap(rayOrigin, radius, false, true);
}
}
//# sourceMappingURL=Raycaster.js.map