Factory function to create Cute Voxel Cat in your world.
export function createAsset(THREE, options = {}) {
const root = new THREE.Group();
root.name = options.name ?? "Cute Voxel Cat";
root.userData = { assetRole: "character", 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":"body","name":"Cat Body","position":[0,0,0],"boxes":[{"name":"body","size":[1.65,0.82,1.05],"position":[0,0.72,0],"color":"#f2ead8"},{"name":"belly","size":[0.72,0.48,1.08],"position":[0,0.66,0.05],"color":"#fff9ef"}]},{"id":"head","name":"Oversized Head","position":[0,1.28,0.25],"boxes":[{"name":"head","size":[1.22,1,0.98],"position":[0,0,0],"color":"#f7f1df"},{"name":"muzzle","size":[0.58,0.28,0.12],"position":[0,-0.14,0.52],"color":"#fffdf8"}],"parent":"body"},{"id":"left_ear","name":"Left Ear","position":[-0.38,0.55,0],"boxes":[{"name":"outer","size":[0.32,0.42,0.28],"position":[0,0,0],"color":"#f2ead8"},{"name":"inner","size":[0.18,0.24,0.3],"position":[0,-0.02,0.02],"color":"#f2a7bc"}],"parent":"head","rotation":[0,0,0.12]},{"id":"right_ear","name":"Right Ear","position":[0.38,0.55,0],"boxes":[{"name":"outer","size":[0.32,0.42,0.28],"position":[0,0,0],"color":"#f2ead8"},{"name":"inner","size":[0.18,0.24,0.3],"position":[0,-0.02,0.02],"color":"#f2a7bc"}],"parent":"head","rotation":[0,0,-0.12]},{"id":"eyes","name":"Eyes","position":[0,1.31,0.77],"boxes":[{"name":"L","size":[0.14,0.2,0.06],"position":[-0.27,0.05,0],"color":"#23303a"},{"name":"R","size":[0.14,0.2,0.06],"position":[0.27,0.05,0],"color":"#23303a"},{"name":"shine L","size":[0.05,0.05,0.07],"position":[-0.3,0.11,0.01],"color":"#fff"},{"name":"shine R","size":[0.05,0.05,0.07],"position":[0.24,0.11,0.01],"color":"#fff"}]},{"id":"nose","name":"Nose","position":[0,1.2,0.82],"boxes":[{"name":"nose","size":[0.12,0.08,0.08],"position":[0,0,0],"color":"#ef7e9f"},{"name":"cheek L","size":[0.12,0.08,0.06],"position":[-0.42,-0.05,-0.02],"color":"#f7a7bb"},{"name":"cheek R","size":[0.12,0.08,0.06],"position":[0.42,-0.05,-0.02],"color":"#f7a7bb"}]},{"id":"whiskers","name":"Whiskers","position":[0,1.17,0.86],"boxes":[{"name":"L1","size":[0.36,0.035,0.04],"position":[-0.47,0.03,0],"color":"#2f3b3f"},{"name":"L2","size":[0.32,0.035,0.04],"position":[-0.47,-0.08,0],"color":"#2f3b3f"},{"name":"R1","size":[0.36,0.035,0.04],"position":[0.47,0.03,0],"color":"#2f3b3f"},{"name":"R2","size":[0.32,0.035,0.04],"position":[0.47,-0.08,0],"color":"#2f3b3f"}]},{"id":"front_legs","name":"Front Legs","position":[0,0.32,0.38],"boxes":[{"name":"front L","size":[0.3,0.5,0.28],"position":[-0.48,0,0],"color":"#efe4d1"},{"name":"front R","size":[0.3,0.5,0.28],"position":[0.48,0,0],"color":"#efe4d1"}],"parent":"body"},{"id":"back_legs","name":"Back Legs","position":[0,0.32,-0.36],"boxes":[{"name":"back L","size":[0.32,0.5,0.32],"position":[-0.52,0,0],"color":"#efe4d1"},{"name":"back R","size":[0.32,0.5,0.32],"position":[0.52,0,0],"color":"#efe4d1"}],"parent":"body"},{"id":"tail","name":"Centered Tail","position":[0,0.9,-0.58],"boxes":[{"name":"base","size":[0.22,0.34,0.28],"position":[0,0.05,-0.08],"color":"#f2ead8"},{"name":"middle","size":[0.24,0.55,0.24],"position":[0,0.38,-0.18],"color":"#f2ead8"},{"name":"tip","size":[0.25,0.28,0.25],"position":[0,0.72,-0.13],"color":"#fff9ef"}],"parent":"body","rotation":[-0.25,0,0]},{"id":"collar","name":"Collar","position":[0,0.98,0.34],"boxes":[{"name":"band","size":[1,0.12,0.12],"position":[0,0,0],"color":"#4c9eea"},{"name":"bell","size":[0.16,0.16,0.16],"position":[0,-0.12,0.08],"color":"#f4c24d"}],"parent":"body"}];
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;
}