Fix camera positioning, label z-fighting, and remove dead code

- Fix desktop camera to be directly above platform by resetting local position
- Increase label back offset from 0.001 to 0.005 to prevent z-fighting
- Use refreshBoundingInfo({}) for consistency with codebase
- Remove unused copyToPublic from pouchData.ts
- Remove dead DiagramEntityAdapter and integration/gizmo module
- Remove unused netlify functions and integration utilities
- Clean up unused imports and commented code across multiple files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2026-01-13 15:06:18 -06:00
parent 8c2b7f9c7d
commit 3155cc930f
22 changed files with 13 additions and 845 deletions

View File

@ -1,49 +0,0 @@
import {Handler, HandlerContext, HandlerEvent} from "@netlify/functions";
import axios from 'axios';
export const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
try {
switch (event.httpMethod) {
case 'POST':
const apiKey = event.headers['api-key'];
const query = event.body;
const response = await axios.post('https://api.newrelic.com/graphql',
query,
{headers: {'Api-Key': apiKey, 'Content-Type': 'application/json'}});
const data = await response.data;
return {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'https://cameras.immersiveidea.com',
'Access-Control-Allow-Credentials': 'true'
},
statusCode: 200,
body: JSON.stringify(data)
}
break;
case 'OPTIONS':
const headers = {
'Access-Control-Allow-Origin': 'https://cameras.immersiveidea.com',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Headers': 'content-type, api-key',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE'
};
return {
statusCode: 204,
headers
}
break;
default:
return {
statusCode: 405,
body: 'Method Not Allowed'
}
}
} catch (error) {
console.log(error);
return {
statusCode: 500,
body: JSON.stringify(error)
}
}
};

View File

