Initial commit, based on src/vr/me2 from Feb/Mar 2021
This commit is contained in:
commit
fa100d4268
|
@ -0,0 +1,8 @@
|
|||
dummykey.crt
|
||||
dummykey.key
|
||||
dummykey.pem
|
||||
index.js
|
||||
index.js.map
|
||||
/lib
|
||||
/node_modules
|
||||
/_site
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<style>
|
||||
html, body {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#renderCanvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
touch-action: none;
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.babylonjs.com/babylon.js"></script>
|
||||
<script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
|
||||
<body>
|
||||
<canvas id="renderCanvas"></canvas>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -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 <tonyg@leastfixedpoint.com>",
|
||||
"dependencies": {
|
||||
"@babylonjs/core": "5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "15.0",
|
||||
"rollup": "3.8",
|
||||
"typescript": "4.9"
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
};
|
|
@ -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
|
|
@ -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<void> {
|
||||
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());
|
||||
}
|
|
@ -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<FreeCamera> {
|
||||
public camera: Nullable<FreeCamera> = null;
|
||||
public gamepad: Nullable<Gamepad> = null;
|
||||
public gamepadAngularSensibility = 200;
|
||||
public gamepadMoveSensibility = 40;
|
||||
|
||||
private _vector3: Vector3 = Vector3.Zero();
|
||||
private _vector2: Vector2 = Vector2.Zero();
|
||||
private _onGamepadConnectedObserver: Nullable<Observer<Gamepad>> = null;
|
||||
private _onGamepadDisconnectedObserver: Nullable<Observer<Gamepad>> = 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";
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
|
@ -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<Mesh> {
|
||||
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<Mesh> {
|
||||
return attachTexture(box(name, width, options.thickness ?? DEFAULT_THICKNESS, depth),
|
||||
scene,
|
||||
options.texturePath);
|
||||
}
|
||||
|
||||
export type Wall = {
|
||||
texture: string,
|
||||
thickness?: number,
|
||||
holes: Array<Mesh>,
|
||||
};
|
||||
|
||||
export async function room(name: string, width: number, depth: number, scene: Scene,
|
||||
floorTexture: string,
|
||||
ceilingTexture: string,
|
||||
wall0: Nullable<Wall>,
|
||||
wall1: Nullable<Wall>,
|
||||
wall2: Nullable<Wall>,
|
||||
wall3: Nullable<Wall>): Promise<Mesh> {
|
||||
const th = (wallDetails: Wall): number => wallDetails.thickness ?? DEFAULT_THICKNESS;
|
||||
const mkw = async (n: number, width: number, wallDetails: Wall): Promise<Mesh> => {
|
||||
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;
|
||||
}
|
|
@ -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<typeof PhotoDome>) {
|
||||
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;
|
||||
}
|
|
@ -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/**/*"]
|
||||
}
|
|
@ -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==
|
Loading…
Reference in New Issue