// src/scripts/XRCameraController.ts
import { Behaviour, serializable, Context } from “@needle-tools/engine”;
import * as THREE from “three”;
// Define your layers (consider putting these in a shared constants file)
const LEFT_EYE_LAYER = 8;
const RIGHT_EYE_LAYER = 9;
const DEFAULT_LAYER = 0;
// Add any other layers you might use
const UI_LAYER = 5;
export class XRCameraController extends Behaviour {
@serializable()
targetLayer: number = DEFAULT_LAYER;
// Keep track if XR layers were set to avoid redundant logs/operations
private xrLayersSet: boolean = false;
start() {
this.setCameraLayers();
}
// Optional: Use update or lateUpdate if layers need to change dynamically
// lateUpdate() {
// // Example: Change layer based on some condition
// // if (someCondition) {
// // this.setCameraLayers(NEW_LAYER);
// // }
// }
/**
* Sets the layers for the specified cameras (Main and/or XR).
* Call this method from other scripts or animations to change layers dynamically.
* @param newTargetLayer The primary layer the camera should render.
*/
public setCameraLayers() {
this.xrLayersSet = false; // Reset XR flag if called again
console.log(`Setting camera layers to target: ${this.targetLayer}, include default: ${this.targetLayer}`);
// --- Apply to Main Camera ---
//this.configureCameraLayers(this.context.mainCamera, DEFAULT_LAYER, "Main Camera");
this.context.mainCamera.layers.disableAll();
this.context.mainCamera.layers.enable(this.targetLayer);
this.context.mainCamera.layers.enable(LEFT_EYE_LAYER);
this.context.mainCamera.layers.enable(RIGHT_EYE_LAYER);
// --- Apply to XR Cameras ---
// We often need to do this continuously in update/lateUpdate for XR,
// as the XR manager might reset layers. The XRCameraController example
// does this in lateUpdate. If using *this* script *instead* of
// XRCameraController for layers, you might move the XR part to lateUpdate.
// For simplicity here, we'll do it once when called.
this.trySetXRLayers();
}
// Use lateUpdate to potentially override XR settings if needed, similar to XRCameraController
lateUpdate() {
// If applying to XR, ensure layers are set correctly each frame,
// especially if another system might be interfering.
if (this.context.renderer?.xr?.isPresenting && !this.xrLayersSet) {
this.trySetXRLayers();
}
// Reset flag if no longer presenting
if (!this.context.renderer?.xr?.isPresenting) {
this.xrLayersSet = false;
}
}
private trySetXRLayers() {
const renderer = this.context.renderer;
if (!renderer) return;
const xrManager = renderer.xr;
// Check if XR is active and we have the camera array
if (xrManager && xrManager.enabled && xrManager.isPresenting) {
const xrCamera = xrManager.getCamera() as THREE.WebXRArrayCamera;
if (xrCamera && xrCamera.isArrayCamera && xrCamera.cameras.length >= 2) {
// Configure Left Eye (typically uses LEFT_EYE_LAYER or the main targetLayer)
// Decide if XR eyes should see *only* their specific layer + default,
// or if they should see the general 'targetLayer' + default.
// Let's assume they should see their specific eye layer + default for stereo separation.
this.configureCameraLayers(xrCamera.cameras[0], LEFT_EYE_LAYER, "XR Left Eye");
// Configure Right Eye
this.configureCameraLayers(xrCamera.cameras[1], RIGHT_EYE_LAYER, "XR Right Eye");
this.xrLayersSet = true; // Mark as set for this frame/session
xrCamera.layers.enable(this.targetLayer);
xrCamera.layers.enable(LEFT_EYE_LAYER);
xrCamera.layers.enable(RIGHT_EYE_LAYER);
}
// Optional: Log if XR is presenting but camera isn't ready yet
else if (this.context.time.frameCount % 120 === 0) { // Log less frequently
console.log("CameraLayerController: XR Presenting, but ArrayCamera not valid yet.");
}
}
}
/**
* Helper function to configure layers for a single camera.
*/
private configureCameraLayers(camera: THREE.Camera, layerToEnable: number, cameraName: string) {
// Start fresh by disabling all layers
camera.layers.disableAll();
// Enable the target layer
camera.layers.enable(layerToEnable);
// Log the resulting mask for debugging
console.log(`${cameraName} layers set. Mask: ${camera.layers.mask}`);
}
}
// Updated SetLayer.ts
import { Behaviour, serializable } from “@needle-tools/engine”;
import * as THREE from “three”;
const LEFT_EYE_LAYER = 8;
const RIGHT_EYE_LAYER = 9;
const DEFAULT_LAYER = 0;
export class SetLayer extends Behaviour {
@serializable()
myLayer: number = DEFAULT_LAYER;
start() {
const obj3d = this.gameObject as THREE.Object3D;
if (obj3d) {
// Apply layer recursively to this object and all its children
obj3d.traverse((child) => {
// Check if it's a Mesh or potentially other renderable types if needed
// if (child instanceof THREE.Mesh /* || child instanceof THREE.Line etc. */) {
child.layers.set(this.myLayer);
// }
});
// Log the mask of the root object this script is attached to for confirmation
console.log(`${this.gameObject.name} root and children layers set for layer ${this.myLayer}. Root mask: ${obj3d.layers.mask}`);
// Optional: Log mask of first Mesh child found for deeper debugging
const meshChild = obj3d.getObjectByProperty("isMesh", true);
if(meshChild) {
console.log(` - First mesh child (${meshChild.name || 'unnamed'}) mask: ${meshChild.layers.mask}`);
}
} else {
console.warn("Could not find Object3D for layer assignment on", this.gameObject.name);
}
}
}