1import {CUBBY,cubbyLayout} from './cubby-layout.js?v=0.67.1'; 2import {HONEYCOMB,honeycombLayout} from './honeycomb-layout.js?v=0.67.1'; 3import {THEMED_FIXTURES} from './themed-layout.js?v=0.67.1'; 4import {BEVERAGE} from './beverage-layout.js?v=0.67.1'; 5import {JUKEBOX,JUKEBOX_CAPACITY,jukeboxLayout} from './jukebox-motion.js?v=0.67.1'; 6import {MINI_PEG_CAPACITY,miniPegColumns,miniPegWidth} from './mini-peg-layout.js?v=0.67.1'; 7 8export const PROFILE_VERSION='design005-2'; 9export const FIXTURE_CAPACITIES=Object.freeze({case:6,grid:6,shelf:6,cabinet:6,mount:2,comic:12,card:400,coin:400,flatfile:12,textile:2,pedestal:1,easel:3,vitrine:2,inclined:1,jewelry:32,longcase:1,modelcase:1,floorcase:1,shelves:6,minipeg:MINI_PEG_CAPACITY,minilightbox:MINI_PEG_CAPACITY,jukebox:JUKEBOX_CAPACITY,beverage:BEVERAGE.capacity,lunchbox:THEMED_FIXTURES.lunchbox.capacity,blisterpack:THEMED_FIXTURES.blisterpack.capacity,garment:THEMED_FIXTURES.garment.capacity,honeycomb:HONEYCOMB.capacity,buildingbrick:6,cubby:CUBBY.capacity}); 10export const PANEL_KINDS=new Set(['grid','case','buildingbrick','shelf','mount','cabinet','minipeg','minilightbox']); 11export const MINI_KINDS=new Set(['minipeg','minilightbox']); 12// These dimensions also supply runtime collision/motion bounds. Mesh checks are 13// deliberately separate: they construct the approved furniture, including labels. 14export const FIXTURE_GEOMETRY=Object.freeze({ 15 comic:{width:1.40,depth:1.40,height:2.75,approach:1.85}, 16 card:{width:2.20,depth:1.48,height:1.92,approach:1.65}, 17 coin:{width:2.20,depth:1.48,height:1.92,approach:1.65}, 18 flatfile:{width:2.04,depth:1.24,height:1.83,travel:1.02,approach:2.15}, 19 jewelry:{width:2.54,depth:1.54,height:1.83,travel:1.35,approach:2.65}, 20 textile:{extra:.08,depth:1.02,height:2.86,approach:1.85}, 21 pedestal:{width:1.60,depth:1.32,height:2.63,approach:1.65}, 22 easel:{extra:.04,depth:1.34,height:2.51,approach:1.85}, 23 vitrine:{extra:.04,depth:1.18,height:2.19,approach:1.65}, 24 inclined:{extra:.04,depth:1.64,height:2.78,approach:1.85}, 25 longcase:{width:2.92,depth:1.24,height:2.24,approach:1.85}, 26 modelcase:{width:2.44,depth:1.56,height:2.56,approach:1.85}, 27 floorcase:{width:2.00,depth:1.34,height:3.29,approach:2.15}, 28 shelves:{extra:0,depth:.86,height:2.64,approach:1.65} 29}); 30export const outward=n=>Math.ceil((n-1e-10)*1000)/1000; 31// Maximum approved photo apertures. Admission checks construct every count and 32// compare these with actual photo surfaces independently of planner arithmetic. 33export const PHOTO_APERTURES=Object.freeze({case:[.725,.55],grid:[.725,.55],shelf:[.725,.55],cabinet:[.77,.60],mount:[1.125,1.49],comic:[.61,.49],card:[.15,.16],coin:[.30,.34],flatfile:[.46,.79],textile:[1.16,1.63],pedestal:[1.16,1.42],easel:[.98,1.20],vitrine:[1.22,.98],inclined:[1.44,1.82],jewelry:[.48,.49],longcase:[2.58,1.06],modelcase:[2.08,1.42],floorcase:[1.63,2.30],shelves:[.725,.55],minipeg:[.375,.43],minilightbox:[.345,.36],jukebox:[.221,.219],beverage:[.60,.49],lunchbox:[.56,.57],blisterpack:[.51,.45],garment:[.80,.49],honeycomb:[.325,.29],buildingbrick:[.725,.55],cubby:[.325,.29]}); 34export const VIEW_ENVELOPE=Object.freeze({width:320,height:568,horizontalFraction:.78,verticalFraction:.27,fov:64}); 35const profileCache=new Map(); 36export function fixtureProfile(unit){ 37 const count=unit.count??unit.items?.length; 38 if(!Number.isSafeInteger(count)||count<1||!Number.isFinite(unit.width)||unit.width<=0)throw new Error('Invalid fixture geometry.'); 39 if(!FIXTURE_CAPACITIES[unit.kind]||count>FIXTURE_CAPACITIES[unit.kind])throw new Error('Unsupported fixture population.'); 40 const kind=unit.kind,key=[kind,count,unit.width,unit.jukeboxLayout?.width||''].join('|'); 41 if(profileCache.has(key))return profileCache.get(key); 42 let shape; 43 if(PANEL_KINDS.has(kind))shape={width:unit.width+.30,depth:kind==='cabinet'?1.20:.94,height:2.78,approach:1.75}; 44 else if(kind==='cubby')shape={...cubbyLayout(count),depth:CUBBY.depth}; 45 else if(kind==='honeycomb')shape={...honeycombLayout(count),depth:HONEYCOMB.depth}; 46 else if(THEMED_FIXTURES[kind])shape=THEMED_FIXTURES[kind]; 47 else if(kind==='beverage')shape={...BEVERAGE,approach:3.35}; 48 else if(kind==='jukebox')shape={...jukeboxLayout(count),width:unit.jukeboxLayout?.width??jukeboxLayout(count).width,depth:Math.max(JUKEBOX.depth,1.40),height:2.80,approach:2.8}; 49 else if(FIXTURE_GEOMETRY[kind]){const spec=FIXTURE_GEOMETRY[kind];shape={...spec,width:spec.width??unit.width+spec.extra};} 50 else throw new Error('This fixture needs a supported geometry profile.'); 51 const width=outward(shape.width),depth=outward(shape.depth),travel=shape.travel||0; 52 const aperture=PHOTO_APERTURES[kind],tan=Math.tan(VIEW_ENVELOPE.fov*Math.PI/360); 53 const photographicDistance=Math.max(aperture[0]/(2*tan*VIEW_ENVELOPE.width/VIEW_ENVELOPE.height*VIEW_ENVELOPE.horizontalFraction),aperture[1]/(2*tan*VIEW_ENVELOPE.verticalFraction)); 54 const near=outward(depth/2+travel+.52),far=outward(Math.max(shape.approach,photographicDistance+depth/2+travel)+.35); 55 const profile={version:PROFILE_VERSION,kind,width,depth,height:outward(shape.height),travel, 56 // Rotating publication racks expose populated sides through the front control. 57 faces:[0],rotations:[0,1,2,3],aperture,approach:outward(Math.max(shape.approach,near+.05)), 58 near,far, 59 // Full independent drawer sweeps are reserved simultaneously. 60 sweep:{x:0,z:travel/2,w:width,d:outward(depth+travel)}, 61 cost:{records:count,photos:kind==='card'?Math.min(32,count):kind==='coin'?Math.min(8,count):count,islands:1}}; 62 if(Object.values(profile).some(v=>typeof v==='number'&&!Number.isFinite(v)))throw new Error('Invalid fixture profile.'); 63 Object.freeze(profile);profileCache.set(key,profile);if(profileCache.size>512)profileCache.delete(profileCache.keys().next().value);return profile; 64} 65export function transformedBox(box,x,z,rotation=0){ 66 const c=Math.cos(rotation),s=Math.sin(rotation); 67 return {x:x+c*(box.x||0)+s*(box.z||0),z:z-s*(box.x||0)+c*(box.z||0),w:outward(Math.abs(c)*box.w+Math.abs(s)*box.d),d:outward(Math.abs(s)*box.w+Math.abs(c)*box.d)}; 68} 69export function fixtureObstacle(unit,x,z,rotation=0){const p=fixtureProfile(unit);return transformedBox({w:p.width,d:p.depth},x,z,rotation);} 70export function extendObstacle(obstacle,rotation,width,depth,previous,next){ 71 obstacle.x+=Math.sin(rotation)*(next-previous)/2;obstacle.z+=Math.cos(rotation)*(next-previous)/2; 72 const box=transformedBox({w:width,d:depth+next},0,0,rotation);obstacle.w=box.w;obstacle.d=box.d; 73} 74export function fixtureIslands(units){ 75 const copied=units.map(unit=>MINI_KINDS.has(unit.kind)?{...unit,pegColumns:miniPegColumns(unit.count),width:miniPegWidth(miniPegColumns(unit.count))}:{...unit}); 76 const panels=copied.filter(u=>PANEL_KINDS.has(u.kind)),islands=[]; 77 for(let i=0;i<panels.length;i+=2){ 78 const front=panels[i],back=panels[i+1]||null; 79 if(MINI_KINDS.has(front.kind)&&MINI_KINDS.has(back?.kind)&&(front.pegColumns===6||back.pegColumns===6))for(const u of [front,back]){u.pegColumns=6;u.width=miniPegWidth(6);} 80 const a=fixtureProfile(front),b=back?fixtureProfile(back):null; 81 islands.push({type:'panel',front,back,width:Math.max(front.width,back?.width||0),depth:[front,back].some(u=>u?.kind==='cabinet')?1.12:.78, 82 profile:{...a,width:Math.max(a.width,b?.width||0),depth:Math.max(a.depth,b?.depth||0),faces:back?[0,2]:[0],near:Math.max(a.near,b?.near||0),aperture:[Math.max(a.aperture[0],b?.aperture[0]||0),Math.max(a.aperture[1],b?.aperture[1]||0)],far:Math.max(a.far,b?.far||0),sweep:{x:0,z:0,w:Math.max(a.width,b?.width||0),d:Math.max(a.depth,b?.depth||0)}}}); 83 } 84 for(const unit of copied.filter(u=>!PANEL_KINDS.has(u.kind))){const profile=fixtureProfile(unit);islands.push({type:unit.kind,front:unit,back:null,width:unit.width,depth:
84profile.depth,profile});} 85 return {units:copied,islands}; 86}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.