Add comprehensive material sharing validation and diagnostics

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 <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-11-13 11:18:07 -06:00
parent bda0735c7f
commit 02c08b35f2
2 changed files with 66 additions and 3 deletions

View File

@ -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;

View File

@ -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) {