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:
parent
bda0735c7f
commit
02c08b35f2
@ -20,6 +20,13 @@ import {xyztovec} from "./vectorConversion";
|
|||||||
import {AnimatedLineTexture} from "../../util/animatedLineTexture";
|
import {AnimatedLineTexture} from "../../util/animatedLineTexture";
|
||||||
import {LightmapGenerator} from "../../util/lightmapGenerator";
|
import {LightmapGenerator} from "../../util/lightmapGenerator";
|
||||||
|
|
||||||
|
// Material sharing statistics
|
||||||
|
let materialStats = {
|
||||||
|
instancesCreated: 0,
|
||||||
|
materialsShared: 0,
|
||||||
|
materialsFallback: 0
|
||||||
|
};
|
||||||
|
|
||||||
export function buildMeshFromDiagramEntity(entity: DiagramEntity, scene: Scene): AbstractMesh {
|
export function buildMeshFromDiagramEntity(entity: DiagramEntity, scene: Scene): AbstractMesh {
|
||||||
const logger = log.getLogger('buildMeshFromDiagramEntity');
|
const logger = log.getLogger('buildMeshFromDiagramEntity');
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
@ -81,12 +88,38 @@ function createNewInstanceIfNecessary(entity: DiagramEntity, scene: Scene): Abst
|
|||||||
case DiagramTemplates.CONE:
|
case DiagramTemplates.CONE:
|
||||||
case DiagramTemplates.PLANE:
|
case DiagramTemplates.PLANE:
|
||||||
case DiagramTemplates.PERSON:
|
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) {
|
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));
|
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};
|
// newMesh.metadata = {template: entity.template, exportable: true, tool: false};
|
||||||
} else {
|
} 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;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -162,9 +195,31 @@ function mapMetadata(entity: DiagramEntity, newMesh: AbstractMesh, scene: Scene)
|
|||||||
/*if (entity.scale) {
|
/*if (entity.scale) {
|
||||||
newMesh.scaling = xyztovec(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") {
|
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);
|
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) {
|
if (entity.text) {
|
||||||
newMesh.metadata.text = entity.text;
|
newMesh.metadata.text = entity.text;
|
||||||
|
|||||||
@ -51,6 +51,14 @@ export async function buildColor(color: Color3, scene: Scene, parent: TransformN
|
|||||||
newItem.position = new Vector3(calculatePosition(++i), .1, 0);
|
newItem.position = new Vector3(calculatePosition(++i), .1, 0);
|
||||||
tools.push(newItem.id);
|
tools.push(newItem.id);
|
||||||
toolMap.set(newItem.id, newItem);
|
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) {
|
if (colorBoxMesh.metadata) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user