Skip to content

Commit b4ae89a

Browse files
jochem25claude
andcommitted
fix: ghost mode depthWrite=false so highlight overlay renders on top
The fragment highlight overlay was being obscured by ghosted materials that still wrote to the depth buffer. Setting depthWrite=false on ghosted materials allows the opaque highlight to render correctly. Reverts from non-working FragmentsModel.setOpacity() back to proven material-level approach with this depth buffer fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a201552 commit b4ae89a

1 file changed

Lines changed: 68 additions & 28 deletions

File tree

viewer/src/engine/ViewerEngine.ts

Lines changed: 68 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ export class ViewerEngine {
104104
/** Whether isolation mode is active. */
105105
private _isolated = false;
106106

107+
/** Saved material state for isolation restore. */
108+
private savedMaterials = new Map<
109+
THREE.Material,
110+
{ opacity: number; transparent: boolean; depthWrite: boolean }
111+
>();
112+
107113
/** Active section planes. */
108114
private sectionPlanes: THREE.Plane[] = [];
109115

@@ -484,49 +490,83 @@ export class ViewerEngine {
484490
}
485491

486492
/**
487-
* Isolate an element: ghost everything at fragment level,
488-
* then restore the selected element to full opacity with accent color.
493+
* Isolate an element: ghost everything transparent,
494+
* then highlight the selected element with an opaque overlay.
489495
*
490-
* Uses FragmentsModel.setOpacity() for per-element control (works correctly
491-
* even when elements share meshes in batched IFC geometry).
496+
* Material-level opacity + depthWrite:false ghosts all geometry.
497+
* Fragment-level highlight renders the selected element on top
498+
* (visible because ghosted materials no longer write to depth buffer).
492499
*/
493500
async isolateElement(globalId: string): Promise<void> {
494-
if (!this.world || !this.fragments) return;
501+
if (!this.world) return;
495502

496503
// Restore any previous isolation first
497-
await this.clearIsolation();
498-
499-
// Get the selected element's model ID map
500-
const selectedMap = await this.fragments.guidsToModelIdMap([globalId]);
501-
502-
// Ghost all elements, then restore + colorize the selected one
503-
for (const [modelId, model] of this.fragments.list) {
504-
// Ghost ALL items in this model at fragment level
505-
await model.setOpacity(undefined, GHOST_OPACITY);
506-
507-
// If this model contains the selected element, restore it
508-
const selectedIds = selectedMap[modelId];
509-
if (selectedIds && selectedIds.size > 0) {
510-
const ids = [...selectedIds];
511-
await model.resetOpacity(ids);
512-
await model.setColor(ids, new THREE.Color("#44B6A8")); // Verdigris accent
504+
this.restoreMaterials();
505+
await this.clearHighlights();
506+
507+
// Ghost ALL meshes: transparent + no depth write so highlight shows through
508+
for (const obj of this.modelObjects.values()) {
509+
obj.traverse((child) => {
510+
if (!(child instanceof THREE.Mesh)) return;
511+
const materials = Array.isArray(child.material)
512+
? child.material
513+
: [child.material];
514+
for (const mat of materials) {
515+
if (!mat || this.savedMaterials.has(mat)) continue;
516+
this.savedMaterials.set(mat, {
517+
opacity: mat.opacity,
518+
transparent: mat.transparent,
519+
depthWrite: mat.depthWrite,
520+
});
521+
mat.transparent = true;
522+
mat.opacity = GHOST_OPACITY;
523+
mat.depthWrite = false;
524+
mat.needsUpdate = true;
525+
}
526+
});
527+
}
528+
529+
// Highlight selected element with opaque overlay
530+
if (this.fragments) {
531+
try {
532+
const modelIdMap = await this.fragments.guidsToModelIdMap([globalId]);
533+
if (Object.keys(modelIdMap).length > 0) {
534+
const style: FRAGS.MaterialDefinition = {
535+
color: new THREE.Color("#44B6A8"),
536+
renderedFaces: FRAGS.RenderedFaces.TWO,
537+
opacity: 1.0,
538+
transparent: false,
539+
};
540+
await this.fragments.highlight(style, modelIdMap);
541+
}
542+
} catch {
543+
// Highlight failed — ghost-only mode still works
513544
}
514545
}
515546

516547
this._isolated = true;
517548
}
518549

519550
/**
520-
* Clear isolation: reset all fragment opacities and colors.
551+
* Restore all materials to their original state.
521552
*/
522-
async clearIsolation(): Promise<void> {
523-
if (!this._isolated || !this.fragments) return;
524-
525-
for (const [, model] of this.fragments.list) {
526-
await model.resetOpacity(undefined);
527-
await model.resetColor(undefined);
553+
private restoreMaterials(): void {
554+
for (const [mat, saved] of this.savedMaterials) {
555+
mat.opacity = saved.opacity;
556+
mat.transparent = saved.transparent;
557+
mat.depthWrite = saved.depthWrite;
558+
mat.needsUpdate = true;
528559
}
560+
this.savedMaterials.clear();
561+
}
529562

563+
/**
564+
* Clear isolation: restore all materials and remove highlight overlay.
565+
*/
566+
async clearIsolation(): Promise<void> {
567+
if (!this._isolated) return;
568+
this.restoreMaterials();
569+
await this.clearHighlights();
530570
this._isolated = false;
531571
}
532572

0 commit comments

Comments
 (0)