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:
parent
8c2b7f9c7d
commit
3155cc930f
@ -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)
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -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}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "immersive",
|
||||
"private": true,
|
||||
"version": "0.0.8-46",
|
||||
"version": "0.0.8-47",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@ -25,10 +25,12 @@ export function buildRig(xr: WebXRDefaultExperience): Mesh {
|
||||
});
|
||||
for (const cam of scene.cameras) {
|
||||
cam.parent = cameratransform;
|
||||
cam.position = new Vector3(0, 1.6, 0); // Reset to local position above platform
|
||||
}
|
||||
scene.onActiveCameraChanged.add(() => {
|
||||
for (const cam of scene.cameras) {
|
||||
cam.parent = cameratransform;
|
||||
cam.position = new Vector3(0, 1.6, 0); // Reset to local position above platform
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -314,7 +314,7 @@ export class DiagramObject {
|
||||
this._label.rotation.y = 0;
|
||||
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 {
|
||||
// Standard object labels - convert world space to parent's local space
|
||||
// This accounts for mesh scaling, which is not included in boundingBox.maximum
|
||||
@ -326,7 +326,7 @@ export class DiagramObject {
|
||||
temp.dispose();
|
||||
this._label.position.y = y + 0.06;
|
||||
this._labelBack.rotation.y = Math.PI;
|
||||
this._labelBack.position.z = 0.001;
|
||||
this._labelBack.position.z = 0.005;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
} from '@babylonjs/core';
|
||||
|
||||
import log from 'loglevel';
|
||||
import { HandleState, CORNER_POSITIONS, FACE_POSITIONS} from './enums';
|
||||
import { CORNER_POSITIONS, FACE_POSITIONS} from './enums';
|
||||
import { ResizeGizmoEvent } from './types';
|
||||
|
||||
/**
|
||||
@ -110,7 +110,7 @@ export class ResizeGizmo {
|
||||
private createHandles(): void {
|
||||
// Ensure world matrix and bounding info are current
|
||||
this._targetMesh.computeWorldMatrix(true);
|
||||
this._targetMesh.refreshBoundingInfo();
|
||||
this._targetMesh.refreshBoundingInfo({});
|
||||
|
||||
// Get bounding box for positioning
|
||||
const targetBoundingInfo = this._targetMesh.getBoundingInfo();
|
||||
@ -489,7 +489,7 @@ export class ResizeGizmo {
|
||||
private updateHandleTransforms(): void {
|
||||
// Ensure world matrix and bounding info are current
|
||||
this._targetMesh.computeWorldMatrix(true);
|
||||
this._targetMesh.refreshBoundingInfo();
|
||||
this._targetMesh.refreshBoundingInfo({});
|
||||
|
||||
const targetBoundingInfo = this._targetMesh.getBoundingInfo();
|
||||
const boundingBox = targetBoundingInfo.boundingBox;
|
||||
|
||||
@ -74,12 +74,12 @@ export class PouchData {
|
||||
}
|
||||
})
|
||||
.on('paused', (info) => {
|
||||
this._logger.debug('[Sync] Paused - up to date');
|
||||
this._logger.debug(`[Sync] Paused - up to date ${info}`);
|
||||
})
|
||||
.on('active', () => {
|
||||
this._logger.debug('[Sync] Active - syncing');
|
||||
})
|
||||
.on('error', (err) => {
|
||||
.on('error', (err: any) => {
|
||||
this._logger.error('[Sync] Error:', err);
|
||||
});
|
||||
}
|
||||
@ -202,52 +202,4 @@ export class PouchData {
|
||||
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');
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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('');
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Gizmo Integration Layer
|
||||
* Adapters for integrating gizmo systems with diagram persistence
|
||||
*/
|
||||
|
||||
export { DiagramEntityAdapter, type MeshToEntityConverter } from './DiagramEntityAdapter';
|
||||
@ -84,30 +84,6 @@ export class AnimatedLineTexture {
|
||||
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
|
||||
@ -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())
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -149,32 +149,6 @@ export class AppConfig {
|
||||
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() {
|
||||
localStorage.setItem('appConfig', JSON.stringify(this._currentConfig));
|
||||
this.onConfigChangedObservable.notifyObservers(this._currentConfig, -1);
|
||||
|
||||
@ -4,13 +4,11 @@ import {
|
||||
Material,
|
||||
MeshBuilder,
|
||||
Observable,
|
||||
PBRMaterial,
|
||||
PhysicsAggregate,
|
||||
PhysicsShapeType,
|
||||
PointsCloudSystem,
|
||||
Scene,
|
||||
Sound,
|
||||
Texture,
|
||||
TransformNode,
|
||||
Vector3
|
||||
} from "@babylonjs/core";
|
||||
|
||||
@ -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 {snapGridVal} from "./functions/snapGridVal";
|
||||
import {snapRotateVal} from "./functions/snapRotateVal";
|
||||
import {isDiagramEntity} from "../diagram/functions/isDiagramEntity";
|
||||
import {appConfigInstance} from "./appConfig";
|
||||
|
||||
export class CustomPhysics {
|
||||
private readonly scene: Scene;
|
||||
|
||||
|
||||
constructor(scene: Scene) {
|
||||
this.scene = scene;
|
||||
}
|
||||
@ -17,47 +12,6 @@ export class CustomPhysics {
|
||||
const havok = await HavokPhysics();
|
||||
const havokPlugin = new HavokPlugin(true, havok);
|
||||
const scene = this.scene;
|
||||
const physicsEnable = 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
scene.enablePhysics(new Vector3(0, -9.8, 0), havokPlugin);
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,3 @@
|
||||
/**
|
||||
* Device detection utilities for VR capability and device type
|
||||
*/
|
||||
import {Scene, WebXRDefaultExperience} from "@babylonjs/core";
|
||||
|
||||
export interface DeviceCapabilities {
|
||||
isVRCapable: boolean;
|
||||
isMobileVR: boolean;
|
||||
@ -65,4 +60,3 @@ export async function getDeviceCapabilities(): Promise<DeviceCapabilities> {
|
||||
deviceType
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -11,33 +11,6 @@ export function getPath(): string {
|
||||
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
|
||||
*/
|
||||
|
||||
@ -157,8 +157,6 @@ function positionComponentsRelativeToCamera(scene: Scene, diagramManager: Diagra
|
||||
logger.info('Horizontal left:', horizontalLeft);
|
||||
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;
|
||||
if (toolbox && toolbox.handleMesh) {
|
||||
logger.info('Toolbox handleMesh using default/saved position:', {
|
||||
@ -166,64 +164,13 @@ function positionComponentsRelativeToCamera(scene: Scene, diagramManager: Diagra
|
||||
absolutePosition: toolbox.handleMesh.getAbsolutePosition().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'];
|
||||
if (inputTextView && inputTextView.handleMesh) {
|
||||
logger.info('InputTextView handleMesh using default/saved position:', {
|
||||
position: inputTextView.handleMesh.position.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()
|
||||
});
|
||||
*/
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
}
|
||||
}
|
||||
});
|
||||
});*/
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {Color3, DynamicTexture, HemisphericLight, PointLight, Scene, StandardMaterial, Vector3} from "@babylonjs/core";
|
||||
import {DefaultScene} from "../defaultScene";
|
||||
import {RenderingMode} from "./renderingMode";
|
||||
|
||||
export class LightmapGenerator {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user