@ -1,216 +0,0 @@
import axios from 'axios';
const baseurl = 'https://syncdb-service-d3f974de56ef.herokuapp.com/';
const auth = 'admin:stM8Lnm@Cuf-tWZHv';
const authToken = Buffer.from(auth).toString('base64');
type Params = {
username: string,
password: string,
db: string
}
async function checkDB(auth: string, db: string) {
try {
console.log('Checking for DB');
const exist = await axios.head(baseurl + db,
{headers: {'Authorization': 'Basic ' + auth}});
if (exist && exist.status == 200) {
console.log("DB Found");
return true;
}
} catch (err) {
console.log("DB not Found");
//console.log(err);
}
return false;
}
enum Access {
DENIED,
MISSING,
ALLOWED,
}
function getUserToken(params: Params) {
const userAuth = params.username + ':' + params.password;
return Buffer.from(userAuth).toString('base64');
}
async function checkIfDbExists(params: Params): Promise<Access> {
console.log("Checking if DB exists");
if (!params.username || !params.password || !params.db) {
throw new Error('No share key provided');
}
if (await checkDB(getUserToken(params), params.db)) {
return Access.ALLOWED;
}
if (await checkDB(authToken, params.db)) {
return Access.DENIED;
}
return Access.MISSING;
}
async function createDB(params: Params) {
console.log("Creating DB");
const response = await axios.put(
baseurl + params.db,
{},
{
headers: {
'Authorization': 'Basic ' + authToken,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
console.log(response.status);
console.log(response.data);
return response;
}
async function createUser(params: Params) {
try {
console.log("Checking for User");
const userResponse = await axios.head(
baseurl + '_users/org.couchdb.user:' + params.username,
{
headers: {
'Authorization': 'Basic ' + getUserToken(params),
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
if (userResponse.status == 200) {
console.log("User Found");
return userResponse;
}
} catch (err) {
console.log("User Missing");
}
console.log("Creating User");
const userResponse = await axios.put(
baseurl + '_users/org.couchdb.user:' + params.username,
{
_id: 'org.couchdb.user:' + params.username,
name: params.username,
password: params.password, roles: [], type: 'user'
},
{
headers: {
'Authorization': 'Basic ' + authToken,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
return userResponse;
}
async function authorizeUser(params: Params) {
console.log("Authorizing User");
return await axios.put(
baseurl + params.db + '/_security',
{admins: {names: [], roles: []}, members: {names: [params.username], roles: []}},
{
headers: {
'Authorization': 'Basic ' + authToken,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
}
export default async (req: Request): Promise<Response> => {
console.log(req.method);
try {
if (req.method == 'OPTIONS') {
const origin = req.headers.get('Origin');
const headers = req.headers.get('Access-Control-Request-Headers');
console.log(origin);
return new Response(
'OK',
{
headers: {
'Allow': 'POST',
'Max-Age': '30',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Origin': origin ? origin : 'https://cameras.immersiveidea.com',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Headers': headers ? headers : 'Content-Type'
},
status: 200
});
}
} catch (err) {
return new Response(
JSON.stringify(err),
{
headers: {
'Allow': 'POST',
'Max-Age': '30',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Origin': origin ? origin : 'https://cameras.immersiveidea.com',
'Access-Control-Allow-Credentials': 'true'
},
status: 500
});
}
try {
const params = JSON.parse(await req.text());
console.log(params);
const createUserResponse = await createUser(params);
console.log(createUserResponse.status);
if (createUserResponse.status != 201 && createUserResponse.status != 200) {
throw new Error('Could not create User');
}
const exists = await checkIfDbExists(params);
switch (exists) {
case Access.ALLOWED:
console.log('Allowed');
return new Response('OK', {status: 200});
case Access.DENIED:
console.log('Denied');
return new Response('Denied', {status: 401});
case Access.MISSING:
console.log('Creating Missing DB');
const createDbResponse = await createDB(params);
if (createDbResponse.status != 201) {
throw new Error('Could not create DB');
}
}
const authorizeUserResponse = await authorizeUser(params);
if (authorizeUserResponse.status != 200) {
throw new Error('could not authorize user');
}
const origin = req.headers.get('origin');
console.log(origin);
return new Response(
'OK',
{
headers: [
['Content-Type', 'application/json'],
['Access-Control-Allow-Origin', origin],
['Access-Control-Allow-Credentials', 'true']
],
status: 200
}
)
} catch (err) {
console.log(err);
const response = {err: err};
return new Response('Error',
{status: 500}
)
}
}

View File

@ -1,22 +0,0 @@
import {Handler, HandlerContext, HandlerEvent} from "@netlify/functions";
import axios from 'axios';
export const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
try {
const response = await axios.post('https://api.assemblyai.com/v2/realtime/token', // use account token to get a temp user token
{expires_in: 3600}, // can set a TTL timer in seconds.
{headers: {authorization: process.env.VOICE_TOKEN}});
const data = await response.data;
return {
headers: {'Content-Type': 'application/json'},
statusCode: 200,
body: JSON.stringify(data)
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify(error)
}
}
};

View File

@ -1,7 +1,7 @@
{ {
"name": "immersive", "name": "immersive",
"private": true, "private": true,
"version": "0.0.8-46", "version": "0.0.8-47",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"engines": { "engines": {

View File

@ -25,10 +25,12 @@ export function buildRig(xr: WebXRDefaultExperience): Mesh {
}); });
for (const cam of scene.cameras) { for (const cam of scene.cameras) {
cam.parent = cameratransform; cam.parent = cameratransform;
cam.position = new Vector3(0, 1.6, 0); // Reset to local position above platform
} }
scene.onActiveCameraChanged.add(() => { scene.onActiveCameraChanged.add(() => {
for (const cam of scene.cameras) { for (const cam of scene.cameras) {
cam.parent = cameratransform; cam.parent = cameratransform;
cam.position = new Vector3(0, 1.6, 0); // Reset to local position above platform
} }
}); });

View File

@ -314,7 +314,7 @@ export class DiagramObject {
this._label.rotation.y = 0; this._label.rotation.y = 0;
this._labelBack.rotation.y = Math.PI; // Back face still needs to be flipped this._labelBack.rotation.y = Math.PI; // Back face still needs to be flipped
} }
this._labelBack.position.z = 0.001; this._labelBack.position.z = 0.005;
} else { } else {
// Standard object labels - convert world space to parent's local space // Standard object labels - convert world space to parent's local space
// This accounts for mesh scaling, which is not included in boundingBox.maximum // This accounts for mesh scaling, which is not included in boundingBox.maximum
@ -326,7 +326,7 @@ export class DiagramObject {
temp.dispose(); temp.dispose();
this._label.position.y = y + 0.06; this._label.position.y = y + 0.06;
this._labelBack.rotation.y = Math.PI; this._labelBack.rotation.y = Math.PI;
this._labelBack.position.z = 0.001; this._labelBack.position.z = 0.005;
} }
} }
} }

View File

@ -16,7 +16,7 @@ import {
} from '@babylonjs/core'; } from '@babylonjs/core';
import log from 'loglevel'; import log from 'loglevel';
import { HandleState, CORNER_POSITIONS, FACE_POSITIONS} from './enums'; import { CORNER_POSITIONS, FACE_POSITIONS} from './enums';
import { ResizeGizmoEvent } from './types'; import { ResizeGizmoEvent } from './types';
/** /**
@ -110,7 +110,7 @@ export class ResizeGizmo {
private createHandles(): void { private createHandles(): void {
// Ensure world matrix and bounding info are current // Ensure world matrix and bounding info are current
this._targetMesh.computeWorldMatrix(true); this._targetMesh.computeWorldMatrix(true);
this._targetMesh.refreshBoundingInfo(); this._targetMesh.refreshBoundingInfo({});
// Get bounding box for positioning // Get bounding box for positioning
const targetBoundingInfo = this._targetMesh.getBoundingInfo(); const targetBoundingInfo = this._targetMesh.getBoundingInfo();
@ -489,7 +489,7 @@ export class ResizeGizmo {
private updateHandleTransforms(): void { private updateHandleTransforms(): void {
// Ensure world matrix and bounding info are current // Ensure world matrix and bounding info are current
this._targetMesh.computeWorldMatrix(true); this._targetMesh.computeWorldMatrix(true);
this._targetMesh.refreshBoundingInfo(); this._targetMesh.refreshBoundingInfo({});
const targetBoundingInfo = this._targetMesh.getBoundingInfo(); const targetBoundingInfo = this._targetMesh.getBoundingInfo();
const boundingBox = targetBoundingInfo.boundingBox; const boundingBox = targetBoundingInfo.boundingBox;

View File

@ -74,12 +74,12 @@ export class PouchData {
} }
}) })
.on('paused', (info) => { .on('paused', (info) => {
this._logger.debug('[Sync] Paused - up to date'); this._logger.debug(`[Sync] Paused - up to date ${info}`);
}) })
.on('active', () => { .on('active', () => {
this._logger.debug('[Sync] Active - syncing'); this._logger.debug('[Sync] Active - syncing');
}) })
.on('error', (err) => { .on('error', (err: any) => {
this._logger.error('[Sync] Error:', err); this._logger.error('[Sync] Error:', err);
}); });
} }
@ -202,52 +202,4 @@ export class PouchData {
this._logger.warn('CONFLICTS!', doc._conflicts); this._logger.warn('CONFLICTS!', doc._conflicts);
} }
} }
/**
* Copy all documents from this database to a new public database.
* Used when sharing a local diagram to make it publicly accessible.
* @param newDbName - The name for the new public database
* @returns The URL path to the new public diagram
*/
public async copyToPublic(newDbName: string): Promise<string> {
this._logger.info(`[Copy] Starting copy to public-${newDbName}`);
// Create the remote public database
const remoteUrl = `${window.location.origin}/pouchdb/public-${newDbName}`;
const remoteDb = new PouchDB(remoteUrl);
try {
// Get all docs from local database
const allDocs = await this._db.allDocs({ include_docs: true });
this._logger.debug(`[Copy] Found ${allDocs.rows.length} documents to copy`);
// Copy each document to the remote database
for (const row of allDocs.rows) {
if (row.doc) {
// Remove PouchDB internal fields for clean insert
const { _rev, ...docWithoutRev } = row.doc;
try {
await remoteDb.put(docWithoutRev);
} catch (err) {
// Document might already exist if this is a retry
this._logger.warn(`[Copy] Failed to copy doc ${row.id}:`, err);
}
}
}
this._logger.info(`[Copy] Successfully copied ${allDocs.rows.length} documents`);
return `/db/public/${newDbName}`;
} catch (err) {
this._logger.error('[Copy] Error copying to public:', err);
throw err;
}
}
/**
* Get all documents in the database (for export/copy operations)
*/
public async getAllDocs(): Promise<any[]> {
const result = await this._db.allDocs({ include_docs: true });
return result.rows.map(row => row.doc).filter(doc => doc && doc.id !== 'metadata');
}
} }

View File

@ -1,27 +0,0 @@
import axios from "axios";
import log from "loglevel";
export async function checkDb(localName: string, remoteDbName: string, password: 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": password,
"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

@ -1,17 +0,0 @@
export function hex_to_ascii(input) {
const hex = input.toString();
let output = '';
for (let n = 0; n < hex.length; n += 2) {
output += String.fromCharCode(parseInt(hex.substr(n, 2), 16));
}
return output;
}
export function ascii_to_hex(str) {
const arr1 = [];
for (let n = 0, l = str.length; n < l; n++) {
const hex = Number(str.charCodeAt(n)).toString(16);
arr1.push(hex);
}
return arr1.join('');
}

View File

@ -1,59 +0,0 @@
import log from "loglevel";
import {DiagramEntity, DiagramEntityType} from "../../diagram/types/diagramEntity";
import {Observable} from "@babylonjs/core";
import {Encryption} from "../encryption";
import {DiagramEventObserverMask} from "../../diagram/types/diagramEventObserverMask";
export async function syncDoc(info: any, onDBRemoveObservable: Observable<DiagramEntity>, onDBUpdateObservable: Observable<DiagramEntity>,
encryption: Encryption, key: string) {
const logger = log.getLogger('syncDoc');
logger.debug(info);
if (info.direction == 'pull') {
// @ts-ignore
const docs = info.change.docs;
let salt = null;
for (const doc of docs) {
if (doc.encrypted) {
if (salt != doc.encrypted.salt || (key && !encryption.ready)) {
await encryption.setPassword(key, doc.encrypted.salt);
salt = doc.encrypted.salt
}
const decrypted = await encryption.decryptToObject(doc.encrypted.encrypted, doc.encrypted.iv);
if (decrypted.type == DiagramEntityType.USER) {
//onUserObservable.notifyObservers(doc, -1);
} else {
logger.debug(decrypted);
if (doc._deleted) {
logger.debug('Delete', doc);
onDBRemoveObservable.notifyObservers({
id: doc._id,
template: decrypted.template,
type: doc.type
}, DiagramEventObserverMask.FROM_DB);
} else {
onDBUpdateObservable.notifyObservers(decrypted, DiagramEventObserverMask.FROM_DB);
}
}
} else {
if (doc.type == 'user') {
//onUserObservable.notifyObservers(doc, -1);
} else {
logger.debug(doc);
if (doc._deleted) {
logger.debug('Delete', doc);
onDBRemoveObservable.notifyObservers({
id: doc._id,
template: doc.template,
type: doc.type
}, DiagramEventObserverMask.FROM_DB);
} else {
if (doc.template) {
onDBUpdateObservable.notifyObservers(doc, DiagramEventObserverMask.FROM_DB);
}
}
}
}
}
}
}

View File

@ -1,166 +0,0 @@
/**
* DiagramEntity Integration Adapter for ResizeGizmo
* Bridges ResizeGizmo events to DiagramManager's persistence system
*
* This adapter lives in the integration layer to keep the ResizeGizmo
* system pure and reusable without diagram-specific dependencies.
*/
import { AbstractMesh } from "@babylonjs/core";
import { ResizeGizmoManager } from "../../gizmos/ResizeGizmo";
import { ResizeGizmoEvent } from "../../gizmos/ResizeGizmo";
/**
* Type definitions for DiagramManager integration (loosely coupled)
* These match the actual types in the codebase without importing them
*/
interface DiagramEntity {
id?: string;
template?: string;
position?: { x: number; y: number; z: number };
rotation?: { x: number; y: number; z: number };
scale?: { x: number; y: number; z: number };
[key: string]: any;
}
enum DiagramEventType {
MODIFY = "MODIFY"
}
interface DiagramEvent {
type: DiagramEventType;
entity: DiagramEntity;
}
enum DiagramEventObserverMask {
TO_DB = 2,
ALL = -1
}
interface DiagramEventNotifier {
notifyObservers(event: DiagramEvent, mask?: number): void;
}
interface DiagramManager {
onDiagramEventObservable: DiagramEventNotifier;
}
/**
* Converter function type for transforming BabylonJS meshes to DiagramEntities
*/
export type MeshToEntityConverter = (mesh: AbstractMesh) => DiagramEntity;
/**
* Adapter that connects ResizeGizmo to DiagramManager for persistence
* Uses dependency injection to remain loosely coupled from diagram internals
*
* @example
* ```typescript
* import { DiagramEntityAdapter } from './integration/gizmo';
* import { toDiagramEntity } from './diagram/functions/toDiagramEntity';
*
* // Create resize gizmo
* const gizmo = new ResizeGizmoManager(scene, {
* mode: ResizeGizmoMode.ALL
* });
*
* // Create adapter with injected converter
* const adapter = new DiagramEntityAdapter(
* gizmo,
* diagramManager,
* toDiagramEntity, // Injected dependency
* false // Don't persist on drag
* );
*
* // Now scale changes will automatically persist to database
* gizmo.attachToMesh(myDiagramMesh);
* ```
*/
export class DiagramEntityAdapter {
private _gizmo: ResizeGizmoManager;
private _diagramManager: DiagramManager;
private _meshConverter: MeshToEntityConverter;
private _persistOnDrag: boolean;
/**
* Create adapter
* @param gizmo ResizeGizmoManager instance
* @param diagramManager DiagramManager instance (or object with onDiagramEventObservable)
* @param meshConverter Function to convert BabylonJS mesh to DiagramEntity (injected dependency)
* @param persistOnDrag If true, persist on every drag update (can be expensive). If false, only persist on scale end.
*/
constructor(
gizmo: ResizeGizmoManager,
diagramManager: DiagramManager,
meshConverter: MeshToEntityConverter,
persistOnDrag: boolean = false
) {
this._gizmo = gizmo;
this._diagramManager = diagramManager;
this._meshConverter = meshConverter;
this._persistOnDrag = persistOnDrag;
this.setupEventListeners();
}
/**
* Setup event listeners
*/
private setupEventListeners(): void {
// Persist on scale end (always)
this._gizmo.onScaleEnd((event) => {
this.persistScaleChange(event);
});
// Optionally persist on drag
if (this._persistOnDrag) {
this._gizmo.onScaleDrag((event) => {
this.persistScaleChange(event);
});
}
}
/**
* Persist scale change to DiagramManager
*/
private persistScaleChange(event: ResizeGizmoEvent): void {
const mesh = event.mesh;
// Convert mesh to DiagramEntity using injected converter
// This properly extracts color from material and all other properties
const entity = this._meshConverter(mesh);
// Notify DiagramManager
this._diagramManager.onDiagramEventObservable.notifyObservers(
{
type: DiagramEventType.MODIFY,
entity
},
DiagramEventObserverMask.TO_DB
);
}
/**
* Enable/disable drag persistence
*/
setPersistOnDrag(enabled: boolean): void {
if (this._persistOnDrag === enabled) {
return;
}
this._persistOnDrag = enabled;
// Re-setup listeners
// Note: In a production implementation, you'd want to properly remove/add observers
// For now, this is a simplified version
console.warn("[DiagramEntityAdapter] Changing persistOnDrag at runtime may cause duplicate events");
}
/**
* Get persist on drag setting
*/
getPersistOnDrag(): boolean {
return this._persistOnDrag;
}
}

View File

@ -1,6 +0,0 @@
/**
* Gizmo Integration Layer
* Adapters for integrating gizmo systems with diagram persistence
*/
export { DiagramEntityAdapter, type MeshToEntityConverter } from './DiagramEntityAdapter';

View File

@ -84,30 +84,6 @@ export class AnimatedLineTexture {
return texture; return texture;
} }
/**
* Removes a texture from the animation set when disposed
* WARNING: Do NOT call this on cached textures! Only for non-cached textures.
* Cached textures are shared across multiple connections.
* Use ClearCache() to dispose cached textures properly.
* @param texture - The texture to stop animating
*/
public static DisposeTexture(texture: Texture): void {
// Safety check: prevent disposing cached textures (they're shared!)
for (const [color, cachedTexture] of this._coloredTextureCache.entries()) {
if (cachedTexture === texture) {
console.error(
`AnimatedLineTexture.DisposeTexture: Attempted to dispose cached texture ` +
`"${texture.name}" (color: ${color}). This will break texture sharing! ` +
`Cached textures should not be disposed individually. Use ClearCache() instead.`
);
return; // Don't dispose - it's shared across multiple connections
}
}
// Only dispose non-cached textures
this._animatedTextures.delete(texture);
texture.dispose();
}
/** /**
* Preload textures for common colors to prevent first-render stutter * Preload textures for common colors to prevent first-render stutter
@ -119,27 +95,5 @@ export class AnimatedLineTexture {
}); });
} }
/**
* Clear the texture cache and dispose all cached textures
* Use with caution - only call when no connections are using these textures
*/
public static ClearCache(): void {
this._coloredTextureCache.forEach((texture) => {
this._animatedTextures.delete(texture);
texture.dispose();
});
this._coloredTextureCache.clear();
}
/**
* Get cache statistics for debugging
* @returns Object with cache stats
*/
public static GetCacheStats(): { cachedColors: number; totalAnimatedTextures: number; colors: string[] } {
return {
cachedColors: this._coloredTextureCache.size,
totalAnimatedTextures: this._animatedTextures.size,
colors: Array.from(this._coloredTextureCache.keys())
};
}
} }

View File

@ -149,32 +149,6 @@ export class AppConfig {
this.save(); this.save();
} }
/**
* Remove handle configuration by ID
* @param id Handle ID to remove
*/
public removeHandleConfig(id: string) {
if (!this._currentConfig.handles) {
return;
}
const initialLength = this._currentConfig.handles.length;
this._currentConfig.handles = this._currentConfig.handles.filter(h => h.id !== id);
if (this._currentConfig.handles.length < initialLength) {
this._logger.debug(`Removed handle config for ${id}`);
this.save();
}
}
/**
* Get all handle configurations
* @returns Array of all HandleConfig objects
*/
public getAllHandleConfigs(): HandleConfig[] {
return this._currentConfig.handles || [];
}
private save() { private save() {
localStorage.setItem('appConfig', JSON.stringify(this._currentConfig)); localStorage.setItem('appConfig', JSON.stringify(this._currentConfig));
this.onConfigChangedObservable.notifyObservers(this._currentConfig, -1); this.onConfigChangedObservable.notifyObservers(this._currentConfig, -1);

View File

@ -4,13 +4,11 @@ import {
Material, Material,
MeshBuilder, MeshBuilder,
Observable, Observable,
PBRMaterial,
PhysicsAggregate, PhysicsAggregate,
PhysicsShapeType, PhysicsShapeType,
PointsCloudSystem, PointsCloudSystem,
Scene, Scene,
Sound, Sound,
Texture,
TransformNode, TransformNode,
Vector3 Vector3
} from "@babylonjs/core"; } from "@babylonjs/core";

View File

@ -1,14 +1,9 @@
import {HavokPlugin, Quaternion, Scene, Vector3} from "@babylonjs/core"; import {HavokPlugin, Scene, Vector3} from "@babylonjs/core";
import HavokPhysics from "@babylonjs/havok"; import HavokPhysics from "@babylonjs/havok";
import {snapGridVal} from "./functions/snapGridVal";
import {snapRotateVal} from "./functions/snapRotateVal";
import {isDiagramEntity} from "../diagram/functions/isDiagramEntity";
import {appConfigInstance} from "./appConfig";
export class CustomPhysics { export class CustomPhysics {
private readonly scene: Scene; private readonly scene: Scene;
constructor(scene: Scene) { constructor(scene: Scene) {
this.scene = scene; this.scene = scene;
} }
@ -17,47 +12,6 @@ export class CustomPhysics {
const havok = await HavokPhysics(); const havok = await HavokPhysics();
const havokPlugin = new HavokPlugin(true, havok); const havokPlugin = new HavokPlugin(true, havok);
const scene = this.scene; const scene = this.scene;
const physicsEnable = scene.enablePhysics(new Vector3(0, -9.8, 0), havokPlugin); scene.enablePhysics(new Vector3(0, -9.8, 0), havokPlugin);
scene.collisionsEnabled = true;
scene.onAfterPhysicsObservable.add(() => {
const config = appConfigInstance.current;
scene.meshes.forEach((mesh) => {
if (isDiagramEntity(mesh) && mesh.physicsBody) {
const body = mesh.physicsBody;
const linearVelocity = new Vector3();
body.getLinearVelocityToRef(linearVelocity);
if (linearVelocity.length() < .1) {
body.disablePreStep = false;
// Apply location snap if enabled
if (config.locationSnap > 0) {
const pos: Vector3 = body.getObjectCenterWorld();
const val: Vector3 = snapGridVal(pos, config.locationSnap);
body.transformNode.position.set(val.x, val.y, val.z);
}
// Apply rotation snap if enabled
if (config.rotateSnap > 0) {
const rot: Quaternion =
Quaternion.FromEulerVector(
snapRotateVal(body.transformNode.rotationQuaternion.toEulerAngles(),
config.rotateSnap));
body.transformNode.rotationQuaternion.set(
rot.x, rot.y, rot.z, rot.w
);
}
scene.onAfterRenderObservable.addOnce(() => {
body.disablePreStep = true;
});
}
}
});
}
);
} }
} }

