From adc825a18ee684642b46371064de155c74d8546e Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Tue, 3 Jun 2025 14:03:13 -0400 Subject: [PATCH 01/24] Ultra 6.0.0 --- .gitignore | 1 + package.json | 4 ++-- src/vim-web/core-viewers/ultra/rpcClient.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c1aa71f2..5d168fff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules dist docs +docs2 src/pages/*.vim \ No newline at end of file diff --git a/package.json b/package.json index aa8e2839..1d7bccda 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,12 @@ "scripts": { "dev": "vite", "eslint": "eslint --ext .js,.ts,.tsx src --fix", - "documentation": "typedoc --entryPoints src/vim-web/index.ts --entryPointStrategy expand --out docs --excludeProtected --excludeExternals --excludePrivate", + "documentation": "typedoc --entryPoints src/vim-web/index.ts --entryPointStrategy expand --out docs2 --excludeProtected --excludeExternals --excludePrivate", "declarations": "tsc --project tsconfig.json --declaration --emitDeclarationOnly --outdir ./dist/types", "build": "vite build --config vite.config.js && npm run declarations", "package": "npm run build && npm publish", "publish:package": "npm run build && npm publish", - "publish:documentation": "npm run documentation && gh-pages -d docs", + "publ ish:documentation": "npm run documentation && gh-pages -d docs2", "publish": "npm run publish:package && npm run publish:documentation" }, "devDependencies": { diff --git a/src/vim-web/core-viewers/ultra/rpcClient.ts b/src/vim-web/core-viewers/ultra/rpcClient.ts index 24047abc..1c1aa300 100644 --- a/src/vim-web/core-viewers/ultra/rpcClient.ts +++ b/src/vim-web/core-viewers/ultra/rpcClient.ts @@ -46,7 +46,7 @@ return this._socket.state.status === "connected" this._socket = _socket } // RPC Generated Code - readonly API_VERSION = "5.1.0" + readonly API_VERSION = "6.0.0" RPCAddNodeFlags(componentHandle: number, nodes: number[], flags: number): void { const marshal = new Marshal(); From b149cbc0006563929c30ee9b9a362057d82fd836 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Wed, 9 Jul 2025 15:03:29 -0400 Subject: [PATCH 02/24] ultra update --- src/vim-web/core-viewers/shared/vim.ts | 2 +- src/vim-web/core-viewers/ultra/camera.ts | 15 +- src/vim-web/core-viewers/ultra/element3d.ts | 24 +- src/vim-web/core-viewers/ultra/index.ts | 3 +- .../core-viewers/ultra/inputAdapter.ts | 2 +- src/vim-web/core-viewers/ultra/nodeState.ts | 293 +++++++--------- src/vim-web/core-viewers/ultra/raycaster.ts | 2 +- src/vim-web/core-viewers/ultra/renderer.ts | 29 +- src/vim-web/core-viewers/ultra/rpcClient.ts | 255 ++++++-------- src/vim-web/core-viewers/ultra/rpcMarshal.ts | 21 +- .../core-viewers/ultra/rpcSafeClient.ts | 314 ++++++------------ src/vim-web/core-viewers/ultra/rpcTypes.ts | 1 + src/vim-web/core-viewers/ultra/selection.ts | 16 +- src/vim-web/core-viewers/ultra/viewport.ts | 2 +- src/vim-web/core-viewers/ultra/vim.ts | 54 +-- src/vim-web/core-viewers/webgl/loader/mesh.ts | 2 +- .../loader/progressive/insertableSubmesh.ts | 2 +- .../loader/progressive/instancedSubmesh.ts | 2 +- .../core-viewers/webgl/loader/scene.ts | 2 +- src/vim-web/core-viewers/webgl/loader/vim.ts | 4 +- src/vim-web/react-viewers/ultra/isolation.ts | 46 ++- src/vim-web/react-viewers/webgl/isolation.ts | 4 +- 22 files changed, 450 insertions(+), 645 deletions(-) diff --git a/src/vim-web/core-viewers/shared/vim.ts b/src/vim-web/core-viewers/shared/vim.ts index c929ab27..6d122a71 100644 --- a/src/vim-web/core-viewers/shared/vim.ts +++ b/src/vim-web/core-viewers/shared/vim.ts @@ -26,7 +26,7 @@ export interface IVim { * @param instance - The instance index of the of one of the instance included in the element. * @returns The object corresponding to the instance, or undefined if not found. */ - getElementFromInstanceIndex(instance: number): T | undefined + getElement(instance: number): T | undefined /** * Retrieves the element associated with the specified id. diff --git a/src/vim-web/core-viewers/ultra/camera.ts b/src/vim-web/core-viewers/ultra/camera.ts index 212d23a6..e00c8924 100644 --- a/src/vim-web/core-viewers/ultra/camera.ts +++ b/src/vim-web/core-viewers/ultra/camera.ts @@ -84,7 +84,7 @@ export class Camera implements ICamera { * @param segment - Optional segment to save as the camera position */ async save(segment?: Segment){ - this._savedPosition = segment ?? await this._rpc.RPCGetCameraPosition() + this._savedPosition = segment ?? await this._rpc.RPCGetCameraView() } /** @@ -92,7 +92,7 @@ export class Camera implements ICamera { */ restoreSavedPosition(blendTime: number = this._defaultBlendTime){ if(!this._savedPosition) return - this._rpc.RPCSetCameraPosition(this._savedPosition, blendTime) + this._rpc.RPCSetCameraView(this._savedPosition, blendTime) } /** @@ -102,7 +102,7 @@ export class Camera implements ICamera { restoreLastPosition(blendTime: number = this._defaultBlendTime){ if(this._lastPosition?.isValid()){ console.log('Restoring camera position: ', this._lastPosition) - this._rpc.RPCSetCameraPosition(this._lastPosition, blendTime) + this._rpc.RPCSetCameraView(this._lastPosition, blendTime) } } @@ -120,7 +120,7 @@ export class Camera implements ICamera { } set(position: THREE.Vector3, target: THREE.Vector3, blendTime: number = this._defaultBlendTime){ - this._rpc.RPCSetCameraPosition(new Segment(position, target), blendTime) + this._rpc.RPCSetCameraView(new Segment(position, target), blendTime) } /** @@ -148,7 +148,8 @@ export class Camera implements ICamera { * @param blendTime - Duration of the camera animation in seconds (defaults to 0.5) */ async frameBox(box: THREE.Box3, blendTime: number = this._defaultBlendTime) : Promise { - const segment = await this._rpc.RPCFrameBox(box, blendTime) + + const segment = await this._rpc.RPCFrameAABB(box, blendTime) this._savedPosition = this._savedPosition ?? segment return segment } @@ -165,14 +166,14 @@ export class Camera implements ICamera { if (nodes === 'all') { segment = await this._rpc.RPCFrameVim(vim.handle, blendTime); } else { - segment = await this._rpc.RPCFrameInstances(vim.handle, nodes, blendTime); + segment = await this._rpc.RPCFrameElements(vim.handle, nodes, blendTime); } this._savedPosition = this._savedPosition ?? segment return segment } async frameObject(object: Element3D, blendTime: number = this._defaultBlendTime) : Promise { - const segment = await this._rpc.RPCFrameInstances(object.vim.handle, [object.instance], blendTime) + const segment = await this._rpc.RPCFrameElements(object.vim.handle, [object.element], blendTime) this._savedPosition = this._savedPosition ?? segment return segment } diff --git a/src/vim-web/core-viewers/ultra/element3d.ts b/src/vim-web/core-viewers/ultra/element3d.ts index f02f35e2..feaa5a8c 100644 --- a/src/vim-web/core-viewers/ultra/element3d.ts +++ b/src/vim-web/core-viewers/ultra/element3d.ts @@ -1,5 +1,5 @@ import { IVimElement } from "../shared/vim"; -import { NodeState } from "./nodeState"; +import { VisibilityState } from "./nodeState"; import { Box3, RGBA32 } from "./rpcTypes"; import { Vim } from "./vim"; @@ -19,7 +19,7 @@ export class Element3D implements IVimElement { /** * The internal instance index within the `Vim` model. */ - readonly instance: number; + readonly element: number; /** * The unique handle of the parent `Vim` model. @@ -31,31 +31,31 @@ export class Element3D implements IVimElement { /** * Creates a new `Element3D` instance. * @param vim - The parent `Vim` model. - * @param instance - The internal instance index. + * @param element - The internal instance index. */ - constructor(vim: Vim, instance: number) { + constructor(vim: Vim, element: number) { this.vim = vim; - this.instance = instance; + this.element = element; } /** * Gets or sets the display state of the element (e.g., visible, hidden). */ - get state(): NodeState { - return this.vim.nodeState.getNodeState(this.instance); + get state(): VisibilityState { + return this.vim.nodeState.getElementState(this.element); } - set state(state: NodeState) { - this.vim.nodeState.setNodeState(this.instance, state); + set state(state: VisibilityState) { + this.vim.nodeState.setElementState(this.element, state); } /** * Gets or sets the color override of the element. */ get color(): RGBA32 | undefined { - return this.vim.getColor(this.instance); + return this.vim.getColor(this.element); } set color(color: RGBA32 | undefined) { - this.vim.setColor([this.instance], color); + this.vim.setColor([this.element], color); } /** @@ -64,6 +64,6 @@ export class Element3D implements IVimElement { * @returns A promise resolving to the element's bounding box. */ async getBoundingBox(): Promise { - return this.vim.getBoundingBoxNodes([this.instance]); + return this.vim.getBoundingBoxNodes([this.element]); } } diff --git a/src/vim-web/core-viewers/ultra/index.ts b/src/vim-web/core-viewers/ultra/index.ts index 538cdfad..0a3b0c1e 100644 --- a/src/vim-web/core-viewers/ultra/index.ts +++ b/src/vim-web/core-viewers/ultra/index.ts @@ -9,8 +9,9 @@ export {RGB, RGBA, RGBA32, Segment, type SectionBoxState, type HitCheckResult, t // We don't want to export RPCClient export {materialHandles, MaterialHandles, type MaterialHandle, } from './rpcClient' -export {NodeState} from './nodeState'; +export {VisibilityState as NodeState} from './nodeState'; export {InputMode, VimLoadingStatus} from './rpcSafeClient'; +export {VisibilityState} from './nodeState'; //Runtime values for enum // Type exports export type * from './camera'; diff --git a/src/vim-web/core-viewers/ultra/inputAdapter.ts b/src/vim-web/core-viewers/ultra/inputAdapter.ts index 5112f0fb..ed86a156 100644 --- a/src/vim-web/core-viewers/ultra/inputAdapter.ts +++ b/src/vim-web/core-viewers/ultra/inputAdapter.ts @@ -39,7 +39,7 @@ export function ultraInputAdapter(viewer: Viewer) { function createAdapter(viewer: Viewer): IInputAdapter { return { init: () => { - viewer.rpc.RPCSetMoveSpeed(10); + viewer.rpc.RPCSetCameraSpeed(10); }, orbitCamera: (value: THREE.Vector2) => { // handled server side diff --git a/src/vim-web/core-viewers/ultra/nodeState.ts b/src/vim-web/core-viewers/ultra/nodeState.ts index e92faee8..8e8583a5 100644 --- a/src/vim-web/core-viewers/ultra/nodeState.ts +++ b/src/vim-web/core-viewers/ultra/nodeState.ts @@ -1,14 +1,16 @@ import { RpcSafeClient } from './rpcSafeClient'; /** - * Represents the possible states a node can have in the UltraVim system. + * Represents the possible states an element can have in the UltraVim system. */ -// TODO: Rename without Node -export enum NodeState { - VISIBLE = 'visible', - HIDDEN = 'hidden', - GHOSTED = 'ghosted', - HIGHLIGHTED = 'highlighted' +export enum VisibilityState { + //TODO: Make this better support the fact that Higlight can be combined with other states + VISIBLE = 0, + HIDDEN = 1, + GHOSTED = 2, + HIGHLIGHTED = 16, + HIDDEN_HIGHLIGHTED = 17, + GHOSTED_HIGHLIGHTED = 18 } /** @@ -16,6 +18,7 @@ export enum NodeState { * It batches updates to optimize performance and handles the communication with the remote system. */ export class StateSynchronizer { + //TODO: Take advantage of the new rpcs that can take multiple states at once private _tracker: StateTracker; private _rpc: RpcSafeClient; @@ -31,14 +34,14 @@ export class StateSynchronizer { * @param getHandle - Function that returns the current handle identifier * @param isConnected - Function that returns whether the connection to the remote system is active * @param onUpdate - Callback function invoked when updates are sent to the remote system - * @param defaultState - The default state for nodes when not explicitly set (defaults to VISIBLE) + * @param defaultState - The default state for elements when not explicitly set (defaults to VISIBLE) */ constructor( rpc: RpcSafeClient, getHandle: () => number, isConnected: () => boolean, onUpdate: () => void, - defaultState: NodeState = NodeState.VISIBLE + defaultState: VisibilityState = VisibilityState.VISIBLE ) { this._tracker = new StateTracker(defaultState); this._rpc = rpc; @@ -50,75 +53,75 @@ export class StateSynchronizer { // --- Getters --- /** - * Checks if all nodes are in the specified state(s). + * Checks if all elements are in the specified state(s). * * @param state - A single state or array of states to check against - * @returns True if all nodes are in the specified state(s), false otherwise + * @returns True if all elements are in the specified state(s), false otherwise */ - areAllInState(state: NodeState | NodeState[]): boolean { + areAllInState(state: VisibilityState | VisibilityState[]): boolean { return this._tracker.areAll(state); } /** - * Gets the current state of a specific node. + * Gets the current state of a specific element. * - * @param node - The node identifier - * @returns The current state of the node + * @param elementIndex - The element index + * @returns The current state of the element */ - getNodeState(node: number): NodeState { - return this._tracker.getState(node); + getElementState(elementIndex: number): VisibilityState { + return this._tracker.getState(elementIndex); } /** - * Gets all nodes that are currently in the specified state. + * Gets all elements that are currently in the specified state. * * @param state - The state to query - * @returns Either 'all' if all nodes are in this state, or an array of node IDs + * @returns Either 'all' if all elements are in this state, or an array of element indices */ - getNodesInState(state: NodeState): number[] | 'all' { + getElementsInState(state: VisibilityState): number[] | 'all' { return this._tracker.getAll(state); } /** - * Gets the default state used for nodes without explicit state settings. + * Gets the default state used for elements without explicit state settings. * * @returns The current default state */ - getDefaultState(): NodeState { + getDefaultState(): VisibilityState { return this._tracker.getDefault(); } // --- Setters --- /** - * Sets the state of a specific node. + * Sets the state of a specific elements. * - * @param nodeId - The identifier of the node + * @param elementIndex - The element index to update * @param state - The new state to apply */ - setNodeState(nodeId: number, state: NodeState): void { - this._tracker.setState(nodeId, state); + setElementState(elementIndex: number, state: VisibilityState): void { + this._tracker.setState(elementIndex, state); this.scheduleUpdate(); } /** - * Sets the state of all nodes to the specified value. + * Sets the state of all elements to the specified value. * - * @param state - The state to apply to all nodes - * @param clear - If true, clears all node-specific overrides + * @param state - The state to apply to all elements + * @param clear - If true, clears all elements-specific overrides */ - setAllNodesState(state: NodeState): void { + setAllState(state: VisibilityState): void { this._tracker.setAll(state); this.scheduleUpdate(); } /** - * Replaces all nodes in one state (or states) with another state. + * Replaces all elements in one state (or states) with another state. * * @param fromState - The state(s) to replace * @param toState - The new state to apply */ - replaceState(fromState: NodeState | NodeState[], toState: NodeState): void { + replaceState(fromState: VisibilityState | VisibilityState[], toState: VisibilityState): void { this._tracker.replace(fromState, toState); this.scheduleUpdate(); } @@ -155,109 +158,54 @@ export class StateSynchronizer { */ private remoteUpdate(): void { // Get the updates then reset right away to let new updates be set - const [defaultUpdate, nodeUpdates] = this._tracker.getUpdates(); + const [defaultUpdate, elementUpdates] = this._tracker.getUpdates(); this._tracker.reset(); // First handle any default state changes - if (defaultUpdate) { - this.callRPCForStateAll(defaultUpdate); + if (defaultUpdate !== undefined) { + this._rpc.RPCSetStateVim(this._getHandle(), defaultUpdate); } - // Then handle individual node updates - for (const [state, nodes] of nodeUpdates.entries()) { - if (nodes.length === 0) continue; - this.callRPCForStateNodes(state, nodes); + // Then handle individual element updates + for (const [state, elements] of elementUpdates.entries()) { + if (elements.length === 0) continue; + this._rpc.RPCSetStateElements(this._getHandle(), elements, state); } // Notify that updates have been sent this._onUpdate(); } - - /** - * Calls the appropriate RPC method to update the state of all nodes. - * - * @param state - The state to apply to all nodes - * @private - */ - private callRPCForStateAll(state: NodeState): void { - if (!this._isConnected()) { - return; - } - switch (state) { - case NodeState.VISIBLE: - this._rpc.RPCShowAll(this._getHandle()); - break; - case NodeState.HIDDEN: - this._rpc.RPCHideAll(this._getHandle()); - break; - case NodeState.GHOSTED: - this._rpc.RPCGhostAll(this._getHandle()); - break; - case NodeState.HIGHLIGHTED: - this._rpc.RPCHighlightAll(this._getHandle()); - break; - } - } - - /** - * Calls the appropriate RPC method to update the state of specific nodes. - * - * @param state - The state to apply - * @param nodes - Array of node IDs to update - * @private - */ - private callRPCForStateNodes(state: NodeState, nodes: number[]): void { - if (!this._isConnected()) { - return; - } - - switch (state) { - case NodeState.VISIBLE: - this._rpc.RPCShow(this._getHandle(), nodes); - break; - case NodeState.HIDDEN: - this._rpc.RPCHide(this._getHandle(), nodes); - break; - case NodeState.GHOSTED: - this._rpc.RPCGhost(this._getHandle(), nodes); - break; - case NodeState.HIGHLIGHTED: - this._rpc.RPCHighlight(this._getHandle(), nodes); - break; - } - } } /** - * A tracker for node state overrides. - * It stores per-node state overrides against a default state. - * When a node's state is set equal to the default, its override is removed + * A tracker for element state overrides. + * It stores per-element state overrides against a default state. + * When a element's state is set equal to the default, its override is removed * but the change is still tracked for remote updates. * * @private Not exported, used internally by StateSynchronizer */ class StateTracker { - private _state = new Map(); + private _state = new Map(); private _updates = new Set(); - private _default: NodeState; + private _default: VisibilityState; private _updatedDefault: boolean = false; /** * Creates a new StateTracker instance. * - * @param defaultState - The default state for nodes when not explicitly set + * @param defaultState - The default state for elements when not explicitly set */ - constructor(defaultState: NodeState = NodeState.VISIBLE) { + constructor(defaultState: VisibilityState = VisibilityState.VISIBLE) { this._default = defaultState; } /** - * Sets the default state for all nodes and optionally clears node-specific overrides. + * Sets the default state for all elements and optionally clears element-specific overrides. * * @param state - The new default state - * @param clearNodes - If true, clears all node-specific overrides */ - setAll(state: NodeState): void { + setAll(state: VisibilityState): void { this._default = state; this._updatedDefault = true; this._state.clear(); @@ -272,39 +220,39 @@ class StateTracker { this._updates.clear(); const toRemove = new Set(); - for (const [nodeId, state] of this._state.entries()) { + for (const [elementIndex, state] of this._state.entries()) { if (state === this._default) { - // If a node's explicit state matches the default, we can remove the override - toRemove.add(nodeId); + // If a element's explicit state matches the default, we can remove the override + toRemove.add(elementIndex); } else { - // Otherwise, mark this node as needing an update - this._updates.add(nodeId); + // Otherwise, mark this element as needing an update + this._updates.add(elementIndex); } } // Clean up redundant overrides - toRemove.forEach(nodeId => this._state.delete(nodeId)); + toRemove.forEach(elementIndex => this._state.delete(elementIndex)); } /** - * Sets the state of a specific node. + * Sets the state of a specific element. * - * @param nodeId - The node identifier + * @param elementIndex - The element index to update * @param state - The new state to apply */ - setState(nodeId: number, state: NodeState): void { + setState(elementIndex: number, state: VisibilityState): void { if (this._default === state) { // If the new state matches the default, remove the override - this._state.delete(nodeId); + this._state.delete(elementIndex); // Only mark for update if we haven't already changed the default if (!this._updatedDefault) { - this._updates.add(nodeId); + this._updates.add(elementIndex); } } else { // Otherwise set the override and mark for update - this._state.set(nodeId, state); - this._updates.add(nodeId); + this._state.set(elementIndex, state); + this._updates.add(elementIndex); } } @@ -313,25 +261,25 @@ class StateTracker { * * @returns The current default state */ - getDefault(): NodeState { + getDefault(): VisibilityState { return this._default; } /** - * Returns whether every node (override or not) is in the given state(s). + * Returns whether every element (override or not) is in the given state(s). * * @param state - A single state or array of states to check against - * @returns True if all nodes are in the specified state(s), false otherwise + * @returns True if all element are in the specified state(s), false otherwise */ - areAll(state: NodeState | NodeState[]): boolean { + areAll(state: VisibilityState | VisibilityState[]): boolean { // First check if the default state matches if (!matchesState(this._default, state)) { return false; } // Then check all overrides - for (const nodeState of this._state.values()) { - if (!matchesState(nodeState, state)) { + for (const currentState of this._state.values()) { + if (!matchesState(currentState, state)) { return false; } } @@ -340,57 +288,58 @@ class StateTracker { } /** - * Returns a node's effective state. + * Returns an element effective state. * - * @param node - The node identifier - * @returns The current state of the node (override or default) + * @param elementIndex - The element index + * @returns The current state of the element (override or default) */ - getState(node: number): NodeState { - return this._state.get(node) ?? this._default; + getState(elementIndex: number): VisibilityState { + return this._state.get(elementIndex) ?? this._default; } /** - * Returns either 'all' if every node is in the given state, or an array - * of node IDs (from the overrides) whose state equals the provided state. + * Returns either 'all' if every element is in the given state, or an array + * of element index (from the overrides) whose state equals the provided state. * * @param state - The state to query - * @returns Either 'all' if all nodes are in this state, or an array of node IDs + * @returns Either 'all' if all elements are in this state, or an array of element indices */ - getAll(state: NodeState): number[] | 'all' { + getAll(state: VisibilityState): number[] | 'all' { if (this.areAll(state)) return 'all'; - const nodes: number[] = []; - for (const [nodeId, nodeState] of this._state.entries()) { - if (nodeState === state) { - nodes.push(nodeId); + const elements: number[] = []; + for (const [elementIndex, currentState] of this._state.entries()) { + if (matchesState(currentState, state)) { + elements.push(elementIndex); } } - return nodes; + return elements; } /** - * Returns a mapping from state to an array of updated node IDs. - * - * @returns A tuple with the updated default state (if any) and a map of states to node IDs + * Returns a mapping from state to an array of updated elementIndices. + * @returns A tuple with the updated default state (if any) and a map of states to element Indices */ - getUpdates(): [NodeState | undefined, Map] { + getUpdates(): [VisibilityState | undefined, Map] { // Initialize the map with all possible states - const nodesByState = new Map(); - Object.values(NodeState).forEach(state => { - nodesByState.set(state, []); + const elementsByState = new Map(); + Object.values(VisibilityState) + .filter((v): v is VisibilityState => typeof v === "number") + .forEach(state => { + elementsByState.set(state, []); }); - // Populate the map with nodes that need updates - for (const nodeId of this._updates) { - const state = this._state.get(nodeId) ?? this._default; - const nodesArray = nodesByState.get(state); - if (nodesArray) { - nodesArray.push(nodeId); + // Populate the map with elements that need updates + for (const elementIndex of this._updates) { + const state = this._state.get(elementIndex) ?? this._default; + const elementArray = elementsByState.get(state); + if (elementArray) { + elementArray.push(elementIndex); } } - return [this._updatedDefault ? this._default : undefined, nodesByState]; + return [this._updatedDefault ? this._default : undefined, elementsByState]; } /** @@ -403,7 +352,7 @@ class StateTracker { } /** - * Resets the update tracking, clearing the list of nodes that need updates. + * Resets the update tracking, clearing the list of elements that need updates. */ reset(): void { this._updates.clear(); @@ -411,22 +360,22 @@ class StateTracker { } /** - * Returns an iterator over all node overrides. + * Returns an iterator over all elements overrides. * - * @returns An iterator of [nodeId, state] pairs + * @returns An iterator of [elementIndex, state] pairs */ - entries(): IterableIterator<[number, NodeState]> { + entries(): IterableIterator<[number, VisibilityState]> { return this._state.entries(); } /** - * Replaces all nodes that match the provided state(s) with a new state. - * If all nodes are in the given state(s), the default is updated. + * Replaces all elements that match the provided state(s) with a new state. + * If all elements are in the given state(s), the default is updated. * * @param fromState - The state(s) to replace * @param toState - The new state to apply */ - replace(fromState: NodeState | NodeState[], toState: NodeState): void { + replace(fromState: VisibilityState | VisibilityState[], toState: VisibilityState): void { this.purge(); // Clean up redundant overrides // If the default state matches what we're replacing, update it @@ -437,11 +386,11 @@ class StateTracker { return } - // Update all matching node overrides - for (const [nodeId, state] of this._state.entries()) { + // Update all matching elements overrides + for (const [elementIndex, state] of this._state.entries()) { if (matchesState(state, fromState)) { - this._state.set(nodeId, toState); - this._updates.add(nodeId); + this._state.set(elementIndex, toState); + this._updates.add(elementIndex); } } } @@ -450,28 +399,28 @@ class StateTracker { private purge(){ const toRemove : number[] = []; - for (const [nodeId, state] of this._state.entries()) { + for (const [elementIndex, state] of this._state.entries()) { if (state === this._default) { - toRemove.push(nodeId); + toRemove.push(elementIndex); } } - toRemove.forEach(nodeId => this._state.delete(nodeId)); + toRemove.forEach(elementIndex => this._state.delete(elementIndex)); } } /** - * Helper function that checks if a node state matches one or more target states. + * Helper function that checks if an element state matches one or more target states. * - * @param nodeState - The current state of a node - * @param state - A single state or array of states to check against - * @returns True if the node state matches any of the target states + * @param state - The current state of an element + * @param targetState - A single state or array of states to check against + * @returns True if the state matches any of the target states */ -function matchesState(nodeState: NodeState, state: NodeState | NodeState[]): boolean { - if (Array.isArray(state)) { - return state.includes(nodeState); +function matchesState(state: VisibilityState, targetState: VisibilityState | VisibilityState[]): boolean { + if (Array.isArray(targetState)) { + return targetState.includes(state); } - return nodeState === state; + return state === targetState; } \ No newline at end of file diff --git a/src/vim-web/core-viewers/ultra/raycaster.ts b/src/vim-web/core-viewers/ultra/raycaster.ts index babace39..1040d3e1 100644 --- a/src/vim-web/core-viewers/ultra/raycaster.ts +++ b/src/vim-web/core-viewers/ultra/raycaster.ts @@ -71,7 +71,7 @@ export class Raycaster implements IUltraRaycaster { const vim = this._vims.getFromHandle(test.vimHandle); if (!vim) return undefined; - const object = vim.getElementFromInstanceIndex(test.nodeIndex); + const object = vim.getElement(test.elementIndex); if (!object) return undefined; return new UltraRaycastResult( diff --git a/src/vim-web/core-viewers/ultra/renderer.ts b/src/vim-web/core-viewers/ultra/renderer.ts index b29fad2c..60dfba08 100644 --- a/src/vim-web/core-viewers/ultra/renderer.ts +++ b/src/vim-web/core-viewers/ultra/renderer.ts @@ -10,8 +10,6 @@ import { ClientStreamError } from "./socketClient"; * Render settings that extend SceneSettings with additional rendering-specific properties */ export type RenderSettings = SceneSettings & { - /** Whether to lock the Image-Based Lighting rotation */ - lockIblRotation: boolean /** Color used for ghost/transparent rendering */ ghostColor: RGBA } @@ -21,7 +19,6 @@ export type RenderSettings = SceneSettings & { */ export const defaultRenderSettings: RenderSettings = { ...defaultSceneSettings, - lockIblRotation: true, ghostColor: new RGBA(14/255, 14/255, 14/255, 64/255) } @@ -31,7 +28,6 @@ export const defaultRenderSettings: RenderSettings = { export interface IRenderer { onSceneUpdated: ISignal ghostColor: RGBA - lockIblRotation: boolean hdrScale: number toneMappingWhitePoint: number hdrBackgroundScale: number @@ -53,7 +49,6 @@ export class Renderer implements IRenderer { private _animationFrame: number | undefined = undefined; private _updateLighting: boolean = false; private _updateGhostColor: boolean = false; - private _updateIblRotation: boolean = false; private readonly _onSceneUpdated = new SignalDispatcher() get onSceneUpdated() { @@ -98,7 +93,6 @@ export class Renderer implements IRenderer { */ onConnect(){ this._rpc.RPCSetGhostColor(this._settings.ghostColor) - this._rpc.RPCLockIblRotation(this._settings.lockIblRotation) } notifySceneUpdated() { @@ -115,14 +109,6 @@ export class Renderer implements IRenderer { return this._settings.ghostColor } - /** - * Gets the IBL rotation lock setting - * @returns Whether IBL rotation is locked - */ - get lockIblRotation(): boolean { - return this._settings.lockIblRotation - } - /** * Gets the tone mapping white point value * @returns Current tone mapping white point @@ -185,17 +171,6 @@ export class Renderer implements IRenderer { this.requestSettingsUpdate() } - /** - * Updates the IBL rotation lock setting - * @param value - Whether to lock IBL rotation - */ - set lockIblRotation(value: boolean){ - if(this._settings.lockIblRotation === value) return - this._settings.lockIblRotation = value - this._updateIblRotation = true - this.requestSettingsUpdate() - } - /** * Sets the tone mapping white point value * @param value - New tone mapping white point value @@ -269,7 +244,7 @@ export class Renderer implements IRenderer { } getBoundingBox(): Promise { - return this._rpc.RPCGetSceneAABB() + return this._rpc.RPCGetAABBForAll() } /** @@ -287,12 +262,10 @@ export class Renderer implements IRenderer { private async applySettings(){ if(this._updateLighting) await this._rpc.RPCSetLighting(this._settings); if(this._updateGhostColor) await this._rpc.RPCSetGhostColor(this._settings.ghostColor); - if(this._updateIblRotation) await this._rpc.RPCLockIblRotation(this._settings.lockIblRotation); // Reset dirty flags this._updateLighting = false; this._updateGhostColor = false; - this._updateIblRotation = false; this._animationFrame = undefined; } diff --git a/src/vim-web/core-viewers/ultra/rpcClient.ts b/src/vim-web/core-viewers/ultra/rpcClient.ts index 1c1aa300..aeb61eec 100644 --- a/src/vim-web/core-viewers/ultra/rpcClient.ts +++ b/src/vim-web/core-viewers/ultra/rpcClient.ts @@ -18,6 +18,20 @@ export class MaterialHandles { static readonly Invisible: MaterialHandle = 8 } +export type VisibilityState = 0 | 1 | 2 | 3 | 16; +export class VisibilityStates { + static readonly Visible: VisibilityState = 0; + static readonly Hidden: VisibilityState = 1; + static readonly Ghosted: VisibilityState = 2; + static readonly Highlighted: VisibilityState = 16; +}; + +export type RenderingMode = 0 | 1; +export class RenderingModes { + static readonly Standard: RenderingMode = 0; + static readonly FlatShaded: RenderingMode = 1; +}; + export const materialHandles : MaterialHandle[] = [ MaterialHandles.Invalid, MaterialHandles.Wireframe, @@ -48,15 +62,6 @@ return this._socket.state.status === "connected" // RPC Generated Code readonly API_VERSION = "6.0.0" - RPCAddNodeFlags(componentHandle: number, nodes: number[], flags: number): void { - const marshal = new Marshal(); - marshal.writeString("RPCAddNodeFlags"); - marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(nodes); - marshal.writeUInt(flags); - this._socket.sendRPC(marshal); - } - RPCClearMaterialOverrides(componentHandle: number): void { const marshal = new Marshal(); marshal.writeString("RPCClearMaterialOverrides"); @@ -64,12 +69,6 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } - RPCClearScene(): void { - const marshal = new Marshal(); - marshal.writeString("RPCClearScene"); - this._socket.sendRPC(marshal); - } - async RPCCreateMaterialInstances(materialHandle: number, smoothness: number, colors: RpcTypes.RGBA32[]): Promise { const marshal = new Marshal(); marshal.writeString("RPCCreateMaterialInstances"); @@ -113,30 +112,30 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } - async RPCFrameAll(blendTime: number): Promise { + async RPCFrameAABB(box: RpcTypes.Box3, blendTime: number): Promise { const marshal = new Marshal(); - marshal.writeString("RPCFrameAll"); + marshal.writeString("RPCFrameAABB"); + marshal.writeBox3(box); marshal.writeFloat(blendTime); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readSegment(); return ret; } - async RPCFrameBox(box: RpcTypes.Box3, blendTime: number): Promise { + async RPCFrameAll(blendTime: number): Promise { const marshal = new Marshal(); - marshal.writeString("RPCFrameBox"); - marshal.writeBox3(box); + marshal.writeString("RPCFrameAll"); marshal.writeFloat(blendTime); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readSegment(); return ret; } - async RPCFrameInstances(componentHandle: number, nodes: number[], blendTime: number): Promise { + async RPCFrameElements(componentHandle: number, elementIndices: number[], blendTime: number): Promise { const marshal = new Marshal(); - marshal.writeString("RPCFrameInstances"); + marshal.writeString("RPCFrameElements"); marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(nodes); + marshal.writeArrayOfUInt(elementIndices); marshal.writeFloat(blendTime); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readSegment(); @@ -153,46 +152,64 @@ return this._socket.state.status === "connected" return ret; } - async RPCGetAPIVersion(): Promise { + async RPCGetAABBForAll(): Promise { const marshal = new Marshal(); - marshal.writeString("RPCGetAPIVersion"); + marshal.writeString("RPCGetAABBForAll"); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); - const ret = returnMarshal.readString(); + const ret = returnMarshal.readBox3(); return ret; } - async RPCGetBoundingBox(componentHandle: number, nodes: number[]): Promise { + async RPCGetAABBForElements(componentHandle: number, elementIndices: number[]): Promise { const marshal = new Marshal(); - marshal.writeString("RPCGetBoundingBox"); + marshal.writeString("RPCGetAABBForElements"); marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(nodes); + marshal.writeArrayOfUInt(elementIndices); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readBox3(); return ret; } - async RPCGetBoundingBoxAll(componentHandle: number): Promise { + async RPCGetAABBForVim(componentHandle: number): Promise { const marshal = new Marshal(); - marshal.writeString("RPCGetBoundingBoxAll"); + marshal.writeString("RPCGetAABBForVim"); marshal.writeUInt(componentHandle); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readBox3(); return ret; } - async RPCGetCameraPosition(): Promise { + async RPCGetAPIVersion(): Promise { const marshal = new Marshal(); - marshal.writeString("RPCGetCameraPosition"); + marshal.writeString("RPCGetAPIVersion"); + const returnMarshal = await this._socket.sendRPCWithReturn(marshal); + const ret = returnMarshal.readString(); + return ret; + } + + async RPCGetCameraView(): Promise { + const marshal = new Marshal(); + marshal.writeString("RPCGetCameraView"); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readSegment(); return ret; } - async RPCGetIblRotation(): Promise { + async RPCGetElementCount(componentHandle: number): Promise { const marshal = new Marshal(); - marshal.writeString("RPCGetIblRotation"); + marshal.writeString("RPCGetElementCount"); + marshal.writeUInt(componentHandle); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); - const ret = returnMarshal.readMatrix44(); + const ret = returnMarshal.readUInt(); + return ret; + } + + async RPCGetElementIds(componentHandle: number): Promise { + const marshal = new Marshal(); + marshal.writeString("RPCGetElementIds"); + marshal.writeUInt(componentHandle); + const returnMarshal = await this._socket.sendRPCWithReturn(marshal); + const ret = returnMarshal.readArrayOfUInt64(); return ret; } @@ -204,11 +221,12 @@ return this._socket.state.status === "connected" return ret; } - async RPCGetSceneAABB(): Promise { + async RPCGetRoomElements(componentHandle: number): Promise { const marshal = new Marshal(); - marshal.writeString("RPCGetSceneAABB"); + marshal.writeString("RPCGetRoomElements"); + marshal.writeUInt(componentHandle); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); - const ret = returnMarshal.readBox3(); + const ret = returnMarshal.readArrayOfUInt(); return ret; } @@ -229,66 +247,6 @@ return this._socket.state.status === "connected" return ret; } - RPCGhost(componentHandle: number, nodes: number[]): void { - const marshal = new Marshal(); - marshal.writeString("RPCGhost"); - marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(nodes); - this._socket.sendRPC(marshal); - } - - RPCGhostAll(componentHandle: number): void { - const marshal = new Marshal(); - marshal.writeString("RPCGhostAll"); - marshal.writeUInt(componentHandle); - this._socket.sendRPC(marshal); - } - - RPCHide(componentHandle: number, nodes: number[]): void { - const marshal = new Marshal(); - marshal.writeString("RPCHide"); - marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(nodes); - this._socket.sendRPC(marshal); - } - - RPCHideAABBs(componentHandle: number, nodes: number[]): void { - const marshal = new Marshal(); - marshal.writeString("RPCHideAABBs"); - marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(nodes); - this._socket.sendRPC(marshal); - } - - RPCHideAll(componentHandle: number): void { - const marshal = new Marshal(); - marshal.writeString("RPCHideAll"); - marshal.writeUInt(componentHandle); - this._socket.sendRPC(marshal); - } - - RPCHideAllAABBs(componentHandle: number): void { - const marshal = new Marshal(); - marshal.writeString("RPCHideAllAABBs"); - marshal.writeUInt(componentHandle); - this._socket.sendRPC(marshal); - } - - RPCHighlight(componentHandle: number, nodes: number[]): void { - const marshal = new Marshal(); - marshal.writeString("RPCHighlight"); - marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(nodes); - this._socket.sendRPC(marshal); - } - - RPCHighlightAll(componentHandle: number): void { - const marshal = new Marshal(); - marshal.writeString("RPCHighlightAll"); - marshal.writeUInt(componentHandle); - this._socket.sendRPC(marshal); - } - RPCKeyEvent(keyCode: number, down: boolean): void { const marshal = new Marshal(); marshal.writeString("RPCKeyEvent"); @@ -316,13 +274,6 @@ return this._socket.state.status === "connected" return ret; } - RPCLockIblRotation(lock: boolean): void { - const marshal = new Marshal(); - marshal.writeString("RPCLockIblRotation"); - marshal.writeBoolean(lock); - this._socket.sendRPC(marshal); - } - RPCMouseButtonEvent(mousePos: RpcTypes.Vector2, mouseButton: number, down: boolean): void { const marshal = new Marshal(); marshal.writeString("RPCMouseButtonEvent"); @@ -362,17 +313,6 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } - RPCMoveCameraTo(usePosition: boolean, useTarget: boolean, position: RpcTypes.Vector3, target: RpcTypes.Vector3, blendTime: number): void { - const marshal = new Marshal(); - marshal.writeString("RPCMoveCameraTo"); - marshal.writeBoolean(usePosition); - marshal.writeBoolean(useTarget); - marshal.writeVector3(position); - marshal.writeVector3(target); - marshal.writeFloat(blendTime); - this._socket.sendRPC(marshal); - } - RPCPauseRendering(pause: boolean): void { const marshal = new Marshal(); marshal.writeString("RPCPauseRendering"); @@ -389,18 +329,9 @@ return this._socket.state.status === "connected" return ret; } - RPCRemoveNodeFlags(componentHandle: number, nodes: number[], flags: number): void { + RPCSetCameraAspectRatio(width: number, height: number): void { const marshal = new Marshal(); - marshal.writeString("RPCRemoveNodeFlags"); - marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(nodes); - marshal.writeUInt(flags); - this._socket.sendRPC(marshal); - } - - RPCSetAspectRatio(width: number, height: number): void { - const marshal = new Marshal(); - marshal.writeString("RPCSetAspectRatio"); + marshal.writeString("RPCSetCameraAspectRatio"); marshal.writeUInt(width); marshal.writeUInt(height); this._socket.sendRPC(marshal); @@ -413,32 +344,41 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } - RPCSetCameraPosition(state: RpcTypes.Segment, blendTime: number): void { + RPCSetCameraPosition(position: RpcTypes.Vector3, blendTime: number): void { const marshal = new Marshal(); marshal.writeString("RPCSetCameraPosition"); - marshal.writeSegment(state); + marshal.writeVector3(position); marshal.writeFloat(blendTime); this._socket.sendRPC(marshal); } - RPCSetGhostColor(ghostColor: RpcTypes.RGBA): void { + RPCSetCameraSpeed(speed: number): void { const marshal = new Marshal(); - marshal.writeString("RPCSetGhostColor"); - marshal.writeRGBA(ghostColor); + marshal.writeString("RPCSetCameraSpeed"); + marshal.writeFloat(speed); this._socket.sendRPC(marshal); } - RPCSetGhostColor2(ghostColor: RpcTypes.RGBA): void { + RPCSetCameraTarget(target: RpcTypes.Vector3, blendTime: number): void { const marshal = new Marshal(); - marshal.writeString("RPCSetGhostColor2"); - marshal.writeRGBA(ghostColor); + marshal.writeString("RPCSetCameraTarget"); + marshal.writeVector3(target); + marshal.writeFloat(blendTime); + this._socket.sendRPC(marshal); + } + + RPCSetCameraView(state: RpcTypes.Segment, blendTime: number): void { + const marshal = new Marshal(); + marshal.writeString("RPCSetCameraView"); + marshal.writeSegment(state); + marshal.writeFloat(blendTime); this._socket.sendRPC(marshal); } - RPCSetIblRotation(transform: RpcTypes.Matrix44): void { + RPCSetGhostColor(ghostColor: RpcTypes.RGBA): void { const marshal = new Marshal(); - marshal.writeString("RPCSetIblRotation"); - marshal.writeMatrix44(transform); + marshal.writeString("RPCSetGhostColor"); + marshal.writeRGBA(ghostColor); this._socket.sendRPC(marshal); } @@ -454,22 +394,15 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } - RPCSetMaterialOverrides(componentHandle: number, nodes: number[], materialInstanceHandles: number[]): void { + RPCSetMaterialOverrides(componentHandle: number, elementIndices: number[], materialInstanceHandles: number[]): void { const marshal = new Marshal(); marshal.writeString("RPCSetMaterialOverrides"); marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(nodes); + marshal.writeArrayOfUInt(elementIndices); marshal.writeArrayOfUInt(materialInstanceHandles); this._socket.sendRPC(marshal); } - RPCSetMoveSpeed(speed: number): void { - const marshal = new Marshal(); - marshal.writeString("RPCSetMoveSpeed"); - marshal.writeFloat(speed); - this._socket.sendRPC(marshal); - } - RPCSetSectionBox(state: RpcTypes.SectionBoxState): void { const marshal = new Marshal(); marshal.writeString("RPCSetSectionBox"); @@ -477,27 +410,29 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } - RPCShow(componentHandle: number, nodes: number[]): void { + RPCSetStateVim(componentHandle: number, state: number): void { const marshal = new Marshal(); - marshal.writeString("RPCShow"); + marshal.writeString("RPCSetStateVim"); marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(nodes); + marshal.writeUInt(state); this._socket.sendRPC(marshal); } - RPCShowAABBs(componentHandle: number, nodes: number[], colors: RpcTypes.RGBA32[]): void { + RPCSetStateElements(componentHandle: number, elementIndices: number[], state: number): void { const marshal = new Marshal(); - marshal.writeString("RPCShowAABBs"); + marshal.writeString("RPCSetStateElements"); marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(nodes); - marshal.writeArrayOfRGBA32(colors); + marshal.writeArrayOfUInt(elementIndices); + marshal.writeUInt(state); this._socket.sendRPC(marshal); } - RPCShowAll(componentHandle: number): void { + RPCSetStatesElements(componentHandle: number, elementIndices: number[], states: number[]): void { const marshal = new Marshal(); - marshal.writeString("RPCShowAll"); + marshal.writeString("RPCSetStatesElements"); marshal.writeUInt(componentHandle); + marshal.writeArrayOfUInt(elementIndices); + marshal.writeArrayOfUInt(states); this._socket.sendRPC(marshal); } @@ -521,6 +456,12 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } + RPCUnloadAll(): void { + const marshal = new Marshal(); + marshal.writeString("RPCUnloadAll"); + this._socket.sendRPC(marshal); + } + RPCUnloadVim(componentHandle: number): void { const marshal = new Marshal(); marshal.writeString("RPCUnloadVim"); diff --git a/src/vim-web/core-viewers/ultra/rpcMarshal.ts b/src/vim-web/core-viewers/ultra/rpcMarshal.ts index c301a835..4d1b9e9b 100644 --- a/src/vim-web/core-viewers/ultra/rpcMarshal.ts +++ b/src/vim-web/core-viewers/ultra/rpcMarshal.ts @@ -103,9 +103,10 @@ export class Marshal { // -------------------- HitCheckResult ------------------- public writeHitCheckResult(data: RpcTypes.HitCheckResult): void { - this.ensureCapacity(4 + 4 + 4 * 3 + 4 * 3) + this.ensureCapacity(4 + 4 +4 + 4 * 3 + 4 * 3) this.writeUInt(data.vimHandle) this.writeUInt(data.nodeIndex) + this.writeUInt(data.elementIndex) this.writeVector3(data.worldPosition) this.writeVector3(data.worldNormal) } @@ -291,6 +292,12 @@ export class ReadMarshal{ return value } + //TODO: Maybe wrong + public readUInt64(): bigint { + const low = this.readUInt(); // lower 32 bits + const high = this.readUInt(); // upper 32 bits + return (BigInt(high) << 32n) | BigInt(low); + } public readFloat(): number { const value = this._dataView.getFloat32(this._offset, true) @@ -315,6 +322,7 @@ export class ReadMarshal{ public readHitCheckResult(): RpcTypes.HitCheckResult { const vimHandle = this.readUInt() const nodeIndex = this.readUInt() + const mElementIndex = this.readUInt() const worldPosition = this.readVector3() const worldNormal = this.readVector3() @@ -322,6 +330,7 @@ export class ReadMarshal{ return { vimHandle, nodeIndex, + elementIndex: mElementIndex, worldPosition, worldNormal } @@ -407,7 +416,15 @@ export class ReadMarshal{ return this.readArray(() => this.readUInt()) } - + public readArrayOfUInt64(): bigint[] { + const length = this.readUInt(); + const array: bigint[] = []; + for (let i = 0; i < length; i++) { + array.push(this.readUInt64()); + } + return array; + } + public readArrayOfFloat(): number[] { return this.readArray(() => this.readFloat()) } diff --git a/src/vim-web/core-viewers/ultra/rpcSafeClient.ts b/src/vim-web/core-viewers/ultra/rpcSafeClient.ts index 8253d35d..ec170825 100644 --- a/src/vim-web/core-viewers/ultra/rpcSafeClient.ts +++ b/src/vim-web/core-viewers/ultra/rpcSafeClient.ts @@ -3,6 +3,7 @@ import { MaterialHandle, RpcClient } from "./rpcClient" import { Validation } from "../../utils"; import { batchArray, batchArrays } from "../../utils/array" import { INVALID_HANDLE } from "./viewer" +import { VisibilityState } from "./nodeState"; const defaultBatchSize = 10000 @@ -111,17 +112,8 @@ export class RpcSafeClient { Validation.clampRGBA01(s.backgroundColor) ) } + - RPCLockIblRotation(lock: boolean): void { - this.rpc.RPCLockIblRotation(lock) - } - - RPCGetSceneAABB(): Promise { - return this.safeCall( - () => this.rpc.RPCGetSceneAABB(), - undefined - ) - } /******************************************************************************* * NODE VISIBILITY METHODS @@ -130,132 +122,45 @@ export class RpcSafeClient { ******************************************************************************/ /** - * Hides all nodes in a component, making the entire component invisible. - * @param componentHandle - The component to hide entirely - * @throws {Error} If the component handle is invalid - */ - RPCHideAll(componentHandle: number): void { - if (!Validation.isComponentHandle(componentHandle)) return - this.rpc.RPCHideAll(componentHandle) - } - - /** - * Shows all nodes in a component, making the entire component visible. - * @param componentHandle - The component to show entirely - * @throws {Error} If the component handle is invalid - */ - RPCShowAll(componentHandle: number): void { - // Validation - if (!Validation.isComponentHandle(componentHandle)) return - - // Run - this.rpc.RPCShowAll(componentHandle) - } - - /** - * Makes all nodes in a component semi-transparent (ghosted). - * @param componentHandle - The component to ghost entirely - * @throws {Error} If the component handle is invalid - */ - RPCGhostAll(componentHandle: number): void { - // Validation - if (!Validation.isComponentHandle(componentHandle)) return - - // Run - this.rpc.RPCGhostAll(componentHandle) - } - - /** - * Highlights all nodes in a component. - * @param componentHandle - The component to highlight entirely - * @throws {Error} If the component handle is invalid - */ - RPCHighlightAll(componentHandle: number): void { - // Validation - if (!Validation.isComponentHandle(componentHandle)) return - - // Run - this.rpc.RPCHighlightAll(componentHandle) - } - - /** - * Hides specified nodes in a component, making them invisible. - * Large node arrays are automatically processed in batches. - * @param componentHandle - The component containing the nodes - * @param nodes - Array of node indices to hide - * @throws {Error} If the component handle is invalid or nodes array is invalid - */ - RPCHide(componentHandle: number, nodes: number[]): void { - - if (!Validation.isComponentHandle(componentHandle)) return - if (!Validation.areComponentHandles(nodes)) return - - const batches = batchArray(nodes, this.batchSize) - for (const batch of batches) { - this.rpc.RPCHide(componentHandle, batch) - } - } - - /** - * Shows specified nodes in a component, making them visible. + * Highlights specified nodes in a component. * Large node arrays are automatically processed in batches. * @param componentHandle - The component containing the nodes - * @param nodes - Array of node indices to show + * @param nodes - Array of node indices to highlight * @throws {Error} If the component handle is invalid or nodes array is invalid */ - RPCShow(componentHandle: number, nodes: number[]): void { - if(nodes.length === 0) return + RPCSetStateElements(componentHandle: number, elements: number[], state: VisibilityState): void { + if(elements.length === 0) return // Validation if (!Validation.isComponentHandle(componentHandle)) return - if (!Validation.areComponentHandles(nodes)) return + if (!Validation.areComponentHandles(elements)) return // Run - const batches = batchArray(nodes, this.batchSize) + const batches = batchArray(elements, this.batchSize) for (const batch of batches) { - this.rpc.RPCShow(componentHandle, batch) + this.rpc.RPCSetStateElements(componentHandle, batch, state) } } - /** - * Makes specified nodes semi-transparent (ghosted) in a component. - * Large node arrays are automatically processed in batches. - * @param componentHandle - The component containing the nodes - * @param nodes - Array of node indices to ghost - * @throws {Error} If the component handle is invalid or nodes array is invalid - */ - RPCGhost(componentHandle: number, nodes: number[]): void { - if(nodes.length === 0) return - // Validation + RPCSetStatesElements(componentHandle: number, elements: number[], states: VisibilityState[]): void { if (!Validation.isComponentHandle(componentHandle)) return - if (!Validation.areComponentHandles(nodes)) return + if (!Validation.areComponentHandles(elements)) return + if (!Validation.areSameLength(elements, states)) return - // Run - const batches = batchArray(nodes, this.batchSize) - for (const batch of batches) { - this.rpc.RPCGhost(componentHandle, batch) + + const batches = batchArrays(elements, states, this.batchSize) + for (const [batchedElements, batchedStates] of batches) { + this.rpc.RPCSetStatesElements(componentHandle, batchedElements, batchedStates) } } - /** - * Highlights specified nodes in a component. - * Large node arrays are automatically processed in batches. - * @param componentHandle - The component containing the nodes - * @param nodes - Array of node indices to highlight - * @throws {Error} If the component handle is invalid or nodes array is invalid - */ - RPCHighlight(componentHandle: number, nodes: number[]): void { - if(nodes.length === 0) return - // Validation - if (!Validation.isComponentHandle(componentHandle)) return - if (!Validation.areComponentHandles(nodes)) return - // Run - const batches = batchArray(nodes, this.batchSize) - for (const batch of batches) { - this.rpc.RPCHighlight(componentHandle, batch) - } + + RPCSetStateVim(componentHandle: number, state: VisibilityState): void { + if (!Validation.isComponentHandle(componentHandle)) return + this.rpc.RPCSetStateVim(componentHandle, state) } + /******************************************************************************* * TEXT AND UI METHODS * Methods for creating and managing 3D text elements in the scene. @@ -331,9 +236,9 @@ export class RpcSafeClient { * Retrieves the current camera position and orientation. * @returns Promise resolving to a segment representing the camera's current position and target */ - async RPCGetCameraPosition(): Promise { + async RPCGetCameraView(): Promise { return await this.safeCall( - () => this.rpc.RPCGetCameraPosition(), + () => this.rpc.RPCGetCameraView(), undefined ) } @@ -342,20 +247,72 @@ export class RpcSafeClient { * Sets the camera position and orientation. * @param segment - The desired camera position and target * @param blendTime - Duration of the camera transition in seconds (non-negative) - * @throws {Error} If segment is invalid or blendTime is negative */ - RPCSetCameraPosition(segment: RpcTypes.Segment, blendTime: number): void { + RPCSetCameraView(segment: RpcTypes.Segment, blendTime: number): void { // Validation if (!Validation.isValidSegment(segment)) return blendTime = Validation.clamp01(blendTime) // Run - this.rpc.RPCSetCameraPosition(segment, blendTime) + this.rpc.RPCSetCameraView(segment, blendTime) } - async RPCGetBoundingBoxAll(componentHandle: number): Promise { + /** + * Sets the camera's position without changing its target. + * The camera will move to the specified position while maintaining its current look-at direction. + * + * @param position - The new position of the camera in world space + * @param blendTime - Duration of the camera transition in seconds (non-negative) + */ + RPCSetCameraPosition(position: RpcTypes.Vector3, blendTime: number): void { + // Validation + if (!Validation.isValidVector3(position)) return + blendTime = Validation.clamp01(blendTime) + + // Run + this.rpc.RPCSetCameraPosition(position, blendTime) + } + + /** + * Sets the camera's look-at target without changing its position. + * The camera will rotate to face the specified target while remaining at its current position. + * + * @param target - The new look-at target of the camera in world space + * @param blendTime - Duration of the camera transition in seconds (non-negative) + */ + RPCSetCameraTarget(target: RpcTypes.Vector3, blendTime: number): void { + // Validation + if (!Validation.isValidVector3(target)) return + blendTime = Validation.clamp01(blendTime) + + // Run + this.rpc.RPCSetCameraTarget(target, blendTime) + } + + + /** + * Retrieves the axis-aligned bounding box (AABB) that encompasses the entire scene. + * This includes all loaded geometry across all VIM components. + * + * @returns Promise resolving to the global AABB of the scene, or undefined on failure + */ + RPCGetAABBForAll(): Promise { + return this.safeCall( + () => this.rpc.RPCGetAABBForAll(), + undefined + ) + } + + /** + * Retrieves the axis-aligned bounding box (AABB) for a specific VIM component. + * This bounding box represents the spatial bounds of all geometry within the given component. + * + * @param componentHandle - The handle of the VIM component to query + * @returns Promise resolving to the component’s bounding box, or undefined on failure + */ + async RPCGetAABBForVim(componentHandle: number): Promise { return await this.safeCall( - () => this.rpc.RPCGetBoundingBoxAll(componentHandle), + () => this.rpc.RPCGetAABBForVim(componentHandle), undefined ) } @@ -364,37 +321,37 @@ export class RpcSafeClient { * Calculates the bounding box for specified nodes in a component. * Large node arrays are automatically processed in batches for better performance. * @param componentHandle - The component containing the nodes - * @param nodes - Array of node indices to calculate bounds for + * @param elements - Array of node indices to calculate bounds for * @returns Promise resolving to the combined bounding box * @throws {Error} If the component handle is invalid or nodes array is invalid */ - async RPCGetBoundingBox( + async RPCGetAABBForElements( componentHandle: number, - nodes: number[] + elements: number[] ): Promise { // Validation if (!Validation.isComponentHandle(componentHandle)) return - if (!Validation.areComponentHandles(nodes)) return + if (!Validation.areComponentHandles(elements)) return // Run return await this.safeCall( - () => this.getBoundingBoxBatched(componentHandle, nodes), + () => this.RPCGetAABBForElementsBatched(componentHandle, elements), undefined ) } - private async getBoundingBoxBatched( + private async RPCGetAABBForElementsBatched( componentHandle: number, - nodes: number[] + elements: number[] ): Promise { - if(nodes.length === 0){ + if(elements.length === 0){ return new RpcTypes.Box3() } - const batches = batchArray(nodes, this.batchSize) + const batches = batchArray(elements, this.batchSize) const promises = batches.map(async (batch) => { - const aabb = await this.rpc.RPCGetBoundingBox(componentHandle, batch) + const aabb = await this.rpc.RPCGetAABBForElements(componentHandle, batch) const v1 = new RpcTypes.Vector3(aabb.min.x, aabb.min.y, aabb.min.z) const v2 = new RpcTypes.Vector3(aabb.max.x, aabb.max.y, aabb.max.z) return new RpcTypes.Box3(v1, v2) @@ -444,35 +401,35 @@ export class RpcSafeClient { * Frames specific instances within a component. For large numbers of instances, * automatically switches to bounding box framing for better performance. * @param componentHandle - The component containing the instances - * @param nodes - Array of node indices to frame + * @param elements - Array of node indices to frame * @param blendTime - Duration of the camera transition in seconds (non-negative) * @returns Promise resolving to camera segment representing the final position * @throws {Error} If the component handle is invalid or nodes array is empty */ - async RPCFrameInstances( + async RPCFrameElements( componentHandle: number, - nodes: number[], + elements: number[], blendTime: number ): Promise { // Validation if (!Validation.isComponentHandle(componentHandle)) return - if (!Validation.areComponentHandles(nodes)) return + if (!Validation.areComponentHandles(elements)) return blendTime = Validation.clamp01(blendTime) // Run - if (nodes.length < this.batchSize) { + if (elements.length < this.batchSize) { return await this.safeCall( - () => this.rpc.RPCFrameInstances(componentHandle, nodes, blendTime), + () => this.rpc.RPCFrameElements(componentHandle, elements, blendTime), undefined ) } else { const box = await this.safeCall( - () => this.getBoundingBoxBatched(componentHandle, nodes), + () => this.RPCGetAABBForElementsBatched(componentHandle, elements), undefined ) if(!box) return undefined return await this.safeCall( - () => this.rpc.RPCFrameBox(box, blendTime), + () => this.rpc.RPCFrameAABB(box, blendTime), undefined ) } @@ -484,14 +441,14 @@ export class RpcSafeClient { * @param blendTime - Duration of the camera transition in seconds (non-negative) * @throws {Error} If the box is invalid (min values must be less than max values) */ - async RPCFrameBox(box: RpcTypes.Box3, blendTime: number): Promise { + async RPCFrameAABB(box: RpcTypes.Box3, blendTime: number): Promise { // Validation if (!Validation.isValidBox(box)) return blendTime = Validation.clamp01(blendTime) // Run return await this.safeCall( - () => this.rpc.RPCFrameBox(box, blendTime), + () => this.rpc.RPCFrameAABB(box, blendTime), undefined ) } @@ -506,12 +463,12 @@ export class RpcSafeClient { * @param speed - The desired movement speed (must be positive) * @throws {Error} If speed is not positive */ - RPCSetMoveSpeed(speed: number) { + RPCSetCameraSpeed(speed: number) { // Validation speed = Validation.min0(speed) // Run - this.rpc.RPCSetMoveSpeed(speed) + this.rpc.RPCSetCameraSpeed(speed) } RPCSetCameraMode(mode: InputMode): void { @@ -524,13 +481,13 @@ export class RpcSafeClient { * @param height - The height component of the aspect ratio * @throws {Error} If width or height are not positive integers */ - RPCSetAspectRatio(width: number, height: number): void { + RPCSetCameraAspectRatio(width: number, height: number): void { // Validation if (!Validation.isPositiveInteger(width)) return if (!Validation.isPositiveInteger(height)) return // Run - this.rpc.RPCSetAspectRatio(width, height) + this.rpc.RPCSetCameraAspectRatio(width, height) } /******************************************************************************* @@ -614,8 +571,8 @@ export class RpcSafeClient { /** * Clears the entire scene, removing all components and resetting to initial state. */ - RPCClearScene(): void { - this.rpc.RPCClearScene() + RPCUnloadAll(): void { + this.rpc.RPCUnloadAll() } /** @@ -624,8 +581,7 @@ export class RpcSafeClient { */ RPCSetGhostColor(ghostColor: RpcTypes.RGBA): void { const color = Validation.clampRGBA01(ghostColor) - // RPCGhostColor is deprecated, use RPCSetGhostColor2 instead - this.rpc.RPCSetGhostColor2(color) + this.rpc.RPCSetGhostColor(color) } /** @@ -901,62 +857,6 @@ export class RpcSafeClient { this.rpc.RPCTriggerRenderDocCapture() } - /** - * Shows axis-aligned bounding boxes (AABBs) for specified nodes with custom colors. - * Large arrays are automatically processed in batches for better performance. - * @param componentHandle - The component containing the nodes - * @param nodes - Array of node indices to show AABBs for - * @param colors - Array of colors for each AABB (must match nodes length) - * @throws {Error} If arrays have different lengths or component handle is invalid - */ - RPCShowAABBs( - componentHandle: number, - nodes: number[], - colors: RpcTypes.RGBA32[] - ): void { - // Validation - if (!Validation.isComponentHandle(componentHandle)) return - if (!Validation.areComponentHandles(nodes)) return - - // Run - const batches = batchArrays(nodes, colors, this.batchSize) - for (const [batchedNodes, batchedColors] of batches) { - this.rpc.RPCShowAABBs(componentHandle, batchedNodes, batchedColors) - } - } - - /** - * Hides the axis-aligned bounding boxes (AABBs) for specified nodes. - * Large node arrays are automatically processed in batches. - * @param componentHandle - The component containing the nodes - * @param nodes - Array of node indices whose AABBs should be hidden - * @throws {Error} If the component handle is invalid or nodes array is invalid - */ - RPCHideAABBs(componentHandle: number, nodes: number[]): void { - // Validation - if (!Validation.isComponentHandle(componentHandle)) return - if (!Validation.areComponentHandles(nodes)) return - - // Run - const batches = batchArray(nodes, this.batchSize) - for (const batch of batches) { - this.rpc.RPCHideAABBs(componentHandle, batch) - } - } - - /** - * Hides all axis-aligned bounding boxes (AABBs) in a component. - * @param componentHandle - The component whose AABBs should be hidden - * @throws {Error} If the component handle is invalid - */ - RPCHideAllAABBs(componentHandle: number): void { - // Validation - if (!Validation.isComponentHandle(componentHandle)) return - - // Run - this.rpc.RPCHideAllAABBs(componentHandle) - } - private async safeCall(func: () => Promise, defaultValue: TDefault): Promise { try{ return await func() diff --git a/src/vim-web/core-viewers/ultra/rpcTypes.ts b/src/vim-web/core-viewers/ultra/rpcTypes.ts index 2bb3afef..38f82028 100644 --- a/src/vim-web/core-viewers/ultra/rpcTypes.ts +++ b/src/vim-web/core-viewers/ultra/rpcTypes.ts @@ -210,6 +210,7 @@ export class RGBA32 { export type HitCheckResult = { vimHandle: number; // uint32_t equivalent nodeIndex: number; // uint32_t equivalent + elementIndex: number // uint32_t equivalent worldPosition: THREE.Vector3; // 3-element array of floats worldNormal: THREE.Vector3; // 3-element array of floats } diff --git a/src/vim-web/core-viewers/ultra/selection.ts b/src/vim-web/core-viewers/ultra/selection.ts index 01ed8735..dcde1d7a 100644 --- a/src/vim-web/core-viewers/ultra/selection.ts +++ b/src/vim-web/core-viewers/ultra/selection.ts @@ -1,6 +1,6 @@ import {Selection, ISelectionAdapter} from "../shared/selection"; import { Element3D } from "./element3d"; -import { NodeState } from "./nodeState"; +import { VisibilityState } from "./nodeState"; export type ISelection = Selection export function createSelection(): ISelection { @@ -9,6 +9,18 @@ export function createSelection(): ISelection { class SelectionAdapter implements ISelectionAdapter{ outline(object: Element3D, state: boolean){ - object.state = state ? NodeState.HIGHLIGHTED : NodeState.VISIBLE; + if(state){ + object.state = + object.state === VisibilityState.VISIBLE ? VisibilityState.HIGHLIGHTED + : object.state === VisibilityState.HIDDEN ? VisibilityState.HIDDEN_HIGHLIGHTED + : object.state === VisibilityState.GHOSTED ? VisibilityState.GHOSTED_HIGHLIGHTED + : VisibilityState.HIGHLIGHTED; + }else{ + object.state = + object.state === VisibilityState.HIGHLIGHTED ? VisibilityState.VISIBLE + : object.state === VisibilityState.HIDDEN_HIGHLIGHTED ? VisibilityState.HIDDEN + : object.state === VisibilityState.GHOSTED_HIGHLIGHTED ? VisibilityState.GHOSTED + : VisibilityState.VISIBLE; + } } } \ No newline at end of file diff --git a/src/vim-web/core-viewers/ultra/viewport.ts b/src/vim-web/core-viewers/ultra/viewport.ts index 043baf52..93cd4266 100644 --- a/src/vim-web/core-viewers/ultra/viewport.ts +++ b/src/vim-web/core-viewers/ultra/viewport.ts @@ -53,7 +53,7 @@ export class Viewport { */ update() { if(this._rpc.connected){ - this._rpc.RPCSetAspectRatio(this.canvas.offsetWidth, this.canvas.offsetHeight) + this._rpc.RPCSetCameraAspectRatio(this.canvas.offsetWidth, this.canvas.offsetHeight) } } diff --git a/src/vim-web/core-viewers/ultra/vim.ts b/src/vim-web/core-viewers/ultra/vim.ts index 49606ccb..a36f717e 100644 --- a/src/vim-web/core-viewers/ultra/vim.ts +++ b/src/vim-web/core-viewers/ultra/vim.ts @@ -4,7 +4,7 @@ import type { ILogger } from './logger'; import { ColorManager } from './colorManager'; import { Element3D } from './element3d'; import { LoadRequest } from './loadRequest'; -import { NodeState, StateSynchronizer } from './nodeState'; +import { VisibilityState, StateSynchronizer } from './nodeState'; import { Renderer } from './renderer'; import { MaterialHandles } from './rpcClient'; import { RpcSafeClient, VimLoadingStatus, VimSource } from './rpcSafeClient'; @@ -55,15 +55,15 @@ export class Vim implements IVim { () => this._handle, () => this.connected, () => this._renderer.notifySceneUpdated(), - NodeState.VISIBLE // default state + VisibilityState.VISIBLE // default state ); } - getElementFromInstanceIndex(instance: number): Element3D { - if (this._objects.has(instance)) { - return this._objects.get(instance)!; + getElement(elementIndex: number): Element3D { + if (this._objects.has(elementIndex)) { + return this._objects.get(elementIndex)!; } - const object = new Element3D(this, instance); - this._objects.set(instance, object); + const object = new Element3D(this, elementIndex); + this._objects.set(elementIndex, object); return object; } getElementsFromId(id: number): Element3D[] { @@ -189,16 +189,16 @@ export class Vim implements IVim { return Promise.resolve(undefined); } if (nodes === 'all') { - return await this._rpc.RPCGetBoundingBoxAll(this._handle); + return await this._rpc.RPCGetAABBForVim(this._handle); } - return await this._rpc.RPCGetBoundingBox(this._handle, nodes); + return await this._rpc.RPCGetAABBForElements(this._handle, nodes); } async getBoundingBox(): Promise { if (!this.connected ) { return Promise.resolve(undefined); } - return await this._rpc.RPCGetBoundingBoxAll(this._handle); + return await this._rpc.RPCGetAABBForVim(this._handle); } getColor(node: number): RGBA32 | undefined { @@ -290,16 +290,16 @@ function wait(ms: number): Promise { * but the change is still tracked for remote updates. */ class StateTracker { - private _state = new Map(); + private _state = new Map(); private _updates = new Set(); - private _default: NodeState; + private _default: VisibilityState; private _updatedDefault: boolean = false; - constructor(defaultState: NodeState = NodeState.VISIBLE) { + constructor(defaultState: VisibilityState = VisibilityState.VISIBLE) { this._default = defaultState; } - setAll(state: NodeState, clearNodes: boolean) { + setAll(state: VisibilityState, clearNodes: boolean) { this._default = state; this._updatedDefault = true; if (clearNodes) { @@ -323,7 +323,7 @@ class StateTracker { toRemove.forEach(k => this._state.delete(k)); } - set(key: number, value: NodeState) { + set(key: number, value: VisibilityState) { if (this._default === value) { this.delete(key); } else { @@ -344,7 +344,7 @@ class StateTracker { /** * Update nodes in bulk. If 'all' is specified, the default is updated. */ - updateNodes(nodes: Utils.ForEachable | 'all', state: NodeState): void { + updateNodes(nodes: Utils.ForEachable | 'all', state: VisibilityState): void { if (nodes === 'all') { this.setAll(state, true); } else { @@ -358,18 +358,18 @@ class StateTracker { } } - get(key: number): NodeState | undefined { + get(key: number): VisibilityState | undefined { return this._state.get(key); } - getDefault(): NodeState { + getDefault(): VisibilityState { return this._default; } /** * Returns whether every node (override or not) is in the given state(s). */ - areAll(state: NodeState | NodeState[]): boolean { + areAll(state: VisibilityState | VisibilityState[]): boolean { if (!this.matchesState(this._default, state)) { return false; } @@ -384,7 +384,7 @@ class StateTracker { /** * Returns a node’s effective state. */ - getState(node: number): NodeState { + getState(node: number): VisibilityState { return this._state.get(node) ?? this._default; } @@ -392,7 +392,7 @@ class StateTracker { * Returns either 'all' if every node is in the given state, or an array * of node IDs (from the overrides) whose state equals the provided state. */ - getAll(state: NodeState): number[] | 'all' { + getAll(state: VisibilityState): number[] | 'all' { if (this.areAll(state)) return 'all'; const nodes: number[] = []; for (const [node, nodeState] of this._state.entries()) { @@ -406,9 +406,9 @@ class StateTracker { /** * Returns a mapping from state to an array of updated node IDs. */ - getUpdates(): Map { - const nodesByState = new Map(); - Object.values(NodeState).forEach((state) => { + getUpdates(): Map { + const nodesByState = new Map(); + Object.values(VisibilityState).forEach((state) => { nodesByState.set(state, []); }); @@ -428,14 +428,14 @@ class StateTracker { this._updatedDefault = false; } - entries(): IterableIterator<[number, NodeState]> { + entries(): IterableIterator<[number, VisibilityState]> { return this._state.entries(); } /** * Helper: checks if a node state matches one or more target states. */ - matchesState(nodeState: NodeState, state: NodeState | NodeState[]): boolean { + matchesState(nodeState: VisibilityState, state: VisibilityState | VisibilityState[]): boolean { if (Array.isArray(state)) { return state.includes(nodeState); } @@ -446,7 +446,7 @@ class StateTracker { * Replaces all nodes that match the provided state(s) with a new state. * If all nodes are in the given state(s), the default is updated. */ - replace(from: NodeState | NodeState[], to: NodeState): void { + replace(from: VisibilityState | VisibilityState[], to: VisibilityState): void { if (this.areAll(from)) { this.setAll(to, false); } diff --git a/src/vim-web/core-viewers/webgl/loader/mesh.ts b/src/vim-web/core-viewers/webgl/loader/mesh.ts index 02827820..00067fdc 100644 --- a/src/vim-web/core-viewers/webgl/loader/mesh.ts +++ b/src/vim-web/core-viewers/webgl/loader/mesh.ts @@ -239,6 +239,6 @@ export class StandardSubmesh { * Returns vim object for this submesh. */ get object () { - return this.mesh.vim.getElementFromInstanceIndex(this.instance) + return this.mesh.vim.getElement(this.instance) } } diff --git a/src/vim-web/core-viewers/webgl/loader/progressive/insertableSubmesh.ts b/src/vim-web/core-viewers/webgl/loader/progressive/insertableSubmesh.ts index 3b814b16..e36e17a7 100644 --- a/src/vim-web/core-viewers/webgl/loader/progressive/insertableSubmesh.ts +++ b/src/vim-web/core-viewers/webgl/loader/progressive/insertableSubmesh.ts @@ -69,7 +69,7 @@ export class InsertableSubmesh { * Returns vim object for this submesh. */ get object () { - return this.mesh.vim.getElementFromInstanceIndex(this.instance) + return this.mesh.vim.getElement(this.instance) } saveColors (colors: Float32Array) { diff --git a/src/vim-web/core-viewers/webgl/loader/progressive/instancedSubmesh.ts b/src/vim-web/core-viewers/webgl/loader/progressive/instancedSubmesh.ts index d78de911..1f337c4a 100644 --- a/src/vim-web/core-viewers/webgl/loader/progressive/instancedSubmesh.ts +++ b/src/vim-web/core-viewers/webgl/loader/progressive/instancedSubmesh.ts @@ -50,6 +50,6 @@ export class InstancedSubmesh { * Returns vim object for this submesh. */ get object () { - return this.mesh.vim.getElementFromInstanceIndex(this.instance) + return this.mesh.vim.getElement(this.instance) } } diff --git a/src/vim-web/core-viewers/webgl/loader/scene.ts b/src/vim-web/core-viewers/webgl/loader/scene.ts index d8939cd6..67d49651 100644 --- a/src/vim-web/core-viewers/webgl/loader/scene.ts +++ b/src/vim-web/core-viewers/webgl/loader/scene.ts @@ -150,7 +150,7 @@ export class Scene { this._instanceToMeshes.set(submesh.instance, meshes) this.setDirty() if (this.vim) { - const obj = this.vim.getElementFromInstanceIndex(submesh.instance) + const obj = this.vim.getElement(submesh.instance) obj._addMesh(submesh) } } diff --git a/src/vim-web/core-viewers/webgl/loader/vim.ts b/src/vim-web/core-viewers/webgl/loader/vim.ts index 4cd00d2e..b5c136c6 100644 --- a/src/vim-web/core-viewers/webgl/loader/vim.ts +++ b/src/vim-web/core-viewers/webgl/loader/vim.ts @@ -149,7 +149,7 @@ export class Vim implements IVim { * @param {number} instance - The instance number of the object. * @returns {THREE.Object3D | undefined} The object corresponding to the instance, or undefined if not found. */ - getElementFromInstanceIndex (instance: number) { + getElement (instance: number) { const element = this.map.getElementFromInstance(instance) if (element === undefined) return return this.getElementFromIndex(element) @@ -211,7 +211,7 @@ export class Vim implements IVim { const count = subset.getInstanceCount() for (let i = 0; i < count; i++) { const instance = subset.getVimInstance(i) - const obj = this.getElementFromInstanceIndex(instance) + const obj = this.getElement(instance) if (!set.has(obj)) { result.push(obj) set.add(obj) diff --git a/src/vim-web/react-viewers/ultra/isolation.ts b/src/vim-web/react-viewers/ultra/isolation.ts index bf065b53..b3c070eb 100644 --- a/src/vim-web/react-viewers/ultra/isolation.ts +++ b/src/vim-web/react-viewers/ultra/isolation.ts @@ -2,7 +2,7 @@ import { IsolationAdapter, useSharedIsolation as useSharedIsolation, VisibilityS import * as Core from "../../core-viewers"; import { useStateRef } from "../helpers/reactUtils"; -import NodeState = Core.Ultra.NodeState +import VisibilityState = Core.Ultra.VisibilityState import Viewer = Core.Ultra.Viewer import Vim = Core.Ultra.Vim import Element3D = Core.Ultra.Element3D @@ -18,12 +18,22 @@ function createAdapter(viewer: Viewer): IsolationAdapter { // Helper function to hide objects in ghost or hidden state const hide = (objects: Element3D[] | 'all') =>{ - const state = ghost.get() ? NodeState.GHOSTED : NodeState.HIDDEN + const state = ghost.get() ? VisibilityState.GHOSTED : VisibilityState.HIDDEN if(objects === 'all'){ - viewer.vims.getAll().forEach(vim => {vim.nodeState.setAllNodesState(state)}) + viewer.vims.getAll().forEach(vim => {vim.nodeState.setAllState(state)}) return } - objects.forEach(obj => {obj.state = state}) + + for(const obj of objects){ + if(viewer.selection.has(obj)){ + obj.state = state == VisibilityState.GHOSTED + ? VisibilityState.GHOSTED_HIGHLIGHTED + : VisibilityState.HIDDEN_HIGHLIGHTED + } + else{ + obj.state = state + } + } } return { @@ -31,8 +41,8 @@ function createAdapter(viewer: Viewer): IsolationAdapter { onSelectionChanged: viewer.selection.onSelectionChanged, computeVisibility: () => getVisibilityState(viewer), hasSelection: () => viewer.selection.any(), - hasVisibleSelection: () => checkSelectionState(viewer, s => s === 'visible' || s === 'highlighted'), - hasHiddenSelection: () => checkSelectionState(viewer, s => s === 'hidden' || s === 'ghosted'), + hasVisibleSelection: () => checkSelectionState(viewer, s => s === VisibilityState.VISIBLE || s === VisibilityState.HIGHLIGHTED), + hasHiddenSelection: () => checkSelectionState(viewer, s => s === VisibilityState.HIDDEN || s === VisibilityState.GHOSTED), clearSelection: () => viewer.selection.clear(), @@ -40,7 +50,7 @@ function createAdapter(viewer: Viewer): IsolationAdapter { hide('all') for(const obj of viewer.selection.getAll()){ - obj.state = NodeState.HIGHLIGHTED + obj.state = VisibilityState.HIGHLIGHTED } }, hideSelection: () => { @@ -49,7 +59,7 @@ function createAdapter(viewer: Viewer): IsolationAdapter { }, showSelection: () => { viewer.selection.getAll().forEach(obj => { - obj.state = NodeState.VISIBLE + obj.state = VisibilityState.VISIBLE }) }, @@ -58,23 +68,23 @@ function createAdapter(viewer: Viewer): IsolationAdapter { }, showAll: () => { for(const vim of viewer.vims.getAll()){ - vim.nodeState.setAllNodesState(NodeState.VISIBLE) + vim.nodeState.setAllState(VisibilityState.VISIBLE) } viewer.selection.getAll().forEach(obj => { - obj.state = NodeState.HIGHLIGHTED + obj.state = VisibilityState.HIGHLIGHTED }) }, isolate: (instances: number[]) => { hide('all') // Hide all objects viewer.selection.getAll().forEach(obj => { - obj.state = NodeState.HIGHLIGHTED + obj.state = VisibilityState.HIGHLIGHTED }) }, show: (instances: number[]) => { for(const vim of viewer.vims.getAll()){ for(const i of instances){ - vim.getElementFromInstanceIndex(i).state = NodeState.VISIBLE + vim.getElement(i).state = VisibilityState.VISIBLE } } }, @@ -82,7 +92,7 @@ function createAdapter(viewer: Viewer): IsolationAdapter { hide: (instances: number[]) => { for(const vim of viewer.vims.getAll()){ for(const i of instances){ - const obj = vim.getElementFromInstanceIndex(i) + const obj = vim.getElement(i) hide([obj]) } } @@ -94,9 +104,9 @@ function createAdapter(viewer: Viewer): IsolationAdapter { for(const vim of viewer.vims.getAll()){ if(show){ - vim.nodeState.replaceState(NodeState.HIDDEN, NodeState.GHOSTED) + vim.nodeState.replaceState(VisibilityState.HIDDEN, VisibilityState.GHOSTED) } else { - vim.nodeState.replaceState(NodeState.GHOSTED, NodeState.HIDDEN) + vim.nodeState.replaceState(VisibilityState.GHOSTED, VisibilityState.HIDDEN) } } }, @@ -115,7 +125,7 @@ function createAdapter(viewer: Viewer): IsolationAdapter { }; } -function checkSelectionState(viewer: Viewer, test: (state: NodeState) => boolean): boolean { +function checkSelectionState(viewer: Viewer, test: (state: VisibilityState) => boolean): boolean { if(!viewer.selection.any()){ return false } @@ -130,8 +140,8 @@ function getVisibilityState(viewer: Viewer): VisibilityStatus { let onlySelectionFlag = true; for (let v of viewer.vims.getAll()) { - const allVisible = v.nodeState.areAllInState([NodeState.VISIBLE, NodeState.HIGHLIGHTED]) - const allHidden = v.nodeState.areAllInState([NodeState.HIDDEN, NodeState.GHOSTED]) + const allVisible = v.nodeState.areAllInState([VisibilityState.VISIBLE, VisibilityState.HIGHLIGHTED]) + const allHidden = v.nodeState.areAllInState([VisibilityState.HIDDEN, VisibilityState.GHOSTED]) all = all && allVisible none = none && allHidden diff --git a/src/vim-web/react-viewers/webgl/isolation.ts b/src/vim-web/react-viewers/webgl/isolation.ts index b5dbff47..1a7c3827 100644 --- a/src/vim-web/react-viewers/webgl/isolation.ts +++ b/src/vim-web/react-viewers/webgl/isolation.ts @@ -40,7 +40,7 @@ function createWebglIsolationAdapter(viewer: Core.Webgl.Viewer): IsolationAdapte show: (instances: number[]) => { for(let i of instances){ for(let v of viewer.vims){ - const o = v.getElementFromInstanceIndex(i) + const o = v.getElement(i) o.visible = true } } @@ -49,7 +49,7 @@ function createWebglIsolationAdapter(viewer: Core.Webgl.Viewer): IsolationAdapte hide: (instances: number[]) => { for(let i of instances){ for(let v of viewer.vims){ - const o = v.getElementFromInstanceIndex(i) + const o = v.getElement(i) o.visible = false; } } From bba80f1e7f2405b9f217728952c94bc5d439b551 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Wed, 9 Jul 2025 15:14:38 -0400 Subject: [PATCH 03/24] removed more node stuff --- src/vim-web/core-viewers/ultra/element3d.ts | 4 +- src/vim-web/core-viewers/ultra/index.ts | 1 - src/vim-web/core-viewers/ultra/nodeState.ts | 2 +- src/vim-web/core-viewers/ultra/vim.ts | 185 +------------------ src/vim-web/react-viewers/ultra/isolation.ts | 58 +----- 5 files changed, 15 insertions(+), 235 deletions(-) diff --git a/src/vim-web/core-viewers/ultra/element3d.ts b/src/vim-web/core-viewers/ultra/element3d.ts index feaa5a8c..a8c1eae8 100644 --- a/src/vim-web/core-viewers/ultra/element3d.ts +++ b/src/vim-web/core-viewers/ultra/element3d.ts @@ -42,10 +42,10 @@ export class Element3D implements IVimElement { * Gets or sets the display state of the element (e.g., visible, hidden). */ get state(): VisibilityState { - return this.vim.nodeState.getElementState(this.element); + return this.vim.visibility.getElementState(this.element); } set state(state: VisibilityState) { - this.vim.nodeState.setElementState(this.element, state); + this.vim.visibility.setElementState(this.element, state); } /** diff --git a/src/vim-web/core-viewers/ultra/index.ts b/src/vim-web/core-viewers/ultra/index.ts index 0a3b0c1e..55f1d50f 100644 --- a/src/vim-web/core-viewers/ultra/index.ts +++ b/src/vim-web/core-viewers/ultra/index.ts @@ -9,7 +9,6 @@ export {RGB, RGBA, RGBA32, Segment, type SectionBoxState, type HitCheckResult, t // We don't want to export RPCClient export {materialHandles, MaterialHandles, type MaterialHandle, } from './rpcClient' -export {VisibilityState as NodeState} from './nodeState'; export {InputMode, VimLoadingStatus} from './rpcSafeClient'; export {VisibilityState} from './nodeState'; //Runtime values for enum diff --git a/src/vim-web/core-viewers/ultra/nodeState.ts b/src/vim-web/core-viewers/ultra/nodeState.ts index 8e8583a5..a01462ea 100644 --- a/src/vim-web/core-viewers/ultra/nodeState.ts +++ b/src/vim-web/core-viewers/ultra/nodeState.ts @@ -110,7 +110,7 @@ export class StateSynchronizer { * @param state - The state to apply to all elements * @param clear - If true, clears all elements-specific overrides */ - setAllState(state: VisibilityState): void { + setStateForAll(state: VisibilityState): void { this._tracker.setAll(state); this.scheduleUpdate(); } diff --git a/src/vim-web/core-viewers/ultra/vim.ts b/src/vim-web/core-viewers/ultra/vim.ts index a36f717e..0509e63a 100644 --- a/src/vim-web/core-viewers/ultra/vim.ts +++ b/src/vim-web/core-viewers/ultra/vim.ts @@ -25,7 +25,7 @@ export class Vim implements IVim { private _logger: ILogger; // The StateSynchronizer wraps a StateTracker and handles RPC synchronization. - readonly nodeState: StateSynchronizer; + readonly visibility: StateSynchronizer; // Color tracking remains unchanged. private _nodeColors: Map = new Map(); @@ -50,7 +50,7 @@ export class Vim implements IVim { this._logger = logger; // Instantiate the synchronizer with a new StateTracker. - this.nodeState = new StateSynchronizer( + this.visibility = new StateSynchronizer( this._rpc, () => this._handle, () => this.connected, @@ -99,7 +99,7 @@ export class Vim implements IVim { if (result.isSuccess) { // Reapply Node state and colors in case this is a reconnection this._logger.log('Successfully loaded vim: ', this.source); - this.nodeState.reapplyStates(); + this.visibility.reapplyStates(); this.reapplyColors() } else { @@ -282,182 +282,3 @@ export class Vim implements IVim { function wait(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } - -/** - * A tracker for node state overrides. - * It stores per-node state overrides against a default state. - * When a node’s state is set equal to the default, its override is removed - * but the change is still tracked for remote updates. - */ -class StateTracker { - private _state = new Map(); - private _updates = new Set(); - private _default: VisibilityState; - private _updatedDefault: boolean = false; - - constructor(defaultState: VisibilityState = VisibilityState.VISIBLE) { - this._default = defaultState; - } - - setAll(state: VisibilityState, clearNodes: boolean) { - this._default = state; - this._updatedDefault = true; - if (clearNodes) { - this._state.clear(); - this._updates.clear(); - } else { - this.reapply(); - } - } - - reapply() { - this._updates.clear(); - const toRemove = new Set(); - for (const [k, s] of this._state.entries()) { - if (s === this._default) { - toRemove.add(k); - } else { - this._updates.add(k); - } - } - toRemove.forEach(k => this._state.delete(k)); - } - - set(key: number, value: VisibilityState) { - if (this._default === value) { - this.delete(key); - } else { - this._state.set(key, value); - this._updates.add(key); - } - } - - delete(key: number) { - if (this._state.has(key)) { - this._state.delete(key); - this._updates.add(key); - } else { - this._updates.add(key); - } - } - - /** - * Update nodes in bulk. If 'all' is specified, the default is updated. - */ - updateNodes(nodes: Utils.ForEachable | 'all', state: VisibilityState): void { - if (nodes === 'all') { - this.setAll(state, true); - } else { - nodes.forEach((n) => { - if (state === this._default) { - this.delete(n); - } else { - this.set(n, state); - } - }); - } - } - - get(key: number): VisibilityState | undefined { - return this._state.get(key); - } - - getDefault(): VisibilityState { - return this._default; - } - - /** - * Returns whether every node (override or not) is in the given state(s). - */ - areAll(state: VisibilityState | VisibilityState[]): boolean { - if (!this.matchesState(this._default, state)) { - return false; - } - for (const st of this._state.values()) { - if (!this.matchesState(st, state)) { - return false; - } - } - return true; - } - - /** - * Returns a node’s effective state. - */ - getState(node: number): VisibilityState { - return this._state.get(node) ?? this._default; - } - - /** - * Returns either 'all' if every node is in the given state, or an array - * of node IDs (from the overrides) whose state equals the provided state. - */ - getAll(state: VisibilityState): number[] | 'all' { - if (this.areAll(state)) return 'all'; - const nodes: number[] = []; - for (const [node, nodeState] of this._state.entries()) { - if (nodeState === state) { - nodes.push(node); - } - } - return nodes; - } - - /** - * Returns a mapping from state to an array of updated node IDs. - */ - getUpdates(): Map { - const nodesByState = new Map(); - Object.values(VisibilityState).forEach((state) => { - nodesByState.set(state, []); - }); - - for (const node of this._updates) { - const state = this._state.get(node) ?? this._default; - nodesByState.get(state).push(node); - } - return nodesByState; - } - - isDefaultUpdated(): boolean { - return this._updatedDefault; - } - - reset() { - this._updates.clear(); - this._updatedDefault = false; - } - - entries(): IterableIterator<[number, VisibilityState]> { - return this._state.entries(); - } - - /** - * Helper: checks if a node state matches one or more target states. - */ - matchesState(nodeState: VisibilityState, state: VisibilityState | VisibilityState[]): boolean { - if (Array.isArray(state)) { - return state.includes(nodeState); - } - return nodeState === state; - } - - /** - * Replaces all nodes that match the provided state(s) with a new state. - * If all nodes are in the given state(s), the default is updated. - */ - replace(from: VisibilityState | VisibilityState[], to: VisibilityState): void { - if (this.areAll(from)) { - this.setAll(to, false); - } - for (const [node, state] of this._state.entries()) { - if (this.matchesState(state, from)) { - if (to === this._default) { - this.delete(node); - } else { - this.set(node, to); - } - } - } - } -} diff --git a/src/vim-web/react-viewers/ultra/isolation.ts b/src/vim-web/react-viewers/ultra/isolation.ts index b3c070eb..93476527 100644 --- a/src/vim-web/react-viewers/ultra/isolation.ts +++ b/src/vim-web/react-viewers/ultra/isolation.ts @@ -20,10 +20,10 @@ function createAdapter(viewer: Viewer): IsolationAdapter { const hide = (objects: Element3D[] | 'all') =>{ const state = ghost.get() ? VisibilityState.GHOSTED : VisibilityState.HIDDEN if(objects === 'all'){ - viewer.vims.getAll().forEach(vim => {vim.nodeState.setAllState(state)}) + viewer.vims.getAll().forEach(vim => {vim.visibility.setStateForAll(state)}) return } - + for(const obj of objects){ if(viewer.selection.has(obj)){ obj.state = state == VisibilityState.GHOSTED @@ -68,7 +68,7 @@ function createAdapter(viewer: Viewer): IsolationAdapter { }, showAll: () => { for(const vim of viewer.vims.getAll()){ - vim.nodeState.setAllState(VisibilityState.VISIBLE) + vim.visibility.setStateForAll(VisibilityState.VISIBLE) } viewer.selection.getAll().forEach(obj => { obj.state = VisibilityState.HIGHLIGHTED @@ -104,9 +104,9 @@ function createAdapter(viewer: Viewer): IsolationAdapter { for(const vim of viewer.vims.getAll()){ if(show){ - vim.nodeState.replaceState(VisibilityState.HIDDEN, VisibilityState.GHOSTED) + vim.visibility.replaceState(VisibilityState.HIDDEN, VisibilityState.GHOSTED) } else { - vim.nodeState.replaceState(VisibilityState.GHOSTED, VisibilityState.HIDDEN) + vim.visibility.replaceState(VisibilityState.GHOSTED, VisibilityState.HIDDEN) } } }, @@ -140,8 +140,8 @@ function getVisibilityState(viewer: Viewer): VisibilityStatus { let onlySelectionFlag = true; for (let v of viewer.vims.getAll()) { - const allVisible = v.nodeState.areAllInState([VisibilityState.VISIBLE, VisibilityState.HIGHLIGHTED]) - const allHidden = v.nodeState.areAllInState([VisibilityState.HIDDEN, VisibilityState.GHOSTED]) + const allVisible = v.visibility.areAllInState([VisibilityState.VISIBLE, VisibilityState.HIGHLIGHTED]) + const allHidden = v.visibility.areAllInState([VisibilityState.HIDDEN, VisibilityState.GHOSTED]) all = all && allVisible none = none && allHidden @@ -158,52 +158,12 @@ function getVisibilityState(viewer: Viewer): VisibilityStatus { return 'some'; } +//returns true if only the selection is visible function onlySelection(viewer: Viewer, vim: Vim): boolean { return false - /* - const selectedInstances = viewer.selection.get().get(vim) - if(selectedInstances === undefined) return false - - // Base state should be hidden or ghosted - const baseState = vim.nodeState.getDefaultState() - if(baseState === 'visible') return false - if(baseState === 'highlighted') return false - - // Assumes that not all instances are selected - const visibleInstances = vim.nodeState.getNodesInState(UltraVimNodeState.VISIBLE) - if(visibleInstances === 'all') return false - - // Check that visible set === selected set - const visibleSet = new Set(visibleInstances) - if(!visibleSet.isSubsetOf(selectedInstances)) return false - if(!visibleSet.isSupersetOf(selectedInstances)) return false - - return true - */ } +//returns true if only the selection is hidden function allButSelection(viewer: Viewer, vim: Vim): boolean { return false - /* - const selectedInstances = viewer.selection.get().get(vim) - if(selectedInstances === undefined) return false - - // Base state should be visible or highlighted - const baseState = vim.nodeState.getDefaultState() - if(baseState === 'hidden') return false - if(baseState === 'ghosted') return false - - // Assumes that not all instances are selected - const hiddenInstances = vim.nodeState.getNodesInState(UltraVimNodeState.HIDDEN) - const ghostedInstances = vim.nodeState.getNodesInState(UltraVimNodeState.GHOSTED) - if(hiddenInstances === 'all') return false - if(ghostedInstances === 'all') return false - - // Check that visible set === selected set - const hiddenSet = new Set([...hiddenInstances, ...ghostedInstances]) - if(!hiddenSet.isSubsetOf(selectedInstances)) return false - if(!hiddenSet.isSupersetOf(selectedInstances)) return false - - return true - */ } From f0edf452d29a1c74c1ba9c860dc15a5b4d78b0b8 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Wed, 9 Jul 2025 15:15:26 -0400 Subject: [PATCH 04/24] renamed nodeState to visibility --- src/vim-web/core-viewers/ultra/element3d.ts | 2 +- src/vim-web/core-viewers/ultra/index.ts | 4 ++-- src/vim-web/core-viewers/ultra/rpcSafeClient.ts | 2 +- src/vim-web/core-viewers/ultra/selection.ts | 2 +- src/vim-web/core-viewers/ultra/vim.ts | 2 +- .../core-viewers/ultra/{nodeState.ts => visibility.ts} | 0 6 files changed, 6 insertions(+), 6 deletions(-) rename src/vim-web/core-viewers/ultra/{nodeState.ts => visibility.ts} (100%) diff --git a/src/vim-web/core-viewers/ultra/element3d.ts b/src/vim-web/core-viewers/ultra/element3d.ts index a8c1eae8..50477c19 100644 --- a/src/vim-web/core-viewers/ultra/element3d.ts +++ b/src/vim-web/core-viewers/ultra/element3d.ts @@ -1,5 +1,5 @@ import { IVimElement } from "../shared/vim"; -import { VisibilityState } from "./nodeState"; +import { VisibilityState } from "./visibility"; import { Box3, RGBA32 } from "./rpcTypes"; import { Vim } from "./vim"; diff --git a/src/vim-web/core-viewers/ultra/index.ts b/src/vim-web/core-viewers/ultra/index.ts index 55f1d50f..fe636800 100644 --- a/src/vim-web/core-viewers/ultra/index.ts +++ b/src/vim-web/core-viewers/ultra/index.ts @@ -10,13 +10,13 @@ export {RGB, RGBA, RGBA32, Segment, type SectionBoxState, type HitCheckResult, t // We don't want to export RPCClient export {materialHandles, MaterialHandles, type MaterialHandle, } from './rpcClient' export {InputMode, VimLoadingStatus} from './rpcSafeClient'; -export {VisibilityState} from './nodeState'; //Runtime values for enum +export {VisibilityState} from './visibility'; //Runtime values for enum // Type exports export type * from './camera'; export type * from './colorManager'; export type * from './decoder'; -export type * from './nodeState'; +export type * from './visibility'; export type * from './element3d'; export type * from './inputAdapter'; export type * from './loadRequest'; diff --git a/src/vim-web/core-viewers/ultra/rpcSafeClient.ts b/src/vim-web/core-viewers/ultra/rpcSafeClient.ts index ec170825..09f34ca3 100644 --- a/src/vim-web/core-viewers/ultra/rpcSafeClient.ts +++ b/src/vim-web/core-viewers/ultra/rpcSafeClient.ts @@ -3,7 +3,7 @@ import { MaterialHandle, RpcClient } from "./rpcClient" import { Validation } from "../../utils"; import { batchArray, batchArrays } from "../../utils/array" import { INVALID_HANDLE } from "./viewer" -import { VisibilityState } from "./nodeState"; +import { VisibilityState } from "./visibility"; const defaultBatchSize = 10000 diff --git a/src/vim-web/core-viewers/ultra/selection.ts b/src/vim-web/core-viewers/ultra/selection.ts index dcde1d7a..23dd3dc8 100644 --- a/src/vim-web/core-viewers/ultra/selection.ts +++ b/src/vim-web/core-viewers/ultra/selection.ts @@ -1,6 +1,6 @@ import {Selection, ISelectionAdapter} from "../shared/selection"; import { Element3D } from "./element3d"; -import { VisibilityState } from "./nodeState"; +import { VisibilityState } from "./visibility"; export type ISelection = Selection export function createSelection(): ISelection { diff --git a/src/vim-web/core-viewers/ultra/vim.ts b/src/vim-web/core-viewers/ultra/vim.ts index 0509e63a..643299dc 100644 --- a/src/vim-web/core-viewers/ultra/vim.ts +++ b/src/vim-web/core-viewers/ultra/vim.ts @@ -4,7 +4,7 @@ import type { ILogger } from './logger'; import { ColorManager } from './colorManager'; import { Element3D } from './element3d'; import { LoadRequest } from './loadRequest'; -import { VisibilityState, StateSynchronizer } from './nodeState'; +import { VisibilityState, StateSynchronizer } from './visibility'; import { Renderer } from './renderer'; import { MaterialHandles } from './rpcClient'; import { RpcSafeClient, VimLoadingStatus, VimSource } from './rpcSafeClient'; diff --git a/src/vim-web/core-viewers/ultra/nodeState.ts b/src/vim-web/core-viewers/ultra/visibility.ts similarity index 100% rename from src/vim-web/core-viewers/ultra/nodeState.ts rename to src/vim-web/core-viewers/ultra/visibility.ts From a328833694a95ee7225a2f668ece04ca98c2b7ac Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Fri, 11 Jul 2025 11:30:32 -0400 Subject: [PATCH 05/24] updated hitcheck --- src/vim-web/core-viewers/ultra/rpcMarshal.ts | 24 ++++++++------------ src/vim-web/core-viewers/ultra/rpcTypes.ts | 6 ++--- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/vim-web/core-viewers/ultra/rpcMarshal.ts b/src/vim-web/core-viewers/ultra/rpcMarshal.ts index 4d1b9e9b..8e2200f9 100644 --- a/src/vim-web/core-viewers/ultra/rpcMarshal.ts +++ b/src/vim-web/core-viewers/ultra/rpcMarshal.ts @@ -104,15 +104,13 @@ export class Marshal { public writeHitCheckResult(data: RpcTypes.HitCheckResult): void { this.ensureCapacity(4 + 4 +4 + 4 * 3 + 4 * 3) - this.writeUInt(data.vimHandle) - this.writeUInt(data.nodeIndex) - this.writeUInt(data.elementIndex) + this.writeUInt(data.vimIndex) + this.writeUInt(data.vimElementIndex) + this.writeUInt(data.sceneElementIndex) this.writeVector3(data.worldPosition) this.writeVector3(data.worldNormal) } - - // -------------------- VimStatus ------------------- public writeVimStatus(data: RpcTypes.VimStatus): void { @@ -121,7 +119,6 @@ export class Marshal { this.writeFloat(data.progress) } - // -------------------- Vector2 ------------------- public writeVector2(data: RpcTypes.Vector2): void { @@ -130,8 +127,6 @@ export class Marshal { this.writeFloat(data.y) } - - // -------------------- Vector3 ------------------- public writeVector3(data: RpcTypes.Vector3): void { @@ -320,17 +315,16 @@ export class ReadMarshal{ } public readHitCheckResult(): RpcTypes.HitCheckResult { - const vimHandle = this.readUInt() - const nodeIndex = this.readUInt() - const mElementIndex = this.readUInt() - + const vimIndex = this.readUInt() + const vimElementIndex = this.readUInt() + const sceneElementIndex = this.readUInt() const worldPosition = this.readVector3() const worldNormal = this.readVector3() return { - vimHandle, - nodeIndex, - elementIndex: mElementIndex, + vimIndex, + vimElementIndex, + sceneElementIndex, worldPosition, worldNormal } diff --git a/src/vim-web/core-viewers/ultra/rpcTypes.ts b/src/vim-web/core-viewers/ultra/rpcTypes.ts index 38f82028..f8a37a0c 100644 --- a/src/vim-web/core-viewers/ultra/rpcTypes.ts +++ b/src/vim-web/core-viewers/ultra/rpcTypes.ts @@ -208,9 +208,9 @@ export class RGBA32 { } export type HitCheckResult = { - vimHandle: number; // uint32_t equivalent - nodeIndex: number; // uint32_t equivalent - elementIndex: number // uint32_t equivalent + vimIndex: number; // uint32_t equivalent + vimElementIndex: number; // uint32_t equivalent + sceneElementIndex: number; // uint32_t equivalent worldPosition: THREE.Vector3; // 3-element array of floats worldNormal: THREE.Vector3; // 3-element array of floats } From 446c98c82c438906c2a4baa873761aca2df978a9 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Fri, 11 Jul 2025 16:26:12 -0400 Subject: [PATCH 06/24] updating ultra node->elements --- package.json | 9 ++++----- src/vim-web/core-viewers/ultra/camera.ts | 8 ++++---- src/vim-web/core-viewers/ultra/raycaster.ts | 4 ++-- src/vim-web/core-viewers/ultra/rpcClient.ts | 20 ++++++++++++------- .../core-viewers/ultra/rpcSafeClient.ts | 7 ++----- src/vim-web/core-viewers/ultra/vim.ts | 14 ++++++------- tsconfig.json | 3 ++- 7 files changed, 34 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 1d7bccda..23f8b507 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vim-web", - "version": "0.4.1", + "version": "0.5.0-dev.2", "description": "A demonstration app built on top of the vim-webgl-viewer", "type": "module", "files": [ @@ -24,11 +24,10 @@ "dev": "vite", "eslint": "eslint --ext .js,.ts,.tsx src --fix", "documentation": "typedoc --entryPoints src/vim-web/index.ts --entryPointStrategy expand --out docs2 --excludeProtected --excludeExternals --excludePrivate", - "declarations": "tsc --project tsconfig.json --declaration --emitDeclarationOnly --outdir ./dist/types", - "build": "vite build --config vite.config.js && npm run declarations", - "package": "npm run build && npm publish", + "declarations": "tsc -p tsconfig.types.json", + "build": "vite build && npm run declarations", "publish:package": "npm run build && npm publish", - "publ ish:documentation": "npm run documentation && gh-pages -d docs2", + "publish:documentation": "npm run documentation && gh-pages -d docs2", "publish": "npm run publish:package && npm run publish:documentation" }, "devDependencies": { diff --git a/src/vim-web/core-viewers/ultra/camera.ts b/src/vim-web/core-viewers/ultra/camera.ts index e00c8924..8fc6dab6 100644 --- a/src/vim-web/core-viewers/ultra/camera.ts +++ b/src/vim-web/core-viewers/ultra/camera.ts @@ -157,16 +157,16 @@ export class Camera implements ICamera { /** * Frames specific nodes of a Vim model in the camera view * @param vim - The Vim model containing the nodes to frame - * @param nodes - Array of node indices to frame, or 'all' to frame the entire model + * @param elements - Array of element indices to frame, or 'all' to frame the entire model * @param blendTime - Duration of the camera animation in seconds (defaults to 0.5) * @returns Promise that resolves when the framing animation is complete */ - async frameVim(vim: Vim, nodes: number[] | 'all', blendTime: number = this._defaultBlendTime): Promise { + async frameVim(vim: Vim, elements: number[] | 'all', blendTime: number = this._defaultBlendTime): Promise { let segment: Segment | undefined - if (nodes === 'all') { + if (elements === 'all') { segment = await this._rpc.RPCFrameVim(vim.handle, blendTime); } else { - segment = await this._rpc.RPCFrameElements(vim.handle, nodes, blendTime); + segment = await this._rpc.RPCFrameElements(vim.handle, elements, blendTime); } this._savedPosition = this._savedPosition ?? segment return segment diff --git a/src/vim-web/core-viewers/ultra/raycaster.ts b/src/vim-web/core-viewers/ultra/raycaster.ts index 1040d3e1..9d64192c 100644 --- a/src/vim-web/core-viewers/ultra/raycaster.ts +++ b/src/vim-web/core-viewers/ultra/raycaster.ts @@ -68,10 +68,10 @@ export class Raycaster implements IUltraRaycaster { const test = await this._rpc.RPCPerformHitTest(position); if (!test) return undefined; - const vim = this._vims.getFromHandle(test.vimHandle); + const vim = this._vims.getFromHandle(test.vimIndex); if (!vim) return undefined; - const object = vim.getElement(test.elementIndex); + const object = vim.getElement(test.vimElementIndex); if (!object) return undefined; return new UltraRaycastResult( diff --git a/src/vim-web/core-viewers/ultra/rpcClient.ts b/src/vim-web/core-viewers/ultra/rpcClient.ts index aeb61eec..23900e1c 100644 --- a/src/vim-web/core-viewers/ultra/rpcClient.ts +++ b/src/vim-web/core-viewers/ultra/rpcClient.ts @@ -62,10 +62,9 @@ return this._socket.state.status === "connected" // RPC Generated Code readonly API_VERSION = "6.0.0" - RPCClearMaterialOverrides(componentHandle: number): void { + RPCClearMaterialOverrides(): void { const marshal = new Marshal(); marshal.writeString("RPCClearMaterialOverrides"); - marshal.writeUInt(componentHandle); this._socket.sendRPC(marshal); } @@ -410,19 +409,26 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } - RPCSetStateVim(componentHandle: number, state: number): void { + RPCSetStateElements(componentHandle: number, elementIndices: number[], state: number): void { const marshal = new Marshal(); - marshal.writeString("RPCSetStateVim"); + marshal.writeString("RPCSetStateElements"); marshal.writeUInt(componentHandle); + marshal.writeArrayOfUInt(elementIndices); marshal.writeUInt(state); this._socket.sendRPC(marshal); } - RPCSetStateElements(componentHandle: number, elementIndices: number[], state: number): void { + RPCSetStateScene(state: number): void { const marshal = new Marshal(); - marshal.writeString("RPCSetStateElements"); + marshal.writeString("RPCSetStateScene"); + marshal.writeUInt(state); + this._socket.sendRPC(marshal); + } + + RPCSetStateVim(componentHandle: number, state: number): void { + const marshal = new Marshal(); + marshal.writeString("RPCSetStateVim"); marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(elementIndices); marshal.writeUInt(state); this._socket.sendRPC(marshal); } diff --git a/src/vim-web/core-viewers/ultra/rpcSafeClient.ts b/src/vim-web/core-viewers/ultra/rpcSafeClient.ts index 09f34ca3..72b1badf 100644 --- a/src/vim-web/core-viewers/ultra/rpcSafeClient.ts +++ b/src/vim-web/core-viewers/ultra/rpcSafeClient.ts @@ -799,12 +799,9 @@ export class RpcSafeClient { * @param componentHandle - The unique identifier of the component * @throws {Error} If the component handle is invalid or INVALID_HANDLE */ - RPCClearMaterialOverrides(componentHandle: number): void { - // Validation - if (!Validation.isComponentHandle(componentHandle)) return - + RPCClearMaterialOverrides(): void { // Run - this.rpc.RPCClearMaterialOverrides(componentHandle) + this.rpc.RPCClearMaterialOverrides() } /******************************************************************************* diff --git a/src/vim-web/core-viewers/ultra/vim.ts b/src/vim-web/core-viewers/ultra/vim.ts index 643299dc..40d2c0bd 100644 --- a/src/vim-web/core-viewers/ultra/vim.ts +++ b/src/vim-web/core-viewers/ultra/vim.ts @@ -231,18 +231,18 @@ export class Vim implements IVim { this.scheduleColorUpdate(); } - clearColor(nodes: number[] | 'all'): void { - if (nodes === 'all') { + clearColor(elements: number[] | 'all'): void { + if (elements === 'all') { this._nodeColors.clear(); } else { - nodes.forEach((n) => this._nodeColors.delete(n)); + elements.forEach((n) => this._nodeColors.delete(n)); } if (!this.connected) return; - if (nodes === 'all') { - this._rpc.RPCClearMaterialOverrides(this._handle); + if (elements === 'all') { + this._rpc.RPCClearMaterialOverrides(); } else { - const ids = new Array(nodes.length).fill(MaterialHandles.Invalid); - this._rpc.RPCSetMaterialOverrides(this._handle, nodes, ids); + const ids = new Array(elements.length).fill(MaterialHandles.Invalid); + this._rpc.RPCSetMaterialOverrides(this._handle, elements, ids); } } diff --git a/tsconfig.json b/tsconfig.json index f49801ec..fab1f9b7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,8 @@ "jsx": "react-jsx", "target": "ESNext", "module": "ESNext", - + "rootDir": "src", + "outDir": "dist", "useDefineForClassFields": true, "allowSyntheticDefaultImports": true, "skipLibCheck": true, From 55b827234f0a257f5e0c27644de6375c715520bf Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Wed, 16 Jul 2025 13:54:32 -0400 Subject: [PATCH 07/24] updated ultra core to match rpcs --- src/vim-web/core-viewers/ultra/camera.ts | 2 +- src/vim-web/core-viewers/ultra/renderer.ts | 8 +- src/vim-web/core-viewers/ultra/rpcClient.ts | 87 ++-- .../core-viewers/ultra/rpcSafeClient.ts | 430 +++++++++++------- src/vim-web/core-viewers/ultra/viewer.ts | 4 + src/vim-web/core-viewers/ultra/vim.ts | 20 +- src/vim-web/core-viewers/ultra/visibility.ts | 10 +- src/vim-web/utils/validation.ts | 12 +- 8 files changed, 347 insertions(+), 226 deletions(-) diff --git a/src/vim-web/core-viewers/ultra/camera.ts b/src/vim-web/core-viewers/ultra/camera.ts index 8fc6dab6..cb4e6523 100644 --- a/src/vim-web/core-viewers/ultra/camera.ts +++ b/src/vim-web/core-viewers/ultra/camera.ts @@ -137,7 +137,7 @@ export class Camera implements ICamera { * @returns Promise that resolves when the framing animation is complete */ async frameAll (blendTime: number = this._defaultBlendTime): Promise { - const segment = await this._rpc.RPCFrameAll(blendTime) + const segment = await this._rpc.RPCFrameScene(blendTime) this._savedPosition = this._savedPosition ?? segment return segment } diff --git a/src/vim-web/core-viewers/ultra/renderer.ts b/src/vim-web/core-viewers/ultra/renderer.ts index 60dfba08..bdd9aba4 100644 --- a/src/vim-web/core-viewers/ultra/renderer.ts +++ b/src/vim-web/core-viewers/ultra/renderer.ts @@ -146,7 +146,7 @@ export class Renderer implements IRenderer { * @returns Current background blur */ get backgroundBlur(): number { - return this._settings.backGroundBlur; + return this._settings.backgroundBlur; } /** @@ -225,8 +225,8 @@ export class Renderer implements IRenderer { */ set backgroundBlur(value: number) { value = Validation.clamp01(value) - if (this._settings.backGroundBlur === value) return; - this._settings.backGroundBlur = value; + if (this._settings.backgroundBlur === value) return; + this._settings.backgroundBlur = value; this._updateLighting = true this.requestSettingsUpdate(); } @@ -244,7 +244,7 @@ export class Renderer implements IRenderer { } getBoundingBox(): Promise { - return this._rpc.RPCGetAABBForAll() + return this._rpc.RPCGetAABBForScene() } /** diff --git a/src/vim-web/core-viewers/ultra/rpcClient.ts b/src/vim-web/core-viewers/ultra/rpcClient.ts index 23900e1c..416983a5 100644 --- a/src/vim-web/core-viewers/ultra/rpcClient.ts +++ b/src/vim-web/core-viewers/ultra/rpcClient.ts @@ -90,10 +90,10 @@ return this._socket.state.status === "connected" return ret; } - RPCDestroyMaterialInstances(materialInstanceHandle: number[]): void { + RPCDestroyMaterialInstances(materialInstanceHandles: number[]): void { const marshal = new Marshal(); marshal.writeString("RPCDestroyMaterialInstances"); - marshal.writeArrayOfUInt(materialInstanceHandle); + marshal.writeArrayOfUInt(materialInstanceHandles); this._socket.sendRPC(marshal); } @@ -121,58 +121,58 @@ return this._socket.state.status === "connected" return ret; } - async RPCFrameAll(blendTime: number): Promise { + async RPCFrameElements(vimIndex: number, elementIndices: number[], blendTime: number): Promise { const marshal = new Marshal(); - marshal.writeString("RPCFrameAll"); + marshal.writeString("RPCFrameElements"); + marshal.writeUInt(vimIndex); + marshal.writeArrayOfUInt(elementIndices); marshal.writeFloat(blendTime); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readSegment(); return ret; } - async RPCFrameElements(componentHandle: number, elementIndices: number[], blendTime: number): Promise { + async RPCFrameScene(blendTime: number): Promise { const marshal = new Marshal(); - marshal.writeString("RPCFrameElements"); - marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(elementIndices); + marshal.writeString("RPCFrameScene"); marshal.writeFloat(blendTime); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readSegment(); return ret; } - async RPCFrameVim(componentHandle: number, blendTime: number): Promise { + async RPCFrameVim(vimIndex: number, blendTime: number): Promise { const marshal = new Marshal(); marshal.writeString("RPCFrameVim"); - marshal.writeUInt(componentHandle); + marshal.writeUInt(vimIndex); marshal.writeFloat(blendTime); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readSegment(); return ret; } - async RPCGetAABBForAll(): Promise { + async RPCGetAABBForElements(vimIndex: number, elementIndices: number[]): Promise { const marshal = new Marshal(); - marshal.writeString("RPCGetAABBForAll"); + marshal.writeString("RPCGetAABBForElements"); + marshal.writeUInt(vimIndex); + marshal.writeArrayOfUInt(elementIndices); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readBox3(); return ret; } - async RPCGetAABBForElements(componentHandle: number, elementIndices: number[]): Promise { + async RPCGetAABBForScene(): Promise { const marshal = new Marshal(); - marshal.writeString("RPCGetAABBForElements"); - marshal.writeUInt(componentHandle); - marshal.writeArrayOfUInt(elementIndices); + marshal.writeString("RPCGetAABBForScene"); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readBox3(); return ret; } - async RPCGetAABBForVim(componentHandle: number): Promise { + async RPCGetAABBForVim(vimIndex: number): Promise { const marshal = new Marshal(); marshal.writeString("RPCGetAABBForVim"); - marshal.writeUInt(componentHandle); + marshal.writeUInt(vimIndex); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readBox3(); return ret; @@ -194,19 +194,27 @@ return this._socket.state.status === "connected" return ret; } - async RPCGetElementCount(componentHandle: number): Promise { + async RPCGetElementCountForScene(): Promise { const marshal = new Marshal(); - marshal.writeString("RPCGetElementCount"); - marshal.writeUInt(componentHandle); + marshal.writeString("RPCGetElementCountForScene"); + const returnMarshal = await this._socket.sendRPCWithReturn(marshal); + const ret = returnMarshal.readUInt(); + return ret; + } + + async RPCGetElementCountForVim(vimIndex: number): Promise { + const marshal = new Marshal(); + marshal.writeString("RPCGetElementCountForVim"); + marshal.writeUInt(vimIndex); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readUInt(); return ret; } - async RPCGetElementIds(componentHandle: number): Promise { + async RPCGetElementIds(vimIndex: number): Promise { const marshal = new Marshal(); marshal.writeString("RPCGetElementIds"); - marshal.writeUInt(componentHandle); + marshal.writeUInt(vimIndex); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readArrayOfUInt64(); return ret; @@ -220,10 +228,10 @@ return this._socket.state.status === "connected" return ret; } - async RPCGetRoomElements(componentHandle: number): Promise { + async RPCGetRoomElements(vimIndex: number): Promise { const marshal = new Marshal(); marshal.writeString("RPCGetRoomElements"); - marshal.writeUInt(componentHandle); + marshal.writeUInt(vimIndex); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readArrayOfUInt(); return ret; @@ -237,10 +245,10 @@ return this._socket.state.status === "connected" return ret; } - async RPCGetVimLoadingState(componentHandle: number): Promise { + async RPCGetVimLoadingState(vimIndex: number): Promise { const marshal = new Marshal(); marshal.writeString("RPCGetVimLoadingState"); - marshal.writeUInt(componentHandle); + marshal.writeUInt(vimIndex); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readVimStatus(); return ret; @@ -393,10 +401,10 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } - RPCSetMaterialOverrides(componentHandle: number, elementIndices: number[], materialInstanceHandles: number[]): void { + RPCSetMaterialOverridesForElements(vimIndex: number, elementIndices: number[], materialInstanceHandles: number[]): void { const marshal = new Marshal(); - marshal.writeString("RPCSetMaterialOverrides"); - marshal.writeUInt(componentHandle); + marshal.writeString("RPCSetMaterialOverridesForElements"); + marshal.writeUInt(vimIndex); marshal.writeArrayOfUInt(elementIndices); marshal.writeArrayOfUInt(materialInstanceHandles); this._socket.sendRPC(marshal); @@ -409,10 +417,10 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } - RPCSetStateElements(componentHandle: number, elementIndices: number[], state: number): void { + RPCSetStateElements(vimIndex: number, elementIndices: number[], state: number): void { const marshal = new Marshal(); marshal.writeString("RPCSetStateElements"); - marshal.writeUInt(componentHandle); + marshal.writeUInt(vimIndex); marshal.writeArrayOfUInt(elementIndices); marshal.writeUInt(state); this._socket.sendRPC(marshal); @@ -425,18 +433,18 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } - RPCSetStateVim(componentHandle: number, state: number): void { + RPCSetStateVim(vimIndex: number, state: number): void { const marshal = new Marshal(); marshal.writeString("RPCSetStateVim"); - marshal.writeUInt(componentHandle); + marshal.writeUInt(vimIndex); marshal.writeUInt(state); this._socket.sendRPC(marshal); } - RPCSetStatesElements(componentHandle: number, elementIndices: number[], states: number[]): void { + RPCSetStatesElements(vimIndex: number, elementIndices: number[], states: number[]): void { const marshal = new Marshal(); marshal.writeString("RPCSetStatesElements"); - marshal.writeUInt(componentHandle); + marshal.writeUInt(vimIndex); marshal.writeArrayOfUInt(elementIndices); marshal.writeArrayOfUInt(states); this._socket.sendRPC(marshal); @@ -468,11 +476,4 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } - RPCUnloadVim(componentHandle: number): void { - const marshal = new Marshal(); - marshal.writeString("RPCUnloadVim"); - marshal.writeUInt(componentHandle); - this._socket.sendRPC(marshal); - } - } diff --git a/src/vim-web/core-viewers/ultra/rpcSafeClient.ts b/src/vim-web/core-viewers/ultra/rpcSafeClient.ts index 72b1badf..111ca46d 100644 --- a/src/vim-web/core-viewers/ultra/rpcSafeClient.ts +++ b/src/vim-web/core-viewers/ultra/rpcSafeClient.ts @@ -4,67 +4,166 @@ import { Validation } from "../../utils"; import { batchArray, batchArrays } from "../../utils/array" import { INVALID_HANDLE } from "./viewer" import { VisibilityState } from "./visibility"; - +/** + * Default maximum number of items to include in a single RPC batch operation. + */ const defaultBatchSize = 10000 -//TODO: Share both VIMSource +/** + * Describes the source location and optional authentication for loading a VIM file. + */ export type VimSource = { + /** + * URL to the VIM file. + * Can be a local path (file://) or remote URL (http:// or https://). + */ url: string; - authToken? : string; + + /** + * Optional authentication token for accessing protected resources. + */ + authToken?: string; } +/** + * Represents the loading state and progress for a single vim. + */ export type VimLoadingState = { + /** + * Current loading status. + */ status: VimLoadingStatus; + + /** + * Loading progress as a percentage from 0 to 100. + */ progress: number; } +/** + * Defines supported input modes for camera control in the viewer. + */ export enum InputMode { + /** + * Orbit mode — rotates around a fixed point. + */ Orbit = 'orbit', + + /** + * Free mode — allows unrestricted movement. + */ Free = 'free' } +/** + * Scene-wide rendering and lighting configuration options. + */ export type SceneSettings = { - toneMappingWhitePoint: number - hdrScale: number - hdrBackgroundScale: number - hdrBackgroundSaturation: number - backGroundBlur: number - backgroundColor: RpcTypes.RGBA + /** + * White point for tone mapping (clamped between 0 and 1). + */ + toneMappingWhitePoint: number; + + /** + * Global HDR intensity multiplier (floored to 0). + */ + hdrScale: number; + + /** + * HDR scale for the background (clamped between 0 and 1). + */ + hdrBackgroundScale: number; + + /** + * Background saturation (clamped between 0 and 1). + */ + hdrBackgroundSaturation: number; + + /** + * Background blur strength (clamped between 0 and 1). + */ + backgroundBlur: number; + + /** + * Background color in linear RGBA format. + */ + backgroundColor: RpcTypes.RGBA; } +/** + * Default scene settings used when none are explicitly provided. + */ export const defaultSceneSettings: SceneSettings = { toneMappingWhitePoint: 0.1009, hdrScale: 1.37, hdrBackgroundScale: 1.0, hdrBackgroundSaturation: 1.0, - backGroundBlur: 1.0, + backgroundBlur: 1.0, backgroundColor: new RpcTypes.RGBA(0.9, 0.9, 0.9, 1.0) } +/** + * Enumerates the possible states of VIM file loading. + */ export enum VimLoadingStatus { + /** + * No known loading activity. + */ Unknown = 0, + + /** + * Actively loading VIM data. + */ Loading = 1, + + /** + * Downloading VIM file from a remote source. + */ Downloading = 2, + + /** + * Load completed successfully. + */ Done = 3, + + /** + * Download failed (e.g., due to network or permission issues). + */ FailedToDownload = 4, + + /** + * VIM file could not be parsed or initialized correctly. + */ FailedToLoad = 5 } /** * Provides safe, validated methods to interact with the RpcClient. - * This class wraps the raw RPC methods with input validation and batching support for large operations. + * This class wraps low-level RPC calls with input validation, error handling, + * and batching support to ensure robustness and performance when dealing with large data. */ export class RpcSafeClient { private readonly rpc: RpcClient private readonly batchSize: number + /** + * The URL used by the underlying RPC connection. + */ get url(): string { return this.rpc.url } + /** + * Indicates whether the RPC client is currently connected. + */ get connected(): boolean { return this.rpc.connected } + /** + * Creates a new RpcSafeClient instance. + * @param rpc - The underlying RpcClient used for communication + * @param batchSize - Maximum size of batched data for operations (default: 10000) + */ constructor(rpc: RpcClient, batchSize: number = defaultBatchSize) { this.rpc = rpc this.batchSize = batchSize @@ -77,9 +176,10 @@ export class RpcSafeClient { ******************************************************************************/ /** - * Initializes and starts the scene with specified settings. + * Initializes and starts the scene with the given settings. * @param settings - Optional partial scene settings to override defaults - * @remarks If no settings are provided, default values will be used + * @returns Promise resolving to true if the scene started successfully, false otherwise + * @remarks Missing values will be filled from {@link defaultSceneSettings} */ async RPCStartScene(settings?: Partial): Promise { const s = { ...defaultSceneSettings, ...(settings ?? {}) } @@ -90,7 +190,7 @@ export class RpcSafeClient { Validation.min0(s.hdrScale), Validation.clamp01(s.hdrBackgroundScale), Validation.clamp01(s.hdrBackgroundSaturation), - Validation.clamp01(s.backGroundBlur), + Validation.clamp01(s.backgroundBlur), Validation.clampRGBA01(s.backgroundColor) ), false @@ -98,8 +198,8 @@ export class RpcSafeClient { } /** - * Sets the lighting settings for the scene. - * @param settings - The lighting settings to apply + * Updates the scene’s lighting configuration. + * @param settings - The complete lighting and background settings to apply */ RPCSetLighting(settings: SceneSettings): void { const s = settings @@ -108,56 +208,83 @@ export class RpcSafeClient { Validation.min0(s.hdrScale), Validation.clamp01(s.hdrBackgroundScale), Validation.clamp01(s.hdrBackgroundSaturation), - Validation.clamp01(s.backGroundBlur), + Validation.clamp01(s.backgroundBlur), Validation.clampRGBA01(s.backgroundColor) ) } - + /** + * Retrieves the total number of elements across the entire scene. + * @returns Promise resolving to the total number of elements (0 on failure). + */ + RPCGetElementCountForScene(): Promise { + return this.safeCall( + () => this.rpc.RPCGetElementCountForScene(), 0) + } + + /** + * Retrieves the number of elements within a specific loaded vim. + * @param vimIndex - Index of the loaded vim to query + * @returns Promise resolving to the element count (0 on failure) + */ + RPCGetElementCountForVim(vimIndex: number): Promise { + return this.safeCall( + () => this.rpc.RPCGetElementCountForVim(vimIndex), 0) + } /******************************************************************************* - * NODE VISIBILITY METHODS - * Methods for controlling node visibility, including show/hide, ghosting, + * ELEMENTS VISIBILITY METHODS + * Methods for controlling element visibility, including show/hide, ghosting, * and highlighting functionality. ******************************************************************************/ - /** - * Highlights specified nodes in a component. - * Large node arrays are automatically processed in batches. - * @param componentHandle - The component containing the nodes - * @param nodes - Array of node indices to highlight - * @throws {Error} If the component handle is invalid or nodes array is invalid + * Sets a single visibility state for given elements within a loaded vim. + * The operation is automatically split into batches if the array is large. + * + * @param vimIndex - The index of the loaded vim containing the elements + * @param vimElementIndices - Array of vim-based element indices to apply the state to + * @param state - The visibility state to apply (e.g., VISIBLE, HIDDEN) */ - RPCSetStateElements(componentHandle: number, elements: number[], state: VisibilityState): void { - if(elements.length === 0) return - // Validation - if (!Validation.isComponentHandle(componentHandle)) return - if (!Validation.areComponentHandles(elements)) return + RPCSetStateElements(vimIndex: number, vimElementIndices: number[], state: VisibilityState): void { + if (vimElementIndices.length === 0) return + if (!Validation.isIndex(vimIndex)) return + if (!Validation.areIndices(vimElementIndices)) return - // Run - const batches = batchArray(elements, this.batchSize) + const batches = batchArray(vimElementIndices, this.batchSize) for (const batch of batches) { - this.rpc.RPCSetStateElements(componentHandle, batch, state) + this.rpc.RPCSetStateElements(vimIndex, batch, state) } } - RPCSetStatesElements(componentHandle: number, elements: number[], states: VisibilityState[]): void { - if (!Validation.isComponentHandle(componentHandle)) return - if (!Validation.areComponentHandles(elements)) return - if (!Validation.areSameLength(elements, states)) return - + /** + * Sets individual visibility states for multiple elements in a vim. + * Each element receives a corresponding visibility state from the input array. + * The operation is automatically split into batches if the array is large. + * + * @param vimIndex - The index of the loaded vim + * @param vimElementIndices - Array of vim-based element indices + * @param states - Array of visibility states to apply, one per element + */ + RPCSetStatesElements(vimIndex: number, vimElementIndices: number[], states: VisibilityState[]): void { + if (!Validation.isIndex(vimIndex)) return + if (!Validation.areIndices(vimElementIndices)) return + if (!Validation.areSameLength(vimElementIndices, states)) return - const batches = batchArrays(elements, states, this.batchSize) + const batches = batchArrays(vimElementIndices, states, this.batchSize) for (const [batchedElements, batchedStates] of batches) { - this.rpc.RPCSetStatesElements(componentHandle, batchedElements, batchedStates) + this.rpc.RPCSetStatesElements(vimIndex, batchedElements, batchedStates) } } - - - RPCSetStateVim(componentHandle: number, state: VisibilityState): void { - if (!Validation.isComponentHandle(componentHandle)) return - this.rpc.RPCSetStateVim(componentHandle, state) + /** + * Applies a single visibility state to all elements of a loaded vim. + * + * @param vimIndex - The index of the loaded vim + * @param state - The visibility state to apply (e.g., VISIBLE, HIDDEN) + */ + RPCSetStateVim(vimIndex: number, state: VisibilityState): void { + if (!Validation.isIndex(vimIndex)) return + this.rpc.RPCSetStateVim(vimIndex, state) } @@ -172,7 +299,6 @@ export class RpcSafeClient { * @param color - The color of the text * @param text - The content to display * @returns Promise resolving to the handle of the created text component - * @throws {Error} If the text is empty */ async RPCCreateText( position: RpcTypes.Vector3, @@ -193,11 +319,10 @@ export class RpcSafeClient { /** * Destroys a text component, removing it from the scene. * @param componentHandle - The handle of the text component to destroy - * @throws {Error} If the component handle is invalid */ RPCDestroyText(componentHandle: number): void { // Validation - if (!Validation.isComponentHandle(componentHandle)) return + if (!Validation.isIndex(componentHandle)) return // Run this.rpc.RPCDestroyText(componentHandle) @@ -208,10 +333,18 @@ export class RpcSafeClient { * Methods for controlling section box visibility and position. ******************************************************************************/ + /** + * Enables or disables the section box. + * @param enable - True to enable the section box, false to disable it + */ RPCEnableSectionBox(enable: boolean): void { this.rpc.RPCEnableSectionBox(enable) } + /** + * Sets the parameters of the section box. + * @param state - The new section box state, including visibility and bounding box + */ RPCSetSectionBox(state: RpcTypes.SectionBoxState): void { this.rpc.RPCSetSectionBox( { @@ -220,6 +353,10 @@ export class RpcSafeClient { }) } + /** + * Retrieves the current section box state. + * @returns Promise resolving to the section box state or undefined on failure + */ async RPCGetSectionBox(): Promise { return await this.safeCall( () => this.rpc.RPCGetSectionBox(), @@ -292,66 +429,66 @@ export class RpcSafeClient { /** * Retrieves the axis-aligned bounding box (AABB) that encompasses the entire scene. - * This includes all loaded geometry across all VIM components. + * This includes all loaded geometry across all loaded vims. * * @returns Promise resolving to the global AABB of the scene, or undefined on failure */ - RPCGetAABBForAll(): Promise { + RPCGetAABBForScene(): Promise { return this.safeCall( - () => this.rpc.RPCGetAABBForAll(), + () => this.rpc.RPCGetAABBForScene(), undefined ) } /** - * Retrieves the axis-aligned bounding box (AABB) for a specific VIM component. - * This bounding box represents the spatial bounds of all geometry within the given component. + * Retrieves the axis-aligned bounding box (AABB) for a specific loaded vim. + * This bounding box represents the spatial bounds of all geometry within the given loaded vim. * - * @param componentHandle - The handle of the VIM component to query - * @returns Promise resolving to the component’s bounding box, or undefined on failure + * @param vimIndex - The index of the loaded vim to query + * @returns Promise resolving to the vim bounding box, or undefined on failure */ - async RPCGetAABBForVim(componentHandle: number): Promise { + async RPCGetAABBForVim(vimIndex: number): Promise { + if (!Validation.isIndex(vimIndex)) return undefined return await this.safeCall( - () => this.rpc.RPCGetAABBForVim(componentHandle), + () => this.rpc.RPCGetAABBForVim(vimIndex), undefined ) } /** - * Calculates the bounding box for specified nodes in a component. - * Large node arrays are automatically processed in batches for better performance. - * @param componentHandle - The component containing the nodes - * @param elements - Array of node indices to calculate bounds for - * @returns Promise resolving to the combined bounding box - * @throws {Error} If the component handle is invalid or nodes array is invalid + * Calculates the bounding box for specified elements of a loaded vim. + * Large element arrays are automatically processed in batches. + * @param vimIndex - The index of the loaded vim + * @param vimElementIndices - Array of vim-based element indices to calculate bounds for + * @returns Promise resolving to the combined bounding box or undefined on failure */ async RPCGetAABBForElements( - componentHandle: number, - elements: number[] + vimIndex: number, + vimElementIndices: number[] ): Promise { // Validation - if (!Validation.isComponentHandle(componentHandle)) return - if (!Validation.areComponentHandles(elements)) return + if (!Validation.isIndex(vimIndex)) return + if (!Validation.areIndices(vimElementIndices)) return // Run return await this.safeCall( - () => this.RPCGetAABBForElementsBatched(componentHandle, elements), + () => this.RPCGetAABBForElementsBatched(vimIndex, vimElementIndices), undefined ) } private async RPCGetAABBForElementsBatched( - componentHandle: number, - elements: number[] + vimIndex: number, + vimElementIndices: number[] ): Promise { - if(elements.length === 0){ + if(vimElementIndices.length === 0){ return new RpcTypes.Box3() } - const batches = batchArray(elements, this.batchSize) + const batches = batchArray(vimElementIndices, this.batchSize) const promises = batches.map(async (batch) => { - const aabb = await this.rpc.RPCGetAABBForElements(componentHandle, batch) + const aabb = await this.rpc.RPCGetAABBForElements(vimIndex, batch) const v1 = new RpcTypes.Vector3(aabb.min.x, aabb.min.y, aabb.min.z) const v2 = new RpcTypes.Vector3(aabb.max.x, aabb.max.y, aabb.max.z) return new RpcTypes.Box3(v1, v2) @@ -363,68 +500,66 @@ export class RpcSafeClient { } /** - * Frames the camera to show all components in the scene. + * Frames the camera to show all elements in the scene. * @param blendTime - Duration of the camera transition in seconds (non-negative) * @returns Promise resolving to camera segment representing the final position */ - async RPCFrameAll(blendTime: number): Promise { + async RPCFrameScene(blendTime: number): Promise { // Validation blendTime = Validation.clamp01(blendTime) // Run return await this.safeCall( - () => this.rpc.RPCFrameAll(blendTime), + () => this.rpc.RPCFrameScene(blendTime), undefined ) } /** - * Frames a specific VIM component in the scene. - * @param componentHandle - The handle of the VIM component to frame + * Frames a specific vim in the scene. + * @param vimIndex - The index of the loaded vim to frame * @param blendTime - Duration of the camera transition in seconds (non-negative) * @returns Promise resolving to camera segment representing the final position - * @throws {Error} If the component handle is invalid */ - async RPCFrameVim(componentHandle: number, blendTime: number): Promise { + async RPCFrameVim(vimIndex: number, blendTime: number): Promise { // Validation - if (!Validation.isComponentHandle(componentHandle)) return + if (!Validation.isIndex(vimIndex)) return blendTime = Validation.clamp01(blendTime) // Run return await this.safeCall( - () => this.rpc.RPCFrameVim(componentHandle, blendTime), + () => this.rpc.RPCFrameVim(vimIndex, blendTime), undefined ) } /** - * Frames specific instances within a component. For large numbers of instances, - * automatically switches to bounding box framing for better performance. - * @param componentHandle - The component containing the instances - * @param elements - Array of node indices to frame + * Frames specific elements of a loaded vim. + * Automatically batches large arrays of elements. + * @param vimIndex - The index of the loaded vim + * @param vimElementIndices - Array of vim-based element indices to frame * @param blendTime - Duration of the camera transition in seconds (non-negative) * @returns Promise resolving to camera segment representing the final position - * @throws {Error} If the component handle is invalid or nodes array is empty */ async RPCFrameElements( - componentHandle: number, - elements: number[], + vimIndex: number, + vimElementIndices: number[], blendTime: number ): Promise { // Validation - if (!Validation.isComponentHandle(componentHandle)) return - if (!Validation.areComponentHandles(elements)) return + if (!Validation.isIndex(vimIndex)) return + if (!Validation.areIndices(vimElementIndices)) return blendTime = Validation.clamp01(blendTime) // Run - if (elements.length < this.batchSize) { + if (vimElementIndices.length < this.batchSize) { return await this.safeCall( - () => this.rpc.RPCFrameElements(componentHandle, elements, blendTime), + () => this.rpc.RPCFrameElements(vimIndex, vimElementIndices, blendTime), undefined ) } else { const box = await this.safeCall( - () => this.RPCGetAABBForElementsBatched(componentHandle, elements), + () => this.RPCGetAABBForElementsBatched(vimIndex, vimElementIndices), undefined ) if(!box) return undefined @@ -439,7 +574,6 @@ export class RpcSafeClient { * Frames the camera to show a specific bounding box. * @param box - The bounding box to frame * @param blendTime - Duration of the camera transition in seconds (non-negative) - * @throws {Error} If the box is invalid (min values must be less than max values) */ async RPCFrameAABB(box: RpcTypes.Box3, blendTime: number): Promise { // Validation @@ -461,7 +595,6 @@ export class RpcSafeClient { /** * Sets the camera movement speed. * @param speed - The desired movement speed (must be positive) - * @throws {Error} If speed is not positive */ RPCSetCameraSpeed(speed: number) { // Validation @@ -471,15 +604,18 @@ export class RpcSafeClient { this.rpc.RPCSetCameraSpeed(speed) } + /** + * Sets the camera control mode. + * @param mode - The desired input mode (e.g., {@link InputMode.Orbit} or {@link InputMode.Free}) + */ RPCSetCameraMode(mode: InputMode): void { this.rpc.RPCSetCameraMode(mode === InputMode.Orbit) } /** * Sets the viewer's aspect ratio. - * @param width - The width component of the aspect ratio - * @param height - The height component of the aspect ratio - * @throws {Error} If width or height are not positive integers + * @param width - The width of the desired aspect ratio + * @param height - The height of the desired aspect ratio */ RPCSetCameraAspectRatio(width: number, height: number): void { // Validation @@ -492,14 +628,13 @@ export class RpcSafeClient { /******************************************************************************* * VIM FILE MANAGEMENT METHODS - * Methods for loading, unloading, and managing VIM files and components. + * Methods for loading, unloading, and managing VIM files. ******************************************************************************/ /** * Loads a VIM file from the local filesystem. * @param source - The path to the VIM file (supports file:// protocol) - * @returns Promise resolving to the handle of the loaded VIM component - * @throws {Error} If the filename is invalid or empty + * @returns Promise resolving to the index of the loaded vim */ async RPCLoadVim(source: VimSource): Promise { // Validation @@ -515,9 +650,8 @@ export class RpcSafeClient { /** * Loads a VIM file from a remote URL. - * @param url - The URL of the VIM file to load - * @returns Promise resolving to the handle of the loaded VIM component - * @throws {Error} If the URL is invalid + * @param source - The URL or file path of the VIM file to load + * @returns Promise resolving to the index of the loaded vim */ async RPCLoadVimURL(source: VimSource): Promise { // Validation @@ -531,20 +665,19 @@ export class RpcSafeClient { } /** - * Retrieves the current loading state and progress of a VIM component. - * @param componentHandle - The handle of the VIM component + * Retrieves the current loading state and progress of a vim. + * @param vimIndex - The index of the vim being loaded * @returns Promise resolving to the current loading state and progress - * @throws {Error} If the component handle is invalid */ - async RPCGetVimLoadingState(componentHandle: number): Promise { + async RPCGetVimLoadingState(vimIndex: number): Promise { // Validation - if (!Validation.isComponentHandle(componentHandle)) { + if (!Validation.isIndex(vimIndex)) { return { status: VimLoadingStatus.Unknown, progress: 0 } } // Run const result = await this.safeCall( - () => this.rpc.RPCGetVimLoadingState(componentHandle), + () => this.rpc.RPCGetVimLoadingState(vimIndex), { status: VimLoadingStatus.Unknown, progress: 0 } ) @@ -556,20 +689,7 @@ export class RpcSafeClient { } /** - * Unloads a VIM component and frees associated resources. - * @param componentHandle - The handle of the component to unload - * @throws {Error} If the component handle is invalid - */ - RPCUnloadVim(componentHandle: number): void { - // Validation - if (!Validation.isComponentHandle(componentHandle)) return - - // Run - this.rpc.RPCUnloadVim(componentHandle) - } - - /** - * Clears the entire scene, removing all components and resetting to initial state. + * Clears the entire scene, unloading all vims and resetting to initial state. */ RPCUnloadAll(): void { this.rpc.RPCUnloadAll() @@ -587,7 +707,7 @@ export class RpcSafeClient { /** * Performs hit testing at a specified screen position. * @param pos - Normalized screen coordinates (0-1, 0-1) - * @returns Promise resolving to hit test result if something was hit, undefined otherwise + * @returns Promise resolving to hit test result if a valid hit was detected, undefined otherwise */ async RPCPerformHitTest(pos: RpcTypes.Vector2): Promise { // Validation @@ -598,7 +718,7 @@ export class RpcSafeClient { () => this.rpc.RPCPerformHitTest(pos), undefined ) - if (!result || result.nodeIndex < 0){ + if (!result || result.vimIndex === INVALID_HANDLE) { return undefined } return result @@ -609,7 +729,6 @@ export class RpcSafeClient { * @param position - The normalized screen coordinates (0-1, 0-1) * @param mouseButton - The mouse button code (0=left, 1=middle, 2=right) * @param down - True if button is pressed down, false if released - * @throws {Error} If mouseButton is not a valid positive integer */ RPCMouseButtonEvent( position: RpcTypes.Vector2, @@ -628,7 +747,6 @@ export class RpcSafeClient { * Sends a mouse double-click event to the viewer. * @param position - The normalized screen coordinates (0-1, 0-1) * @param mouseButton - The mouse button code (0=left, 1=middle, 2=right) - * @throws {Error} If mouseButton is not a valid positive integer */ RPCMouseDoubleClickEvent( position: RpcTypes.Vector2, @@ -669,7 +787,6 @@ export class RpcSafeClient { * Sends a mouse selection event to the viewer. * @param position - The normalized screen coordinates (0-1, 0-1) * @param mouseButton - The mouse button code (0=left, 1=middle, 2=right) - * @throws {Error} If mouseButton is not a valid positive integer */ RPCMouseSelectEvent( position: RpcTypes.Vector2, @@ -708,7 +825,6 @@ export class RpcSafeClient { * @param smoothness - The smoothness value to apply (clamped between 0 and 1) * @param colors - Array of colors for each material instance * @returns Array of handles for the created material instances - * @throws {Error} If the material handle is invalid or smoothness is out of range */ async RPCCreateMaterialInstances( materialHandle: MaterialHandle, @@ -742,62 +858,58 @@ export class RpcSafeClient { /** * Destroys multiple material instances, freeing associated resources. - * @param materialInstanceHandle - Array of handles for material instances to destroy - * @throws {Error} If any handle in the array is invalid + * @param materialInstanceHandles - Array of handles for material instances to destroy */ - RPCDestroyMaterialInstances(materialInstanceHandle: number[]): void { + RPCDestroyMaterialInstances(materialInstanceHandles: number[]): void { // Validation - if (!Validation.areComponentHandles(materialInstanceHandle)) return + if (!Validation.areIndices(materialInstanceHandles)) return // Run - this.rpc.RPCDestroyMaterialInstances(materialInstanceHandle) + this.rpc.RPCDestroyMaterialInstances(materialInstanceHandles) } /** - * Sets material overrides for specific nodes in a component. - * Large arrays are automatically processed in batches for better performance. - * @param componentHandle - The component containing the nodes - * @param nodes - Array of node indices to override - * @param materialInstanceHandles - Array of material instance handles to apply (must match nodes length) - * @throws {Error} If arrays have different lengths or any handle is invalid + * Sets material overrides for specific elements in a loaded vim. + * Large arrays are automatically processed in batches. + * @param vimIndex - The index of the loaded vim + * @param vimElementIndices - Array of vim-based element indices to override + * @param materialInstanceHandles - Array of material instance handles to apply (must match element length) */ - RPCSetMaterialOverrides( - componentHandle: number, - nodes: number[], + RPCSetMaterialOverridesForElements( + vimIndex: number, + vimElementIndices: number[], materialInstanceHandles: number[] ): void { // Validation - if (!Validation.areSameLength(nodes, materialInstanceHandles)) return - if (!Validation.isComponentHandle(componentHandle)) return - if (!Validation.areComponentHandles(nodes)) return + if (!Validation.areSameLength(vimElementIndices, materialInstanceHandles)) return + if (!Validation.isIndex(vimIndex)) return + if (!Validation.areIndices(vimElementIndices)) return if (!Validation.areIntegers(materialInstanceHandles)) return // Run this.setMaterialOverridesBatched( - componentHandle, - nodes, + vimIndex, + vimElementIndices, materialInstanceHandles ) } private setMaterialOverridesBatched( - componentHandle: number, - nodes: number[], + vimIndex: number, + vimElementIndices: number[], materialInstanceHandles: number[] ): void { // Run - const batches = batchArrays(nodes, materialInstanceHandles, this.batchSize) + const batches = batchArrays(vimElementIndices, materialInstanceHandles, this.batchSize) - for (const [batchedNodes, batchedMaterials] of batches) { - this.rpc.RPCSetMaterialOverrides(componentHandle, batchedNodes, batchedMaterials) + for (const [batchedElements, batchedMaterials] of batches) { + this.rpc.RPCSetMaterialOverridesForElements(vimIndex, batchedElements, batchedMaterials) } } /** - * Clears all material overrides for the specified component, restoring default materials. - * @param componentHandle - The unique identifier of the component - * @throws {Error} If the component handle is invalid or INVALID_HANDLE + * Clears all material overrides for the entire scene. */ RPCClearMaterialOverrides(): void { // Run diff --git a/src/vim-web/core-viewers/ultra/viewer.ts b/src/vim-web/core-viewers/ultra/viewer.ts index eada1c90..3d2fdeb3 100644 --- a/src/vim-web/core-viewers/ultra/viewer.ts +++ b/src/vim-web/core-viewers/ultra/viewer.ts @@ -297,6 +297,10 @@ export class Viewer { this._vims.clear() } + getElement3Ds() : Promise { + return this.rpc.RPCGetElementCountForScene() + } + /** * Disposes all resources used by the viewer and disconnects from the server. */ diff --git a/src/vim-web/core-viewers/ultra/vim.ts b/src/vim-web/core-viewers/ultra/vim.ts index 40d2c0bd..8164b498 100644 --- a/src/vim-web/core-viewers/ultra/vim.ts +++ b/src/vim-web/core-viewers/ultra/vim.ts @@ -4,7 +4,7 @@ import type { ILogger } from './logger'; import { ColorManager } from './colorManager'; import { Element3D } from './element3d'; import { LoadRequest } from './loadRequest'; -import { VisibilityState, StateSynchronizer } from './visibility'; +import { VisibilityState, VisibilitySynchronizer } from './visibility'; import { Renderer } from './renderer'; import { MaterialHandles } from './rpcClient'; import { RpcSafeClient, VimLoadingStatus, VimSource } from './rpcSafeClient'; @@ -25,7 +25,8 @@ export class Vim implements IVim { private _logger: ILogger; // The StateSynchronizer wraps a StateTracker and handles RPC synchronization. - readonly visibility: StateSynchronizer; + // Should be private + readonly visibility: VisibilitySynchronizer; // Color tracking remains unchanged. private _nodeColors: Map = new Map(); @@ -34,6 +35,7 @@ export class Vim implements IVim { // Delayed update flag. private _updateScheduled: boolean = false; + private _elementCount: number = 0; private _objects: Map = new Map(); constructor( @@ -50,7 +52,7 @@ export class Vim implements IVim { this._logger = logger; // Instantiate the synchronizer with a new StateTracker. - this.visibility = new StateSynchronizer( + this.visibility = new VisibilitySynchronizer( this._rpc, () => this._handle, () => this.connected, @@ -76,7 +78,10 @@ export class Vim implements IVim { throw new Error('Method not implemented.'); } getAllElements(): Element3D[] { - throw new Error('Method not implemented.'); + for(var i = 0; i < this._elementCount; i++) { + this.getElement(i); + } + return Array.from(this._objects.values()); } get handle(): number { @@ -113,7 +118,6 @@ export class Vim implements IVim { this._request?.error('cancelled', 'The request was cancelled'); this._request = undefined; if (this.connected) { - this._rpc.RPCUnloadVim(this._handle); this._handle = -1; } } @@ -136,12 +140,12 @@ export class Vim implements IVim { case VimLoadingStatus.FailedToDownload: case VimLoadingStatus.FailedToLoad: case VimLoadingStatus.Unknown: - this._rpc.RPCUnloadVim(handle); const details = await this._rpc.RPCGetLastError(); const error = this.getErrorType(state.status); return result.error(error, details); case VimLoadingStatus.Done: this._handle = handle; + this._elementCount = await this._rpc.RPCGetElementCountForVim(handle); return result.success(this); } } catch (e) { @@ -242,7 +246,7 @@ export class Vim implements IVim { this._rpc.RPCClearMaterialOverrides(); } else { const ids = new Array(elements.length).fill(MaterialHandles.Invalid); - this._rpc.RPCSetMaterialOverrides(this._handle, elements, ids); + this._rpc.RPCSetMaterialOverridesForElements(this._handle, elements, ids); } } @@ -271,7 +275,7 @@ export class Vim implements IVim { const colors = nodes.map(n => this._nodeColors.get(n)); const remoteColors = await this._colors.getColors(colors); const colorIds = remoteColors.map((c) => c?.id ?? -1); - this._rpc.RPCSetMaterialOverrides(this._handle, nodes, colorIds); + this._rpc.RPCSetMaterialOverridesForElements(this._handle, nodes, colorIds); this._updatedColors.clear(); } } diff --git a/src/vim-web/core-viewers/ultra/visibility.ts b/src/vim-web/core-viewers/ultra/visibility.ts index a01462ea..e65e6784 100644 --- a/src/vim-web/core-viewers/ultra/visibility.ts +++ b/src/vim-web/core-viewers/ultra/visibility.ts @@ -17,9 +17,9 @@ export enum VisibilityState { * A class that wraps a StateTracker and is responsible for synchronizing its state updates with the remote RPCs. * It batches updates to optimize performance and handles the communication with the remote system. */ -export class StateSynchronizer { +export class VisibilitySynchronizer { //TODO: Take advantage of the new rpcs that can take multiple states at once - private _tracker: StateTracker; + private _tracker: VisibilityTracker; private _rpc: RpcSafeClient; private _getHandle: () => number; @@ -43,7 +43,7 @@ export class StateSynchronizer { onUpdate: () => void, defaultState: VisibilityState = VisibilityState.VISIBLE ) { - this._tracker = new StateTracker(defaultState); + this._tracker = new VisibilityTracker(defaultState); this._rpc = rpc; this._onUpdate = onUpdate; this._getHandle = getHandle; @@ -99,7 +99,7 @@ export class StateSynchronizer { * @param elementIndex - The element index to update * @param state - The new state to apply */ - setElementState(elementIndex: number, state: VisibilityState): void { + setStateForElement(elementIndex: number, state: VisibilityState): void { this._tracker.setState(elementIndex, state); this.scheduleUpdate(); } @@ -185,7 +185,7 @@ export class StateSynchronizer { * * @private Not exported, used internally by StateSynchronizer */ -class StateTracker { +class VisibilityTracker { private _state = new Map(); private _updates = new Set(); private _default: VisibilityState; diff --git a/src/vim-web/utils/validation.ts b/src/vim-web/utils/validation.ts index b99f88fe..cd24d4d9 100644 --- a/src/vim-web/utils/validation.ts +++ b/src/vim-web/utils/validation.ts @@ -60,17 +60,17 @@ export class Validation { //= =========================================================================== // HANDLE VALIDATIONS //= =========================================================================== - static isComponentHandle (handle: number): boolean { - if (!this.isPositiveInteger(handle)) return false - if (handle === Core.Ultra.INVALID_HANDLE) { - console.warn(`Invalid handle ${handle}. Aborting operation.`) + static isIndex (index: number): boolean { + if (!this.isPositiveInteger(index)) return false + if (index === Core.Ultra.INVALID_HANDLE) { + console.warn(`Invalid index ${index}. Aborting operation.`) return false } return true } - static areComponentHandles (handles: number[]): boolean { - return handles.every((h) => this.isComponentHandle(h)) + static areIndices (indices: number[]): boolean { + return indices.every((h) => this.isIndex(h)) } static isMaterialHandle (handle: number): boolean { From 7f6294a645b275139d43e60444bd78467b0aaa39 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Tue, 22 Jul 2025 11:26:34 -0400 Subject: [PATCH 08/24] renames --- package.json | 2 +- src/vim-web/core-viewers/ultra/element3d.ts | 2 +- src/vim-web/core-viewers/ultra/sectionBox.ts | 2 +- src/vim-web/react-viewers/ultra/sectionBox.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 23f8b507..49f2483f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vim-web", - "version": "0.5.0-dev.2", + "version": "0.5.0-dev.3", "description": "A demonstration app built on top of the vim-webgl-viewer", "type": "module", "files": [ diff --git a/src/vim-web/core-viewers/ultra/element3d.ts b/src/vim-web/core-viewers/ultra/element3d.ts index 50477c19..81114425 100644 --- a/src/vim-web/core-viewers/ultra/element3d.ts +++ b/src/vim-web/core-viewers/ultra/element3d.ts @@ -45,7 +45,7 @@ export class Element3D implements IVimElement { return this.vim.visibility.getElementState(this.element); } set state(state: VisibilityState) { - this.vim.visibility.setElementState(this.element, state); + this.vim.visibility.setStateForElement(this.element, state); } /** diff --git a/src/vim-web/core-viewers/ultra/sectionBox.ts b/src/vim-web/core-viewers/ultra/sectionBox.ts index a717eb84..db08f464 100644 --- a/src/vim-web/core-viewers/ultra/sectionBox.ts +++ b/src/vim-web/core-viewers/ultra/sectionBox.ts @@ -109,7 +109,7 @@ export class SectionBox { * Fits the given box, invalid dimensions will be reversed. * @param box - The new bounding box. */ - fitBox(box: THREE.Box3) { + setBox(box: THREE.Box3) { box = safeBox(box) this._box = box this.scheduleUpdate() diff --git a/src/vim-web/react-viewers/ultra/sectionBox.ts b/src/vim-web/react-viewers/ultra/sectionBox.ts index 54b1ba4f..0617d9a4 100644 --- a/src/vim-web/react-viewers/ultra/sectionBox.ts +++ b/src/vim-web/react-viewers/ultra/sectionBox.ts @@ -12,7 +12,7 @@ export function useUltraSectionBox(viewer: Core.Ultra.Viewer): SectionBoxRef { viewer.sectionBox.interactive = b; }, getBox: () => viewer.sectionBox.getBox(), - setBox: (box) => viewer.sectionBox.fitBox(box), + setBox: (box) => viewer.sectionBox.setBox(box), onSelectionChanged: viewer.selection.onSelectionChanged, From da4bec816f7e5ed7f673e6d79fabb3433e9d35e8 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Tue, 22 Jul 2025 16:26:53 -0400 Subject: [PATCH 09/24] making ultra viewer api use three.color --- .../core-viewers/ultra/colorManager.ts | 35 ++++++----- src/vim-web/core-viewers/ultra/element3d.ts | 8 +-- src/vim-web/core-viewers/ultra/index.ts | 2 +- src/vim-web/core-viewers/ultra/remoteColor.ts | 40 ++----------- src/vim-web/core-viewers/ultra/renderer.ts | 59 ++++++++++++------- src/vim-web/core-viewers/ultra/rpcMarshal.ts | 1 - src/vim-web/core-viewers/ultra/rpcTypes.ts | 12 ++++ src/vim-web/core-viewers/ultra/rpcUtils.ts | 28 +++++++++ src/vim-web/core-viewers/ultra/vim.ts | 30 +++++----- src/vim-web/react-viewers/ultra/isolation.ts | 6 +- src/vim-web/utils/validation.ts | 37 +++++------- 11 files changed, 137 insertions(+), 121 deletions(-) create mode 100644 src/vim-web/core-viewers/ultra/rpcUtils.ts diff --git a/src/vim-web/core-viewers/ultra/colorManager.ts b/src/vim-web/core-viewers/ultra/colorManager.ts index 4456442d..fecde96b 100644 --- a/src/vim-web/core-viewers/ultra/colorManager.ts +++ b/src/vim-web/core-viewers/ultra/colorManager.ts @@ -1,7 +1,8 @@ import { MaterialHandles } from './rpcClient' import { RpcSafeClient } from './rpcSafeClient' import { RemoteColor } from './remoteColor' -import { RGBA32 } from './rpcTypes' +import * as RpcUtils from './rpcUtils' +import * as THREE from 'three' const MAX_BATCH_SIZE = 3000 @@ -30,8 +31,8 @@ export class ColorManager { * @param hex - The RGBA32 color value * @returns Promise resolving to a ColorHandle, or undefined if creation fails */ - async getColor (hex: RGBA32) : Promise { - const colors = await this.getColors([hex]) + async getColor (color: THREE.Color) : Promise { + const colors = await this.getColors([color]) if (!colors) return undefined return colors[0] } @@ -42,23 +43,24 @@ export class ColorManager { * @returns Promise resolving to an array of ColorHandles in the same order as input, or undefined if creation fails * @remarks Duplicate hex values will be mapped to the same color instance for efficiency */ - async getColors (c : RGBA32[]) { + async getColors (c : THREE.Color[]) { const result = new Array(c.length) const hexToIndices = new Map() - const toCreate: RGBA32[] = [] + const toCreate: THREE.Color[] = [] for (let i = 0; i < c.length; i++) { const color = c[i] - - if (this._hexToColor.has(color.hex)) { + const hex = color.getHex() + + if (this._hexToColor.has(hex)) { // If the color already exists, reuse it - result[i] = this._hexToColor.get(color.hex)! - } else if (hexToIndices.has(color.hex)) { + result[i] = this._hexToColor.get(hex)! + } else if (hexToIndices.has(hex)) { // If the color is being created, add the index to the list - hexToIndices.get(color.hex).push(i) + hexToIndices.get(hex)!.push(i) } else { // If the color is new, add it to the list to be created toCreate.push(color) - hexToIndices.set(color.hex, [i]) + hexToIndices.set(hex, [i]) } } @@ -68,7 +70,7 @@ export class ColorManager { for (let i = 0; i < colors.length; i++) { const color = toCreate[i] - const indices = hexToIndices.get(color.hex) + const indices = hexToIndices.get(color.getHex()) for (const index of indices) { result[index] = colors[i] } @@ -113,13 +115,14 @@ export class ColorManager { * @returns Promise resolving to an array of ColorHandles, or undefined if creation fails * @private */ - private async _createColors (colors : RGBA32[]) : Promise { + private async _createColors (colors : THREE.Color[]) : Promise { const result : RemoteColor[] = [] if (colors.length === 0) { return result } - const instances = await this._rpc.RPCCreateMaterialInstances(MaterialHandles.StandardOpaque, 1, colors) + const rpcColors = colors.map(c => RpcUtils.RGBA32fromThree(c)) + const instances = await this._rpc.RPCCreateMaterialInstances(MaterialHandles.StandardOpaque, 1, rpcColors) if (!instances) return undefined for (let i = 0; i < colors.length; i++) { @@ -136,9 +139,9 @@ export class ColorManager { * @returns The created ColorHandle * @private */ - private _createColor (color: RGBA32, id: number) { + private _createColor (color: THREE.Color, id: number) { const handle = new RemoteColor(color, id, this) - this._hexToColor.set(color.hex, handle) + this._hexToColor.set(color.getHex(), handle) this._idToColor.set(handle.id, handle) return handle } diff --git a/src/vim-web/core-viewers/ultra/element3d.ts b/src/vim-web/core-viewers/ultra/element3d.ts index 81114425..eed415a3 100644 --- a/src/vim-web/core-viewers/ultra/element3d.ts +++ b/src/vim-web/core-viewers/ultra/element3d.ts @@ -1,7 +1,7 @@ import { IVimElement } from "../shared/vim"; import { VisibilityState } from "./visibility"; -import { Box3, RGBA32 } from "./rpcTypes"; import { Vim } from "./vim"; +import * as THREE from "three"; /** * Represents a single 3D element within a `Vim` model. @@ -51,10 +51,10 @@ export class Element3D implements IVimElement { /** * Gets or sets the color override of the element. */ - get color(): RGBA32 | undefined { + get color(): THREE.Color | undefined { return this.vim.getColor(this.element); } - set color(color: RGBA32 | undefined) { + set color(color: THREE.Color | undefined) { this.vim.setColor([this.element], color); } @@ -63,7 +63,7 @@ export class Element3D implements IVimElement { * Returns undefined if the element is abstract. * @returns A promise resolving to the element's bounding box. */ - async getBoundingBox(): Promise { + async getBoundingBox(): Promise { return this.vim.getBoundingBoxNodes([this.element]); } } diff --git a/src/vim-web/core-viewers/ultra/index.ts b/src/vim-web/core-viewers/ultra/index.ts index fe636800..a6741387 100644 --- a/src/vim-web/core-viewers/ultra/index.ts +++ b/src/vim-web/core-viewers/ultra/index.ts @@ -5,7 +5,7 @@ export * from './viewer'; // Partial export // We don't want to reexport THREE.Box3 and THREE.Vector3 -export {RGB, RGBA, RGBA32, Segment, type SectionBoxState, type HitCheckResult, type VimStatus} from './rpcTypes' +export {Segment, type SectionBoxState, type HitCheckResult, type VimStatus} from './rpcTypes' // We don't want to export RPCClient export {materialHandles, MaterialHandles, type MaterialHandle, } from './rpcClient' diff --git a/src/vim-web/core-viewers/ultra/remoteColor.ts b/src/vim-web/core-viewers/ultra/remoteColor.ts index 2a543d87..86757ec5 100644 --- a/src/vim-web/core-viewers/ultra/remoteColor.ts +++ b/src/vim-web/core-viewers/ultra/remoteColor.ts @@ -1,5 +1,5 @@ -import { RGBA32 } from './rpcTypes' import { ColorManager } from './colorManager'; +import * as THREE from 'three'; /** * Represents a handle to a color in the color management system. @@ -10,7 +10,7 @@ export class RemoteColor { /** Unique identifier for the color instance */ readonly id: number; /** The RGBA color value */ - readonly color: RGBA32; + readonly color: THREE.Color; private _disposed = false; /** @@ -26,39 +26,7 @@ export class RemoteColor { * @returns {number} The color value as a hexadecimal number. */ get hex(): number { - return this.color.hex; - } - - /** - * Gets the red component of the color. - * @returns {number} The red component value in the range [0-255]. - */ - get r(): number { - return this.color.r; - } - - /** - * Gets the green component of the color. - * @returns {number} The green component value in the range [0-255]. - */ - get g(): number { - return this.color.g; - } - - /** - * Gets the blue component of the color. - * @returns {number} The blue component value in the range [0-255]. - */ - get b(): number { - return this.color.b; - } - - /** - * Gets the alpha (opacity) component of the color. - * @returns {number} The alpha component value in the range [0-255]. - */ - get a(): number { - return this.color.a; + return this.color.getHex(); } /** @@ -67,7 +35,7 @@ export class RemoteColor { * @param {number} serverId - The unique identifier assigned by the server. * @param {ColorManager} manager - The color manager instance that manages this color handle. */ - constructor(color: RGBA32, serverId: number, manager: ColorManager) { + constructor(color: THREE.Color, serverId: number, manager: ColorManager) { this._manager = manager; this.color = color; this.id = serverId; diff --git a/src/vim-web/core-viewers/ultra/renderer.ts b/src/vim-web/core-viewers/ultra/renderer.ts index bdd9aba4..994c9b87 100644 --- a/src/vim-web/core-viewers/ultra/renderer.ts +++ b/src/vim-web/core-viewers/ultra/renderer.ts @@ -3,15 +3,17 @@ import * as THREE from "three"; import { Validation } from "../../utils"; import { ILogger } from "./logger"; import { defaultSceneSettings, RpcSafeClient, SceneSettings } from "./rpcSafeClient"; -import { RGBA } from "./rpcTypes"; import { ClientStreamError } from "./socketClient"; +import * as RpcUtils from "./rpcUtils"; + /** * Render settings that extend SceneSettings with additional rendering-specific properties */ export type RenderSettings = SceneSettings & { /** Color used for ghost/transparent rendering */ - ghostColor: RGBA + ghostColor: THREE.Color + ghostOpacity: number } /** @@ -19,7 +21,8 @@ export type RenderSettings = SceneSettings & { */ export const defaultRenderSettings: RenderSettings = { ...defaultSceneSettings, - ghostColor: new RGBA(14/255, 14/255, 14/255, 64/255) + ghostColor: new THREE.Color(14/255, 14/255, 14/255), + ghostOpacity: 64/255 } /** @@ -27,13 +30,14 @@ export const defaultRenderSettings: RenderSettings = { */ export interface IRenderer { onSceneUpdated: ISignal - ghostColor: RGBA + ghostColor: THREE.Color + ghostOpacity: number hdrScale: number toneMappingWhitePoint: number hdrBackgroundScale: number hdrBackgroundSaturation: number backgroundBlur: number - backgroundColor: RGBA + backgroundColor: THREE.Color getBoundingBox(): Promise } @@ -92,7 +96,8 @@ export class Renderer implements IRenderer { * Sets up initial scene settings, ghost color, and IBL rotation */ onConnect(){ - this._rpc.RPCSetGhostColor(this._settings.ghostColor) + const color = RpcUtils.RGBAfromThree(this._settings.ghostColor, this._settings.ghostOpacity) + this._rpc.RPCSetGhostColor(color) } notifySceneUpdated() { @@ -103,12 +108,16 @@ export class Renderer implements IRenderer { /** * Gets the ghost color used for transparent rendering - * @returns Current ghost color as RGBA + * @returns Current ghost color as a THREE.Color */ - get ghostColor(): RGBA { + get ghostColor(): THREE.Color { return this._settings.ghostColor } + get ghostOpacity(): number { + return this._settings.ghostOpacity + } + /** * Gets the tone mapping white point value * @returns Current tone mapping white point @@ -153,20 +162,27 @@ export class Renderer implements IRenderer { * Gets the background color * @returns Current background color as RGBA */ - get backgroundColor(): RGBA { - return this._settings.backgroundColor; + get backgroundColor(): THREE.Color { + return this._settings.backgroundColor.toThree(); } // Setters /** * Updates the ghost color used for transparent rendering - * @param value - New ghost color as RGBA + * @param value - New ghost color as THREE.Color */ - set ghostColor(value: RGBA){ - value = Validation.clampRGBA01(value) + set ghostColor(value: THREE.Color) { if(this._settings.ghostColor.equals(value)) return - this._settings.ghostColor = value + this._settings.ghostColor = value + this._updateGhostColor = true + this.requestSettingsUpdate() + } + + set ghostOpacity(value: number) { + value = Validation.clamp01(value) + if (this._settings.ghostOpacity === value) return + this._settings.ghostOpacity = value this._updateGhostColor = true this.requestSettingsUpdate() } @@ -233,12 +249,12 @@ export class Renderer implements IRenderer { /** * Sets the background color - * @param value - New background color as RGBA + * @param value - New background color as THREE.Color */ - set backgroundColor(value: RGBA) { - value = Validation.clampRGBA01(value) - if (this._settings.backgroundColor.equals(value)) return; - this._settings.backgroundColor = value; + set backgroundColor(value: THREE.Color) { + const color = RpcUtils.RGBAfromThree(value, 1); + if (this._settings.backgroundColor.equals(color)) return; + this._settings.backgroundColor = color; this._updateLighting = true this.requestSettingsUpdate(); } @@ -261,7 +277,10 @@ export class Renderer implements IRenderer { private async applySettings(){ if(this._updateLighting) await this._rpc.RPCSetLighting(this._settings); - if(this._updateGhostColor) await this._rpc.RPCSetGhostColor(this._settings.ghostColor); + if(this._updateGhostColor){ + const color = RpcUtils.RGBAfromThree(this._settings.ghostColor, this._settings.ghostOpacity) + await this._rpc.RPCSetGhostColor(color); + } // Reset dirty flags this._updateLighting = false; diff --git a/src/vim-web/core-viewers/ultra/rpcMarshal.ts b/src/vim-web/core-viewers/ultra/rpcMarshal.ts index 8e2200f9..f8ed1739 100644 --- a/src/vim-web/core-viewers/ultra/rpcMarshal.ts +++ b/src/vim-web/core-viewers/ultra/rpcMarshal.ts @@ -431,7 +431,6 @@ export class ReadMarshal{ return this.readArray(() => this.readRGBA32()) } - public readArray(read: () => T): T[] { const length = this.readUInt() const array = [] diff --git a/src/vim-web/core-viewers/ultra/rpcTypes.ts b/src/vim-web/core-viewers/ultra/rpcTypes.ts index f8a37a0c..182800c1 100644 --- a/src/vim-web/core-viewers/ultra/rpcTypes.ts +++ b/src/vim-web/core-viewers/ultra/rpcTypes.ts @@ -45,6 +45,14 @@ export class RGBA { this.a = a } + static fromThree (color: THREE.Color, opacity: number = 1): RGBA { + return new RGBA(color.r, color.g, color.b, opacity) + } + + toThree(): THREE.Color { + return new THREE.Color(this.r, this.g, this.b) + } + clone(): RGBA { return new RGBA(this.r, this.g, this.b, this.a) } @@ -117,6 +125,10 @@ export class RGBA32 { this.hex = hex } + static fromThree (color: THREE.Color, opacity: number = 1): RGBA32 { + return this.fromFloats(color.r, color.g, color.b, opacity) + } + static fromInts (r: number, g: number, b: number, a: number = 1): RGBA32 { // Ensure each component is within the valid range (0-255) if ( diff --git a/src/vim-web/core-viewers/ultra/rpcUtils.ts b/src/vim-web/core-viewers/ultra/rpcUtils.ts new file mode 100644 index 00000000..6ad33308 --- /dev/null +++ b/src/vim-web/core-viewers/ultra/rpcUtils.ts @@ -0,0 +1,28 @@ + +import * as rpcTypes from "./rpcTypes"; +import * as THREE from "three"; + +export function RGBToThree(color: rpcTypes.RGB): THREE.Color { + return new THREE.Color(color.r, color.g, color.b); +} + +export function RGBAToThree(color: rpcTypes.RGBA): THREE.Color { + return new THREE.Color(color.r, color.g, color.b); +} + +export function RGBA32ToThree(color: rpcTypes.RGBA32): THREE.Color { + return new THREE.Color(color.r / 255, color.g / 255, color.b / 255); +} + +export function RGBfromThree(color: THREE.Color): rpcTypes.RGB { + return new rpcTypes.RGB(color.r, color.g, color.b); +} + +export function RGBAfromThree(color: THREE.Color, opacity: number = 1): rpcTypes.RGBA { + return new rpcTypes.RGBA(color.r, color.g, color.b, opacity); +} + +export function RGBA32fromThree(color: THREE.Color, opacity: number = 1): rpcTypes.RGBA32 { + return rpcTypes.RGBA32.fromFloats(color.r, color.g, color.b, opacity); +} + diff --git a/src/vim-web/core-viewers/ultra/vim.ts b/src/vim-web/core-viewers/ultra/vim.ts index 8164b498..66c96f7d 100644 --- a/src/vim-web/core-viewers/ultra/vim.ts +++ b/src/vim-web/core-viewers/ultra/vim.ts @@ -11,8 +11,6 @@ import { RpcSafeClient, VimLoadingStatus, VimSource } from './rpcSafeClient'; import { INVALID_HANDLE } from './viewer'; import * as THREE from 'three'; -import { RGBA32 } from './rpcTypes'; - export class Vim implements IVim { readonly source: VimSource; @@ -29,7 +27,7 @@ export class Vim implements IVim { readonly visibility: VisibilitySynchronizer; // Color tracking remains unchanged. - private _nodeColors: Map = new Map(); + private _elementColors: Map = new Map(); private _updatedColors = new Set(); // Delayed update flag. @@ -205,30 +203,30 @@ export class Vim implements IVim { return await this._rpc.RPCGetAABBForVim(this._handle); } - getColor(node: number): RGBA32 | undefined { - return this._nodeColors.get(node); + getColor(elementIndex: number): THREE.Color | undefined { + return this._elementColors.get(elementIndex); } - async setColor(nodes: number[], color: RGBA32 | undefined) { - const colors = new Array(nodes.length).fill(color); - this.applyColor(nodes, colors); + async setColor(elementIndex: number[], color: THREE.Color | undefined) { + const colors = new Array(elementIndex.length).fill(color); + this.applyColor(elementIndex, colors); } - async setColors(nodes: number[], color: (RGBA32 | undefined)[]) { + async setColors(nodes: number[], color: (THREE.Color | undefined)[]) { if (color.length !== nodes.length) { throw new Error('Color and nodes length must be equal'); } this.applyColor(nodes, color); } - private applyColor(nodes: number[], color: (RGBA32 | undefined)[]) { + private applyColor(nodes: number[], color: (THREE.Color | undefined)[]) { for (let i = 0; i < color.length; i++) { const c = color[i]; const n = nodes[i]; if (c === undefined) { - this._nodeColors.delete(n); + this._elementColors.delete(n); } else { - this._nodeColors.set(n, c); + this._elementColors.set(n, c); } this._updatedColors.add(n); } @@ -237,9 +235,9 @@ export class Vim implements IVim { clearColor(elements: number[] | 'all'): void { if (elements === 'all') { - this._nodeColors.clear(); + this._elementColors.clear(); } else { - elements.forEach((n) => this._nodeColors.delete(n)); + elements.forEach((n) => this._elementColors.delete(n)); } if (!this.connected) return; if (elements === 'all') { @@ -252,7 +250,7 @@ export class Vim implements IVim { reapplyColors(): void { this._updatedColors.clear(); - this._nodeColors.forEach((c, n) => this._updatedColors.add(n)); + this._elementColors.forEach((c, n) => this._updatedColors.add(n)); this.scheduleColorUpdate(); } @@ -272,7 +270,7 @@ export class Vim implements IVim { private async updateRemoteColors() { const nodes = Array.from(this._updatedColors); - const colors = nodes.map(n => this._nodeColors.get(n)); + const colors = nodes.map(n => this._elementColors.get(n)); const remoteColors = await this._colors.getColors(colors); const colorIds = remoteColors.map((c) => c?.id ?? -1); this._rpc.RPCSetMaterialOverridesForElements(this._handle, nodes, colorIds); diff --git a/src/vim-web/react-viewers/ultra/isolation.ts b/src/vim-web/react-viewers/ultra/isolation.ts index 93476527..f0ef5005 100644 --- a/src/vim-web/react-viewers/ultra/isolation.ts +++ b/src/vim-web/react-viewers/ultra/isolation.ts @@ -111,11 +111,9 @@ function createAdapter(viewer: Viewer): IsolationAdapter { } }, - getGhostOpacity: () => viewer.renderer.ghostColor.a, + getGhostOpacity: () => viewer.renderer.ghostOpacity, setGhostOpacity: (opacity: number) => { - const c = viewer.renderer.ghostColor.clone() - c.a = opacity - viewer.renderer.ghostColor = c + viewer.renderer.ghostOpacity = opacity }, getShowRooms: () => true, diff --git a/src/vim-web/utils/validation.ts b/src/vim-web/utils/validation.ts index cd24d4d9..fe0d7571 100644 --- a/src/vim-web/utils/validation.ts +++ b/src/vim-web/utils/validation.ts @@ -1,6 +1,7 @@ import * as THREE from 'three' import * as Core from '../core-viewers' import { isURL } from './url' +import { RGBA } from '../core-viewers/ultra/rpcTypes' export class Validation { //= =========================================================================== @@ -126,25 +127,6 @@ export class Validation { return true } - //= =========================================================================== - // COLOR VALIDATIONS - //= =========================================================================== - static isRelativeRGBA (color: Core.Ultra.RGBA): boolean { - if (color.r < 0 || color.r > 1 || color.g < 0 || color.g > 1 || color.b < 0 || color.b > 1) { - console.warn('Invalid value: must be a relative color (0-1, 0-1, 0-1)') - return false - } - return true - } - - static isRelativeRGB (color: Core.Ultra.RGB): boolean { - if (color.r < 0 || color.r > 1 || color.g < 0 || color.g > 1 || color.b < 0 || color.b > 1) { - console.warn('Invalid value: must be a relative color (0-1, 0-1, 0-1)') - return false - } - return true - } - //= =========================================================================== // STRING AND URL VALIDATIONS //= =========================================================================== @@ -241,8 +223,17 @@ export class Validation { return value } - static clampRGBA01 (value: Core.Ultra.RGBA): Core.Ultra.RGBA { - return new Core.Ultra.RGBA( + static clampColor01 (value: THREE.Color): THREE.Color { + return new THREE.Color( + this.clamp01(value.r), + this.clamp01(value.g), + this.clamp01(value.b), + ) + } + + + static clampRGBA01 (value: RGBA): RGBA { + return new RGBA( this.clamp01(value.r), this.clamp01(value.g), this.clamp01(value.b), @@ -250,8 +241,8 @@ export class Validation { ) } - static clampRGB01 (value: Core.Ultra.RGBA): Core.Ultra.RGBA { - return new Core.Ultra.RGBA( + static clampRGB01 (value: RGBA): RGBA { + return new RGBA( this.clamp01(value.r), this.clamp01(value.g), this.clamp01(value.b) From b20cc175e144845d1533d129e9887806f7737765 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Wed, 23 Jul 2025 18:24:21 -0400 Subject: [PATCH 10/24] fixed colors --- .../core-viewers/ultra/colorManager.ts | 55 ++++++++++++------- src/vim-web/core-viewers/ultra/vim.ts | 28 +++++----- src/vim-web/react-viewers/ultra/isolation.ts | 1 + 3 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/vim-web/core-viewers/ultra/colorManager.ts b/src/vim-web/core-viewers/ultra/colorManager.ts index fecde96b..bc0245e6 100644 --- a/src/vim-web/core-viewers/ultra/colorManager.ts +++ b/src/vim-web/core-viewers/ultra/colorManager.ts @@ -39,40 +39,53 @@ export class ColorManager { /** * Creates or retrieves cached color instances for multiple hex values. - * @param c - Array of RGBA32 color values + * @param colors - Array of color values or undefined for no color * @returns Promise resolving to an array of ColorHandles in the same order as input, or undefined if creation fails * @remarks Duplicate hex values will be mapped to the same color instance for efficiency */ - async getColors (c : THREE.Color[]) { - const result = new Array(c.length) + async getColors (colors : (THREE.Color | undefined)[]) { + const result = new Array(colors.length) const hexToIndices = new Map() const toCreate: THREE.Color[] = [] - for (let i = 0; i < c.length; i++) { - const color = c[i] - const hex = color.getHex() - - if (this._hexToColor.has(hex)) { - // If the color already exists, reuse it - result[i] = this._hexToColor.get(hex)! - } else if (hexToIndices.has(hex)) { - // If the color is being created, add the index to the list - hexToIndices.get(hex)!.push(i) - } else { - // If the color is new, add it to the list to be created - toCreate.push(color) - hexToIndices.set(hex, [i]) + for (let i = 0; i < colors.length; i++) { + const color = colors[i] + + // If the color is undefined, no need to create it + if(color === undefined) { + result[i] = undefined + continue + } + + const hex = color?.getHex() ?? -1 + + // If remote color already exists, reuse it + const remoteColor = this._hexToColor.get(hex) + if (remoteColor) { + result[i] = remoteColor + continue } + + // If remote color is being created, join the existing creation + const indices = hexToIndices.get(hex) + if(indices){ + hexToIndices.get(hex)!.push(i) + continue + } + + // If the color is new, add it to the list to be created + toCreate.push(color) + hexToIndices.set(hex, [i]) } // Create the colors and map them to the indices - const colors = await this._createColors(toCreate) - if (!colors) return undefined + const remoteColors = await this._createColors(toCreate) + if (!remoteColors) return undefined - for (let i = 0; i < colors.length; i++) { + for (let i = 0; i < remoteColors.length; i++) { const color = toCreate[i] const indices = hexToIndices.get(color.getHex()) for (const index of indices) { - result[index] = colors[i] + result[index] = remoteColors[i] } } diff --git a/src/vim-web/core-viewers/ultra/vim.ts b/src/vim-web/core-viewers/ultra/vim.ts index 66c96f7d..f5eadff9 100644 --- a/src/vim-web/core-viewers/ultra/vim.ts +++ b/src/vim-web/core-viewers/ultra/vim.ts @@ -58,6 +58,8 @@ export class Vim implements IVim { VisibilityState.VISIBLE // default state ); } + + //TODO: Rename this to getElementFromNode, prefer using element instead getElement(elementIndex: number): Element3D { if (this._objects.has(elementIndex)) { return this._objects.get(elementIndex)!; @@ -70,7 +72,7 @@ export class Vim implements IVim { throw new Error('Method not implemented.'); } getElementFromIndex(element: number): Element3D { - throw new Error('Method not implemented.'); + return this.getElement(element); } getObjectsInBox(box: THREE.Box3): Element3D[] { throw new Error('Method not implemented.'); @@ -212,23 +214,23 @@ export class Vim implements IVim { this.applyColor(elementIndex, colors); } - async setColors(nodes: number[], color: (THREE.Color | undefined)[]) { - if (color.length !== nodes.length) { - throw new Error('Color and nodes length must be equal'); + async setColors(elements: number[], color: (THREE.Color | undefined)[]) { + if (color.length !== elements.length) { + throw new Error('Color and elements length must be equal'); } - this.applyColor(nodes, color); + this.applyColor(elements, color); } - private applyColor(nodes: number[], color: (THREE.Color | undefined)[]) { + private applyColor(elements: number[], color: (THREE.Color | undefined)[]) { for (let i = 0; i < color.length; i++) { const c = color[i]; - const n = nodes[i]; + const element = elements[i]; if (c === undefined) { - this._elementColors.delete(n); + this._elementColors.delete(element); } else { - this._elementColors.set(n, c); + this._elementColors.set(element, c); } - this._updatedColors.add(n); + this._updatedColors.add(element); } this.scheduleColorUpdate(); } @@ -269,11 +271,11 @@ export class Vim implements IVim { } private async updateRemoteColors() { - const nodes = Array.from(this._updatedColors); - const colors = nodes.map(n => this._elementColors.get(n)); + const elements = Array.from(this._updatedColors); + const colors = elements.map(n => this._elementColors.get(n)); const remoteColors = await this._colors.getColors(colors); const colorIds = remoteColors.map((c) => c?.id ?? -1); - this._rpc.RPCSetMaterialOverridesForElements(this._handle, nodes, colorIds); + this._rpc.RPCSetMaterialOverridesForElements(this._handle, elements, colorIds); this._updatedColors.clear(); } } diff --git a/src/vim-web/react-viewers/ultra/isolation.ts b/src/vim-web/react-viewers/ultra/isolation.ts index f0ef5005..1d211b70 100644 --- a/src/vim-web/react-viewers/ultra/isolation.ts +++ b/src/vim-web/react-viewers/ultra/isolation.ts @@ -75,6 +75,7 @@ function createAdapter(viewer: Viewer): IsolationAdapter { }) }, + // TODO: Change this api to use elements isolate: (instances: number[]) => { hide('all') // Hide all objects viewer.selection.getAll().forEach(obj => { From 88a4ffd1f341f80394c7d2b1f48b824d28e9393e Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Fri, 25 Jul 2025 10:49:58 -0400 Subject: [PATCH 11/24] work for ultra --- package.json | 4 +- src/vim-web/core-viewers/ultra/element3d.ts | 2 +- src/vim-web/core-viewers/ultra/rpcClient.ts | 39 +++++++++++++------ .../core-viewers/ultra/rpcSafeClient.ts | 27 +++++++++++-- src/vim-web/core-viewers/ultra/viewer.ts | 6 +++ src/vim-web/core-viewers/ultra/vim.ts | 16 ++++---- src/vim-web/core-viewers/webgl/loader/vim.ts | 6 +++ .../core-viewers/webgl/viewer/viewer.ts | 5 +++ 8 files changed, 79 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 49f2483f..550987a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vim-web", - "version": "0.5.0-dev.3", + "version": "0.5.0-dev.10", "description": "A demonstration app built on top of the vim-webgl-viewer", "type": "module", "files": [ @@ -28,7 +28,7 @@ "build": "vite build && npm run declarations", "publish:package": "npm run build && npm publish", "publish:documentation": "npm run documentation && gh-pages -d docs2", - "publish": "npm run publish:package && npm run publish:documentation" + "publish:both": "npm run publish:package && npm run publish:documentation" }, "devDependencies": { "@types/dom-webcodecs": "^0.1.13", diff --git a/src/vim-web/core-viewers/ultra/element3d.ts b/src/vim-web/core-viewers/ultra/element3d.ts index eed415a3..32d46729 100644 --- a/src/vim-web/core-viewers/ultra/element3d.ts +++ b/src/vim-web/core-viewers/ultra/element3d.ts @@ -64,6 +64,6 @@ export class Element3D implements IVimElement { * @returns A promise resolving to the element's bounding box. */ async getBoundingBox(): Promise { - return this.vim.getBoundingBoxNodes([this.element]); + return this.vim.getBoundingBoxForElements([this.element]); } } diff --git a/src/vim-web/core-viewers/ultra/rpcClient.ts b/src/vim-web/core-viewers/ultra/rpcClient.ts index 416983a5..524c9a4a 100644 --- a/src/vim-web/core-viewers/ultra/rpcClient.ts +++ b/src/vim-web/core-viewers/ultra/rpcClient.ts @@ -62,9 +62,24 @@ return this._socket.state.status === "connected" // RPC Generated Code readonly API_VERSION = "6.0.0" - RPCClearMaterialOverrides(): void { + RPCClearMaterialOverridesForElements(vimIndex: number, elementIndices: number[]): void { const marshal = new Marshal(); - marshal.writeString("RPCClearMaterialOverrides"); + marshal.writeString("RPCClearMaterialOverridesForElements"); + marshal.writeUInt(vimIndex); + marshal.writeArrayOfUInt(elementIndices); + this._socket.sendRPC(marshal); + } + + RPCClearMaterialOverridesForScene(): void { + const marshal = new Marshal(); + marshal.writeString("RPCClearMaterialOverridesForScene"); + this._socket.sendRPC(marshal); + } + + RPCClearMaterialOverridesForVim(vimIndex: number): void { + const marshal = new Marshal(); + marshal.writeString("RPCClearMaterialOverridesForVim"); + marshal.writeUInt(vimIndex); this._socket.sendRPC(marshal); } @@ -186,9 +201,9 @@ return this._socket.state.status === "connected" return ret; } - async RPCGetCameraView(): Promise { + async RPCGetCameraPose(): Promise { const marshal = new Marshal(); - marshal.writeString("RPCGetCameraView"); + marshal.writeString("RPCGetCameraPose"); const returnMarshal = await this._socket.sendRPCWithReturn(marshal); const ret = returnMarshal.readSegment(); return ret; @@ -351,6 +366,14 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } + RPCSetCameraPose(state: RpcTypes.Segment, blendTime: number): void { + const marshal = new Marshal(); + marshal.writeString("RPCSetCameraPose"); + marshal.writeSegment(state); + marshal.writeFloat(blendTime); + this._socket.sendRPC(marshal); + } + RPCSetCameraPosition(position: RpcTypes.Vector3, blendTime: number): void { const marshal = new Marshal(); marshal.writeString("RPCSetCameraPosition"); @@ -374,14 +397,6 @@ return this._socket.state.status === "connected" this._socket.sendRPC(marshal); } - RPCSetCameraView(state: RpcTypes.Segment, blendTime: number): void { - const marshal = new Marshal(); - marshal.writeString("RPCSetCameraView"); - marshal.writeSegment(state); - marshal.writeFloat(blendTime); - this._socket.sendRPC(marshal); - } - RPCSetGhostColor(ghostColor: RpcTypes.RGBA): void { const marshal = new Marshal(); marshal.writeString("RPCSetGhostColor"); diff --git a/src/vim-web/core-viewers/ultra/rpcSafeClient.ts b/src/vim-web/core-viewers/ultra/rpcSafeClient.ts index 111ca46d..4117654f 100644 --- a/src/vim-web/core-viewers/ultra/rpcSafeClient.ts +++ b/src/vim-web/core-viewers/ultra/rpcSafeClient.ts @@ -375,7 +375,7 @@ export class RpcSafeClient { */ async RPCGetCameraView(): Promise { return await this.safeCall( - () => this.rpc.RPCGetCameraView(), + () => this.rpc.RPCGetCameraPose(), undefined ) } @@ -391,7 +391,7 @@ export class RpcSafeClient { blendTime = Validation.clamp01(blendTime) // Run - this.rpc.RPCSetCameraView(segment, blendTime) + this.rpc.RPCSetCameraPose(segment, blendTime) } /** @@ -911,9 +911,28 @@ export class RpcSafeClient { /** * Clears all material overrides for the entire scene. */ - RPCClearMaterialOverrides(): void { + RPCClearMaterialOverridesForScene(): void { // Run - this.rpc.RPCClearMaterialOverrides() + this.rpc.RPCClearMaterialOverridesForScene() + } + + /** + * Clears all material overrides for a specific loaded vim. + * @param vimIndex - The index of the loaded vim + */ + RPCClearMaterialOverridesForVim(vimIndex: number): void { + // Run + this.rpc.RPCClearMaterialOverridesForVim(vimIndex) + } + + /** + * Clears all material overrides for specific elements in a loaded vim. + * @param vimIndex - The index of the loaded vim + * @param vimElementIndices - Array of vim-based element indices to clear overrides for + */ + RPCClearMaterialOverridesForElements(vimIndex: number, vimElementIndices: number[]): void { + // Run + this.rpc.RPCClearMaterialOverridesForElements(vimIndex, vimElementIndices) } /******************************************************************************* diff --git a/src/vim-web/core-viewers/ultra/viewer.ts b/src/vim-web/core-viewers/ultra/viewer.ts index 3d2fdeb3..b21a4f14 100644 --- a/src/vim-web/core-viewers/ultra/viewer.ts +++ b/src/vim-web/core-viewers/ultra/viewer.ts @@ -25,6 +25,12 @@ export const INVALID_HANDLE = 0xffffffff * handling connections, and coordinating various components like the camera, decoder, and inputs. */ export class Viewer { + /** + * The type of the viewer, indicating it is a WebGL viewer. + * Useful for distinguishing between different viewer types in a multi-viewer application. + */ + public readonly type = 'ultra' + private readonly _decoder: Decoder | DecoderWithWorker private readonly _socketClient: SocketClient private readonly _input: InputHandler diff --git a/src/vim-web/core-viewers/ultra/vim.ts b/src/vim-web/core-viewers/ultra/vim.ts index f5eadff9..5543499b 100644 --- a/src/vim-web/core-viewers/ultra/vim.ts +++ b/src/vim-web/core-viewers/ultra/vim.ts @@ -13,6 +13,8 @@ import { INVALID_HANDLE } from './viewer'; import * as THREE from 'three'; export class Vim implements IVim { + readonly type = 'ultra'; + readonly source: VimSource; private _handle: number = -1; private _request: LoadRequest | undefined; @@ -188,14 +190,14 @@ export class Vim implements IVim { return handle; } - async getBoundingBoxNodes(nodes: number[] | 'all'): Promise { - if (!this.connected || (nodes !== 'all' && nodes.length === 0)) { + async getBoundingBoxForElements(elements: number[] | 'all'): Promise { + if (!this.connected || (elements !== 'all' && elements.length === 0)) { return Promise.resolve(undefined); } - if (nodes === 'all') { + if (elements === 'all') { return await this._rpc.RPCGetAABBForVim(this._handle); } - return await this._rpc.RPCGetAABBForElements(this._handle, nodes); + return await this._rpc.RPCGetAABBForElements(this._handle, elements); } async getBoundingBox(): Promise { @@ -235,6 +237,7 @@ export class Vim implements IVim { this.scheduleColorUpdate(); } + //TODO: Remove and rely on element.color clearColor(elements: number[] | 'all'): void { if (elements === 'all') { this._elementColors.clear(); @@ -243,10 +246,9 @@ export class Vim implements IVim { } if (!this.connected) return; if (elements === 'all') { - this._rpc.RPCClearMaterialOverrides(); + this._rpc.RPCClearMaterialOverridesForVim(this._handle); } else { - const ids = new Array(elements.length).fill(MaterialHandles.Invalid); - this._rpc.RPCSetMaterialOverridesForElements(this._handle, elements, ids); + this._rpc.RPCClearMaterialOverridesForElements(this._handle, elements); } } diff --git a/src/vim-web/core-viewers/webgl/loader/vim.ts b/src/vim-web/core-viewers/webgl/loader/vim.ts index b5c136c6..7b40ab3d 100644 --- a/src/vim-web/core-viewers/webgl/loader/vim.ts +++ b/src/vim-web/core-viewers/webgl/loader/vim.ts @@ -25,6 +25,12 @@ type VimFormat = 'vim' | 'vimx' * Facilitates high-level scene manipulation by providing access to objects. */ export class Vim implements IVim { + /** + * The type of the viewer, indicating it is a WebGL viewer. + * Useful for distinguishing between different viewer types in a multi-viewer application. + */ + readonly type = 'webgl'; + /** * Indicates whether the vim was opened from a vim or vimx file. */ diff --git a/src/vim-web/core-viewers/webgl/viewer/viewer.ts b/src/vim-web/core-viewers/webgl/viewer/viewer.ts index 855428ae..cf20a4c4 100644 --- a/src/vim-web/core-viewers/webgl/viewer/viewer.ts +++ b/src/vim-web/core-viewers/webgl/viewer/viewer.ts @@ -26,6 +26,11 @@ import { Renderer } from './rendering/renderer' * Viewer and loader for vim files. */ export class Viewer { + /** + * The type of the viewer, indicating it is a WebGL viewer. + * Useful for distinguishing between different viewer types in a multi-viewer application. + */ + public readonly type = 'webgl' /** * The settings configuration used by the viewer. */ From 5cda560d090fb2cf556c1a9e6ae0ac2c73b6615f Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Fri, 25 Jul 2025 12:01:36 -0400 Subject: [PATCH 12/24] updated ultra to use remove overrides --- src/vim-web/core-viewers/ultra/vim.ts | 33 +++++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/vim-web/core-viewers/ultra/vim.ts b/src/vim-web/core-viewers/ultra/vim.ts index 5543499b..b4d0d378 100644 --- a/src/vim-web/core-viewers/ultra/vim.ts +++ b/src/vim-web/core-viewers/ultra/vim.ts @@ -31,6 +31,7 @@ export class Vim implements IVim { // Color tracking remains unchanged. private _elementColors: Map = new Map(); private _updatedColors = new Set(); + private _removedColors = new Set(); // Delayed update flag. private _updateScheduled: boolean = false; @@ -223,16 +224,20 @@ export class Vim implements IVim { this.applyColor(elements, color); } - private applyColor(elements: number[], color: (THREE.Color | undefined)[]) { - for (let i = 0; i < color.length; i++) { - const c = color[i]; + private applyColor(elements: number[], colors: (THREE.Color | undefined)[]) { + for (let i = 0; i < colors.length; i++) { + const color = colors[i]; const element = elements[i]; - if (c === undefined) { + const existingColor = this._elementColors.get(element); + + if (color === undefined && existingColor !== undefined) { this._elementColors.delete(element); - } else { - this._elementColors.set(element, c); + this._removedColors.add(element); + } + else if (color !== existingColor){ + this._elementColors.set(element, color); + this._updatedColors.add(element); } - this._updatedColors.add(element); } this.scheduleColorUpdate(); } @@ -254,6 +259,7 @@ export class Vim implements IVim { reapplyColors(): void { this._updatedColors.clear(); + this._removedColors.clear(); this._elementColors.forEach((c, n) => this._updatedColors.add(n)); this.scheduleColorUpdate(); } @@ -273,12 +279,19 @@ export class Vim implements IVim { } private async updateRemoteColors() { - const elements = Array.from(this._updatedColors); - const colors = elements.map(n => this._elementColors.get(n)); + const updatedElement = Array.from(this._updatedColors); + const removedElement = Array.from(this._removedColors); + + const colors = updatedElement.map(n => this._elementColors.get(n)); const remoteColors = await this._colors.getColors(colors); const colorIds = remoteColors.map((c) => c?.id ?? -1); - this._rpc.RPCSetMaterialOverridesForElements(this._handle, elements, colorIds); + + this._rpc.RPCClearMaterialOverridesForElements(this._handle, removedElement); + this._rpc.RPCSetMaterialOverridesForElements(this._handle, updatedElement, colorIds); + + this._updatedColors.clear(); + this._removedColors.clear(); } } From efab984c7910d490a852cd3769ccf1e1ccdebddd Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Fri, 25 Jul 2025 12:02:11 -0400 Subject: [PATCH 13/24] version": "0.5.0-dev.11 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 550987a1..a3a263e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vim-web", - "version": "0.5.0-dev.10", + "version": "0.5.0-dev.11", "description": "A demonstration app built on top of the vim-webgl-viewer", "type": "module", "files": [ From 71e28131ce3e6f9be6f9a4db38e2db8ef1c58c20 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Tue, 29 Jul 2025 12:25:07 -0400 Subject: [PATCH 14/24] no longer double click if mouse moves --- .../core-viewers/shared/mouseHandler.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/vim-web/core-viewers/shared/mouseHandler.ts b/src/vim-web/core-viewers/shared/mouseHandler.ts index 10080e7d..fbef4357 100644 --- a/src/vim-web/core-viewers/shared/mouseHandler.ts +++ b/src/vim-web/core-viewers/shared/mouseHandler.ts @@ -143,13 +143,25 @@ class CaptureHandler { class DoubleClickHandler { private _lastClickTime: number = 0; - private _clickDelay: number = 300; // Delay in milliseconds to consider a double click + private _clickDelay: number = 300; // Max time between clicks for double-click + private _lastClickPosition: THREE.Vector2 | null = null; + private _positionThreshold: number = 5; // Max pixel distance between clicks - checkForDoubleClick(event: MouseEvent) { + checkForDoubleClick(event: MouseEvent): boolean { const currentTime = Date.now(); + const currentPosition = new THREE.Vector2(event.clientX, event.clientY); const timeDiff = currentTime - this._lastClickTime; + + const isClose = + this._lastClickPosition !== null && + this._lastClickPosition.distanceTo(currentPosition) < this._positionThreshold; + + const isWithinTime = timeDiff < this._clickDelay; + this._lastClickTime = currentTime; - return timeDiff < this._clickDelay; + this._lastClickPosition = currentPosition; + + return isClose && isWithinTime; } } From 999efea168abe24ca8b3827c9f074f8295733201 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Tue, 29 Jul 2025 12:25:29 -0400 Subject: [PATCH 15/24] webgl, frame all on double click void --- src/vim-web/core-viewers/webgl/viewer/inputAdapter.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vim-web/core-viewers/webgl/viewer/inputAdapter.ts b/src/vim-web/core-viewers/webgl/viewer/inputAdapter.ts index e776110e..354a755a 100644 --- a/src/vim-web/core-viewers/webgl/viewer/inputAdapter.ts +++ b/src/vim-web/core-viewers/webgl/viewer/inputAdapter.ts @@ -1,5 +1,5 @@ import {type IInputAdapter} from "../../shared/inputAdapter" -import {InputHandler} from "../../shared/inputHandler" +import {InputHandler, PointerMode} from "../../shared/inputHandler" import { Viewer } from "./viewer" import * as THREE from 'three' @@ -33,6 +33,11 @@ function createAdapter(viewer: Viewer ) : IInputAdapter { toggleOrthographic: () => { viewer.camera.orthographic = !viewer.camera.orthographic }, + toggleCameraOrbitMode: () => { + this._pointerActive = this._pointerActive === PointerMode.ORBIT ? PointerMode.LOOK : PointerMode.ORBIT; + this._pointerFallback = this._pointerActive; + this._onPointerModeChanged.dispatch(); + }, resetCamera: () => { viewer.camera.lerp(0.75).reset() @@ -63,7 +68,7 @@ function createAdapter(viewer: Viewer ) : IInputAdapter { frameAtPointer: async (pos: THREE.Vector2) => { //TODO: This logic should happen in shared code const result = await viewer.raycaster.raycastFromScreen(pos) - viewer.camera.lerp(0.75).frame(result.object) + viewer.camera.lerp(0.75).frame(result.object ?? 'all') }, zoom: (value: number) => { viewer.camera.lerp(0.75).zoom(value) From 8f8e3619a06bc0196000ce74d1d0c41700d2f794 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Tue, 29 Jul 2025 12:33:29 -0400 Subject: [PATCH 16/24] only one settings panel at a time --- src/vim-web/react-viewers/ultra/viewer.tsx | 25 ++++++++++++++++------ src/vim-web/react-viewers/webgl/viewer.tsx | 15 +++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/vim-web/react-viewers/ultra/viewer.tsx b/src/vim-web/react-viewers/ultra/viewer.tsx index 86d61fa5..3b2e3ef8 100644 --- a/src/vim-web/react-viewers/ultra/viewer.tsx +++ b/src/vim-web/react-viewers/ultra/viewer.tsx @@ -77,8 +77,8 @@ export function Viewer (props: { onMount: (viewer: ViewerRef) => void}) { - const sectionBox = useUltraSectionBox(props.core) - const camera = useUltraCamera(props.core, sectionBox) + const sectionBoxRef = useUltraSectionBox(props.core) + const camera = useUltraCamera(props.core, sectionBoxRef) const isolationPanelHandle = useRef(null) const sectionBoxPanelHandle = useRef(null) const modalHandle = useRef(null) @@ -86,12 +86,25 @@ export function Viewer (props: { const side = useSideState(true, 400) const [_, setSelectState] = useState(0) const [controlBarCustom, setControlBarCustom] = useState(() => c => c) - const isolation = useUltraIsolation(props.core) - const controlBar = useUltraControlBar(props.core, sectionBox, isolation, camera, _ =>_) + const isolationRef = useUltraIsolation(props.core) + const controlBar = useUltraControlBar(props.core, sectionBoxRef, isolationRef, camera, _ =>_) useViewerInput(props.core.inputs, camera) + // On First render useEffect(() => { + // Close isolation panel when offset panel is shown and vice versa + sectionBoxRef.showOffsetPanel.onChange.subscribe((show) => { + if(show) { + isolationRef.showPanel.set(false) + } + }) + isolationRef.showPanel.onChange.subscribe((show) => { + if(show) { + sectionBoxRef.showOffsetPanel.set(false) + } + }) + props.core.onStateChanged.subscribe(state => updateModal(modalHandle, state)) props.core.selection.onSelectionChanged.subscribe(() =>{ setSelectState(i => (i+1)%2) @@ -99,8 +112,8 @@ export function Viewer (props: { props.onMount({ core: props.core, get modal() { return modalHandle.current }, - isolation, - sectionBox, + isolation: isolationRef, + sectionBox: sectionBoxRef, camera, get isolationPanel(){ return isolationPanelHandle.current diff --git a/src/vim-web/react-viewers/webgl/viewer.tsx b/src/vim-web/react-viewers/webgl/viewer.tsx index 5096097f..9d60c0d8 100644 --- a/src/vim-web/react-viewers/webgl/viewer.tsx +++ b/src/vim-web/react-viewers/webgl/viewer.tsx @@ -135,12 +135,27 @@ export function Viewer (props: { const controlBar = useControlBar(props.viewer, camera, modal.current, side, cursor, settings.value, sectionBoxRef, isolationRef, controlBarCustom) + + + useEffect(() => { side.setHasBim(viewerState.vim.get()?.bim !== undefined) }) // On first render useEffect(() => { + // Close isolation panel when offset panel is shown and vice versa + sectionBoxRef.showOffsetPanel.onChange.subscribe((show) => { + if(show) { + isolationRef.showPanel.set(false) + } + }) + isolationRef.showPanel.onChange.subscribe((show) => { + if(show) { + sectionBoxRef.showOffsetPanel.set(false) + } + }) + if (performanceRef.current) { addPerformanceCounter(performanceRef.current) } From f974c548e88d0cc99d16019d68a9864577989f8b Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Tue, 29 Jul 2025 12:33:59 -0400 Subject: [PATCH 17/24] fixed ultra orbit/free toggle on space --- src/vim-web/core-viewers/shared/inputAdapter.ts | 1 + src/vim-web/core-viewers/shared/inputHandler.ts | 6 +----- src/vim-web/core-viewers/ultra/inputAdapter.ts | 5 +++++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vim-web/core-viewers/shared/inputAdapter.ts b/src/vim-web/core-viewers/shared/inputAdapter.ts index 6efa8c6a..0d90cb88 100644 --- a/src/vim-web/core-viewers/shared/inputAdapter.ts +++ b/src/vim-web/core-viewers/shared/inputAdapter.ts @@ -4,6 +4,7 @@ export interface IInputAdapter{ init: () => void toggleOrthographic: () => void + toggleCameraOrbitMode: () => void resetCamera: () => void clearSelection: () => void frameCamera: () => void diff --git a/src/vim-web/core-viewers/shared/inputHandler.ts b/src/vim-web/core-viewers/shared/inputHandler.ts index 373c6064..6e9c0477 100644 --- a/src/vim-web/core-viewers/shared/inputHandler.ts +++ b/src/vim-web/core-viewers/shared/inputHandler.ts @@ -78,11 +78,7 @@ export class InputHandler extends BaseInputHandler { this.keyboard.registerKeyUp('KeyP', 'replace', () => adapter.toggleOrthographic()); this.keyboard.registerKeyUp('Equal', 'replace', () => this.moveSpeed++); this.keyboard.registerKeyUp('Minus', 'replace', () => this.moveSpeed--); - this.keyboard.registerKeyUp('Space', 'replace', () => { - this._pointerActive = this._pointerActive === PointerMode.ORBIT ? PointerMode.LOOK : PointerMode.ORBIT; - this._pointerFallback = this._pointerActive; - this._onPointerModeChanged.dispatch(); - }); + this.keyboard.registerKeyUp('Space', 'replace', () => adapter.toggleCameraOrbitMode()); this.keyboard.registerKeyUp('Home', 'replace', () => adapter.resetCamera()); this.keyboard.registerKeyUp('Escape', 'replace', () => adapter.clearSelection()); this.keyboard.registerKeyUp('KeyF', 'replace', () => { diff --git a/src/vim-web/core-viewers/ultra/inputAdapter.ts b/src/vim-web/core-viewers/ultra/inputAdapter.ts index ed86a156..48920b67 100644 --- a/src/vim-web/core-viewers/ultra/inputAdapter.ts +++ b/src/vim-web/core-viewers/ultra/inputAdapter.ts @@ -7,6 +7,7 @@ import * as THREE from 'three'; * Maps keyboard `code` strings to numeric keycodes. */ const CODE_TO_KEYCODE: Record = { + 'Space': 32, 'ArrowUp': 38, 'ArrowDown': 40, 'ArrowLeft': 37, @@ -56,6 +57,10 @@ function createAdapter(viewer: Viewer): IInputAdapter { toggleOrthographic: () => { console.log('toggleOrthographic. Not supported yet'); }, + toggleCameraOrbitMode: () => { + // A bit hacky, but we send a space key event to toggle orbit mode + viewer.rpc.RPCKeyEvent(CODE_TO_KEYCODE['Space'], true); + }, resetCamera: () => { viewer.camera.restoreSavedPosition(); }, From 84a2f79d1eb94901eb6c0ad39bae3f5b1b89c904 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Tue, 29 Jul 2025 17:06:11 -0400 Subject: [PATCH 18/24] added support for minimize and icon to message box --- .../react-viewers/panels/isolationPanel.tsx | 1 - .../react-viewers/panels/messageBox.tsx | 23 +++++++++++++++---- src/vim-web/react-viewers/ultra/viewer.tsx | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/vim-web/react-viewers/panels/isolationPanel.tsx b/src/vim-web/react-viewers/panels/isolationPanel.tsx index 842673d9..c61b46ef 100644 --- a/src/vim-web/react-viewers/panels/isolationPanel.tsx +++ b/src/vim-web/react-viewers/panels/isolationPanel.tsx @@ -8,7 +8,6 @@ export const Ids = { } export const IsolationPanel = forwardRef( - (props, ref) => { return ( void; } @@ -13,22 +15,26 @@ export type MessageBoxPropsTyped = MessageBoxProps & { } export function MessageBox (props: {value: MessageBoxProps}) { + const [minimized, setMinimized] = React.useState(true) + const p = props.value if (!p.title || !p.body) return null return (
{/* Header Section */}
+ {props.value.icon} {title(p.title)} - {closeBtn(p.onClose)} + {props.value.canClose && closeBtn(p.onClose)} + {props.value.minimize && minimizeButton(minimized, setMinimized)}
{/* Body Section */} - {divider()} - {body(p.body)} + {!minimized && divider()} + {!minimized && body(p.body)} {/* Footer Section */} - {footer(p.footer)} + {!minimized && footer(p.footer)}
) } @@ -44,6 +50,15 @@ function closeBtn (onClose: () => void) { } +function minimizeButton (minimized: boolean, setMinimized: (value:boolean) => void) { + return +} + function body (content: string | JSX.Element) { if (content === undefined) return null if (typeof content === 'string') { diff --git a/src/vim-web/react-viewers/ultra/viewer.tsx b/src/vim-web/react-viewers/ultra/viewer.tsx index 3b2e3ef8..93e83ab3 100644 --- a/src/vim-web/react-viewers/ultra/viewer.tsx +++ b/src/vim-web/react-viewers/ultra/viewer.tsx @@ -138,8 +138,8 @@ export function Viewer (props: { content={controlBarCustom(controlBar)} show={true} /> - - + + }}/> From 81dc5e7cae2aec16c514f33237b6dacaec3ac30c Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Tue, 29 Jul 2025 17:06:31 -0400 Subject: [PATCH 19/24] version": "0.5.0-dev.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a3a263e2..e4ce8527 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vim-web", - "version": "0.5.0-dev.11", + "version": "0.5.0-dev.13", "description": "A demonstration app built on top of the vim-webgl-viewer", "type": "module", "files": [ From d16376d966dfc4c360275afd1e9b77c306db2629 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Tue, 29 Jul 2025 17:06:31 -0400 Subject: [PATCH 20/24] version": "0.5.0-dev.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a3a263e2..e4ce8527 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vim-web", - "version": "0.5.0-dev.11", + "version": "0.5.0-dev.13", "description": "A demonstration app built on top of the vim-webgl-viewer", "type": "module", "files": [ From 45d52709d4810c62ab7db75fd514b51f4b6f718e Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Wed, 30 Jul 2025 10:51:51 -0400 Subject: [PATCH 21/24] fixed pointer override and context menu on mouse drag --- package.json | 2 +- .../core-viewers/shared/inputHandler.ts | 29 ++++++++++++------- .../core-viewers/shared/mouseHandler.ts | 16 ++++++++++ .../core-viewers/webgl/viewer/inputAdapter.ts | 6 ++-- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index e4ce8527..9faabd01 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vim-web", - "version": "0.5.0-dev.13", + "version": "0.5.0-dev.14", "description": "A demonstration app built on top of the vim-webgl-viewer", "type": "module", "files": [ diff --git a/src/vim-web/core-viewers/shared/inputHandler.ts b/src/vim-web/core-viewers/shared/inputHandler.ts index 6e9c0477..9516c1a0 100644 --- a/src/vim-web/core-viewers/shared/inputHandler.ts +++ b/src/vim-web/core-viewers/shared/inputHandler.ts @@ -63,10 +63,6 @@ export class InputHandler extends BaseInputHandler { this.rotateSpeed = settings.rotateSpeed ?? 1 this.orbitSpeed = settings.orbitSpeed ?? 1 - this.reg(document, 'contextmenu', (e: MouseEvent) => { - this._onContextMenu.dispatch(new THREE.Vector2(e.clientX, e.clientY)) - e.preventDefault() - }) this.keyboard = new KeyboardHandler(canvas) this.mouse = new MouseHandler(canvas) this.touch = new TouchHandler(canvas) @@ -91,18 +87,29 @@ export class InputHandler extends BaseInputHandler { } // Mouse controls + this.mouse.onContextMenu = (pos: THREE.Vector2) => this._onContextMenu.dispatch(pos); this.mouse.onButtonDown = adapter.mouseDown this.mouse.onMouseMove = adapter.mouseMove - this.mouse.onButtonUp = adapter.mouseUp + this.mouse.onButtonUp = (pos: THREE.Vector2, button: number) => { + this.pointerOverride = undefined + adapter.mouseUp(pos, button) + } this.mouse.onDrag = (delta: THREE.Vector2, button: number) =>{ if(button === 0){ - if(this._pointerActive === PointerMode.ORBIT) adapter.orbitCamera(toRotation(delta, this.orbitSpeed)) - if(this._pointerActive === PointerMode.LOOK) adapter.rotateCamera(toRotation(delta, this.rotateSpeed)) - if(this._pointerActive === PointerMode.PAN) adapter.panCamera(delta) - if(this._pointerActive === PointerMode.ZOOM) adapter.dollyCamera(delta) + if(this.pointerActive === PointerMode.ORBIT) adapter.orbitCamera(toRotation(delta, this.orbitSpeed)) + if(this.pointerActive === PointerMode.LOOK) adapter.rotateCamera(toRotation(delta, this.rotateSpeed)) + if(this.pointerActive === PointerMode.PAN) adapter.panCamera(delta) + if(this.pointerActive === PointerMode.ZOOM) adapter.dollyCamera(delta) + } + if(button === 2){ + this.pointerOverride = PointerMode.LOOK + adapter.rotateCamera(toRotation(delta,1)) + } - if(button === 2) adapter.rotateCamera(toRotation(delta,1)) - if(button === 1) adapter.panCamera(delta) + if(button === 1){ + this.pointerOverride = PointerMode.PAN + adapter.panCamera(delta) + } } this.mouse.onClick = (pos: THREE.Vector2, modif: boolean) => adapter.selectAtPointer(pos, modif) diff --git a/src/vim-web/core-viewers/shared/mouseHandler.ts b/src/vim-web/core-viewers/shared/mouseHandler.ts index fbef4357..4d1de34c 100644 --- a/src/vim-web/core-viewers/shared/mouseHandler.ts +++ b/src/vim-web/core-viewers/shared/mouseHandler.ts @@ -19,6 +19,7 @@ export class MouseHandler extends BaseInputHandler { onClick: (position: THREE.Vector2, ctrl: boolean) => void; onDoubleClick: (position: THREE.Vector2) => void; onWheel: (value: number, ctrl: boolean) => void; + onContextMenu: (position: THREE.Vector2) => void; constructor(canvas: HTMLCanvasElement) { super(canvas); @@ -64,6 +65,7 @@ export class MouseHandler extends BaseInputHandler { this.handleDoubleClick(event); }else{ this.handleMouseClick(event); + this.handleContextMenu(event); } event.preventDefault(); } @@ -82,6 +84,20 @@ export class MouseHandler extends BaseInputHandler { this.onClick?.(pos, modif); } + private async handleContextMenu(event: PointerEvent): Promise { + if (event.pointerType !== 'mouse') return; + if(event.button !== 2) return; + + const pos = this.relativePosition(event); + + if (!Utils.almostEqual(this._lastMouseDownPosition, pos, 0.01)) { + return; + } + + this.onContextMenu?.(new THREE.Vector2(event.clientX, event.clientY)); + } + + private handlePointerMove(event: PointerEvent): void { if (event.pointerType !== 'mouse') return; this._canvas.focus(); diff --git a/src/vim-web/core-viewers/webgl/viewer/inputAdapter.ts b/src/vim-web/core-viewers/webgl/viewer/inputAdapter.ts index 354a755a..2852e8bc 100644 --- a/src/vim-web/core-viewers/webgl/viewer/inputAdapter.ts +++ b/src/vim-web/core-viewers/webgl/viewer/inputAdapter.ts @@ -34,9 +34,9 @@ function createAdapter(viewer: Viewer ) : IInputAdapter { viewer.camera.orthographic = !viewer.camera.orthographic }, toggleCameraOrbitMode: () => { - this._pointerActive = this._pointerActive === PointerMode.ORBIT ? PointerMode.LOOK : PointerMode.ORBIT; - this._pointerFallback = this._pointerActive; - this._onPointerModeChanged.dispatch(); + viewer.inputs.pointerActive = viewer.inputs.pointerActive === PointerMode.ORBIT + ? PointerMode.LOOK + : PointerMode.ORBIT; }, resetCamera: () => { From 78a12e4fdfaf0ccdf4ce156c2132523b712b512e Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Wed, 30 Jul 2025 14:14:00 -0400 Subject: [PATCH 22/24] errors are not minimized by default, numpad +- buttons --- src/vim-web/core-viewers/shared/inputHandler.ts | 4 ++-- src/vim-web/core-viewers/shared/keyboardHandler.ts | 8 ++++++-- src/vim-web/react-viewers/panels/messageBox.tsx | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vim-web/core-viewers/shared/inputHandler.ts b/src/vim-web/core-viewers/shared/inputHandler.ts index 9516c1a0..549c8197 100644 --- a/src/vim-web/core-viewers/shared/inputHandler.ts +++ b/src/vim-web/core-viewers/shared/inputHandler.ts @@ -72,8 +72,8 @@ export class InputHandler extends BaseInputHandler { this.keyboard.onKeyUp = (key: string) => adapter.keyUp(key) this.keyboard.registerKeyUp('KeyP', 'replace', () => adapter.toggleOrthographic()); - this.keyboard.registerKeyUp('Equal', 'replace', () => this.moveSpeed++); - this.keyboard.registerKeyUp('Minus', 'replace', () => this.moveSpeed--); + this.keyboard.registerKeyUp(['Equal', 'NumpadAdd'], 'replace', () => this.moveSpeed++); + this.keyboard.registerKeyUp(['Minus', 'NumpadSubtract'], 'replace', () => this.moveSpeed--); this.keyboard.registerKeyUp('Space', 'replace', () => adapter.toggleCameraOrbitMode()); this.keyboard.registerKeyUp('Home', 'replace', () => adapter.resetCamera()); this.keyboard.registerKeyUp('Escape', 'replace', () => adapter.clearSelection()); diff --git a/src/vim-web/core-viewers/shared/keyboardHandler.ts b/src/vim-web/core-viewers/shared/keyboardHandler.ts index d0b60a9a..3801aa01 100644 --- a/src/vim-web/core-viewers/shared/keyboardHandler.ts +++ b/src/vim-web/core-viewers/shared/keyboardHandler.ts @@ -126,8 +126,12 @@ export class KeyboardHandler extends BaseInputHandler { * @param code The event.code of the key. * @param handler Callback invoked on key up. */ - public registerKeyUp(code: string, mode: CallbackMode, handler: () => void): void { - this.registerKey(this.keyUpHandlers, code, mode, handler); + public registerKeyUp(code: string | string[], mode: CallbackMode, handler: () => void): void { + if (Array.isArray(code)) { + code.forEach(c => this.registerKey(this.keyUpHandlers, c, mode, handler)); + } else { + this.registerKey(this.keyUpHandlers, code, mode, handler); + } } private registerKey(map: Map void>, code: string, mode: CallbackMode, callback: () => void){ diff --git a/src/vim-web/react-viewers/panels/messageBox.tsx b/src/vim-web/react-viewers/panels/messageBox.tsx index 87aeed7f..7fa2677b 100644 --- a/src/vim-web/react-viewers/panels/messageBox.tsx +++ b/src/vim-web/react-viewers/panels/messageBox.tsx @@ -15,7 +15,7 @@ export type MessageBoxPropsTyped = MessageBoxProps & { } export function MessageBox (props: {value: MessageBoxProps}) { - const [minimized, setMinimized] = React.useState(true) + const [minimized, setMinimized] = React.useState(false) const p = props.value if (!p.title || !p.body) return null From 97823fa4bce8168962b673851efacf72f17787a2 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Wed, 30 Jul 2025 14:14:08 -0400 Subject: [PATCH 23/24] "version": "0.5.0-dev.15", --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9faabd01..b4add9b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vim-web", - "version": "0.5.0-dev.14", + "version": "0.5.0-dev.15", "description": "A demonstration app built on top of the vim-webgl-viewer", "type": "module", "files": [ From af5f611a540e0153ba3532b54d0e485f7d54e3f9 Mon Sep 17 00:00:00 2001 From: vim-sroberge Date: Wed, 15 Oct 2025 15:23:14 -0400 Subject: [PATCH 24/24] types, scroll fix, message box props --- package.json | 2 +- src/vim-web/react-viewers/bim/bimTree.tsx | 2 +- src/vim-web/react-viewers/panels/messageBox.tsx | 2 +- src/vim-web/react-viewers/webgl/viewer.tsx | 2 +- tsconfig.types.json | 10 ++++++++++ 5 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 tsconfig.types.json diff --git a/package.json b/package.json index b4add9b5..92a73670 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vim-web", - "version": "0.5.0-dev.15", + "version": "0.5.0-dev.17", "description": "A demonstration app built on top of the vim-webgl-viewer", "type": "module", "files": [ diff --git a/src/vim-web/react-viewers/bim/bimTree.tsx b/src/vim-web/react-viewers/bim/bimTree.tsx index 21e2f069..9eea50f5 100644 --- a/src/vim-web/react-viewers/bim/bimTree.tsx +++ b/src/vim-web/react-viewers/bim/bimTree.tsx @@ -328,7 +328,7 @@ function scrollToSelection (div: HTMLDivElement) { // Scroll to Top if (rectElem.top < rectContainer.top || rectElem.top < 0) { - selection.scrollIntoView() + selection.scrollIntoView({ block: 'nearest', inline: 'nearest' }) } } diff --git a/src/vim-web/react-viewers/panels/messageBox.tsx b/src/vim-web/react-viewers/panels/messageBox.tsx index 7fa2677b..49c9b89c 100644 --- a/src/vim-web/react-viewers/panels/messageBox.tsx +++ b/src/vim-web/react-viewers/panels/messageBox.tsx @@ -15,7 +15,7 @@ export type MessageBoxPropsTyped = MessageBoxProps & { } export function MessageBox (props: {value: MessageBoxProps}) { - const [minimized, setMinimized] = React.useState(false) + const [minimized, setMinimized] = React.useState(props.value.minimize ?? false) const p = props.value if (!p.title || !p.body) return null diff --git a/src/vim-web/react-viewers/webgl/viewer.tsx b/src/vim-web/react-viewers/webgl/viewer.tsx index 9d60c0d8..bc1d9a75 100644 --- a/src/vim-web/react-viewers/webgl/viewer.tsx +++ b/src/vim-web/react-viewers/webgl/viewer.tsx @@ -11,7 +11,7 @@ import { AxesPanelMemo } from '../panels/axesPanel' import { ControlBar, ControlBarCustomization } from '../controlbar/controlBar' import { useControlBar } from '../state/controlBarState' import { RestOfScreen } from '../panels/restOfScreen' -import { OptionalBimPanel } from '../bim/bimPanel' +import { OptionalBimPanel } from '../bim/bimPanel' import { ContextMenuCustomization, showContextMenu, diff --git a/tsconfig.types.json b/tsconfig.types.json new file mode 100644 index 00000000..a58a0e48 --- /dev/null +++ b/tsconfig.types.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "./dist/types", + "rootDir": "./src/vim-web" + }, + "include": ["src/vim-web/**/*"] +}