immersive2/src/diagram/functions/buildMeshFromDiagramEntity.ts
Michael Mainguy c7887d7d8f Optimize lightmap rendering using emissive texture approach
Changed from lightmapTexture with lighting enabled to emissiveTexture with lighting disabled for better performance. The new approach provides the same lighting illusion without expensive per-pixel lighting calculations.

- Added LightmapGenerator.ENABLED toggle for performance testing
- Updated buildColor.ts to use emissiveColor + emissiveTexture with disableLighting = true
- Updated buildMissingMaterial() to match new rendering approach
- Fixed buildTool.ts to access emissiveColor instead of diffuseColor for material color detection

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 09:44:56 -06:00

211 lines
7.8 KiB
TypeScript

import {DiagramEntity, DiagramEntityType, DiagramTemplates} from "../types/diagramEntity";
import {
AbstractMesh,
Color3,
CreateGreasedLine,
Curve3,
GreasedLineMesh,
GreasedLineMeshColorMode,
InstancedMesh,
Mesh,
MeshBuilder,
Scene,
StandardMaterial,
Texture,
Vector3
} from "@babylonjs/core";
import log from "loglevel";
import {v4 as uuidv4} from 'uuid';
import {xyztovec} from "./vectorConversion";
import {AnimatedLineTexture} from "../../util/animatedLineTexture";
import {LightmapGenerator} from "../../util/lightmapGenerator";
export function buildMeshFromDiagramEntity(entity: DiagramEntity, scene: Scene): AbstractMesh {
const logger = log.getLogger('buildMeshFromDiagramEntity');
if (!entity) {
logger.error("buildMeshFromDiagramEntity: entity is null");
return null;
}
switch (entity.type) {
case DiagramEntityType.USER:
logger.debug("buildMeshFromDiagramEntity: entity is user");
break;
default:
}
generateId(entity);
const newMesh: AbstractMesh = createNewInstanceIfNecessary(entity, scene);
if (!newMesh) {
logger.error("buildMeshFromDiagramEntity: newMesh is null", JSON.stringify((entity)));
return null;
}
return mapMetadata(entity, newMesh, scene);
}
function createNewInstanceIfNecessary(entity: DiagramEntity, scene: Scene): AbstractMesh {
const logger = log.getLogger('createNewInstanceIfNecessary');
const oldMesh: AbstractMesh = scene.getMeshById(entity.id);
let newMesh: AbstractMesh;
if (oldMesh) {
logger.debug(`mesh ${oldMesh.id} already exists`);
newMesh = oldMesh;
} else {
switch (entity.template) {
case DiagramTemplates.USER:
break;
case DiagramTemplates.IMAGE:
newMesh = buildImage(entity, scene);
break;
case DiagramTemplates.CONNECTION:
const origin = new Vector3(0, 0, 0);
const control1 = new Vector3(0, 2, 0);
const control2 = new Vector3(0, 5, -5);
const end = new Vector3(0, 5, -8);
const curve = Curve3.CreateCubicBezier(origin, control1, control2, end, 40);
const path = curve.getPoints();
newMesh = CreateGreasedLine(entity.id, {points: path, updatable: true}, {
width: .02,
colorMode: GreasedLineMeshColorMode.COLOR_MODE_MULTIPLY
}, scene);
(newMesh as GreasedLineMesh).intersectionThreshold = 2;
const material = (newMesh.material as StandardMaterial);
material.emissiveTexture = AnimatedLineTexture.Texture();
material.opacityTexture = AnimatedLineTexture.Texture();
material.disableLighting = true;
newMesh.setEnabled(false);
break;
case DiagramTemplates.BOX:
case DiagramTemplates.SPHERE:
case DiagramTemplates.CYLINDER:
case DiagramTemplates.CONE:
case DiagramTemplates.PLANE:
case DiagramTemplates.PERSON:
const toolMesh = scene.getMeshById("tool-" + entity.template + "-" + entity.color);
if (toolMesh && !oldMesh) {
newMesh = new InstancedMesh(entity.id, (toolMesh as Mesh));
// newMesh.metadata = {template: entity.template, exportable: true, tool: false};
} else {
logger.warn('no tool mesh found for ' + entity.template + "-" + entity.color);
}
break;
default:
logger.warn('no tool mesh found for ' + entity.template + "-" + entity.color);
break;
}
if (newMesh) {
if (!newMesh.metadata) {
newMesh.metadata = {template: entity.template, exportable: true, tool: false};
} else {
newMesh.metadata.template = entity.template;
newMesh.metadata.exportable = true;
newMesh.metadata.tool = false;
}
}
}
return newMesh;
}
function buildImage(entity: DiagramEntity, scene: Scene): AbstractMesh {
const logger = log.getLogger('buildImage');
logger.error(entity);
logger.debug("buildImage: entity is image");
const plane = MeshBuilder.CreatePlane(entity.id, {size: 1}, scene);
const material = new StandardMaterial("planeMaterial", scene);
const image = new Image();
image.src = entity.image;
material.emissiveTexture = new Texture(entity.image, scene);
material.backFaceCulling = false;
material.disableLighting = true;
plane.material = material;
plane.metadata = {image: entity.image};
plane.scaling = xyztovec(entity.scale);
image.decode().then(() => {
plane.scaling.x = image.width / image.height;
}).catch((error) => {
logger.error("buildImage: error decoding image", error);
});
return plane;
}
function generateId(entity: DiagramEntity) {
if (!entity.id) {
entity.id = "id" + uuidv4();
}
}
function mapMetadata(entity: DiagramEntity, newMesh: AbstractMesh, scene: Scene): AbstractMesh {
const logger = log.getLogger('mapMetaqdata');
if (newMesh) {
if (!newMesh.metadata) {
newMesh.metadata = {};
}
/*if (entity.position) {
newMesh.position = xyztovec(entity.position);
}
if (entity.rotation) {
if (newMesh.rotationQuaternion) {
newMesh.rotationQuaternion = Quaternion.FromEulerAngles(entity.rotation.x, entity.rotation.y, entity.rotation.z);
} else {
newMesh.rotation = xyztovec(entity.rotation);
}
}*/
/*if (entity.parent) {
const parent_node = scene.getNodeById(entity.parent);
if (parent_node) {
newMesh.parent = parent_node;
newMesh.metadata.parent = entity.parent;
}
}*/
/*if (entity.scale) {
newMesh.scaling = xyztovec(entity.scale);
}*/
if (!newMesh.material && newMesh?.metadata?.template != "#object-template") {
logger.warn("new material created, this shouldn't happen");
newMesh.material = buildMissingMaterial("material-" + entity.id, scene, entity.color);
}
if (entity.text) {
newMesh.metadata.text = entity.text;
//updateTextNode(newMesh, entity.text);
}
if (entity.from) {
newMesh.metadata.from = entity.from;
}
if (entity.to) {
newMesh.metadata.to = entity.to;
}
if (entity.image) {
newMesh.metadata.image = entity.image;
}
} else {
logger.error("buildMeshFromDiagramEntity: mesh is null after it should have been created");
}
return newMesh;
}
export function buildMissingMaterial(name: string, scene: Scene, color: string): StandardMaterial {
const existingMaterial = scene.getMaterialById(name);
if (existingMaterial) {
return (existingMaterial as StandardMaterial);
}
const colorObj = Color3.FromHexString(color);
const newMaterial = new StandardMaterial(name, scene);
newMaterial.id = name;
if (LightmapGenerator.ENABLED) {
// Lightmap as emissive texture (lighting illusion, no lighting calculations)
newMaterial.emissiveColor = colorObj;
newMaterial.emissiveTexture = LightmapGenerator.generateLightmapForColor(colorObj, scene);
newMaterial.disableLighting = true;
} else {
// Flat emissive-only rendering (no lighting illusion)
newMaterial.emissiveColor = colorObj;
newMaterial.disableLighting = true;
}
newMaterial.alpha = 1;
return newMaterial;
}