View File

@ -1,8 +1,3 @@
/**
* Device detection utilities for VR capability and device type
*/
import {Scene, WebXRDefaultExperience} from "@babylonjs/core";
export interface DeviceCapabilities { export interface DeviceCapabilities {
isVRCapable: boolean; isVRCapable: boolean;
isMobileVR: boolean; isMobileVR: boolean;
@ -64,5 +59,4 @@ export async function getDeviceCapabilities(): Promise<DeviceCapabilities> {
isDesktop, isDesktop,
deviceType deviceType
}; };
} }

View File

@ -11,33 +11,6 @@ export function getPath(): string {
return null; return null;
} }
/**
* Check if the current path is a local database (no sync)
* Local paths: /db/local/:db
*/
export function isLocalPath(): boolean {
const path = window.location.pathname.split('/');
return path.length >= 3 && path[1] === 'db' && path[2] === 'local';
}
/**
* Check if the current path is a public database
* Public paths: /db/public/:db
*/
export function isPublicPath(): boolean {
const path = window.location.pathname.split('/');
return path.length >= 3 && path[1] === 'db' && path[2] === 'public';
}
/**
* Check if the current path is a private database
* Private paths: /db/private/:db
*/
export function isPrivatePath(): boolean {
const path = window.location.pathname.split('/');
return path.length >= 3 && path[1] === 'db' && path[2] === 'private';
}
/** /**
* Get the database type from the current path * Get the database type from the current path
*/ */

