@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.
188 lines • 7.02 kB
JavaScript
import { Color, DirectionalLight, Fog, GridHelper, MeshStandardMaterial, PerspectiveCamera, PointLight, Scene, WebGLRenderer } from "three";
import { ObjectUtils, PrimitiveType } from "../engine_create_objects.js";
import { Mathf } from "../engine_math.js";
import { delay } from "../engine_utils.js";
/** Create with static `start`- used to start an XR session while waiting for session granted */
export class TemporaryXRContext {
static _active = null;
static get active() {
return this._active;
}
static _requestInFlight = false;
static async start(mode, init) {
if (this._active) {
console.error("Cannot start a new XR session while one is already active");
return null;
}
if (this._requestInFlight) {
console.error("Cannot start a new XR session while a request is already in flight");
return null;
}
if ('xr' in navigator && navigator.xr) {
if (!init) {
console.error("XRSessionInit must be provided");
return null;
}
this._requestInFlight = true;
const session = await navigator.xr.requestSession(mode, init);
session.addEventListener("end", () => {
this._active = null;
});
if (!this._requestInFlight) {
session.end();
return null;
}
this._requestInFlight = false;
this._active = new TemporaryXRContext(mode, init, session);
return this._active;
}
return null;
}
static async handoff() {
if (this._active) {
return this._active.handoff();
}
return null;
}
static async stop() {
this._requestInFlight = false;
if (this._active) {
await this._active.end();
await delay(100);
}
this._active = null;
}
_session;
_mode;
_init;
get isAR() {
return this._mode === "immersive-ar";
}
_renderer;
_camera;
_scene;
constructor(mode, init, session) {
this._mode = mode;
this._init = init;
this._session = session;
this._session.addEventListener("end", this.onEnd);
this._renderer = new WebGLRenderer({ alpha: true });
this._renderer.setAnimationLoop(this.onFrame);
this._renderer.xr.setSession(session);
this._renderer.xr.enabled = true;
this._camera = new PerspectiveCamera();
this._scene = new Scene();
this._scene.fog = new Fog(0x444444, 10, 250);
this._scene.add(this._camera);
this.setupScene();
}
end() {
if (!this._session)
return Promise.resolve();
return this._session.end();
}
/** returns the session and session info and stops the temporary rendering */
async handoff() {
if (!this._session)
throw new Error("Cannot handoff a session that has already ended");
const info = {
session: this._session,
mode: this._mode,
init: this._init
};
await this.onBeforeHandoff();
// calling onEnd here directly because we dont end the session
this.onEnd();
// set the session to null because we dont want this class to accidentaly end the session
//@ts-ignore
this._session = null;
return info;
}
onEnd = () => {
this._session?.removeEventListener("end", this.onEnd);
this._renderer.setAnimationLoop(null);
this._renderer.dispose();
this._scene.clear();
};
_lastTime = 0;
onFrame = (time, _frame) => {
const dt = time - this._lastTime;
this.update(time, dt);
if (this._camera.parent !== this._scene) {
this._scene.add(this._camera);
}
this._renderer.render(this._scene, this._camera);
};
/** can be used to prepare the user or fade to black */
async onBeforeHandoff() {
// for(const sphere of this._spheres) {
// sphere.removeFromParent();
// await delay(10);
// }
// const obj = ObjectUtils.createPrimitive(PrimitiveType.Cube);
// obj.position.z = -3;
// obj.position.y = .5;
// this._scene.add(obj);
await delay(1000);
this._scene.clear();
// await delay(100);
}
_objects = [];
setupScene() {
this._scene.background = new Color(0x000000);
this._scene.add(new GridHelper(5, 10, 0x111111, 0x111111));
const light = new DirectionalLight(0xffffff, 1);
light.position.set(0, 20, 0);
light.castShadow = false;
this._scene.add(light);
const light2 = new DirectionalLight(0xffffff, 1);
light2.position.set(0, -1, 0);
light2.castShadow = false;
this._scene.add(light2);
const light3 = new PointLight(0xffffff, 1, 100, 1);
light3.position.set(0, 2, 0);
light3.castShadow = false;
light3.distance = 200;
this._scene.add(light3);
const range = 50;
for (let i = 0; i < 100; i++) {
const material = new MeshStandardMaterial({
color: 0x222222,
metalness: 1,
roughness: .8,
});
// if we're in passthrough
if (this.isAR) {
material.emissive = new Color(Math.random(), Math.random(), Math.random());
material.emissiveIntensity = Math.random();
}
const type = Mathf.random(0, 1) > .5 ? PrimitiveType.Sphere : PrimitiveType.Cube;
const obj = ObjectUtils.createPrimitive(type, {
material
});
obj.position.x = Mathf.random(-range, range);
obj.position.y = Mathf.random(-2, range);
obj.position.z = Mathf.random(-range, range);
// random rotation
obj.rotation.x = Mathf.random(0, Math.PI * 2);
obj.rotation.y = Mathf.random(0, Math.PI * 2);
obj.rotation.z = Mathf.random(0, Math.PI * 2);
obj.scale.multiplyScalar(.5 + Math.random() * 10);
const dist = obj.position.distanceTo(this._camera.position) - obj.scale.x;
if (dist < 1) {
obj.position.multiplyScalar(1 + 1 / dist);
}
this._objects.push(obj);
this._scene.add(obj);
}
}
update(time, _deltaTime) {
const speed = time * .0004;
for (let i = 0; i < this._objects.length; i++) {
const obj = this._objects[i];
obj.position.y += Math.sin(speed + i * .5) * 0.005;
obj.rotateY(.002);
}
}
}
//# sourceMappingURL=TempXRContext.js.map