It sounds like the text on a material approach is going to be better than using images on ADTs. The developer who generates the image was able to create a texture atlas version. There was some work to do to turn the coordinates he sent me into UV data, but it totally worked.
This is very much a work in progress, but it lets us use a single material for all the meshes.
import * as BABYLON from "@babylonjs/core";
import { useDataStore } from "../composables/DataStore";
import { watch } from "vue";
const { clipboardDataFlat, clipboardDataDictionary, setMainSelectionID, mainSelectionID, textureAtlasImage } = useDataStore();
export class LLProcessor {
private scene: BABYLON.Scene;
private layoutMat!: BABYLON.StandardMaterial;
private selectMat!: BABYLON.StandardMaterial;
private textureAtlas: BABYLON.Texture | null = null;
constructor(scene: BABYLON.Scene) {
this.scene = scene;
this.initializeLayoutMat();
}
private initializeLayoutMat(): void {
this.layoutMat = new BABYLON.StandardMaterial("timeline-material", this.scene);
this.layoutMat.alpha = 0.75;
this.layoutMat.backFaceCulling = false;
this.selectMat = new BABYLON.StandardMaterial("timeline-material", this.scene);
this.selectMat.diffuseColor = BABYLON.Color3.FromHexString("#818CF8");
this.selectMat.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2);
}
clean() {
this.scene.meshes.forEach((mesh) => {
if (mesh.name !== "grid") {
mesh.dispose();
}
});
}
generate() {
if (textureAtlasImage.value) {
const b64String = `data:image/png;base64,${textureAtlasImage.value}`;
this.textureAtlas = new BABYLON.Texture(b64String, this.scene);
this.textureAtlas.hasAlpha = true;
this.layoutMat.diffuseTexture = this.textureAtlas;
this.layoutMat.emissiveTexture = this.textureAtlas;
this.layoutMat.useAlphaFromDiffuseTexture = true;
this.textureAtlas.onLoadObservable.add(() => {
clipboardDataFlat.forEach((element: any) => {
this.createLayerBox(element, this.scene);
});
});
}
}
// TODO Conssider a custom type or interface for the clipboard data instead of using any
private createLayerBox = (item: any, scene: BABYLON.Scene) => {
const offset = 100;
const parentID = item.idParent;
const parent = (clipboardDataDictionary as any)[parentID];
const width = (item.boundsRight - item.boundsLeft) / offset;
const height = (item.boundsBottom - item.boundsTop) / offset;
let posX = item.boundsLeft; // get the item's left position
if (parent) {
posX += parent.boundsLeft; // add the parent's left position
}
posX /= offset; // divide by the offset
posX += width / 2; // add half the width
let posY = item.boundsTop; // get the item's top position
if (parent) {
posY += parent.boundsTop; // add the parent's top position
}
posY /= offset; // divide by the offset
posY += height / 2; // add half the height
let posZ = item.containmentDepth + item.renderDepth / 100;
if (this.textureAtlas) {
const atlasSize = this.textureAtlas.getSize(); // Get the size of the texture atlas
const { upperLeftX, upperLeftY, lowerRightX, lowerRightY } = item.textureCoordinates;
const u1 = upperLeftX / atlasSize.width;
const v1 = 1 - upperLeftY / atlasSize.height; // v coordinates are flipped
const u2 = lowerRightX / atlasSize.width;
const v2 = 1 - lowerRightY / atlasSize.height; // v coordinates are flipped
console.log(item.name, `u1: ${u1}, v1: ${v1}, u2: ${u2}, v2: ${v2}`);
// Create a custom mesh with the calculated UV coordinates
// The UV coordinates are assigned in the order: bottom-left, bottom-right, top-right, top-left
const verticesData = [
-width / 2,
-height / 2,
0,
u1,
v2, // bottom-left
width / 2,
-height / 2,
0,
u2,
v2, // bottom-right
width / 2,
height / 2,
0,
u2,
v1, // top-right
-width / 2,
height / 2,
0,
u1,
v1 // top-left
];
const indices = [0, 1, 2, 0, 2, 3];
const mesh = new BABYLON.Mesh(item.id, scene);
const vertexData = new BABYLON.VertexData();
vertexData.positions = verticesData.filter((_, i) => i % 5 < 3);
vertexData.indices = indices;
vertexData.uvs = verticesData.filter((_, i) => i % 5 >= 3);
vertexData.applyToMesh(mesh);
// Set the position, rotation and material of the mesh
mesh.position.x = -posX;
mesh.position.y = -posY;
mesh.position.z = posZ;
mesh.rotation.y = Math.PI;
mesh.material = this.layoutMat;
// Add an action manager to the mesh
const am = new BABYLON.ActionManager(scene);
mesh.actionManager = am;
mesh.actionManager.registerAction(
new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPickTrigger, () => {
setMainSelectionID(item.id);
})
);
}
// Watch mainSelectionID for changes
watch(mainSelectionID, (newValue, oldValue) => {
// get mesh by id: old value
const oldMesh = scene.getMeshById(oldValue)! as BABYLON.Mesh;
if (oldMesh) {
oldMesh.showBoundingBox = false;
}
const oldMat = scene.getMaterialByName(oldValue)! as BABYLON.StandardMaterial;
if (oldMat) {
oldMat.alpha = 0.75;
}
// get mesh by id: new value
const newMesh = scene.getMeshById(newValue);
if (newMesh) {
newMesh.showBoundingBox = true;
// newMesh.material?.alpha = 1;
const camera = scene.activeCamera as BABYLON.ArcRotateCamera;
if (camera) {
camera.setTarget(newMesh);
}
}
const newMat = scene.getMaterialByName(newValue)! as BABYLON.StandardMaterial;
if (newMat) {
newMat.alpha = 1;
}
});
};
}