View File

@ -157,8 +157,6 @@ function positionComponentsRelativeToCamera(scene: Scene, diagramManager: Diagra
logger.info('Horizontal left:', horizontalLeft); logger.info('Horizontal left:', horizontalLeft);
logger.info('Platform world position:', platform.getAbsolutePosition()); logger.info('Platform world position:', platform.getAbsolutePosition());
// Position toolbox: Camera-relative positioning disabled to respect default/saved positions
// Handles now use their configured defaults or saved localStorage positions
const toolbox = diagramManager.diagramMenuManager.toolbox; const toolbox = diagramManager.diagramMenuManager.toolbox;
if (toolbox && toolbox.handleMesh) { if (toolbox && toolbox.handleMesh) {
logger.info('Toolbox handleMesh using default/saved position:', { logger.info('Toolbox handleMesh using default/saved position:', {
@ -166,64 +164,13 @@ function positionComponentsRelativeToCamera(scene: Scene, diagramManager: Diagra
absolutePosition: toolbox.handleMesh.getAbsolutePosition().clone(), absolutePosition: toolbox.handleMesh.getAbsolutePosition().clone(),
rotation: toolbox.handleMesh.rotation.clone() rotation: toolbox.handleMesh.rotation.clone()
}); });
// Camera-relative positioning commented out - handles use their own defaults
/*
// Position at 45 degrees to the left, 0.45m away, slightly below eye level
// NOTE: User faces -Z direction by design, so negate forward offset
const forwardOffset = horizontalForward.scale(-0.3);
const leftOffset = horizontalLeft.scale(0.35);
const toolboxWorldPos = cameraWorldPos.add(forwardOffset).add(leftOffset);
toolboxWorldPos.y = cameraWorldPos.y - 0.3; // Below eye level
logger.info('Calculated toolbox world position:', toolboxWorldPos);
logger.info('Forward offset:', forwardOffset);
logger.info('Left offset:', leftOffset);
const toolboxLocalPos = Vector3.TransformCoordinates(toolboxWorldPos, platform.getWorldMatrix().invert());
logger.info('Calculated toolbox local position:', toolboxLocalPos);
toolbox.handleMesh.position = toolboxLocalPos;
// Orient toolbox to face the user
const toolboxToCamera = cameraWorldPos.subtract(toolboxWorldPos).normalize();
const toolboxYaw = Math.atan2(toolboxToCamera.x, toolboxToCamera.z);
toolbox.handleMesh.rotation.y = toolboxYaw;
logger.info('Toolbox handleMesh AFTER positioning:', {
position: toolbox.handleMesh.position.clone(),
absolutePosition: toolbox.handleMesh.getAbsolutePosition().clone(),
rotation: toolbox.handleMesh.rotation.clone()
});
*/
} }
// Position input text view: Camera-relative positioning disabled to respect default/saved positions
// Handles now use their configured defaults or saved localStorage positions
const inputTextView = diagramManager.diagramMenuManager['_inputTextView']; const inputTextView = diagramManager.diagramMenuManager['_inputTextView'];
if (inputTextView && inputTextView.handleMesh) { if (inputTextView && inputTextView.handleMesh) {
logger.info('InputTextView handleMesh using default/saved position:', { logger.info('InputTextView handleMesh using default/saved position:', {
position: inputTextView.handleMesh.position.clone(), position: inputTextView.handleMesh.position.clone(),
absolutePosition: inputTextView.handleMesh.getAbsolutePosition().clone() absolutePosition: inputTextView.handleMesh.getAbsolutePosition().clone()
}); });
// Camera-relative positioning commented out - handles use their own defaults
/*
// NOTE: User faces -Z direction by design, so negate forward offset
const inputWorldPos = cameraWorldPos.add(horizontalForward.scale(-0.5));
inputWorldPos.y = cameraWorldPos.y - 0.4; // Below eye level
logger.info('Calculated input world position:', inputWorldPos);
const inputLocalPos = Vector3.TransformCoordinates(inputWorldPos, platform.getWorldMatrix().invert());
logger.info('Calculated input local position:', inputLocalPos);
inputTextView.handleMesh.position = inputLocalPos;
logger.info('InputTextView handleMesh AFTER positioning:', {
position: inputTextView.handleMesh.position.clone(),
absolutePosition: inputTextView.handleMesh.getAbsolutePosition().clone()
});
*/
} }
} }

View File

@ -23,23 +23,6 @@ export function addSceneInspector() {
}); });
}); });
} }
/*import("@babylonjs/core/Debug").then(() => {
import("@babylonjs/inspector").then(() => {
const web = document.querySelector('#webApp');
if (scene.debugLayer.isVisible()) {
if (web) {
(web as HTMLDivElement).style.display = 'block';
}
scene.debugLayer.hide();
} else {
scene.debugLayer.show();
if (web) {
(web as HTMLDivElement).style.display = 'none';
}
}
});
});*/
} }
}); });
} }

View File

@ -1,5 +1,4 @@
import {Color3, DynamicTexture, HemisphericLight, PointLight, Scene, StandardMaterial, Vector3} from "@babylonjs/core"; import {Color3, DynamicTexture, HemisphericLight, PointLight, Scene, StandardMaterial, Vector3} from "@babylonjs/core";
import {DefaultScene} from "../defaultScene";
import {RenderingMode} from "./renderingMode"; import {RenderingMode} from "./renderingMode";
export class LightmapGenerator { export class LightmapGenerator {