Factory function to create 반딧불이 종유등 항아리 in your world.
export function createAsset(THREE, options = {}) {
const group = new THREE.Group();
group.name = 'firefly_dew_lantern_jar_reward';
const jarHeight = Math.max(0.8, Math.min(1.45, Number(options.jarHeight ?? 1.1)));
const fireflyCount = Math.max(0, Math.min(5, Math.round(Number(options.fireflyCount ?? 5))));
const stemTilt = THREE.MathUtils.degToRad(Math.max(-16, Math.min(16, Number(options.stemTilt ?? 8))));
const assetScale = Math.max(0.75, Math.min(1.5, Number(options.assetScale ?? 1)));
const ribbonColor = options.ribbonColor ?? '#F2CB66';
const glowColor = options.glowColor ?? '#84D8E8';
group.scale.setScalar(assetScale);
const matStem = new THREE.MeshStandardMaterial({ color: '#4E8F66', roughness: 0.72, metalness: 0.02 });
const matNode = new THREE.MeshStandardMaterial({ color: '#9FD67A', roughness: 0.68, metalness: 0.02 });
const matJar = new THREE.MeshStandardMaterial({ color: '#BDEFF4', roughness: 0.28, metalness: 0.02, transparent: true, opacity: 0.48 });
const matLid = new THREE.MeshStandardMaterial({ color: '#6B4B37', roughness: 0.76, metalness: 0.04 });
const matRing = new THREE.MeshStandardMaterial({ color: '#8A6549', roughness: 0.72, metalness: 0.06 });
const matGlow = new THREE.MeshStandardMaterial({ color: glowColor, emissive: glowColor, emissiveIntensity: 1.8, roughness: 0.35, metalness: 0 });
const matRibbon = new THREE.MeshStandardMaterial({ color: ribbonColor, roughness: 0.66, metalness: 0.02 });
const matHoney = new THREE.MeshStandardMaterial({ color: '#F2CB66', emissive: '#C78D2F', emissiveIntensity: 0.35, roughness: 0.55, metalness: 0 });
function mark(object, partId, label) {
object.name = partId;
object.userData.partId = partId;
object.userData.selection = { id: partId, kind: 'asset', label };
return object;
}
const stemPivot = new THREE.Group();
mark(stemPivot, 'stem', '줄기 지지대');
stemPivot.rotation.z = stemTilt;
stemPivot.position.set(-0.18, 0, 0);
group.add(stemPivot);
const stemMesh = new THREE.Mesh(new THREE.BoxGeometry(0.18, 1.22, 0.18), matStem);
stemMesh.position.set(0, 0.62, 0);
stemPivot.add(stemMesh);
const nodeData = [
['stem_node_01', -0.09, 0.36, 0.05],
['stem_node_02', 0.09, 0.68, -0.04],
['stem_node_03', -0.08, 0.94, -0.05]
];
for (const data of nodeData) {
const node = new THREE.Mesh(new THREE.BoxGeometry(0.16, 0.12, 0.14), matNode);
node.position.set(data[1], data[2], data[3]);
mark(node, data[0], '줄기 결절');
stemPivot.add(node);
}
const standRing = new THREE.Group();
mark(standRing, 'stand_ring', '바닥 받침 고리');
standRing.position.set(0, 0.1, 0);
group.add(standRing);
const ringBase = new THREE.Mesh(new THREE.CylinderGeometry(0.54, 0.54, 0.1, 12), matRing);
ringBase.scale.z = 0.72;
standRing.add(ringBase);
const ringHole = new THREE.Mesh(new THREE.CylinderGeometry(0.32, 0.32, 0.12, 12), new THREE.MeshStandardMaterial({ color: '#5D422F', roughness: 0.8, metalness: 0.03 }));
ringHole.position.y = 0.01;
ringHole.scale.z = 0.68;
standRing.add(ringHole);
const pendantLeft = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.18, 0.08), matHoney);
pendantLeft.position.set(-0.48, -0.13, 0);
mark(pendantLeft, 'pendant_left', '왼쪽 미니 펜던트');
standRing.add(pendantLeft);
const pendantRight = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.18, 0.08), matHoney);
pendantRight.position.set(0.48, -0.13, 0);
mark(pendantRight, 'pendant_right', '오른쪽 미니 펜던트');
standRing.add(pendantRight);
const jarY = 1.05 + jarHeight * 0.28;
const jarBody = new THREE.Group();
mark(jarBody, 'jar_body', '반투명 항아리 본체');
jarBody.position.set(0.18, jarY, 0);
group.add(jarBody);
const jarCore = new THREE.Mesh(new THREE.BoxGeometry(0.72, jarHeight, 0.58), matJar);
jarBody.add(jarCore);
const jarTopLip = new THREE.Mesh(new THREE.BoxGeometry(0.82, 0.12, 0.68), matJar);
jarTopLip.position.y = jarHeight * 0.52 + 0.02;
jarBody.add(jarTopLip);
const jarBottomLip = new THREE.Mesh(new THREE.BoxGeometry(0.78, 0.12, 0.64), matJar);
jarBottomLip.position.y = -jarHeight * 0.52 - 0.01;
jarBody.add(jarBottomLip);
const jarLid = new THREE.Group();
mark(jarLid, 'jar_lid', '항아리 마개');
jarLid.position.set(0.18, jarY + jarHeight * 0.62 + 0.11, 0);
group.add(jarLid);
const lidBlock = new THREE.Mesh(new THREE.BoxGeometry(0.62, 0.16, 0.5), matLid);
jarLid.add(lidBlock);
const lidBead = new THREE.Mesh(new THREE.BoxGeometry(0.22, 0.22, 0.22), matHoney);
lidBead.position.y = 0.18;
mark(lidBead, 'lid_bead', '상단 마개 구슬');
jarLid.add(lidBead);
const fireflyCluster = new THREE.Group();
mark(fireflyCluster, 'firefly_cluster', '반딧불 무리');
fireflyCluster.position.copy(jarBody.position);
group.add(fireflyCluster);
const flyPositions = [
[-0.18, 0.14, 0.04],
[0.14, -0.02, -0.1],
[0.02, 0.28, 0.12],
[0.22, 0.18, 0.08],
[-0.12, -0.18, -0.08]
];
for (let i = 0; i < 5; i++) {
const fly = new THREE.Mesh(new THREE.BoxGeometry(0.11, 0.11, 0.11), matGlow);
fly.position.set(flyPositions[i][0], flyPositions[i][1], flyPositions[i][2]);
fly.visible = i < fireflyCount;
mark(fly, 'glow_fly_0' + (i + 1), '반딧불');
fireflyCluster.add(fly);
}
const ribbonLeft = new THREE.Group();
mark(ribbonLeft, 'ribbon_left', '왼쪽 리본');
ribbonLeft.position.set(-0.18, jarY + jarHeight * 0.61 + 0.1, 0.02);
group.add(ribbonLeft);
const ribbonLeftWing = new THREE.Mesh(new THREE.BoxGeometry(0.28, 0.16, 0.08), matRibbon);
ribbonLeftWing.position.set(-0.14, 0, 0);
ribbonLeft.add(ribbonLeftWing);
const ribbonRight = new THREE.Group();
mark(ribbonRight, 'ribbon_right', '오른쪽 리본');
ribbonRight.position.set(0.54, jarY + jarHeight * 0.61 + 0.1, 0.02);
group.add(ribbonRight);
const ribbonRightWing = new THREE.Mesh(new THREE.BoxGeometry(0.28, 0.16, 0.08), matRibbon);
ribbonRightWing.position.set(0.14, 0, 0);
ribbonRight.add(ribbonRightWing);
const ribbonKnot = new THREE.Mesh(new THREE.BoxGeometry(0.16, 0.18, 0.1), matRibbon);
ribbonKnot.position.set(0.18, jarY + jarHeight * 0.61 + 0.1, 0.04);
mark(ribbonKnot, 'ribbon_knot', '리본 매듭');
group.add(ribbonKnot);
return group;
}