Refactored some larger classses...added enhanced map capability.

This commit is contained in:
Michael Mainguy 2024-06-03 08:34:48 -05:00
parent 7806760153
commit 7315e3397a
19 changed files with 271 additions and 353 deletions

View File

@ -18,6 +18,9 @@
<link href="/manifest.webmanifest" rel="manifest"/> <link href="/manifest.webmanifest" rel="manifest"/>
<!--<script src='/niceware.js'></script>--> <!--<script src='/niceware.js'></script>-->
<style> <style>
#feed {
display: none;
}
#keyboardHelp { #keyboardHelp {
display: none; display: none;
width: 665px; width: 665px;
@ -81,6 +84,7 @@
</div> </div>
<!--<video id="feed" controls="" autoplay="" name="media"><source src="https://listen.broadcastify.com/1drb2xhywkg8nvz.mp3?nc=49099&amp;xan=xtf9912b41c" type="audio/mpeg"></video> -->
<div class="scene"> <div class="scene">
<canvas id="gameCanvas"></canvas> <canvas id="gameCanvas"></canvas>
</div> </div>

View File

@ -8,6 +8,7 @@ import {DefaultScene} from "../defaultScene";
import {DiagramMenuManager} from "./diagramMenuManager"; import {DiagramMenuManager} from "./diagramMenuManager";
import {DiagramEventObserverMask} from "./types/diagramEventObserverMask"; import {DiagramEventObserverMask} from "./types/diagramEventObserverMask";
import {DiagramObject} from "./diagramObject"; import {DiagramObject} from "./diagramObject";
import {getMe} from "../util/me";
export class DiagramManager { export class DiagramManager {
@ -19,8 +20,9 @@ export class DiagramManager {
private readonly _diagramMenuManager: DiagramMenuManager; private readonly _diagramMenuManager: DiagramMenuManager;
private readonly _scene: Scene; private readonly _scene: Scene;
private readonly _diagramObjects: Map<string, DiagramObject> = new Map<string, DiagramObject>(); private readonly _diagramObjects: Map<string, DiagramObject> = new Map<string, DiagramObject>();
private readonly _me: string;
constructor() { constructor() {
this._me = getMe();
this._scene = DefaultScene.Scene; this._scene = DefaultScene.Scene;
this._config = new AppConfig(); this._config = new AppConfig();
this._controllers = new Controllers(); this._controllers = new Controllers();
@ -104,6 +106,8 @@ export class DiagramManager {
} }
if (diagramObject) { if (diagramObject) {
this._diagramObjects.set(event.entity.id, diagramObject); this._diagramObjects.set(event.entity.id, diagramObject);
} else {
this._logger.error('failed to create diagram object for ', event.entity);
} }
break; break;
case DiagramEventType.REMOVE: case DiagramEventType.REMOVE:

View File

@ -11,6 +11,7 @@ import {AppConfig} from "../util/appConfig";
import {DiagramEventObserverMask} from "./types/diagramEventObserverMask"; import {DiagramEventObserverMask} from "./types/diagramEventObserverMask";
import {ConnectionPreview} from "../menus/connectionPreview"; import {ConnectionPreview} from "../menus/connectionPreview";
import {ScaleMenu2} from "../menus/ScaleMenu2"; import {ScaleMenu2} from "../menus/ScaleMenu2";
import {CameraMenu} from "../menus/cameraMenu";
export class DiagramMenuManager { export class DiagramMenuManager {
@ -20,11 +21,14 @@ export class DiagramMenuManager {
private readonly _notifier: Observable<DiagramEvent>; private readonly _notifier: Observable<DiagramEvent>;
private readonly _inputTextView: InputTextView; private readonly _inputTextView: InputTextView;
private readonly _scene: Scene; private readonly _scene: Scene;
private _cameraMenu: CameraMenu;
private logger = log.getLogger('DiagramMenuManager'); private logger = log.getLogger('DiagramMenuManager');
private _connectionPreview: ConnectionPreview; private _connectionPreview: ConnectionPreview;
constructor(notifier: Observable<DiagramEvent>, controllers: Controllers, config: AppConfig) { constructor(notifier: Observable<DiagramEvent>, controllers: Controllers, config: AppConfig) {
this._scene = DefaultScene.Scene; this._scene = DefaultScene.Scene;
this._notifier = notifier; this._notifier = notifier;
this._inputTextView = new InputTextView(controllers); this._inputTextView = new InputTextView(controllers);
this.configMenu = new ConfigMenu(config); this.configMenu = new ConfigMenu(config);
@ -34,12 +38,7 @@ export class DiagramMenuManager {
}); });
this.toolbox = new Toolbox(); this.toolbox = new Toolbox();
this.scaleMenu = new ScaleMenu2(this._notifier); this.scaleMenu = new ScaleMenu2(this._notifier);
/*this.scaleMenu.onScaleChangeObservable.add((mesh: AbstractMesh) => {
this.notifyAll({type: DiagramEventType.MODIFY, entity: {id: mesh.id, scale: mesh.scaling}});
const position = mesh.absolutePosition.clone();
position.y = mesh.getBoundingInfo().boundingBox.maximumWorld.y + .1;
this.scaleMenu.changePosition(position);
});*/
controllers.controllerObservable.add((event: ControllerEvent) => { controllers.controllerObservable.add((event: ControllerEvent) => {
if (event.type == ControllerEventType.B_BUTTON) { if (event.type == ControllerEventType.B_BUTTON) {
if (event.value > .8) { if (event.value > .8) {

View File

@ -7,8 +7,7 @@ import {
MeshBuilder, MeshBuilder,
Scene, Scene,
StandardMaterial, StandardMaterial,
Texture, Texture
Vector3
} from "@babylonjs/core"; } from "@babylonjs/core";
import log from "loglevel"; import log from "loglevel";
import {v4 as uuidv4} from 'uuid'; import {v4 as uuidv4} from 'uuid';
@ -154,10 +153,6 @@ function mapMetadata(entity: DiagramEntity, newMesh: AbstractMesh, scene: Scene)
return newMesh; return newMesh;
} }
function xyztovec(xyz: { x, y, z }): Vector3 {
return new Vector3(xyz.x, xyz.y, xyz.z);
}
export function buildMissingMaterial(name: string, scene: Scene, color: string): StandardMaterial { export function buildMissingMaterial(name: string, scene: Scene, color: string): StandardMaterial {
const existingMaterial = scene.getMaterialById(name); const existingMaterial = scene.getMaterialById(name);

View File

@ -55,4 +55,16 @@ export type DiagramEntity = {
parent?: string; parent?: string;
diagramlistingid?: string; diagramlistingid?: string;
friendly?: string; friendly?: string;
rightHand?: {
position: { x: number, y: number, z: number },
rotation: { x: number, y: number, z: number }
},
leftHand?: {
position: { x: number, y: number, z: number },
rotation: { x: number, y: number, z: number }
},
head?: {
position: { x: number, y: number, z: number },
rotation: { x: number, y: number, z: number }
}
} }

View File

@ -0,0 +1,27 @@
import axios from "axios";
import log from "loglevel";
export async function checkDb(localName: string, remoteDbName: string) {
const logger = log.getLogger('checkDb');
const dbs = await axios.get(import.meta.env.VITE_SYNCDB_ENDPOINT + 'list');
logger.debug(dbs.data);
if (dbs.data.indexOf(remoteDbName) == -1) {
logger.warn('sync target missing attempting to create');
const newdb = await axios.post(import.meta.env.VITE_CREATE_ENDPOINT,
{
"_id": "org.couchdb.user:" + localName,
"name": localName,
"password": localName,
"roles": ["readers"],
"type": "user"
}
);
if (newdb.status == 201) {
logger.info('sync target created');
} else {
logger.warn('sync target not created', newdb);
return false;
}
}
return true;
}

View File

@ -0,0 +1,22 @@
import log from "loglevel";
import {DiagramEntity} from "../../diagram/types/diagramEntity";
import {Observable} from "@babylonjs/core";
export function syncDoc(info: any, onDBRemoveObservable: Observable<DiagramEntity>, onDBUpdateObservable: Observable<DiagramEntity>) {
const logger = log.getLogger('syncDoc');
logger.debug(info);
if (info.direction == 'pull') {
const docs = info.change.docs;
for (const doc of docs) {
logger.debug(doc);
if (doc._deleted) {
logger.debug('Delete', doc);
onDBRemoveObservable.notifyObservers({id: doc._id, template: doc.template}, 1);
} else {
onDBUpdateObservable.notifyObservers(doc, 1);
}
}
}
}

View File

@ -7,10 +7,11 @@ import log, {Logger} from "loglevel";
import {ascii_to_hex} from "./functions/hexFunctions"; import {ascii_to_hex} from "./functions/hexFunctions";
import {getPath} from "../util/functions/getPath"; import {getPath} from "../util/functions/getPath";
import {DiagramEventObserverMask} from "../diagram/types/diagramEventObserverMask"; import {DiagramEventObserverMask} from "../diagram/types/diagramEventObserverMask";
import {syncDoc} from "./functions/syncDoc";
import {checkDb} from "./functions/checkDb";
export class PouchdbPersistenceManager { export class PouchdbPersistenceManager {
private logger: Logger = log.getLogger('PouchdbPersistenceManager'); private _logger: Logger = log.getLogger('PouchdbPersistenceManager');
onDBUpdateObservable: Observable<DiagramEntity> = new Observable<DiagramEntity>(); onDBUpdateObservable: Observable<DiagramEntity> = new Observable<DiagramEntity>();
onDBRemoveObservable: Observable<DiagramEntity> = new Observable<DiagramEntity>(); onDBRemoveObservable: Observable<DiagramEntity> = new Observable<DiagramEntity>();
@ -18,12 +19,13 @@ export class PouchdbPersistenceManager {
private remote: PouchDB; private remote: PouchDB;
private user: string; private user: string;
constructor() { constructor() {
} }
public setDiagramManager(diagramManager: DiagramManager) { public setDiagramManager(diagramManager: DiagramManager) {
diagramManager.onDiagramEventObservable.add((evt) => { diagramManager.onDiagramEventObservable.add((evt) => {
this.logger.debug(evt); this._logger.debug(evt);
switch (evt.type) { switch (evt.type) {
case DiagramEventType.REMOVE: case DiagramEventType.REMOVE:
this.remove(evt.entity.id); this.remove(evt.entity.id);
@ -34,23 +36,25 @@ export class PouchdbPersistenceManager {
this.upsert(evt.entity); this.upsert(evt.entity);
break; break;
default: default:
this.logger.warn('unknown diagram event type', evt); this._logger.warn('unknown diagram event type', evt);
} }
}, DiagramEventObserverMask.TO_DB); }, DiagramEventObserverMask.TO_DB);
this.onDBUpdateObservable.add((evt) => { this.onDBUpdateObservable.add((evt) => {
this.logger.debug(evt); this._logger.debug(evt);
if (!evt.friendly) { if (evt.id != 'metadata') {
diagramManager.onDiagramEventObservable.notifyObservers({ diagramManager.onDiagramEventObservable.notifyObservers({
type: DiagramEventType.ADD, type: DiagramEventType.ADD,
entity: evt entity: evt
}, DiagramEventObserverMask.FROM_DB); }, DiagramEventObserverMask.FROM_DB);
} else {
} }
}); });
this.onDBRemoveObservable.add((entity) => { this.onDBRemoveObservable.add((entity) => {
this.logger.debug(entity); this._logger.debug(entity);
diagramManager.onDiagramEventObservable.notifyObservers( diagramManager.onDiagramEventObservable.notifyObservers(
{type: DiagramEventType.REMOVE, entity: entity}, DiagramEventObserverMask.FROM_DB); {type: DiagramEventType.REMOVE, entity: entity}, DiagramEventObserverMask.FROM_DB);
}); });
@ -64,7 +68,7 @@ export class PouchdbPersistenceManager {
const doc = await this.db.get(id); const doc = await this.db.get(id);
this.db.remove(doc); this.db.remove(doc);
} catch (err) { } catch (err) {
this.logger.error(err); this._logger.error(err);
} }
} }
@ -84,10 +88,10 @@ export class PouchdbPersistenceManager {
const newEntity = {...entity, _id: entity.id}; const newEntity = {...entity, _id: entity.id};
this.db.put(newEntity); this.db.put(newEntity);
} catch (err2) { } catch (err2) {
this.logger.error(err2); this._logger.error(err2);
} }
} else { } else {
this.logger.error(err); this._logger.error(err);
} }
} }
} }
@ -105,22 +109,27 @@ export class PouchdbPersistenceManager {
if (doc && doc.friendly) { if (doc && doc.friendly) {
localStorage.setItem(current, doc.friendly); localStorage.setItem(current, doc.friendly);
} }
if (doc && doc.camera) {
}
} catch (err) { } catch (err) {
if (err.status == 404) { if (err.status == 404) {
console.log('no metadata found'); this._logger.debug('no metadata found');
const friendly = localStorage.getItem(current); const friendly = localStorage.getItem(current);
if (friendly) { if (friendly) {
console.log('local friendly name found ', friendly, ' setting metadata'); this._logger.debug('local friendly name found ', friendly, ' setting metadata');
const newDoc = {_id: 'metadata', friendly: friendly}; const newDoc = {_id: 'metadata', friendly: friendly};
await this.db.put(newDoc); await this.db.put(newDoc);
} else { } else {
console.log('no friendly name found'); this._logger.debug('no friendly name found');
} }
} }
} }
} }
private async initLocal(): Promise<boolean> { private async initLocal(): Promise<boolean> {
try { try {
let sync = false; let sync = false;
let current = getPath(); let current = getPath();
if (current && current != 'localdb') { if (current && current != 'localdb') {
@ -136,8 +145,8 @@ export class PouchdbPersistenceManager {
} }
return true; return true;
} catch (err) { } catch (err) {
this.logger.error(err); this._logger.error(err);
this.logger.error('cannot initialize pouchdb for sync'); this._logger.error('cannot initialize pouchdb for sync');
return false; return false;
} }
} }
@ -145,13 +154,14 @@ export class PouchdbPersistenceManager {
private async sendLocalDataToScene() { private async sendLocalDataToScene() {
const clear = localStorage.getItem('clearLocal'); const clear = localStorage.getItem('clearLocal');
try { try {
const all = await this.db.allDocs({include_docs: true}); const all = await this.db.allDocs({include_docs: true});
for (const entity of all.rows) { for (const entity of all.rows) {
this.logger.debug(entity.doc); this._logger.debug(entity.doc);
if (clear) { if (clear) {
this.remove(entity.id); this.remove(entity.id);
} else { } else {
this.onDBUpdateObservable.notifyObservers(entity.doc, 1); this.onDBUpdateObservable.notifyObservers(entity.doc, DiagramEventObserverMask.FROM_DB);
} }
} }
@ -159,29 +169,7 @@ export class PouchdbPersistenceManager {
localStorage.removeItem('clearLocal'); localStorage.removeItem('clearLocal');
} }
} catch (err) { } catch (err) {
this.logger.error(err); this._logger.error(err);
}
}
sync() {
}
private syncDoc(info) {
this.logger.debug(info);
if (info.direction == 'pull') {
const docs = info.change.docs;
for (const doc of docs) {
this.logger.debug(doc);
if (doc._deleted) {
this.logger.debug('Delete', doc);
this.onDBRemoveObservable.notifyObservers({id: doc._id, template: doc.template}, 1);
} else {
this.onDBUpdateObservable.notifyObservers(doc, 1);
}
}
} }
} }
@ -191,32 +179,17 @@ export class PouchdbPersistenceManager {
const remoteDbName = 'userdb-' + userHex; const remoteDbName = 'userdb-' + userHex;
const remoteUserName = localName; const remoteUserName = localName;
const password = localName; const password = localName;
const dbs = await axios.get(import.meta.env.VITE_SYNCDB_ENDPOINT + 'list');
this.logger.debug(dbs.data); if (await checkDb(localName, remoteDbName) == false) {
if (dbs.data.indexOf(remoteDbName) == -1) { return;
this.logger.warn('sync target missing attempting to create');
const newdb = await axios.post(import.meta.env.VITE_CREATE_ENDPOINT,
{
"_id": "org.couchdb.user:" + localName,
"name": localName,
"password": localName,
"roles": ["readers"],
"type": "user"
}
);
if (newdb.status == 201) {
this.logger.info('sync target created');
} else {
this.logger.warn('sync target not created', newdb);
return;
}
} }
const userEndpoint: string = import.meta.env.VITE_USER_ENDPOINT const userEndpoint: string = import.meta.env.VITE_USER_ENDPOINT
this.logger.debug(userEndpoint); this._logger.debug(userEndpoint);
this.logger.debug(remoteDbName); this._logger.debug(remoteDbName);
const target = await axios.get(userEndpoint); const target = await axios.get(userEndpoint);
if (target.status != 200) { if (target.status != 200) {
this.logger.warn(target.statusText); this._logger.warn(target.statusText);
return; return;
} }
if (target.data && target.data.userCtx) { if (target.data && target.data.userCtx) {
@ -224,36 +197,36 @@ export class PouchdbPersistenceManager {
const buildTarget = await axios.post(userEndpoint, const buildTarget = await axios.post(userEndpoint,
{username: remoteUserName, password: password}); {username: remoteUserName, password: password});
if (buildTarget.status != 200) { if (buildTarget.status != 200) {
this.logger.info(buildTarget.statusText); this._logger.info(buildTarget.statusText);
return; return;
} else { } else {
this.user = buildTarget.data.userCtx; this.user = buildTarget.data.userCtx;
this.logger.debug(this.user); this._logger.debug(this.user);
} }
} }
} }
const remoteEndpoint: string = import.meta.env.VITE_SYNCDB_ENDPOINT; const remoteEndpoint: string = import.meta.env.VITE_SYNCDB_ENDPOINT;
this.logger.debug(remoteEndpoint + remoteDbName); this._logger.debug(remoteEndpoint + remoteDbName);
this.remote = new PouchDB(remoteEndpoint + remoteDbName, this.remote = new PouchDB(remoteEndpoint + remoteDbName,
{auth: {username: remoteUserName, password: password}, skip_setup: true}); {auth: {username: remoteUserName, password: password}, skip_setup: true});
const dbInfo = await this.remote.info(); const dbInfo = await this.remote.info();
this.logger.debug(dbInfo); this._logger.debug(dbInfo);
this.db.sync(this.remote, {live: true, retry: true}) this.db.sync(this.remote, {live: true, retry: true})
.on('change', (info) => { .on('change', (info) => {
this.syncDoc(info) syncDoc(info, this.onDBRemoveObservable, this.onDBUpdateObservable);
}) })
.on('active', (info) => { .on('active', (info) => {
this.logger.debug('sync active', info) this._logger.debug('sync active', info)
}) })
.on('paused', (info) => { .on('paused', (info) => {
this.logger.debug('sync paused', info) this._logger.debug('sync paused', info)
}) })
.on('error', (err) => { .on('error', (err) => {
this.logger.error('sync error', err) this._logger.error('sync error', err)
}); });
} catch (err) { } catch (err) {
this.logger.error(err); this._logger.error(err);
} }
} }
} }

