Factory function to create Floating Spell Book in your world.
export function createAsset(THREE, options = {}) {
const root = new THREE.Group();
root.name = options.name ?? "Floating Spell Book";
root.userData = { assetRole: "item", generatedBy: 'public asset batch' };
const cache = new Map();
function mat(def) {
const key = [def.color, def.opacity ?? 1, def.metalness ?? 0, def.roughness ?? 0.72, def.emissive ?? '', def.emissiveIntensity ?? 0].join('|');
if (cache.has(key)) return cache.get(key);
const material = new THREE.MeshStandardMaterial({ color: def.color, roughness: def.roughness ?? 0.72, metalness: def.metalness ?? 0, opacity: def.opacity ?? 1, transparent: (def.opacity ?? 1) < 1, emissive: def.emissive ?? '#000000', emissiveIntensity: def.emissiveIntensity ?? 0 });
cache.set(key, material);
return material;
}
const defs = [{"id":"book_root","name":"Book Root","position":[0,0.8,0],"boxes":[{"name":"spine","size":[0.24,0.34,1.35],"position":[0,0,0],"color":"#4c1d95"},{"name":"clasp","size":[0.18,0.16,0.28],"position":[0,0.16,0.58],"color":"#facc15","metalness":0.25}]},{"id":"cover_left","name":"Left Cover","position":[-0.12,0.82,0],"boxes":[{"name":"cover","size":[1.05,0.24,1.42],"position":[-0.52,0,0],"color":"#6d28d9"},{"name":"trim","size":[1,0.08,1.52],"position":[-0.52,0.16,0],"color":"#facc15","metalness":0.2}]},{"id":"cover_right","name":"Right Cover","position":[0.12,0.82,0],"boxes":[{"name":"cover","size":[1.05,0.24,1.42],"position":[0.52,0,0],"color":"#7c3aed"},{"name":"trim","size":[1,0.08,1.52],"position":[0.52,0.16,0],"color":"#facc15","metalness":0.2}]},{"id":"pages","name":"Pages","position":[0,0.98,0],"boxes":[{"name":"stack","size":[1.55,0.18,1.22],"position":[0,0,0],"color":"#fff7d6"},{"name":"line A","size":[1.25,0.035,0.05],"position":[0,0.11,0.25],"color":"#c4a66a"},{"name":"line B","size":[1,0.035,0.05],"position":[0,0.12,-0.18],"color":"#c4a66a"}]},{"id":"ribbon","name":"Ribbon","position":[0.05,0.74,0.62],"boxes":[{"name":"ribbon","size":[0.14,0.08,0.62],"position":[0,0,0],"color":"#ef4444"},{"name":"tail","size":[0.16,0.24,0.16],"position":[0,-0.15,0.32],"color":"#dc2626"}]},{"id":"rune_glyphs","name":"Rune Glyphs","position":[0,1.18,0.08],"boxes":[{"name":"star","size":[0.16,0.16,0.08],"position":[-0.38,0,0],"color":"#fef08a","emissive":"#facc15","emissiveIntensity":0.55},{"name":"dot","size":[0.12,0.12,0.08],"position":[0.02,0.08,0],"color":"#a7f3d0","emissive":"#34d399","emissiveIntensity":0.45},{"name":"square","size":[0.14,0.14,0.08],"position":[0.42,-0.02,0],"color":"#f0abfc","emissive":"#d946ef","emissiveIntensity":0.45}]},{"id":"spell_spark","name":"Spell Spark","position":[0,1.55,0.1],"boxes":[{"name":"core","size":[0.22,0.22,0.22],"position":[0,0,0],"color":"#fff","emissive":"#fff","emissiveIntensity":0.8},{"name":"vertical","size":[0.08,0.42,0.08],"position":[0,0,0],"color":"#fef08a","emissive":"#facc15","emissiveIntensity":0.7},{"name":"horizontal","size":[0.42,0.08,0.08],"position":[0,0,0],"color":"#fef08a","emissive":"#facc15","emissiveIntensity":0.7}]}];
const groups = new Map();
for (const def of defs) {
const group = new THREE.Group();
group.name = def.name;
group.position.set(def.position[0], def.position[1], def.position[2]);
if (def.rotation) group.rotation.set(def.rotation[0], def.rotation[1], def.rotation[2]);
group.userData.partId = def.id;
group.userData.editable = true;
group.userData.selection = { id: def.id, kind: 'asset', label: def.name };
for (const item of def.boxes) {
const mesh = new THREE.Mesh(new THREE.BoxGeometry(item.size[0], item.size[1], item.size[2]), mat(item));
mesh.name = item.name;
mesh.position.set(item.position[0], item.position[1], item.position[2]);
if (item.rotation) mesh.rotation.set(item.rotation[0], item.rotation[1], item.rotation[2]);
mesh.castShadow = true;
mesh.receiveShadow = true;
group.add(mesh);
}
groups.set(def.id, group);
}
for (const def of defs) (def.parent ? groups.get(def.parent) : root).add(groups.get(def.id));
const scale = Number(options.scale ?? 1);
root.scale.setScalar(Number.isFinite(scale) ? scale : 1);
return root;
}