Factory function to create Training Dummy Knight in your world.
export function createAsset(THREE, options = {}) {
const root = new THREE.Group();
root.name = options.name ?? "Training Dummy Knight";
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":"stand","name":"Wood Stand","position":[0,0.45,0],"boxes":[{"name":"post","size":[0.28,0.9,0.28],"position":[0,0,0],"color":"#7a4f2b"},{"name":"cross","size":[1.5,0.18,0.32],"position":[0,-0.43,0],"color":"#8b5a2b"},{"name":"front","size":[0.28,0.18,1.25],"position":[0,-0.42,0],"color":"#8b5a2b"}]},{"id":"body","name":"Straw Body","position":[0,1.25,0],"boxes":[{"name":"torso","size":[0.95,1.15,0.55],"position":[0,0,0],"color":"#d5a653"},{"name":"belt","size":[1.02,0.16,0.6],"position":[0,-0.18,0.01],"color":"#5b3925"}]},{"id":"head","name":"Dummy Head","position":[0,2.05,0],"boxes":[{"name":"head","size":[0.72,0.62,0.58],"position":[0,0,0],"color":"#e0b86a"},{"name":"eye slit","size":[0.42,0.08,0.62],"position":[0,0.04,0.03],"color":"#2f2a24"}],"parent":"body"},{"id":"helmet","name":"Helmet","position":[0,2.42,0],"boxes":[{"name":"cap","size":[0.82,0.22,0.68],"position":[0,0,0],"color":"#94a3b8","metalness":0.2},{"name":"crest","size":[0.22,0.28,0.18],"position":[0,0.21,0],"color":"#ef4444"}],"parent":"body"},{"id":"shield","name":"Shield","position":[-0.72,1.35,0.2],"boxes":[{"name":"face","size":[0.48,0.76,0.12],"position":[0,0,0],"color":"#64748b","metalness":0.18},{"name":"v","size":[0.12,0.58,0.13],"position":[0,0,0.03],"color":"#e2e8f0"},{"name":"h","size":[0.4,0.12,0.13],"position":[0,0,0.04],"color":"#e2e8f0"}],"parent":"body"},{"id":"sword_arm","name":"Sword Arm","position":[0.72,1.45,0.05],"boxes":[{"name":"arm","size":[0.55,0.18,0.18],"position":[0,0,0],"color":"#8b5a2b"},{"name":"blade","size":[0.14,0.85,0.12],"position":[0.38,0.26,0],"color":"#94a3b8","metalness":0.12}],"parent":"body","rotation":[0,0,-0.22]},{"id":"target_mark","name":"Target Mark","position":[0,1.48,0.34],"boxes":[{"name":"outer","size":[0.48,0.48,0.08],"position":[0,0,0],"color":"#f8fafc"},{"name":"inner","size":[0.26,0.26,0.09],"position":[0,0,0.02],"color":"#dc2626"}],"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;
}