View File

@ -12,12 +12,12 @@ import {
Vector3 Vector3
} from "@babylonjs/core"; } from "@babylonjs/core";
import {DefaultScene} from "../defaultScene"; import {DefaultScene} from "../defaultScene";
import {CameraWindow} from "./cameraWindow"; import {CameraWindow} from "../objects/cameraWindow";
export class CameraIcon { export class CameraIcon {
private static _baseMesh: AbstractMesh; private static _baseMesh: AbstractMesh;
private readonly _scene: Scene; private readonly _scene: Scene;
private _cam: CameraWindow; private _cams: CameraWindow[] = [];
constructor(scene: Scene, mapNode: TransformNode, position: Vector3) { constructor(scene: Scene, mapNode: TransformNode, position: Vector3) {
this._scene = scene; this._scene = scene;
@ -29,14 +29,20 @@ export class CameraIcon {
newInstance.position = position; newInstance.position = position;
newInstance.actionManager = new ActionManager(this._scene); newInstance.actionManager = new ActionManager(this._scene);
newInstance.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnPickTrigger, () => { newInstance.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnPickTrigger, () => {
if (this._cam) { if (this._cams && this._cams.length > 0) {
this._cam.dispose(); for (const cam of this._cams) {
this._cam = null; cam.dispose();
}
this._cams = [];
} else { } else {
this._cam = new CameraWindow(scene, null, 'https://cameras.immersiveidea.com/mjpg/video.mjpg?camera=3'); for (let i = 1; i < 7; i++) {
this._cam.mesh.position.x = newInstance.absolutePosition.x; const cam = new CameraWindow(scene, null, 'https://cameras.immersiveidea.com/mjpg/video.mjpg?camera=' + i);
this._cam.mesh.position.z = newInstance.absolutePosition.z; cam.mesh.position.x = newInstance.absolutePosition.x + (-2 + i);
this._cam.mesh.position.y = newInstance.absolutePosition.y + .5; cam.mesh.position.z = newInstance.absolutePosition.z;
cam.mesh.position.y = newInstance.absolutePosition.y + .5;
this._cams.push(cam);
}
} }
})); }));

View File

@ -0,0 +1,38 @@
export function tile2long(x, z) {
return (x / Math.pow(2, z) * 360 - 180);
}
export function tile2lat(y, z) {
var n = Math.PI - 2 * Math.PI * y / Math.pow(2, z);
return (180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))));
}
const EARTH_CIR_METERS = 40075016.686;
const TILE_SIZE = 512;
const degreesPerMeter = 360 / EARTH_CIR_METERS;
const LIMIT_Y = toDegrees(Math.atan(Math.sinh(Math.PI))) // around 85.0511...
function toRadians(degrees) {
return degrees * Math.PI / 180;
}
function toDegrees(radians) {
return (radians / Math.PI) * 180
}
export function lonOnTile(lon, zoom) {
return ((lon + 180) / 360) * Math.pow(2, zoom)
}
export function latOnTile(lat, zoom) {
return (
((1 -
Math.log(
Math.tan((lat * Math.PI) / 180) + 1 / Math.cos((lat * Math.PI) / 180)
) /
Math.PI) /
2) *
Math.pow(2, zoom)
)
}

