diff --git a/web/js/viewer/core/BaseViewer.js b/web/js/viewer/core/BaseViewer.js index f1d07c3d..ddc87370 100644 --- a/web/js/viewer/core/BaseViewer.js +++ b/web/js/viewer/core/BaseViewer.js @@ -384,6 +384,7 @@ export class BaseViewer { format: result.format, actors: result.actors.length, hasTexture: result.hasTexture || false, + hasVertexColors: result.hasVertexColors || false, fields: result.fields || [] }); @@ -408,6 +409,20 @@ export class BaseViewer { if (this.config.enableTextures && this.textureManager && result.hasTexture) { this.textureManager.applyTextureFixesMultiple(this.actors); this.textureManager.configureRenderer(this.renderer); + } else if (result.hasVertexColors) { + // Mesh has vertex colors - don't override with default color + // Just configure edges if needed, colors are already set up by loader + this.actors.forEach(actor => { + const property = actor.getProperty(); + if (property) { + if (this.config.showEdges) { + property.setEdgeVisibility(true); + if (this.config.edgeColor) { + property.setEdgeColor(...this.config.edgeColor); + } + } + } + }); } else { // Apply standard actor configuration this.actorManager.configureActors(this.actors, { diff --git a/web/js/viewer/loaders/GLTFLoader.js b/web/js/viewer/loaders/GLTFLoader.js index 4685d8b0..e4dfdc5a 100644 --- a/web/js/viewer/loaders/GLTFLoader.js +++ b/web/js/viewer/loaders/GLTFLoader.js @@ -75,9 +75,10 @@ export async function loadGLTF(filepath, vtk, renderer, options = {}) { const actorsMap = importer.getActors(); const actors = Array.from(actorsMap.values()); - // Track texture information + // Track texture and vertex color information let hasTexture = false; let hasMaterial = false; + let hasVertexColors = false; // Process actors and apply texture fixes if (applyTextureFixes) { @@ -87,6 +88,12 @@ export async function loadGLTF(filepath, vtk, renderer, options = {}) { const input = mapper?.getInputData(); if (input && input.getNumberOfPoints() > 0) { + // Check for vertex colors (stored as scalars in VTK) + const scalars = input.getPointData()?.getScalars(); + if (scalars && scalars.getNumberOfComponents() >= 3) { + hasVertexColors = true; + } + if (property) { // CRITICAL FIX: Remove metallicRoughnessTexture if present // VTK.js GLTFImporter may use this instead of baseColorTexture @@ -131,9 +138,17 @@ export async function loadGLTF(filepath, vtk, renderer, options = {}) { } } - // Configure mapper for texture rendering + // Configure mapper for rendering if (mapper) { - mapper.setScalarVisibility(false); + if (hasVertexColors && !hasTexture) { + // Enable scalar visibility to show vertex colors + mapper.setScalarVisibility(true); + mapper.setScalarModeToUsePointData(); + mapper.setColorModeToDirectScalars(); + } else { + // Disable scalars for texture rendering + mapper.setScalarVisibility(false); + } mapper.modified(); } } @@ -171,6 +186,8 @@ export async function loadGLTF(filepath, vtk, renderer, options = {}) { actors, importer, bounds, + hasTexture, + hasVertexColors, metadata: { format: 'GLTF', numActors: actors.length, @@ -178,6 +195,7 @@ export async function loadGLTF(filepath, vtk, renderer, options = {}) { numPolys: totalPolys, bounds, hasTexture, + hasVertexColors, hasMaterial } }; diff --git a/web/js/viewer/viewers/DualViewer.js b/web/js/viewer/viewers/DualViewer.js index 0487e36b..9ceaf346 100644 --- a/web/js/viewer/viewers/DualViewer.js +++ b/web/js/viewer/viewers/DualViewer.js @@ -306,8 +306,17 @@ export class DualViewer { // Apply texture fixes for GLTF/GLB vp.textureManager.applyTextureFixesMultiple(result.actors); vp.textureManager.configureRenderer(vp.renderer); + } else if (result.hasVertexColors) { + // Mesh has vertex colors - don't override with default color + // Just configure edges if needed, colors are already set up by loader + result.actors.forEach(actor => { + const property = actor.getProperty(); + if (property && this.config.showEdges) { + property.setEdgeVisibility(true); + } + }); } else { - // Standard actor configuration + // Standard actor configuration - apply default color this.actorManager.configureActors(result.actors, { color: defaultColor, showEdges: this.config.showEdges @@ -325,7 +334,8 @@ export class DualViewer { viewport, filename: vp.filename, format: result.format, - hasTexture: result.hasTexture || false + hasTexture: result.hasTexture || false, + hasVertexColors: result.hasVertexColors || false }); return result; diff --git a/web/viewer_dual_slider.html b/web/viewer_dual_slider.html index 67e4b520..1519cdc9 100644 --- a/web/viewer_dual_slider.html +++ b/web/viewer_dual_slider.html @@ -238,6 +238,10 @@ let mesh1Polydata = null; let mesh2Polydata = null; + // GLTF actors (stored separately since they replace the pre-made actors) + let mesh1GltfActors = null; + let mesh2GltfActors = null; + // Camera sync state let isSyncing = false; let isAnimating1 = false; @@ -429,21 +433,88 @@ const arrayBuffer = await response.arrayBuffer(); if (isGLB) { - reader.parseAsArrayBuffer(arrayBuffer); - await reader.update(); - const source = reader.getOutputData(); - targetMapper.setInputData(source); - targetMapper.update(); + // GLTFImporter requires using its own actors - we can't extract polydata + // So we need to replace the pre-made actors with the GLTF actors + const targetRenderer = isMesh1 ? renderer1 : renderer2; + const targetActor = isMesh1 ? mesh1Actor : mesh2Actor; - const polydata = targetMapper.getInputData(); - const bounds = polydata.getBounds(); + // Clean up any previous GLTF actors + const prevGltfActors = isMesh1 ? mesh1GltfActors : mesh2GltfActors; + if (prevGltfActors) { + prevGltfActors.forEach(actor => targetRenderer.removeActor(actor)); + } + + const importer = vtkGLTFImporter.newInstance(); + importer.setRenderer(targetRenderer); + importer.setUrl(filepath); + + // Wait for GLTF parsing using onReady callback + await new Promise((resolve, reject) => { + importer.onReady(() => resolve()); + importer.loadData().catch(reject); + }); + + // Import actors - this adds them to the renderer + importer.importActors(); + const actorsMap = importer.getActors(); + const actors = Array.from(actorsMap.values()); + + console.log(`[GeomPack VTK Slider Viewer] GLTF actors found: ${actors.length}`); + + if (actors.length === 0) { + throw new Error('No actors found in GLTF'); + } + + // Remove the pre-made actor (it has no data) + targetRenderer.removeActor(targetActor); + + // Calculate bounds from all GLTF actors and apply vertex color settings + let bounds = null; + actors.forEach((actor, idx) => { + const mapper = actor.getMapper(); + const property = actor.getProperty(); + const input = mapper?.getInputData(); + + if (input) { + const actorBounds = actor.getBounds(); + if (!bounds) { + bounds = [...actorBounds]; + } else { + bounds[0] = Math.min(bounds[0], actorBounds[0]); + bounds[1] = Math.max(bounds[1], actorBounds[1]); + bounds[2] = Math.min(bounds[2], actorBounds[2]); + bounds[3] = Math.max(bounds[3], actorBounds[3]); + bounds[4] = Math.min(bounds[4], actorBounds[4]); + bounds[5] = Math.max(bounds[5], actorBounds[5]); + } + + // Check for vertex colors + const scalars = input.getPointData()?.getScalars(); + if (scalars && scalars.getNumberOfComponents() >= 3) { + mapper.setScalarVisibility(true); + mapper.setScalarModeToUsePointData(); + mapper.setColorModeToDirectScalars(); + console.log(`[GeomPack VTK Slider Viewer] Mesh ${isMesh1 ? '1' : '2'} actor ${idx} has vertex colors`); + } + + // Apply material fixes for better rendering + if (property) { + if (typeof property.setMetallic === 'function') property.setMetallic(0.0); + if (typeof property.setRoughness === 'function') property.setRoughness(1.0); + } + } + }); + + console.log(`[GeomPack VTK Slider Viewer] Mesh ${isMesh1 ? '1' : '2'} bounds:`, bounds); if (isMesh1) { mesh1Bounds = bounds; - mesh1Polydata = polydata; + mesh1Polydata = null; // GLTF doesn't provide simple polydata access + mesh1GltfActors = actors; // Store for later reference } else { mesh2Bounds = bounds; - mesh2Polydata = polydata; + mesh2Polydata = null; + mesh2GltfActors = actors; } } else { if (isSTL) { @@ -712,12 +783,20 @@ positionCamera(combinedBounds); - // Apply opacity values + // Apply opacity values (handle both regular actors and GLTF actors) if (data.opacity1 !== undefined) { - mesh1Actor.getProperty().setOpacity(data.opacity1); + if (mesh1GltfActors) { + mesh1GltfActors.forEach(actor => actor.getProperty().setOpacity(data.opacity1)); + } else { + mesh1Actor.getProperty().setOpacity(data.opacity1); + } } if (data.opacity2 !== undefined) { - mesh2Actor.getProperty().setOpacity(data.opacity2); + if (mesh2GltfActors) { + mesh2GltfActors.forEach(actor => actor.getProperty().setOpacity(data.opacity2)); + } else { + mesh2Actor.getProperty().setOpacity(data.opacity2); + } } renderWindow1.render(); @@ -737,8 +816,17 @@ // Control event handlers document.getElementById('showEdges').addEventListener('change', (e) => { const edgeViz = e.target.checked; - mesh1Actor.getProperty().setEdgeVisibility(edgeViz); - mesh2Actor.getProperty().setEdgeVisibility(edgeViz); + // Handle both regular actors and GLTF actors + if (mesh1GltfActors) { + mesh1GltfActors.forEach(actor => actor.getProperty().setEdgeVisibility(edgeViz)); + } else { + mesh1Actor.getProperty().setEdgeVisibility(edgeViz); + } + if (mesh2GltfActors) { + mesh2GltfActors.forEach(actor => actor.getProperty().setEdgeVisibility(edgeViz)); + } else { + mesh2Actor.getProperty().setEdgeVisibility(edgeViz); + } renderWindow1.render(); renderWindow2.render(); }); diff --git a/web/viewer_dual_textured.html b/web/viewer_dual_textured.html index a9cff2e0..17660685 100644 --- a/web/viewer_dual_textured.html +++ b/web/viewer_dual_textured.html @@ -606,6 +606,11 @@ ]; } + // Check for vertex colors early + const scalars = input.getPointData()?.getScalars(); + const hasVertexColors = scalars && scalars.getNumberOfComponents() >= 3; + let hasTexture = false; + // Apply texture fixes if (property) { // CRITICAL FIX: Remove metallicRoughnessTexture if present @@ -630,6 +635,7 @@ if (texture) { actor.addTexture(texture); + hasTexture = true; } // Set material to non-metallic for proper texture display @@ -638,9 +644,17 @@ property.setInterpolation(2); } - // Configure mapper for texture rendering + // Configure mapper for rendering if (mapper) { - mapper.setScalarVisibility(false); + if (hasVertexColors && !hasTexture) { + // Enable vertex colors + mapper.setScalarVisibility(true); + mapper.setScalarModeToUsePointData(); + mapper.setColorModeToDirectScalars(); + } else { + // Disable scalars for texture rendering + mapper.setScalarVisibility(false); + } mapper.modified(); } }