Refactor Handle class and fix VR positioning
Major improvements to Handle class architecture: - Replace positional constructor parameters with options object pattern (HandleOptions interface) - Add automatic platform parenting - handles now find and parent themselves to platform - Rename idStored → hasStoredPosition for better clarity - Remove unused staort() method - Improve position/rotation persistence with better error handling - Add comprehensive JSDoc documentation - Use .parent instead of setParent() for proper local space coordinates Update all Handle usage sites: - Toolbox: Use new API with position (-.5, 1.5, .5) and zero rotation - InputTextView: Use new API with position (0, 1.5, .5) and zero rotation - VRConfigPanel: Use new API with position (.5, 1.5, .5) and zero rotation - Remove manual platform parenting logic (61 lines of duplicated code removed) - Remove local position offsets that were overriding handle positions Fix VR entry positioning: - Disable camera-relative positioning in groundMeshObserver - Handles now use their configured defaults or saved localStorage positions - Positions are now in platform local space as intended 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
15c6617151
commit
aa0810be02
@ -28,9 +28,13 @@ export class InputTextView {
|
|||||||
this.controllerObservable = controllerObservable;
|
this.controllerObservable = controllerObservable;
|
||||||
this.scene = DefaultScene.Scene;
|
this.scene = DefaultScene.Scene;
|
||||||
this.inputMesh = MeshBuilder.CreatePlane("input", {width: 1, height: .5}, this.scene);
|
this.inputMesh = MeshBuilder.CreatePlane("input", {width: 1, height: .5}, this.scene);
|
||||||
this.handle = new Handle(this.inputMesh, 'Input');
|
this.handle = new Handle({
|
||||||
this.inputMesh.position.y = .06;
|
contentMesh: this.inputMesh,
|
||||||
this.inputMesh.position.z = .02;
|
label: 'Input',
|
||||||
|
defaultPosition: new Vector3(0, 1.5, .5),
|
||||||
|
defaultRotation: new Vector3(0, 0, 0)
|
||||||
|
});
|
||||||
|
// Position is now controlled by Handle class
|
||||||
this.createKeyboard();
|
this.createKeyboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,36 +56,6 @@ export class InputTextView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public createKeyboard() {
|
public createKeyboard() {
|
||||||
const platform = this.scene.getMeshById('platform');
|
|
||||||
const position = new Vector3(0, 1.66, .53);
|
|
||||||
const rotation = new Vector3(.9, 0, 0);
|
|
||||||
const handle = this.handle;
|
|
||||||
/*if (handle.mesh.position.x != 0 && handle.mesh.position.y != 0 && handle.mesh.position.z != 0) {
|
|
||||||
position = handle.mesh.position;
|
|
||||||
}
|
|
||||||
if (handle.mesh.rotation.x != 0 && handle.mesh.rotation.y != 0 && handle.mesh.rotation.z != 0) {
|
|
||||||
rotation = handle.mesh.rotation;
|
|
||||||
}*/
|
|
||||||
if (!platform) {
|
|
||||||
this.scene.onNewMeshAddedObservable.add((mesh) => {
|
|
||||||
if (mesh.id == 'platform') {
|
|
||||||
this.logger.debug("platform added");
|
|
||||||
handle.transformNode.parent = mesh;
|
|
||||||
if (!handle.idStored) {
|
|
||||||
handle.transformNode.position = position;
|
|
||||||
handle.transformNode.rotation = rotation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, -1, false, this, false);
|
|
||||||
} else {
|
|
||||||
handle.transformNode.setParent(platform);
|
|
||||||
if (!handle.idStored) {
|
|
||||||
handle.transformNode.position = position;
|
|
||||||
handle.transformNode.rotation = rotation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//setMenuPosition(handle.mesh, this.scene, new Vector3(0, .4, 0));
|
|
||||||
const advancedTexture = AdvancedDynamicTexture.CreateForMesh(this.inputMesh, 2048, 1024, false);
|
const advancedTexture = AdvancedDynamicTexture.CreateForMesh(this.inputMesh, 2048, 1024, false);
|
||||||
|
|
||||||
const input = new InputText();
|
const input = new InputText();
|
||||||
|
|||||||
@ -83,12 +83,12 @@ export class VRConfigPanel {
|
|||||||
this._baseTransform.scaling = new Vector3(0.6, 0.6, 0.6);
|
this._baseTransform.scaling = new Vector3(0.6, 0.6, 0.6);
|
||||||
|
|
||||||
// Create handle for grabbing (Handle will become parent of baseTransform)
|
// Create handle for grabbing (Handle will become parent of baseTransform)
|
||||||
this._handle = new Handle(
|
this._handle = new Handle({
|
||||||
this._baseTransform,
|
contentMesh: this._baseTransform,
|
||||||
'Configuration',
|
label: 'Configuration',
|
||||||
new Vector3(0.5, 1.6, 0.4), // Default position relative to platform
|
defaultPosition: new Vector3(.5, 1.5, .5), // Default position relative to platform
|
||||||
new Vector3(0.5, 0.6, 0) // Default rotation
|
defaultRotation: new Vector3(0, 0, 0) // Default rotation
|
||||||
);
|
});
|
||||||
|
|
||||||
// Build the panel mesh and UI
|
// Build the panel mesh and UI
|
||||||
this.buildPanel();
|
this.buildPanel();
|
||||||
@ -185,14 +185,8 @@ export class VRConfigPanel {
|
|||||||
// Parent to base transform
|
// Parent to base transform
|
||||||
this._panelMesh.parent = this._baseTransform;
|
this._panelMesh.parent = this._baseTransform;
|
||||||
|
|
||||||
// Calculate position to place panel bottom just above handle
|
// Position is now controlled by Handle class
|
||||||
// Panel is 1.5m tall, so center needs to be at half-height + small gap above handle
|
// Panel is positioned at origin relative to baseTransform
|
||||||
const panelHeight = 1.5;
|
|
||||||
const gapAboveHandle = 0.05; // 5cm gap above handle for spacing
|
|
||||||
const panelCenterY = (panelHeight / 2) + gapAboveHandle; // 0.75 + 0.05 = 0.8m
|
|
||||||
|
|
||||||
// Position panel so bottom edge sits just above handle, matching toolbox appearance
|
|
||||||
this._panelMesh.position = new Vector3(0, panelCenterY, 0);
|
|
||||||
|
|
||||||
// Create material for panel backing
|
// Create material for panel backing
|
||||||
const material = new StandardMaterial("vrConfigPanelMaterial", this._scene);
|
const material = new StandardMaterial("vrConfigPanelMaterial", this._scene);
|
||||||
@ -233,9 +227,6 @@ export class VRConfigPanel {
|
|||||||
// Build configuration sections
|
// Build configuration sections
|
||||||
this.buildConfigSections();
|
this.buildConfigSections();
|
||||||
|
|
||||||
// Parent handle to platform when available
|
|
||||||
this.setupPlatformParenting();
|
|
||||||
|
|
||||||
this._logger.debug('VR config panel built successfully');
|
this._logger.debug('VR config panel built successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -797,25 +788,6 @@ export class VRConfigPanel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up parenting to platform for world movement tracking
|
|
||||||
*/
|
|
||||||
private setupPlatformParenting(): void {
|
|
||||||
const platform = this._scene.getMeshById('platform');
|
|
||||||
if (platform) {
|
|
||||||
this._handle.transformNode.parent = platform;
|
|
||||||
this._logger.debug('VRConfigPanel parented to existing platform');
|
|
||||||
} else {
|
|
||||||
// Wait for platform to be added
|
|
||||||
const handler = this._scene.onNewMeshAddedObservable.add((mesh) => {
|
|
||||||
if (mesh && mesh.id === 'platform') {
|
|
||||||
this._handle.transformNode.parent = mesh;
|
|
||||||
this._logger.debug('VRConfigPanel parented to newly added platform');
|
|
||||||
this._scene.onNewMeshAddedObservable.remove(handler);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update all UI elements to reflect current config
|
* Update all UI elements to reflect current config
|
||||||
|
|||||||
@ -11,38 +11,106 @@ import {
|
|||||||
import log, {Logger} from "loglevel";
|
import log, {Logger} from "loglevel";
|
||||||
import {split} from "canvas-hypertxt";
|
import {split} from "canvas-hypertxt";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for creating a Handle instance
|
||||||
|
*/
|
||||||
|
export interface HandleOptions {
|
||||||
|
/** The TransformNode that will be parented to this handle */
|
||||||
|
contentMesh: TransformNode;
|
||||||
|
/** Display label for the handle (default: 'Handle') */
|
||||||
|
label?: string;
|
||||||
|
/** Default position offset if no stored position exists (default: Vector3.Zero()) */
|
||||||
|
defaultPosition?: Vector3;
|
||||||
|
/** Default rotation if no stored rotation exists (default: Vector3.Zero()) */
|
||||||
|
defaultRotation?: Vector3;
|
||||||
|
/** Whether to automatically parent the handle to the platform (default: true) */
|
||||||
|
autoParentToPlatform?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A grabbable, labeled plane mesh for VR interfaces with automatic position persistence.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Automatically saves and restores position/rotation to localStorage
|
||||||
|
* - Can automatically parent itself to the platform mesh
|
||||||
|
* - Creates a labeled visual handle for VR interaction
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const handle = new Handle({
|
||||||
|
* contentMesh: myMenu,
|
||||||
|
* label: 'Configuration',
|
||||||
|
* defaultPosition: new Vector3(0.5, 1.6, 0.4),
|
||||||
|
* defaultRotation: new Vector3(0.5, 0.6, 0)
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export class Handle {
|
export class Handle {
|
||||||
|
/** The TransformNode representing the handle mesh */
|
||||||
public transformNode: TransformNode;
|
public transformNode: TransformNode;
|
||||||
private readonly _menuItem: TransformNode;
|
|
||||||
private _isStored: boolean = false;
|
private readonly _contentMesh: TransformNode;
|
||||||
private _offset: Vector3;
|
private _hasStoredPosition: boolean = false;
|
||||||
private _rotation: Vector3;
|
private readonly _defaultPosition: Vector3;
|
||||||
|
private readonly _defaultRotation: Vector3;
|
||||||
private readonly _label: string;
|
private readonly _label: string;
|
||||||
|
private readonly _autoParentToPlatform: boolean;
|
||||||
private readonly _logger: Logger = log.getLogger('Handle');
|
private readonly _logger: Logger = log.getLogger('Handle');
|
||||||
|
|
||||||
constructor(mesh: TransformNode, label: string = 'Handle', offset: Vector3 = Vector3.Zero(), rotation: Vector3 = Vector3.Zero()) {
|
/**
|
||||||
this._menuItem = mesh;
|
* Creates a new Handle instance
|
||||||
this._offset = offset;
|
* @param options Configuration options for the handle
|
||||||
this._rotation = rotation;
|
*/
|
||||||
this._label = label;
|
constructor(options: HandleOptions) {
|
||||||
this._logger.debug('Handle created with label ' + label);
|
this._contentMesh = options.contentMesh;
|
||||||
|
this._label = options.label ?? 'Handle';
|
||||||
|
this._defaultPosition = options.defaultPosition ?? Vector3.Zero();
|
||||||
|
this._defaultRotation = options.defaultRotation ?? Vector3.Zero();
|
||||||
|
this._autoParentToPlatform = options.autoParentToPlatform ?? true;
|
||||||
|
|
||||||
|
this._logger.debug(`Handle created with label: ${this._label}`);
|
||||||
this.buildHandle();
|
this.buildHandle();
|
||||||
|
|
||||||
|
if (this._autoParentToPlatform) {
|
||||||
|
this.setupPlatformParenting();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get idStored() {
|
/**
|
||||||
return this._isStored;
|
* Returns true if a stored position was found and restored from localStorage
|
||||||
|
*/
|
||||||
|
public get hasStoredPosition(): boolean {
|
||||||
|
return this._hasStoredPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public staort() {
|
/**
|
||||||
|
* Automatically parents the handle to the platform mesh when it becomes available
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private setupPlatformParenting(): void {
|
||||||
|
const scene: Scene = this._contentMesh.getScene();
|
||||||
|
const platform = scene.getMeshByID('platform');
|
||||||
|
|
||||||
|
if (platform) {
|
||||||
|
this._logger.debug(`Platform found, parenting handle to platform`);
|
||||||
|
this.transformNode.parent = platform;
|
||||||
|
} else {
|
||||||
|
this._logger.debug(`Platform not found, waiting for platform creation`);
|
||||||
|
// Wait for platform to be created
|
||||||
|
const observer = scene.onNewMeshAddedObservable.add((mesh) => {
|
||||||
|
if (mesh.id === 'platform') {
|
||||||
|
this._logger.debug(`Platform created, parenting handle to platform`);
|
||||||
|
this.transformNode.parent = mesh;
|
||||||
|
scene.onNewMeshAddedObservable.remove(observer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildHandle() {
|
private buildHandle(): void {
|
||||||
const scene: Scene = this._menuItem.getScene();
|
const scene: Scene = this._contentMesh.getScene();
|
||||||
|
|
||||||
|
const handle = MeshBuilder.CreatePlane('handle-' + this._contentMesh.id, {width: .4, height: .4 / 8}, scene);
|
||||||
const handle = MeshBuilder.CreatePlane('handle-' + this._menuItem.id, {width: .4, height: .4 / 8}, scene);
|
|
||||||
//button.transform.scaling.set(.1,.1,.1);
|
|
||||||
const texture = this.drawText(this._label, Color3.White(), Color3.Black());
|
const texture = this.drawText(this._label, Color3.White(), Color3.Black());
|
||||||
const material = new StandardMaterial('handleMaterial', scene);
|
const material = new StandardMaterial('handleMaterial', scene);
|
||||||
material.metadata = { isUI: true }; // Mark as UI to prevent rendering mode modifications
|
material.metadata = { isUI: true }; // Mark as UI to prevent rendering mode modifications
|
||||||
@ -50,38 +118,77 @@ export class Handle {
|
|||||||
material.opacityTexture = texture;
|
material.opacityTexture = texture;
|
||||||
material.disableLighting = true;
|
material.disableLighting = true;
|
||||||
handle.material = material;
|
handle.material = material;
|
||||||
//handle.rotate(Vector3.Up(), Math.PI);
|
|
||||||
handle.id = 'handle-' + this._menuItem.id;
|
handle.id = 'handle-' + this._contentMesh.id;
|
||||||
if (this._menuItem) {
|
handle.metadata = {handle: true};
|
||||||
this._menuItem.setParent(handle);
|
|
||||||
|
// Parent content mesh to handle
|
||||||
|
if (this._contentMesh) {
|
||||||
|
this._contentMesh.setParent(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stored = localStorage.getItem(handle.id);
|
// Attempt to restore position/rotation from localStorage
|
||||||
if (stored) {
|
this.restorePosition(handle);
|
||||||
this._logger.debug('Stored location found for ' + handle.id);
|
|
||||||
try {
|
|
||||||
const locationdata = JSON.parse(stored);
|
|
||||||
this._logger.debug('Stored location data found ', locationdata);
|
|
||||||
|
|
||||||
handle.position = new Vector3(locationdata.position.x, locationdata.position.y, locationdata.position.z);
|
this.transformNode = handle;
|
||||||
handle.rotation = new Vector3(locationdata.rotation.x, locationdata.rotation.y, locationdata.rotation.z);
|
}
|
||||||
this._isStored = true;
|
|
||||||
|
/**
|
||||||
|
* Restores position and rotation from localStorage, or applies defaults
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private restorePosition(handle: TransformNode): void {
|
||||||
|
const storageKey = handle.id;
|
||||||
|
const stored = localStorage.getItem(storageKey);
|
||||||
|
|
||||||
|
if (stored) {
|
||||||
|
this._logger.debug(`Stored location found for ${storageKey}`);
|
||||||
|
try {
|
||||||
|
const locationData = JSON.parse(stored);
|
||||||
|
this._logger.debug('Stored location data:', locationData);
|
||||||
|
|
||||||
|
if (locationData.position && locationData.rotation) {
|
||||||
|
handle.position = new Vector3(
|
||||||
|
locationData.position.x,
|
||||||
|
locationData.position.y,
|
||||||
|
locationData.position.z
|
||||||
|
);
|
||||||
|
handle.rotation = new Vector3(
|
||||||
|
locationData.rotation.x,
|
||||||
|
locationData.rotation.y,
|
||||||
|
locationData.rotation.z
|
||||||
|
);
|
||||||
|
this._hasStoredPosition = true;
|
||||||
|
this._logger.debug(`Position restored from storage for ${storageKey}`);
|
||||||
|
} else {
|
||||||
|
this._logger.warn(`Invalid stored data format for ${storageKey}, using defaults`);
|
||||||
|
this.applyDefaultPosition(handle);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._logger.error(e);
|
this._logger.error(`Error parsing stored location for ${storageKey}:`, e);
|
||||||
handle.position = Vector3.Zero();
|
this.applyDefaultPosition(handle);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._logger.debug('No stored location found for ' + handle.id + ', using defaults');
|
this._logger.debug(`No stored location found for ${storageKey}, using defaults`);
|
||||||
handle.position = this._offset;
|
this.applyDefaultPosition(handle);
|
||||||
handle.rotation = this._rotation;
|
|
||||||
}
|
}
|
||||||
handle.metadata = {handle: true};
|
|
||||||
this.transformNode = handle;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the default position and rotation to the handle
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private applyDefaultPosition(handle: TransformNode): void {
|
||||||
|
handle.position = this._defaultPosition;
|
||||||
|
handle.rotation = this._defaultRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws text label onto a dynamic texture for the handle
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
private drawText(name: string, foreground: Color3, background: Color3): DynamicTexture {
|
private drawText(name: string, foreground: Color3, background: Color3): DynamicTexture {
|
||||||
const texture = new DynamicTexture('handleTexture', {width: 512, height: 64}, this._menuItem.getScene());
|
const texture = new DynamicTexture(`${name}-handle-texture}`, {width: 512, height: 64}, this._contentMesh.getScene());
|
||||||
const ctx: ICanvasRenderingContext = texture.getContext();
|
const ctx: ICanvasRenderingContext = texture.getContext();
|
||||||
const ctx2d: CanvasRenderingContext2D = (ctx.canvas.getContext('2d') as CanvasRenderingContext2D);
|
const ctx2d: CanvasRenderingContext2D = (ctx.canvas.getContext('2d') as CanvasRenderingContext2D);
|
||||||
const font = `900 24px Arial`;
|
const font = `900 24px Arial`;
|
||||||
@ -102,6 +209,4 @@ export class Handle {
|
|||||||
texture.update();
|
texture.update();
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -36,10 +36,15 @@ export class Toolbox {
|
|||||||
constructor(readyObservable: Observable<boolean>) {
|
constructor(readyObservable: Observable<boolean>) {
|
||||||
this._scene = DefaultScene.Scene;
|
this._scene = DefaultScene.Scene;
|
||||||
this._toolboxBaseNode = new TransformNode("toolbox", this._scene);
|
this._toolboxBaseNode = new TransformNode("toolbox", this._scene);
|
||||||
this._handle = new Handle(this._toolboxBaseNode, 'Toolbox');
|
this._handle = new Handle({
|
||||||
this._toolboxBaseNode.position.y = .2;
|
contentMesh: this._toolboxBaseNode,
|
||||||
|
label: 'Toolbox',
|
||||||
|
defaultPosition: new Vector3(-.5, 1.5, .5),
|
||||||
|
defaultRotation: new Vector3(0, 0, 0)
|
||||||
|
});
|
||||||
|
// Position is now controlled by Handle class
|
||||||
this._toolboxBaseNode.scaling = new Vector3(0.6, 0.6, 0.6);
|
this._toolboxBaseNode.scaling = new Vector3(0.6, 0.6, 0.6);
|
||||||
|
this._toolboxBaseNode.position.y = .2;
|
||||||
// Preload lightmaps for all toolbox colors for better first-render performance
|
// Preload lightmaps for all toolbox colors for better first-render performance
|
||||||
LightmapGenerator.preloadLightmaps(colors, this._scene);
|
LightmapGenerator.preloadLightmaps(colors, this._scene);
|
||||||
|
|
||||||
@ -79,19 +84,6 @@ export class Toolbox {
|
|||||||
private async buildToolbox() {
|
private async buildToolbox() {
|
||||||
this.setupPointerObservable();
|
this.setupPointerObservable();
|
||||||
await this.buildColorPicker();
|
await this.buildColorPicker();
|
||||||
if (this._toolboxBaseNode.parent) {
|
|
||||||
const platform = this._scene.getMeshById("platform");
|
|
||||||
if (platform) {
|
|
||||||
this.assignHandleParentAndStore(platform);
|
|
||||||
} else {
|
|
||||||
const observer = this._scene.onNewMeshAddedObservable.add((mesh: AbstractMesh) => {
|
|
||||||
if (mesh && mesh.id == "platform") {
|
|
||||||
this.assignHandleParentAndStore(mesh);
|
|
||||||
this._scene.onNewMeshAddedObservable.remove(observer);
|
|
||||||
}
|
|
||||||
}, -1, false, this, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupPointerObservable() {
|
private setupPointerObservable() {
|
||||||
@ -143,18 +135,6 @@ export class Toolbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private assignHandleParentAndStore(mesh: TransformNode) {
|
|
||||||
const offset = new Vector3(-.50, 1.6, .38);
|
|
||||||
const rotation = new Vector3(.5, -.6, .18);
|
|
||||||
|
|
||||||
const handle = this._handle;
|
|
||||||
handle.transformNode.parent = mesh;
|
|
||||||
if (!handle.idStored) {
|
|
||||||
handle.transformNode.position = offset;
|
|
||||||
handle.transformNode.rotation = rotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupXRButton() {
|
private setupXRButton() {
|
||||||
if (!this._xr) {
|
if (!this._xr) {
|
||||||
|
|||||||
@ -157,16 +157,18 @@ 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: On left side following VR best practices
|
// Position toolbox: Camera-relative positioning disabled to respect default/saved positions
|
||||||
// Meta guidelines: 45-60 degrees to the side, comfortable arm's reach (~0.4-0.5m)
|
// 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 BEFORE positioning:', {
|
logger.info('Toolbox handleMesh using default/saved position:', {
|
||||||
position: toolbox.handleMesh.position.clone(),
|
position: toolbox.handleMesh.position.clone(),
|
||||||
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
|
// 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
|
// NOTE: User faces -Z direction by design, so negate forward offset
|
||||||
const forwardOffset = horizontalForward.scale(-0.3);
|
const forwardOffset = horizontalForward.scale(-0.3);
|
||||||
@ -193,16 +195,20 @@ 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()
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// Position input text view: Centered in front, slightly below eye level
|
// 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 BEFORE positioning:', {
|
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
|
// NOTE: User faces -Z direction by design, so negate forward offset
|
||||||
const inputWorldPos = cameraWorldPos.add(horizontalForward.scale(-0.5));
|
const inputWorldPos = cameraWorldPos.add(horizontalForward.scale(-0.5));
|
||||||
inputWorldPos.y = cameraWorldPos.y - 0.4; // Below eye level
|
inputWorldPos.y = cameraWorldPos.y - 0.4; // Below eye level
|
||||||
@ -218,5 +224,6 @@ function positionComponentsRelativeToCamera(scene: Scene, diagramManager: Diagra
|
|||||||
position: inputTextView.handleMesh.position.clone(),
|
position: inputTextView.handleMesh.position.clone(),
|
||||||
absolutePosition: inputTextView.handleMesh.getAbsolutePosition().clone()
|
absolutePosition: inputTextView.handleMesh.getAbsolutePosition().clone()
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user