From 02c08b35f20eb2af2e549514d81c5ab3b14cf4f4 Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Thu, 13 Nov 2025 11:18:07 -0600 Subject: [PATCH] Add comprehensive material sharing validation and diagnostics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented extensive logging and validation to diagnose material sharing issues and prevent unnecessary material creation: **Validation Added:** - Pre-creation check: Verify tool meshes have materials before creating instances - Early exit if tool mesh lacks material to prevent bad instances - Post-creation validation in buildColor.ts to catch tool creation issues **Enhanced Diagnostics:** - Detailed debug logging for tool mesh lookup and instance creation - Error logging with full context when material sharing fails - Source mesh material validation for InstancedMesh - Lists available tool meshes when lookup fails **Statistics Tracking:** - Tracks instances created vs materials shared - Counts fallback material creations - Logs sharing rate every 10 instances (target: 100%) - Helps identify material sharing failures in production **Expected Outcome:** - 100% material sharing rate for tool-based entities - Zero fallback material creations - All instances inherit materials from tool templates - Better draw call batching (same material = batched rendering) This diagnostic infrastructure will identify: 1. Timing issues (tools not ready when entities created) 2. Tool mesh creation failures 3. BabylonJS InstancedMesh material inheritance issues 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../functions/buildMeshFromDiagramEntity.ts | 61 ++++++++++++++++++- src/toolbox/functions/buildColor.ts | 8 +++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/diagram/functions/buildMeshFromDiagramEntity.ts b/src/diagram/functions/buildMeshFromDiagramEntity.ts index 33a0446..fc27a9f 100644 --- a/src/diagram/functions/buildMeshFromDiagramEntity.ts +++ b/src/diagram/functions/buildMeshFromDiagramEntity.ts @@ -20,6 +20,13 @@ import {xyztovec} from "./vectorConversion"; import {AnimatedLineTexture} from "../../util/animatedLineTexture"; import {LightmapGenerator} from "../../util/lightmapGenerator"; +// Material sharing statistics +let materialStats = { + instancesCreated: 0, + materialsShared: 0, + materialsFallback: 0 +}; + export function buildMeshFromDiagramEntity(entity: DiagramEntity, scene: Scene): AbstractMesh { const logger = log.getLogger('buildMeshFromDiagramEntity'); if (!entity) { @@ -81,12 +88,38 @@ function createNewInstanceIfNecessary(entity: DiagramEntity, scene: Scene): Abst case DiagramTemplates.CONE: case DiagramTemplates.PLANE: case DiagramTemplates.PERSON: - const toolMesh = scene.getMeshById("tool-" + entity.template + "-" + entity.color); + const toolMeshId = "tool-" + entity.template + "-" + entity.color; + const toolMesh = scene.getMeshById(toolMeshId); if (toolMesh && !oldMesh) { + // Verify tool mesh has material before creating instance + if (!toolMesh.material) { + logger.error(`Tool mesh ${toolMeshId} found but has no material! This should never happen.`); + logger.error(`Tool mesh state: enabled=${toolMesh.isEnabled()}, parent=${toolMesh.parent?.name}`); + // Don't create instance without material + break; + } + + logger.debug(`Found tool mesh: ${toolMeshId}, material: ${toolMesh.material.id}`); newMesh = new InstancedMesh(entity.id, (toolMesh as Mesh)); + // InstancedMesh.material property delegates to sourceMesh.material automatically + logger.debug(`Created instance ${entity.id}, inherited material: ${newMesh.material?.id}`); + + // Track material sharing statistics + materialStats.instancesCreated++; + if (newMesh.material) { + materialStats.materialsShared++; + } + // newMesh.metadata = {template: entity.template, exportable: true, tool: false}; } else { - logger.warn('no tool mesh found for ' + entity.template + "-" + entity.color); + if (!toolMesh) { + logger.warn(`No tool mesh found for ${toolMeshId}. Available tool meshes: ${ + scene.meshes.filter(m => m.id.startsWith('tool-')).map(m => m.id).slice(0, 5).join(', ') + }...`); + } + if (oldMesh) { + logger.debug(`Skipping instance creation, mesh ${entity.id} already exists`); + } } break; default: @@ -162,9 +195,31 @@ function mapMetadata(entity: DiagramEntity, newMesh: AbstractMesh, scene: Scene) /*if (entity.scale) { newMesh.scaling = xyztovec(entity.scale); }*/ + // Material validation - InstancedMesh should automatically inherit material from source mesh if (!newMesh.material && newMesh?.metadata?.template != "#object-template") { - logger.warn("new material created, this shouldn't happen"); + logger.error(`MATERIAL SHARING FAILURE for mesh ${newMesh.id}:`); + logger.error(` Template: ${newMesh.metadata?.template}`); + logger.error(` Color: ${entity.color}`); + logger.error(` Is InstancedMesh: ${newMesh instanceof InstancedMesh}`); + + if (newMesh instanceof InstancedMesh) { + logger.error(` Source mesh: ${newMesh.sourceMesh?.id}`); + logger.error(` Source mesh material: ${newMesh.sourceMesh?.material?.id || 'MISSING'}`); + logger.error(` This indicates tool mesh was created without material!`); + } + + // Create fallback material as last resort to prevent crashes + logger.warn(`Creating fallback material to prevent crash - this impacts performance!`); newMesh.material = buildMissingMaterial("material-" + entity.id, scene, entity.color); + + // Track fallback material creation + materialStats.materialsFallback++; + } + + // Log material sharing statistics periodically + if (materialStats.instancesCreated > 0 && materialStats.instancesCreated % 10 === 0) { + const sharingRate = (materialStats.materialsShared / materialStats.instancesCreated * 100).toFixed(1); + logger.info(`Material Sharing Stats: ${materialStats.materialsShared}/${materialStats.instancesCreated} (${sharingRate}%), Fallbacks: ${materialStats.materialsFallback}`); } if (entity.text) { newMesh.metadata.text = entity.text; diff --git a/src/toolbox/functions/buildColor.ts b/src/toolbox/functions/buildColor.ts index 97b46e0..1130864 100644 --- a/src/toolbox/functions/buildColor.ts +++ b/src/toolbox/functions/buildColor.ts @@ -51,6 +51,14 @@ export async function buildColor(color: Color3, scene: Scene, parent: TransformN newItem.position = new Vector3(calculatePosition(++i), .1, 0); tools.push(newItem.id); toolMap.set(newItem.id, newItem); + + // Validate that tool instance has proper material inheritance + if (!newItem.material || !newItem.sourceMesh?.material) { + console.error(`Tool creation validation FAILED for ${newItem.id}:`); + console.error(` Tool material: ${!!newItem.material}`); + console.error(` Source mesh: ${newItem.sourceMesh?.id}`); + console.error(` Source material: ${!!newItem.sourceMesh?.material}`); + } } } if (colorBoxMesh.metadata) {