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>
211 lines
7.8 KiB
TypeScript
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;
|
|
} |