@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.
124 lines • 6.28 kB
JavaScript
import { Color, PerspectiveCamera, PMREMGenerator } from "three";
import { getCameraController } from "../engine/engine_camera.js";
import { addNewComponent, getOrAddComponent } from "../engine/engine_components.js";
import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry.js";
import { getParam } from "../engine/engine_utils.js";
import { Camera, ClearFlags } from "./Camera.js";
import { OrbitControls } from "./OrbitControls.js";
import { EnvironmentScene } from "./utils/EnvironmentScene.js";
const debug = getParam("debugmissingcamera");
/**
* Handler for missing camera events. Creates a default fallback camera when no camera is found in the scene.
* Sets up camera properties based on the context and HTML element attributes.
*
* @param evt The context event containing scene and configuration information
* @returns The created camera component
*/
ContextRegistry.registerCallback(ContextEvent.MissingCamera, (evt) => {
if (debug)
console.warn("Creating missing camera");
const scene = evt.context.scene;
const cameraObject = new PerspectiveCamera();
cameraObject.name = "Default Fallback Camera";
scene.add(cameraObject);
const camInstance = new Camera();
camInstance.sourceId = evt.files?.[0]?.src ?? "unknown";
camInstance.fieldOfView = 35;
const transparentAttribute = evt.context.domElement.getAttribute("transparent");
if (transparentAttribute != undefined) {
camInstance.clearFlags = ClearFlags.Uninitialized;
}
// Set the clearFlags to a skybox if we have one OR if the user set a background-image attribute
else if (evt.context.domElement.getAttribute("background-image")?.length || evt.context.lightmaps.tryGetSkybox(camInstance.sourceId)) {
camInstance.clearFlags = ClearFlags.Skybox;
// TODO: can we store the backgroundBlurriness in the gltf file somewhere except inside the camera?
// e.g. when we export a scene from blender without a camera in the scene
if (evt.context.domElement.getAttribute("background-blurriness") === null) {
evt.context.scene.backgroundBlurriness = 0.2; // default value, same as in Blender
}
}
else {
camInstance.clearFlags = ClearFlags.SolidColor;
// Don't set the background color if the user set a background color in the <needle-engine> element
if (!evt.context.domElement.getAttribute("background-color")) {
let backgroundColor = "#efefef";
if (typeof window !== undefined && (window.matchMedia('(prefers-color-scheme: dark)').matches)) {
backgroundColor = "#1f1f1f";
}
scene.background = new Color(backgroundColor); // dont set it on the camera because this might be controlled from "background-color" attribute which is set on the scene directly. If the camera has a background color, it will override the scene's background color
}
// Generate a default environment map if none is set
if (!scene.environment) {
const pmremGenerator = new PMREMGenerator(evt.context.renderer);
const env = new EnvironmentScene("neutral");
scene.environment = pmremGenerator.fromScene(env, .025).texture;
}
}
const cam = addNewComponent(cameraObject, camInstance, true);
cameraObject.position.x = 0;
cameraObject.position.y = 1;
cameraObject.position.z = 2;
const engineElement = evt.context.domElement;
// If the camera is missing and the <needle-engine controls> is not set to false, create default camera controls
// That way we still create controls if the attribute is not added to <needle-engine> at all
if (engineElement?.cameraControls != false) {
createDefaultCameraControls(evt.context, cam);
}
return cam;
});
/**
* Handler for context creation events. Checks if camera controls should be added
* to the main camera when the context is created.
*
* @param evt The context creation event containing the context information
*/
ContextRegistry.registerCallback(ContextEvent.ContextCreated, (evt) => {
if (!evt.context.mainCamera) {
if (debug)
console.log("Will not auto-fit because a default camera exists");
return;
}
// check if <needle-engine camera-controls> attribute is present or enabled
const engineElement = evt.context.domElement;
if (engineElement?.cameraControls == true) {
// Check if something else already acts as a camera controller
const existing = getCameraController(evt.context.mainCamera);
if (existing?.isCameraController == true) {
if (debug)
console.log("Will not auto-fit because a camera controller exists");
return;
}
createDefaultCameraControls(evt.context);
}
});
/**
* Creates default orbit camera controls for the specified camera.
* Configures auto-rotation and auto-fit settings based on HTML attributes.
*
* @param context The rendering context
* @param cam Optional camera component to attach controls to (uses main camera if not specified)
*/
function createDefaultCameraControls(context, cam) {
cam = cam ?? context.mainCameraComponent;
const cameraObject = cam?.gameObject;
if (debug)
console.log("Creating default camera controls", cam?.name);
if (cameraObject) {
const orbit = getOrAddComponent(cameraObject, OrbitControls);
orbit.sourceId = cam?.sourceId ?? "unknown";
const autoRotate = context.domElement.getAttribute("auto-rotate");
orbit.autoRotate = autoRotate !== undefined && autoRotate !== null && (autoRotate != "0" && autoRotate?.toLowerCase() != "false");
orbit.autoRotateSpeed = 0.5;
orbit.autoFit = true;
if (orbit.autoRotate && autoRotate) {
const autoRotateValue = parseFloat(autoRotate);
if (!isNaN(autoRotateValue)) {
orbit.autoRotateSpeed = autoRotateValue;
}
}
}
else {
console.warn("Missing camera object, can not add orbit controls");
}
}
//# sourceMappingURL=CameraUtils.js.map