import * as THREE from 'three'; import { CRUSH } from '../../stages/Crush'; import type { EquipmentModel } from '../equipment/EquipmentModels'; import { precompileHidden } from '../equipment/fx/precompileHidden'; type PressSnapshot = {clock:number;phase:'idle'|'down'|'contact'|'up';strikeTime:number}; type Motion = {speed:number;down:number;hold:number;up:number}; /** 컨베이어와 압착기 장면. 받은 압착 모델은 이 장면이 소유·해제하고, 젤리 자원은 PieceStore가 별도로 소유한다. */ export class CrushScene { readonly root=new THREE.Group(); readonly press=new THREE.Group(); private readonly stripes:THREE.InstancedMesh; private readonly materials:THREE.Material[]=[]; private readonly geometries:THREE.BufferGeometry[]=[]; /** pressModel: 장착한 압착 장비 모델(원점 = 접촉면 바닥 중앙, 위가 +Y) */ constructor(private readonly motion:Motion,private readonly pressModel:EquipmentModel) { const body=this.material(0x667b7d), belt=this.material(0x334849), metal=this.material(0xe4d9c2), gold=this.material(0xf5bc50); this.box(this.root,CRUSH.beltHalfWidth*2,0.15,2.5,body,0,CRUSH.beltY-0.12,0); this.box(this.root,CRUSH.beltHalfWidth*2,0.06,2.2,belt,0,CRUSH.beltY-0.03,0); // 긴 벨트도 이음선 간격과 드로 콜 수를 유지한다. 매 프레임 전체를 한 칸 안에서만 이동한다. const stripeGeometry=new THREE.BoxGeometry(0.025,0.008,2.05); this.geometries.push(stripeGeometry); this.stripes=new THREE.InstancedMesh(stripeGeometry,body,Math.ceil(CRUSH.beltHalfWidth*2/0.52)); const matrix=new THREE.Matrix4(); for(let i=0;i { return precompileHidden(this.pressModel.object,renderer,camera,scene); } /** 장면 자원을 해제한다. 압착 모델은 보관소(PressModelPool) 것이라 떼기만 한다 */ dispose() {this.root.removeFromParent();this.stripes.dispose();for(const g of this.geometries)g.dispose();for(const m of this.materials)m.dispose();this.pressModel.object.removeFromParent();} }