View File

@ -1,6 +1,9 @@
import * as mapTilerClient from '@maptiler/client'; import * as mapTilerClient from '@maptiler/client';
import { import {
AbstractMesh, AbstractMesh,
ActionEvent,
ActionManager,
ExecuteCodeAction,
MeshBuilder, MeshBuilder,
Observable, Observable,
Scene, Scene,
@ -8,9 +11,9 @@ import {
Texture, Texture,
TransformNode, TransformNode,
Vector2, Vector2,
Vector3
} from "@babylonjs/core"; } from "@babylonjs/core";
import {CameraIcon} from "./cameraIcon"; import log from "loglevel";
import {latOnTile, lonOnTile, tile2lat, tile2long} from "./functions/tileFunctions";
export type MaptilerMapTile = { export type MaptilerMapTile = {
lat: number, lat: number,
@ -21,22 +24,29 @@ export type MaptilerMapTile = {
y: number, y: number,
bounds: Vector2[]; bounds: Vector2[];
} }
type PlotPointType = {
position: Vector2;
mesh: AbstractMesh;
}
export class MaptilerMap { export class MaptilerMap {
public readonly onReadyObservable = new Observable<MaptilerMapTile>(); public readonly onReadyObservable = new Observable<MaptilerMapTile>();
public readonly onPickObservable: Observable<{ lat: number, lon: number }> = new Observable()
private readonly _scene: Scene;
private readonly _baseNode: TransformNode;
private readonly _key: string;
private _lat: number; private _lat: number;
private _lon: number; private _lon: number;
private _min: Vector2; private _min: Vector2;
private _max: Vector2; private _max: Vector2;
private _zoom: number; private _zoom: number;
//private _bounds: Vector2[];
private readonly _scene: Scene;
private _tileXYCount: number = 2; private _tileXYCount: number = 2;
private readonly _baseNode: TransformNode;
private readonly _key: string;
private _pendingPoints: Array<number> = []; private _pendingPoints: Array<number> = [];
private _points: Vector2[] = []; private readonly _logger = log.getLogger('MaptilerMap');
private _points: PlotPointType[] = [];
private _actionManager: ActionManager;
public constructor(key: string, scene: Scene, name: string = 'map-node', tileXYCount: number = 2) { public constructor(key: string, scene: Scene, name: string = 'map-node', tileXYCount: number = 2) {
this._scene = scene; this._scene = scene;
@ -45,6 +55,16 @@ export class MaptilerMap {
this._baseNode = new TransformNode(name, this._scene); this._baseNode = new TransformNode(name, this._scene);
this.onReadyObservable.addOnce(this.buildNodes.bind(this)); this.onReadyObservable.addOnce(this.buildNodes.bind(this));
this.onReadyObservable.addOnce(this.waitForMeshAdded.bind(this)); this.onReadyObservable.addOnce(this.waitForMeshAdded.bind(this));
this._actionManager = new ActionManager(this._scene);
this._actionManager.registerAction(new ExecuteCodeAction({trigger: ActionManager.OnPickDownTrigger},
(evt: ActionEvent) => {
const coordinates = evt.additionalData.getTextureCoordinates();
const tile = evt.meshUnderPointer.metadata.mapTile;
const lat = tile2lat(tile.y + (1 - coordinates.y), this._zoom);
const lon = tile2long(tile.x + coordinates.x, this._zoom);
this.onPickObservable.notifyObservers({lat: lat, lon: lon});
})
);
} }
private _startFallback: number = 10; private _startFallback: number = 10;
@ -80,7 +100,7 @@ export class MaptilerMap {
bounds: [] bounds: []
}); });
} else { } else {
console.error(JSON.stringify(result)); this._logger.error(JSON.stringify(result));
} }
} }
@ -99,8 +119,8 @@ export class MaptilerMap {
}); });
} }
public async plotPoint(lat: number, lon: number) { public async plotPoint(lat: number, lon: number, mesh: AbstractMesh) {
const len = this._points.push(new Vector2(lat, lon)); const len = this._points.push({position: new Vector2(lat, lon), mesh: mesh});
this._pendingPoints.push(len - 1); this._pendingPoints.push(len - 1);
} }
@ -113,21 +133,24 @@ export class MaptilerMap {
if (this._pendingPoints.length > 0) { if (this._pendingPoints.length > 0) {
this._pendingPoints = this._pendingPoints.filter((item) => { this._pendingPoints = this._pendingPoints.filter((item) => {
const point = this._points[item]; const point = this._points[item];
const tileXY = this.getTileXY(point.x, point.y); const tileXY = this.getTileXY(point.position.x, point.position.y);
console.log(tileXY); this._logger.log(tileXY);
const mesh = this._scene.getMeshByName(`map-${tileXY[0]}-${tileXY[1]}-plane`); const mesh = this._scene.getMeshByName(`map-${tileXY[0]}-${tileXY[1]}-plane`);
const oldPoint = this._scene.getMeshByName(`map-${point.x}-${point.y}-point`); const oldPoint = this._scene.getMeshByName(`map-${point.position.x}-${point.position.y}-point`);
if (!mesh) { if (!mesh) {
console.error(`map-${tileXY[0]}-${tileXY[1]}-plane not found`); this._logger.error(`map-${tileXY[0]}-${tileXY[1]}-plane not found`);
return true; return true;
} else { } else {
if (!oldPoint) { if (!oldPoint) {
const pixelx = lonOnTile(point.y, this._zoom) % 1; const pixely = latOnTile(point.position.x, this._zoom) % 1;
const pixely = latOnTile(point.x, this._zoom) % 1; const pixelx = lonOnTile(point.position.y, this._zoom) % 1;
console.log(`pixelx: ${pixelx}, pixely: ${pixely} found`); this._logger.log(`pixelx: ${pixelx}, pixely: ${pixely} found`);
try { try {
const newIcon = new CameraIcon(this._scene, this._baseNode, const pointMesh = point.mesh;
new Vector3(mesh.position.x - .5 + pixelx, mesh.position.y + .5 - pixely, mesh.position.z - .05)); pointMesh.parent = this._baseNode;
pointMesh.position.x = mesh.position.x - .5 + pixelx;
pointMesh.position.y = mesh.position.y + .5 - pixely;
pointMesh.position.z = mesh.position.z - .05;
return false; return false;
} catch (err) { } catch (err) {
return true; return true;
@ -177,22 +200,22 @@ export class MaptilerMap {
} }
private buildMapTile(x: number, y: number, url: string, xTile: number, yTile: number): AbstractMesh { private buildMapTile(x: number, y: number, url: string, xTile: number, yTile: number): AbstractMesh {
const map = MeshBuilder.CreatePlane(`map-${xTile}-${yTile}-plane`, {width: 1, height: 1}, this._scene); const tile = MeshBuilder.CreatePlane(`map-${xTile}-${yTile}-plane`, {width: 1, height: 1}, this._scene);
const mapMaterial = new StandardMaterial(`map-${xTile}-${yTile}-material`, this._scene); const mapMaterial = new StandardMaterial(`map-${xTile}-${yTile}-material`, this._scene);
const mapTexture = new Texture(url, this._scene); const mapTexture = new Texture(url, this._scene);
const lon = tile2long(xTile, this._zoom); const lon = tile2long(xTile, this._zoom);
const lat = tile2lat(yTile, this._zoom); const lat = tile2lat(yTile, this._zoom);
if (!this._min || lat < this._min.x || lon < this._min.y) { if (!this._min || lat < this._min.x || lon < this._min.y) {
this._min = new Vector2(lat, lon); this._min = new Vector2(lat, lon);
console.log(`min: ${lat}, ${lon}`); this._logger.log(`min: ${lat}, ${lon}`);
} }
const maxLat = tile2lat(yTile + 1, this._zoom); const maxLat = tile2lat(yTile + 1, this._zoom);
const maxLon = tile2long(xTile + 1, this._zoom); const maxLon = tile2long(xTile + 1, this._zoom);
if (!this._max || maxLat > this._max.y || maxLon > this._max.y) { if (!this._max || maxLat > this._max.y || maxLon > this._max.y) {
this._min = new Vector2(maxLat, maxLon); this._min = new Vector2(maxLat, maxLon);
console.log(`max: ${maxLat}, ${maxLon}`); this._logger.log(`max: ${maxLat}, ${maxLon}`);
} }
map.metadata = { tile.metadata = {
mapTile: {x: xTile, y: yTile}, bounds: mapTile: {x: xTile, y: yTile}, bounds:
{ {
topleft: topleft:
@ -208,49 +231,15 @@ export class MaptilerMap {
mapMaterial.emissiveTexture = mapTexture; mapMaterial.emissiveTexture = mapTexture;
mapMaterial.disableLighting = true; mapMaterial.disableLighting = true;
mapMaterial.backFaceCulling = false; mapMaterial.backFaceCulling = false;
map.material = mapMaterial; tile.material = mapMaterial;
map.position.x = x; //tile.material.freeze();
map.position.y = y; tile.position.x = x;
map.isPickable = true; tile.position.y = y;
return map; tile.renderOutline = true;
tile.isPickable = true;
tile.actionManager = this._actionManager;
return tile;
} }
} }
function tile2long(x, z) {
return (x / Math.pow(2, z) * 360 - 180);
}
function tile2lat(y, z) {
var n = Math.PI - 2 * Math.PI * y / Math.pow(2, z);
return (180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))));
}
const EARTH_CIR_METERS = 40075016.686;
const TILE_SIZE = 512;
const degreesPerMeter = 360 / EARTH_CIR_METERS;
const LIMIT_Y = toDegrees(Math.atan(Math.sinh(Math.PI))) // around 85.0511...
function toRadians(degrees) {
return degrees * Math.PI / 180;
}
function toDegrees(radians) {
return (radians / Math.PI) * 180
}
function lonOnTile(lon, zoom) {
return ((lon + 180) / 360) * Math.pow(2, zoom)
}
function latOnTile(lat, zoom) {
return (
((1 -
Math.log(
Math.tan((lat * Math.PI) / 180) + 1 / Math.cos((lat * Math.PI) / 180)
) /
Math.PI) /
2) *
Math.pow(2, zoom)
)
}

View File

@ -2,6 +2,7 @@ import {AbstractMesh, GizmoManager, IAxisScaleGizmo, Observable} from "@babylonj
import {DefaultScene} from "../defaultScene"; import {DefaultScene} from "../defaultScene";
import {DiagramEvent, DiagramEventType} from "../diagram/types/diagramEntity"; import {DiagramEvent, DiagramEventType} from "../diagram/types/diagramEntity";
import {toDiagramEntity} from "../diagram/functions/toDiagramEntity"; import {toDiagramEntity} from "../diagram/functions/toDiagramEntity";
import {displayDebug} from "../util/displayDebug";
export class ScaleMenu2 { export class ScaleMenu2 {
private readonly _gizmoManager: GizmoManager; private readonly _gizmoManager: GizmoManager;
@ -28,6 +29,7 @@ export class ScaleMenu2 {
if (this.mesh.scaling.z < .01) { if (this.mesh.scaling.z < .01) {
this.mesh.scaling.z = .01; this.mesh.scaling.z = .01;
} }
displayDebug([this.mesh.scaling.x.toString(), this.mesh.scaling.y.toString(), this.mesh.scaling.z.toString()])
const entity = toDiagramEntity(this.mesh); const entity = toDiagramEntity(this.mesh);
this._notifier.notifyObservers({type: DiagramEventType.MODIFY, entity: entity}); this._notifier.notifyObservers({type: DiagramEventType.MODIFY, entity: entity});
}); });

View File

@ -1,102 +1,37 @@
import {Color3, DynamicTexture, MeshBuilder, Scene, StandardMaterial, Vector3} from "@babylonjs/core"; import {AbstractMesh, Angle, Color3, MeshBuilder, Scene, StandardMaterial, Vector3} from "@babylonjs/core";
import log, {Logger} from "loglevel"; import log, {Logger} from "loglevel";
import {MaptilerMap} from "../objects/maptilerMap"; import {MaptilerMap} from "../maps/maptilerMap";
export class CameraMenu { export class CameraMenu {
private readonly scene: Scene; private readonly _scene: Scene;
private readonly logger: Logger = log.getLogger('CameraMenu'); private readonly _logger: Logger = log.getLogger('CameraMenu');
constructor(scene) {
this.scene = scene;
//this.buildMenu(3, new Vector3(-1, 2, 0));
//this.buildMenu(4, new Vector3(0,2,0));
//this.buildMenu(5, new Vector3(1,2,0));
//this.buildMenu(6, new Vector3(2,2,0));
//this.buildIcon();
//this.loadIcon();
constructor(scene: Scene) {
this._scene = scene;
this.buildMap(); this.buildMap();
} }
private buildMap() { private buildMap() {
const maptilerMap = new MaptilerMap('YnvhjBiU8oCWP0GXNdHL', this.scene, 'map-node', 3); const maptilerMap = new MaptilerMap('YnvhjBiU8oCWP0GXNdHL', this._scene, 'map-node', 3);
maptilerMap.node.position.y = 1; maptilerMap.node.position.y = 3;
maptilerMap.node.position.z = -4; maptilerMap.node.position.z = -4;
maptilerMap.node.rotation.y = Math.PI; maptilerMap.node.rotation.y = Math.PI;
maptilerMap.node.rotation.x = Math.PI / 6; maptilerMap.node.rotation.x = Angle.FromDegrees(10).radians()
maptilerMap.node.scaling = new Vector3(1, 1, 1); maptilerMap.node.scaling = new Vector3(1, 1, 1);
//maptilerMap.setLocation('loves park, il' , 15); maptilerMap.setLocation('loves park, il', 16).then(() => {
maptilerMap.setLocation('rockford, il', 12).then(() => { //maptilerMap.plotPoint(42.33181896128866, -88.86844896012006, this.buildPoint());
maptilerMap.plotPoint(42.33181896128866, -88.86844896012006); });
maptilerMap.onPickObservable.add((evt) => {
maptilerMap.plotPoint(evt.lat, evt.lon, this.buildPoint());
}); });
} }
//https://maps.geoapify.com/v1/staticmap?style=osm-carto&scaleFactor=2&width=4096&height=4096&center=lonlat:-89.0940,42.2711&zoom=12.4318&apiKey=d548c5ed24604be6a9dd0d989631f783 private buildPoint(): AbstractMesh {
private buildIcon() { const mesh = MeshBuilder.CreateIcoSphere('point', {radius: .02}, this._scene);
const icon = MeshBuilder.CreatePlane('camera-icon', {width: .1, height: .1}, this.scene); const material: StandardMaterial = new StandardMaterial('pointMat', this._scene);
icon.position = new Vector3(0, 3, 0); material.diffuseColor = Color3.Red();
icon.metadata = {grabbable: true}; mesh.material = material;
const material = new StandardMaterial('icon-material', this.scene); mesh.isPickable = true;
return mesh;
material.backFaceCulling = false;
const texture = new DynamicTexture('icon-texture', {width: 256, height: 256}, this.scene);
//const texture = new DynamicTexture('/assets/icons/video.png', this.scene);
const image = new Image();
//image.setAttribute('width', '256');
//image.setAttribute('height', '256');
//image.width=32;
//image.height=32;
image.src = '/assets/icons/video.png';
image.onload = () => {
texture.getContext().drawImage(image, 0, 0);
texture.update();
}
material.emissiveColor = new Color3(.1, .1, .8);
material.opacityTexture = texture;
icon.material = material;
material.disableLighting = true;
//material.diffuseTexture = texture;
//material.disableLighting;
//material.emissiveColor = new Color3(1, 1, 1);
//texture.uScale = 1;
//texture.vScale = 1;
}
private buildMenu(camnum: number, position: Vector3) {
const camerasphere = MeshBuilder.CreatePlane('camera-' + camnum, {width: 1, height: 1}, this.scene);
camerasphere.position = position;
const material = new StandardMaterial("cameramaterial", this.scene);
//material.emissiveColor = new Color3(1, 1, 1);
material.backFaceCulling = false;
const texture = new DynamicTexture('texture', {width: 1600, height: 1600}, this.scene);
material.emissiveTexture = texture;
material.disableLighting = true;
const img = new Image();
img.setAttribute('crossorigin', 'anonymous');
img.src = 'https://cameras.immersiveidea.com/mjpg/video.mjpg?camera=' + camnum;
const ctx = texture.getContext();
img.onload = () => {
ctx.drawImage(img, 0, 0);
texture.update();
window.setInterval((texture, img, ctx) => {
ctx.drawImage(img, 0, 0);
texture.update();
}, 1000, texture, img, ctx);
}
texture.onLoadObservable.add(() => {
this.logger.debug('texture loaded');
});
camerasphere.material = material;
this.logger.info('camera built');
} }
} }

View File

@ -39,6 +39,13 @@ export class ClickMenu {
} }
}, -1, false, this, false); }, -1, false, this, false);
this.makeNewButton("Size", "size", scene, x += .11)
.onPointerObservable.add((eventData) => {
if (isUp(eventData)) {
this.onClickMenuObservable.notifyObservers(eventData);
}
}, -1, false, this, false);
this.makeNewButton("Close", "close", scene, x += .11) this.makeNewButton("Close", "close", scene, x += .11)
.onPointerObservable.add((eventData) => { .onPointerObservable.add((eventData) => {
if (isUp(eventData)) { if (isUp(eventData)) {
@ -47,17 +54,11 @@ export class ClickMenu {
} }
}, -1, false, this, false); }, -1, false, this, false);
this.makeNewButton("Size", "size", scene, x += .11)
.onPointerObservable.add((eventData) => {
if (isUp(eventData)) {
this.onClickMenuObservable.notifyObservers(eventData);
}
}, -1, false, this, false);
const platform = scene.getMeshByName("platform"); const platform = scene.getMeshByName("platform");
this._transformNode.parent = scene.activeCamera; this._transformNode.parent = scene.activeCamera;
this._transformNode.position.z = .7; this._transformNode.position.z = .7;
this._transformNode.position.y = -.1; this._transformNode.position.y = -.3;
this._transformNode.setParent(platform); this._transformNode.setParent(platform);
this._transformNode.rotation.z = 0; this._transformNode.rotation.z = 0;
} }

View File

@ -1,46 +0,0 @@
import {AbstractMenu} from "./abstractMenu";
import {Color3, MeshBuilder, StandardMaterial, Vector2, WebXRDefaultExperience} from "@babylonjs/core";
import {Controllers} from "../controllers/controllers";
export class DeckMenu extends AbstractMenu {
private static instance: DeckMenu;
public constructor(xr: WebXRDefaultExperience, controllers: Controllers) {
super(xr, controllers);
this.buildMenu();
}
private feetToMeters(feet: number, inches: number) {
return (feet * 12 + inches) * .0254;
}
private buildMenu() {
const base = MeshBuilder.CreateBox("base", {
width: this.feetToMeters(14, 6),
height: this.feetToMeters(0, .75),
depth: this.feetToMeters(14, 6)
}, this.scene);
base.position.y = this.feetToMeters(0, .375);
this.buildPost(new Vector2(7, 3), new Vector2(7, 3));
this.buildPost(new Vector2(-7, -3), new Vector2(7, 3));
this.buildPost(new Vector2(-4, -0), new Vector2(7, 3));
this.buildPost(new Vector2(7 - 4, 3), new Vector2(7, 3));
this.buildPost(new Vector2(-7, -3), new Vector2(7, 3 - 16));
this.buildPost(new Vector2(7, 3), new Vector2(7, 3 - 16));
}
private buildPost(x: Vector2, y: Vector2) {
const material = new StandardMaterial("material", this.scene);
material.diffuseColor = new Color3(.02, .02, .02);
const base = MeshBuilder.CreateBox("base", {
width: this.feetToMeters(0, 2.5),
height: this.feetToMeters(0, 38),
depth: this.feetToMeters(0, 2.5)
}, this.scene);
base.position.y = this.feetToMeters(0, 38 / 2);
base.position.x = this.feetToMeters(x.x, x.y);
base.position.z = this.feetToMeters(y.x, y.y);
base.material = material;
}
}

View File

@ -1,33 +0,0 @@
import {AbstractMesh, MeshBuilder, WebXRDefaultExperience} from "@babylonjs/core";
import {Controllers} from "../controllers/controllers";
import {AbstractMenu} from "./abstractMenu";
import {AdvancedDynamicTexture, Grid, TextBlock} from "@babylonjs/gui";
export class IntegrationMenu extends AbstractMenu {
private plane: AbstractMesh = null;
constructor(xr: WebXRDefaultExperience, controllers: Controllers) {
super(xr, controllers);
this.buildMenu();
}
public toggle() {
this.plane.isVisible = !this.plane.isVisible;
}
private buildMenu() {
this.plane = MeshBuilder.CreatePlane("plane", {size: 1}, this.scene);
const advancedTexture2 = AdvancedDynamicTexture.CreateForMesh(this.plane, 1024, 1024, false);
const grid = new Grid("grid");
advancedTexture2.addControl(grid);
grid.addColumnDefinition(.25);
grid.addColumnDefinition(.75);
const labelText1 = new TextBlock("labelText1", "New Relic Key");
grid.addControl(labelText1, 0, 0);
const labelText2 = new TextBlock("labelText1", "New Relic Account");
grid.addControl(labelText2, 1, 0);
}
}

View File

@ -1,20 +0,0 @@
import {Scene} from "@babylonjs/core";
export class MainMenu {
private readonly scene: Scene;
private parent: HTMLElement;
constructor(scene) {
this.scene = scene;
this.buildMenu();
}
private buildMenu() {
//const button = document.createElement("button");
//this.parent.appendChild(button);
//this.file = new InputFile();
}
}

10
src/util/me.ts Normal file
View File

@ -0,0 +1,10 @@
import {v4 as uuidv4} from 'uuid';
export function getMe(): string {
let me = localStorage.getItem('me');
if (!me) {
me = 'user' + uuidv4();
localStorage.setItem('me', me);
}
return me;
}

View File

@ -35,12 +35,12 @@ export class VrApp {
if (webGpu) { if (webGpu) {
engine = new WebGPUEngine(canvas); engine = new WebGPUEngine(canvas);
await (engine as WebGPUEngine).initAsync(); await (engine as WebGPUEngine).initAsync();
console.log("WebGPU enabled");
} else { } else {
engine = new Engine(canvas, true); engine = new Engine(canvas, true);
} }
engine.setHardwareScalingLevel(1 / window.devicePixelRatio); engine.setHardwareScalingLevel(1 / window.devicePixelRatio);
console.log(engine.getCaps().multiview);
window.onresize = () => { window.onresize = () => {
engine.resize(); engine.resize();
} }