From fa100d4268afed2f0ee67b69a6e07f57e6a46342 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 3 Jan 2023 14:18:34 +0100 Subject: [PATCH] Initial commit, based on src/vr/me2 from Feb/Mar 2021 --- .gitignore | 8 +++ index.html | 24 +++++++ package.json | 21 ++++++ rollup.config.mjs | 21 ++++++ serve.sh | 8 +++ src/engine.ts | 174 ++++++++++++++++++++++++++++++++++++++++++++++ src/gamepad.ts | 77 ++++++++++++++++++++ src/index.ts | 150 +++++++++++++++++++++++++++++++++++++++ src/interiors.ts | 100 ++++++++++++++++++++++++++ src/shapes.ts | 49 +++++++++++++ tsconfig.json | 17 +++++ yarn.lock | 126 +++++++++++++++++++++++++++++++++ 12 files changed, 775 insertions(+) create mode 100644 .gitignore create mode 100644 index.html create mode 100644 package.json create mode 100644 rollup.config.mjs create mode 100755 serve.sh create mode 100644 src/engine.ts create mode 100644 src/gamepad.ts create mode 100644 src/index.ts create mode 100644 src/interiors.ts create mode 100644 src/shapes.ts create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c7579fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +dummykey.crt +dummykey.key +dummykey.pem +index.js +index.js.map +/lib +/node_modules +/_site diff --git a/index.html b/index.html new file mode 100644 index 0000000..d7554fd --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..f68194b --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "vr-experiments3", + "version": "0.0.0", + "description": "vr experiments 3", + "scripts": { + "compile": "tsc", + "compile:watch": "tsc -w", + "rollup": "rollup -c", + "rollup:watch": "rollup -c -w", + "prepare": "yarn run compile && yarn run rollup" + }, + "author": "Tony Garnock-Jones ", + "dependencies": { + "@babylonjs/core": "5" + }, + "devDependencies": { + "@rollup/plugin-node-resolve": "15.0", + "rollup": "3.8", + "typescript": "4.9" + } +} diff --git a/rollup.config.mjs b/rollup.config.mjs new file mode 100644 index 0000000..4cedf2b --- /dev/null +++ b/rollup.config.mjs @@ -0,0 +1,21 @@ +import resolve from '@rollup/plugin-node-resolve'; +import path from 'path'; + +const babylon = '@babylonjs/core/Legacy/legacy'; + +export default { + input: 'lib/index.js', + plugins: [resolve({ browser: true })], + external: [ + babylon, + ], + output: { + file: 'index.js', + format: 'umd', + name: 'Main', + sourcemap: true, + globals: { + [babylon]: 'BABYLON', + }, + }, +}; diff --git a/serve.sh b/serve.sh new file mode 100755 index 0000000..66f1e17 --- /dev/null +++ b/serve.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -e +if [ ! -f dummykey.crt ] +then + openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout dummykey.key -out dummykey.crt + cat dummykey.key dummykey.crt > dummykey.pem +fi +exec jekyll serve -H 0.0.0.0 --ssl-key dummykey.key --ssl-cert dummykey.crt --port 8443 diff --git a/src/engine.ts b/src/engine.ts new file mode 100644 index 0000000..8d9e81e --- /dev/null +++ b/src/engine.ts @@ -0,0 +1,174 @@ +import { + AbstractMesh, + DualShockPad, + Engine, + FreeCamera, + Gamepad as b_Gamepad, + Mesh, + Quaternion, + Scene, + Vector3, +} from '@babylonjs/core/Legacy/legacy'; + +import { GamepadInput } from './gamepad.js'; + +let buttonDown : { [button: number]: boolean } = {}; + +function latch(gp: Gamepad, button: number): boolean { + let result = false; + const b = gp.buttons[button]; + if (b) { + if (b.pressed) { + if (!(buttonDown[button] ?? false)) result = true; + buttonDown[button] = true; + } else { + buttonDown[button] = false; + } + } + return result; +} + +if ((navigator as any).oscpu?.startsWith('Linux')) { + // ^ oscpu is undefined on chrome on Android, at least... + + DualShockPad.prototype.update = function () { + b_Gamepad.prototype.update.call(this); + (window as any).G = this; + + (this as any)._rightStickAxisX = 3; + (this as any)._rightStickAxisY = 4; + + this.buttonCross = this.browserGamepad.buttons[0].value; + this.buttonCircle = this.browserGamepad.buttons[1].value; + this.buttonTriangle = this.browserGamepad.buttons[2].value; + this.buttonSquare = this.browserGamepad.buttons[3].value; + + this.buttonL1 = this.browserGamepad.buttons[4].value; + this.buttonR1 = this.browserGamepad.buttons[5].value; + this.leftTrigger = this.browserGamepad.buttons[6].value; + this.rightTrigger = this.browserGamepad.buttons[7].value; + this.buttonShare = this.browserGamepad.buttons[8].value; + this.buttonOptions = this.browserGamepad.buttons[9].value; + + this.buttonLeftStick = this.browserGamepad.buttons[11].value; + this.buttonRightStick = this.browserGamepad.buttons[12].value; + + this.dPadUp = this.browserGamepad.axes[7].value < 0 ? 1 : 0; + this.dPadDown = this.browserGamepad.axes[7].value > 0 ? 1 : 0; + this.dPadLeft = this.browserGamepad.axes[6].value < 0 ? 1 : 0; + this.dPadRight = this.browserGamepad.axes[6].value > 0 ? 1 : 0; + }; +} + +export type CreateScene = (canvas: HTMLCanvasElement, engine: Engine) => Promise<{ + scene: Scene, + floorMeshes: Mesh[] +}>; + +export async function startEngine( + createScene: CreateScene, + initialPos = new Vector3(0, 1.6, -3.5), + initialRotation = new Vector3(0, 0.5, 0).scaleInPlace(2 * Math.PI), +): Promise { + const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement; + const engine = new Engine(canvas, true); + const { scene, floorMeshes } = await createScene(canvas, engine); + + const xr = await scene.createDefaultXRExperienceAsync({}); + const xrAvailable = xr.baseExperience !== void 0; + + let camera: FreeCamera; + if (xrAvailable) { + camera = xr.baseExperience.camera; + } else { + camera = new FreeCamera("camera", initialPos, scene); + camera.minZ = 0.1; + camera.rotation = initialRotation; + camera.attachControl(canvas, true); + } + const sm = xrAvailable ? xr.baseExperience.sessionManager : null; + + const gamepadInput = new GamepadInput(); + camera.inputs.add(gamepadInput); + gamepadInput.attachControl(); + + scene.gravity = new Vector3(0, -9.81 / 90, 0); + scene.collisionsEnabled = true; + + camera.checkCollisions = true; + camera.applyGravity = true; + camera.ellipsoid = new Vector3(0.25, 0.8, 0.25); + + scene.getNodes().forEach(n => { + if (n instanceof AbstractMesh) { + n.checkCollisions = true; + } + }); + + const enableVR = () => { + xr.baseExperience.enterXRAsync('immersive-vr', 'local').then(() => { + xr.baseExperience.camera.position = initialPos; + xr.baseExperience.camera.rotation = initialRotation; + }); + }; + + document.body.onclick = enableVR; + + let leanBase: { position: Vector3 } | null = null; + + engine.runRenderLoop(() => { + + for (const gp of Array.from(navigator.getGamepads())) { + if (gp !== null) { + const pos = new Vector3((gp.axes[0]), (-gp.axes[1]), (-gp.axes[3])); + if (pos.length() > 0.0625) { + if (leanBase === null) { + leanBase = { position: xr.baseExperience.camera.position }; + } + xr.baseExperience.camera.position = + pos.applyRotationQuaternion(xr.baseExperience.camera.absoluteRotation) + .scale(0.25) + .add(leanBase.position); + } else { + if (leanBase !== null) { + xr.baseExperience.camera.position = leanBase.position; + } + leanBase = null; + } + + if (sm && latch(gp, 0)) { + const ray = xr.baseExperience.camera.getForwardRay(); + const hit = scene.pickWithRay(ray, m => floorMeshes.indexOf(m as any) !== -1); + if (hit !== null) { + if (floorMeshes.indexOf(hit.pickedMesh as any) !== -1) { + if (hit.pickedPoint !== null) { + xr.baseExperience.camera.position = + hit.pickedPoint.add(new Vector3(0, 1.6, 0)); + xr.baseExperience.camera.rotation = Vector3.Zero(); + if (leanBase !== null) { + leanBase.position = xr.baseExperience.camera.position; + } + } + } + } + } + if (latch(gp, 2)) { + location.reload(); + } + if (sm && latch(gp, 3)) { + enableVR(); + } + if (sm && latch(gp, 5)) { + const q = xr.baseExperience.camera.rotationQuaternion; + const r = q.toEulerAngles(); + r.y += Math.PI; + r.y %= 2 * Math.PI; + q.copyFrom(Quaternion.FromEulerAngles(r.x, r.y, r.z)); + } + } + } + + scene.render(); + }); + window.addEventListener("resize", () => engine.resize()); +} diff --git a/src/gamepad.ts b/src/gamepad.ts new file mode 100644 index 0000000..280de77 --- /dev/null +++ b/src/gamepad.ts @@ -0,0 +1,77 @@ +import { Observer } from "@babylonjs/core/Misc/observable"; +import { Nullable } from "@babylonjs/core/types"; +import { ICameraInput } from "@babylonjs/core/Cameras/cameraInputsManager"; +import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera"; +import { Matrix, Vector3, Vector2 } from "@babylonjs/core/Maths/math.vector"; +import { Gamepad } from "@babylonjs/core/Gamepads/gamepad"; +import "@babylonjs/core/Gamepads/gamepadSceneComponent"; + +export class GamepadInput implements ICameraInput { + public camera: Nullable = null; + public gamepad: Nullable = null; + public gamepadAngularSensibility = 200; + public gamepadMoveSensibility = 40; + + private _vector3: Vector3 = Vector3.Zero(); + private _vector2: Vector2 = Vector2.Zero(); + private _onGamepadConnectedObserver: Nullable> = null; + private _onGamepadDisconnectedObserver: Nullable> = null; + + public attachControl(): void { + if (this.camera === null) return; + let manager = this.camera.getScene().gamepadManager; + this._onGamepadConnectedObserver = manager.onGamepadConnectedObservable.add((gamepad) => { + this.gamepad = this.gamepad ?? gamepad; + }); + this._onGamepadDisconnectedObserver = manager.onGamepadDisconnectedObservable.add((gamepad) => { + if (this.gamepad === gamepad) this.gamepad = null; + }); + this.gamepad = manager.gamepads[0]; + } + + public detachControl(): void { + if (this.camera !== null) { + let manager = this.camera.getScene().gamepadManager; + manager.onGamepadConnectedObservable.remove(this._onGamepadConnectedObserver); + manager.onGamepadDisconnectedObservable.remove(this._onGamepadDisconnectedObserver); + } + this.gamepad = null; + } + + public checkInputs(): void { + if (this.gamepad && this.gamepad.leftStick) { + const camera = this.camera!; + + const LSValues = this.gamepad.leftStick; + const normalizedLX = LSValues.x / this.gamepadMoveSensibility; + const normalizedLY = LSValues.y / this.gamepadMoveSensibility; + LSValues.x = Math.abs(normalizedLX) > 0.005 ? 0 + normalizedLX : 0; + LSValues.y = Math.abs(normalizedLY) > 0.005 ? 0 + normalizedLY : 0; + + const RSValues = this.gamepad.rightStick ?? { x: 0, y: 0 }; + var normalizedRX = RSValues.x / this.gamepadAngularSensibility; + var normalizedRY = RSValues.y / this.gamepadAngularSensibility; + RSValues.x = Math.abs(normalizedRX) > 0.001 ? 0 + normalizedRX : 0; + RSValues.y = Math.abs(normalizedRY) > 0.001 ? 0 + normalizedRY : 0; + + const _cameraTransform = Matrix.Identity(); + Matrix.RotationYawPitchRollToRef(camera.rotation.y, 0, 0, _cameraTransform); + var speed = camera._computeLocalCameraSpeed() * 10.0; + this._vector3.copyFromFloats(LSValues.x * speed, 0, -LSValues.y * speed); + const _deltaTransform = Vector3.Zero(); + Vector3.TransformCoordinatesToRef(this._vector3, _cameraTransform, _deltaTransform); + camera.cameraDirection.addInPlace(_deltaTransform); + + this._vector2.copyFromFloats(RSValues.y, RSValues.x); + camera.cameraRotation.addInPlace(this._vector2); + } + } + + public getClassName(): string { + return "GamepadInput"; + } + + public getSimpleName(): string { + return "gamepad"; + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..d9f73fc --- /dev/null +++ b/src/index.ts @@ -0,0 +1,150 @@ +import { + Engine, + HemisphericLight, + Mesh, + MeshBuilder, + Scene, + Vector3, +} from '@babylonjs/core/Legacy/legacy'; + +import { box, PhotoSemiDome } from './shapes.js'; +import { DEFAULT_THICKNESS, room } from './interiors.js'; +import { startEngine } from './engine.js'; + +async function createScene(_canvas: HTMLCanvasElement, engine: Engine): Promise<{ + scene: Scene, + floorMeshes: Mesh[] +}> { + const scene = new Scene(engine); + + new HemisphericLight("light", new Vector3(0, 1, 0), scene); + + const streetDome = new PhotoSemiDome("street", "papenweg-textures/individual/street-panorama.jpg", { + halfDomeMode: true, + size: 10, + }, scene); + streetDome.position.z = (3.80 / 2) - 0.4; + streetDome.position.y = 0.4; + + const gardenDome = new PhotoSemiDome("garden", "papenweg-textures/individual/garden-panorama.jpg", { + halfDomeMode: true, + size: 10, + }, scene); + gardenDome.position.z = -4.06 - (3.80 / 2) - 1.2; + gardenDome.position.y = 1.2; + gardenDome.rotation.y = Math.PI; + + const sittingroom = await room( + "sittingroom", 3.80, 4.06, scene, + "papenweg-textures/individual/floor1.jpg", + "papenweg-textures/individual/wallpaper1.jpg", + { + texture: "papenweg-textures/individual/sittingroom_0_window.jpg", + thickness: DEFAULT_THICKNESS / 2, + holes: [ + box("w0b", 0.55, 1.25, DEFAULT_THICKNESS, -0.4, 1.55), + box("w0c", 0.55, 1.25, DEFAULT_THICKNESS, 0.3, 1.55), + box("w0d", 0.55, 1.25, DEFAULT_THICKNESS, 1.0, 1.55), + box("w0a", 0.55, 1.25, DEFAULT_THICKNESS, -1.1, 1.55), + ] + }, + { + texture: "papenweg-textures/individual/sittingroom_1_tvwall.jpg", + holes: [] + }, + { + texture: "papenweg-textures/individual/sittingroom_2_doors.jpg", + thickness: DEFAULT_THICKNESS / 2, + holes: [ + box("w2a", 0.6, 1.99, DEFAULT_THICKNESS, 0.2), + box("ww1", 0.28, 0.28, DEFAULT_THICKNESS, -0.64, 1.7), + box("ww2", 0.28, 0.28, DEFAULT_THICKNESS, -0.64, 1.4), + box("ww3", 0.28, 0.28, DEFAULT_THICKNESS, -0.64, 1.1), + box("ww4", 0.28, 0.28, DEFAULT_THICKNESS, -0.64, 0.8), + box("ww5", 0.28, 0.28, DEFAULT_THICKNESS, -0.34, 1.7), + box("ww6", 0.28, 0.28, DEFAULT_THICKNESS, -0.34, 1.4), + box("ww7", 0.28, 0.28, DEFAULT_THICKNESS, -0.34, 1.1), + box("ww8", 0.28, 0.28, DEFAULT_THICKNESS, -0.34, 0.8), + box("ww9", 0.20, 0.28, DEFAULT_THICKNESS, 0.74, 1.7), + box("wwA", 0.20, 0.28, DEFAULT_THICKNESS, 0.74, 1.4), + box("wwB", 0.20, 0.28, DEFAULT_THICKNESS, 0.74, 1.1), + box("wwC", 0.20, 0.28, DEFAULT_THICKNESS, 0.74, 0.8), + ] + }, + { + texture: "papenweg-textures/individual/sittingroom_3_sofawall.jpg", + holes: [ + box("w3a", 0.83, 2.08, DEFAULT_THICKNESS, -1.33), + ] + }); + + const diningroom = await room( + "diningroom", 3.80, 4.06, scene, + "papenweg-textures/individual/floor1.jpg", + "papenweg-textures/individual/wallpaper1.jpg", + { + texture: "papenweg-textures/individual/diningroom_0_doors.jpg", + thickness: DEFAULT_THICKNESS / 2, + holes: [ + box("dw2a", 0.6, 1.99, DEFAULT_THICKNESS, -0.2), + box("dww1", 0.28, 0.28, DEFAULT_THICKNESS, 0.64, 1.7), + box("dww2", 0.28, 0.28, DEFAULT_THICKNESS, 0.64, 1.4), + box("dww3", 0.28, 0.28, DEFAULT_THICKNESS, 0.64, 1.1), + box("dww4", 0.28, 0.28, DEFAULT_THICKNESS, 0.64, 0.8), + box("dww5", 0.28, 0.28, DEFAULT_THICKNESS, 0.34, 1.7), + box("dww6", 0.28, 0.28, DEFAULT_THICKNESS, 0.34, 1.4), + box("dww7", 0.28, 0.28, DEFAULT_THICKNESS, 0.34, 1.1), + box("dww8", 0.28, 0.28, DEFAULT_THICKNESS, 0.34, 0.8), + box("dww9", 0.20, 0.28, DEFAULT_THICKNESS, -0.74, 1.7), + box("dwwA", 0.20, 0.28, DEFAULT_THICKNESS, -0.74, 1.4), + box("dwwB", 0.20, 0.28, DEFAULT_THICKNESS, -0.74, 1.1), + box("dwwC", 0.20, 0.28, DEFAULT_THICKNESS, -0.74, 0.8), + ] + }, + { + texture: "papenweg-textures/individual/diningroom_1_fireplace.jpg", + holes: [] + }, + { + texture: "papenweg-textures/individual/diningroom_2_window.jpg", + thickness: DEFAULT_THICKNESS / 2, + holes: (() => { + const bc = (name: string, left: number, top: number, bw: number, bh: number) => [ + box(name + "1", bw, bh, DEFAULT_THICKNESS, left + (0 * (bw + 0.02)), top - (0 * (bh + 0.02))), + box(name + "2", bw, bh, DEFAULT_THICKNESS, left + (0 * (bw + 0.02)), top - (1 * (bh + 0.02))), + box(name + "3", bw, bh, DEFAULT_THICKNESS, left + (0 * (bw + 0.02)), top - (2 * (bh + 0.02))), + box(name + "4", bw, bh, DEFAULT_THICKNESS, left + (0 * (bw + 0.02)), top - (3 * (bh + 0.02))), + box(name + "5", bw, bh, DEFAULT_THICKNESS, left + (1 * (bw + 0.02)), top - (0 * (bh + 0.02))), + box(name + "6", bw, bh, DEFAULT_THICKNESS, left + (1 * (bw + 0.02)), top - (1 * (bh + 0.02))), + box(name + "7", bw, bh, DEFAULT_THICKNESS, left + (1 * (bw + 0.02)), top - (2 * (bh + 0.02))), + box(name + "8", bw, bh, DEFAULT_THICKNESS, left + (1 * (bw + 0.02)), top - (3 * (bh + 0.02))), + ]; + return [ + ... bc("dwA", -0.84, 1.69, 0.25, 0.29), + ... bc("dwB", -0.09, 1.69, 0.25, 0.29), + ... bc("dwC", 0.57, 1.69, 0.24, 0.28), + ... bc("dwD", -1.47, 1.69, 0.24, 0.28), + ]; + })(), + }, + { + texture: "papenweg-textures/individual/diningroom_3_heater.jpg", + holes: [ + box("w3a", 0.83, 2.01, DEFAULT_THICKNESS, 1.46), + ] + }); + diningroom.position.z = -(4.06 + DEFAULT_THICKNESS); + + return { + scene, + floorMeshes: [ + sittingroom, + diningroom, + MeshBuilder.CreateGround("ground", {width:30, height:30}), + ], + }; +} + +window.addEventListener('load', async () => { + await startEngine(createScene); +}); diff --git a/src/interiors.ts b/src/interiors.ts new file mode 100644 index 0000000..ed71fb4 --- /dev/null +++ b/src/interiors.ts @@ -0,0 +1,100 @@ +import { + Mesh, + Nullable, + Scene, + StandardMaterial, + Texture, +} from '@babylonjs/core/Legacy/legacy'; + +import { adjust, box, subtractMany } from './shapes.js'; + +export interface SurfaceOptions { + height?: number, + thickness?: number, + texturePath?: string, +}; + +export function attachTexture(b: Mesh, scene: Scene, texturePath: string | undefined): Mesh { + if (texturePath !== void 0) { + const mat = new StandardMaterial(b.name + "_mat", scene); + mat.diffuseTexture = new Texture(texturePath, scene); + b.material = mat; + } + return b; +} + +export const DEFAULT_THICKNESS = 0.125; + +export async function wall(name: string, length: number, scene: Scene, options: SurfaceOptions = {}): Promise { + return attachTexture(box(name, length, options.height ?? 2.90, options.thickness ?? DEFAULT_THICKNESS), + scene, + options.texturePath); +} + +export async function floor(name: string, width: number, depth: number, scene: Scene, options: SurfaceOptions = {}): Promise { + return attachTexture(box(name, width, options.thickness ?? DEFAULT_THICKNESS, depth), + scene, + options.texturePath); +} + +export type Wall = { + texture: string, + thickness?: number, + holes: Array, +}; + +export async function room(name: string, width: number, depth: number, scene: Scene, + floorTexture: string, + ceilingTexture: string, + wall0: Nullable, + wall1: Nullable, + wall2: Nullable, + wall3: Nullable): Promise { + const th = (wallDetails: Wall): number => wallDetails.thickness ?? DEFAULT_THICKNESS; + const mkw = async (n: number, width: number, wallDetails: Wall): Promise => { + return adjust(subtractMany, + await wall(`${name}_${n}`, width, scene, { + texturePath: wallDetails.texture, + thickness: wallDetails.thickness, + }), + ... wallDetails.holes); + }; + + const f = await floor(name +"_f", width, depth, scene, { texturePath: floorTexture }); + f.position.y = -DEFAULT_THICKNESS / 2; + + const c = await floor(name + "_c", width, depth, scene, { texturePath: ceilingTexture }); + c.position.y = 2.90 + DEFAULT_THICKNESS / 2; + c.setParent(f); + + if (wall0 !== null) { + const w0 = await mkw(0, width, wall0); + w0.position.z = f.scaling.z / 2 + th(wall0) / 2; + w0.setParent(f); + } + + if (wall1 !== null) { + const w1 = await mkw(1, depth, wall1); + w1.position.x = f.scaling.x / 2 + th(wall1) / 2; + w1.rotation.y = Math.PI / 2; + w1.setParent(f); + } + + if (wall2 !== null) { + const w2 = await mkw(2, width, wall2); + w2.position.z = -(f.scaling.z / 2 + th(wall2) / 2); + w2.rotation.y = Math.PI; + w2.setParent(f); + } + + if (wall3 !== null) { + const w3 = await mkw(3, depth, wall3); + w3.position.x = -(f.scaling.x / 2 + th(wall3) / 2); + w3.rotation.y = -Math.PI / 2; + w3.setParent(f); + } + + f.position.y = DEFAULT_THICKNESS / 2; + + return f; +} diff --git a/src/shapes.ts b/src/shapes.ts new file mode 100644 index 0000000..ed03b97 --- /dev/null +++ b/src/shapes.ts @@ -0,0 +1,49 @@ +import { + CSG, + Mesh, + MeshBuilder, + PhotoDome, + Vector3, +} from '@babylonjs/core/Legacy/legacy'; + +export function adjust(f: (... csgs: CSG[]) => CSG, ... ms: Mesh[]): Mesh { + const csgs = ms.map(m => CSG.FromMesh(m)); + const c = f(... csgs); + if (ms.length > 0) { + ms.forEach(m => m.dispose()); + const scene = ms[0].getScene(); + const ans = c.toMesh(ms[0].name, null, scene, true); + ans.material = ms[0].material; + return ans; + } else { + return c.toMesh("adjusted"); + } +} + +export const subtractMany = (a: CSG, ... bs: CSG[]) => bs.reduce((a, b) => a.subtract(b), a); + +export class PhotoSemiDome extends PhotoDome { + readonly _size: number; + + constructor(... args: ConstructorParameters) { + super(... args); + this._size = args[2].size!; + this._chopMesh(); + } + + _chopMesh() { + const d = this._size; + this._mesh.getChildMeshes()[0].dispose(); // remove the covering half-sphere + this._mesh = adjust(subtractMany, + this._mesh, + box(this._mesh.name + "_chop", d, d, d, 0, 0, -d/2)); + this._mesh.parent = this; + } +} + +export function box(name: string, width: number, height: number, depth: number, x?: number, y?: number, z?: number): Mesh { + const b = MeshBuilder.CreateBox(name, {}); + b.scaling = new Vector3(width, height, depth); + b.position = new Vector3(x ?? 0, y ?? b.scaling.y/2, z ?? 0); + return b; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..983aa20 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["ES2017", "DOM"], + "declaration": true, + "baseUrl": "./src", + "rootDir": "./src", + "outDir": "./lib", + "declarationDir": "./lib", + "esModuleInterop": true, + "moduleResolution": "node", + "module": "es6", + "sourceMap": true, + "strict": true + }, + "include": ["src/**/*"] +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..f870d80 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,126 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babylonjs/core@5": + version "5.39.0" + resolved "https://registry.yarnpkg.com/@babylonjs/core/-/core-5.39.0.tgz#8343fdc2ef105005bc564fc98bd36008e4301d08" + integrity sha512-bGDsxFbIq9GeiwpojdnQB58s1EvDxJSzUK5C8hL3Wj8UqK6tCELNTEHonmJ8KruilLGQRpm2mrrPZWEzUvAKkA== + +"@rollup/plugin-node-resolve@15.0": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz#72be449b8e06f6367168d5b3cd5e2802e0248971" + integrity sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg== + dependencies: + "@rollup/pluginutils" "^5.0.1" + "@types/resolve" "1.20.2" + deepmerge "^4.2.2" + is-builtin-module "^3.2.0" + is-module "^1.0.0" + resolve "^1.22.1" + +"@rollup/pluginutils@^5.0.1": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz#012b8f53c71e4f6f9cb317e311df1404f56e7a33" + integrity sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" + +"@types/estree@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== + +"@types/resolve@1.20.2": + version "1.20.2" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" + integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q== + +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +is-builtin-module@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.0.tgz#bb0310dfe881f144ca83f30100ceb10cf58835e0" + integrity sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw== + dependencies: + builtin-modules "^3.3.0" + +is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== + dependencies: + has "^1.0.3" + +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +resolve@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +rollup@3.8: + version "3.8.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.8.1.tgz#d4af8aca7c60d5b8c0281be79ea2fab6b41d458f" + integrity sha512-4yh9eMW7byOroYcN8DlF9P/2jCpu6txVIHjEqquQVSx7DI0RgyCCN3tjrcy4ra6yVtV336aLBB3v2AarYAxePQ== + optionalDependencies: + fsevents "~2.3.2" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +typescript@4.9: + version "4.9.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" + integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==