import {floors,walls,outline,pointInPolygon} from './plan.js?v=1850'; export function buildHouse(ctx){ const {THREE,houseGroup,modelRoot,roofGroup,groundGroup,boundaryGroup,parkingGroup,planPoint,box,mesh,group,siteData}=ctx; const mat=(color,extra={})=>new THREE.MeshStandardMaterial({color,roughness:.86,...extra}); const m={wall:mat('#e5e0d5'),trim:mat('#cfbfa5'),plinth:mat('#99998f'),roof:mat('#786e63',{side:THREE.DoubleSide}),frame:mat('#e3dfd3'),glass:mat('#a5c6cc',{transparent:true,opacity:.36,roughness:.2,side:THREE.DoubleSide}),oak:mat('#c5a77e'),tile:mat('#c8c6bc'),brick:mat('#ae9180'),deck:mat('#b29c7d'),ceiling:mat('#f1eee6',{side:THREE.DoubleSide}),metal:mat('#606964',{metalness:.3}),grass:mat('#a3b091'),road:mat('#969c94')}; const block=(p,n,x,z,w,d,b,t,material)=>{const [X,Z]=planPoint(x,z);return box(p,n,X,(b+t)/2,Z,w,t-b,d,material);}; function surface(p,n,polygon,height,material,holes=[]){ const pts=polygon.map(q=>new THREE.Vector2(...q)),hs=holes.map(h=>h.map(q=>new THREE.Vector2(...q))); const all=[...polygon,...holes.flat()];const g=new THREE.BufferGeometry(); g.setAttribute('position',new THREE.Float32BufferAttribute(all.flatMap(([x,y])=>{const [X,Z]=planPoint(x,y);return [X,typeof height==='function'?height(x,y):height,Z];}),3)); g.setIndex(THREE.ShapeUtils.triangulateShape(pts,hs).flatMap(([a,b,c])=>[c,b,a]));g.computeVertexNormals();return mesh(g,material,p,n); } function face(p,n,pts,material){const g=new THREE.BufferGeometry();g.setAttribute('position',new THREE.Float32BufferAttribute(pts.flatMap(([x,h,z])=>{const [X,Z]=planPoint(x,z);return[X,h,Z];}),3));const ix=[];for(let i=1;i({...f,root:group(houseGroup,f.title),colliders:[]})); const wallBox=(p,axis,fixed,a,b,t,bottom,top,ma,n)=>block(p,n,axis==='x'?fixed:(a+b)/2,axis==='y'?fixed:(a+b)/2,axis==='x'?t:b-a,axis==='y'?t:b-a,bottom,top,ma); for(const f of levels){ f.full=group(f.root,'Stěny a otvory');f.cut=group(f.root,'Stěny v řezu');f.ground=group(f.root,'Podlahy');f.ceiling=group(f.root,'Podhledy');f.furniture=group(f.root,'Orientační vybavení');f.stairs=group(f.root,'Schodiště'); const b=f.base; if(f.id==='ground')surface(f.ground,'Prahy 1NP',outline,-.055,m.tile,[[[5.07,1.0],[6.15,1.0],[6.15,4.27],[5.07,4.27]]]); if(f.id==='upper'){ surface(f.ground,'Nosná deska hlavní části',[[4.19,5.45],[10.44,5.45],[10.44,12.36],[4.19,12.36]],3.54,m.tile); surface(f.ground,'Podlaha podkroví',[[1.22,1.67],[4.72,1.67],[4.72,11.09],[1.22,11.09]],3.24,m.oak); } for(const r of f.rooms){ if(!r.id.endsWith('.01')||r.id==='1.01')surface(f.ground,r.id+' '+r.name+' · '+r.area+' m²',r.polygon,r.level+.007,m[r.floor]||m.oak); if(r.height && r.name!=='Schodiště')surface(f.ceiling,'Podhled '+r.id,r.polygon,r.level+r.height,m.ceiling); // Floor pattern is clipped to each polygon. const x0=Math.min(...r.polygon.map(v=>v[0])),x1=Math.max(...r.polygon.map(v=>v[0])),y0=Math.min(...r.polygon.map(v=>v[1])),y1=Math.max(...r.polygon.map(v=>v[1])); const pos=[],step=['wood','deck'].includes(r.floor)?.22:.6; for(let y=Math.ceil(y0/step)*step;ysill)wallBox(parent,axis,fixed,edge-.025,edge+.025,t+.025,sill,cap,m.frame,'Rám otvoru'); if(head<=cap)wallBox(parent,axis,fixed,a,b,t+.035,head-.05,head,m.frame,'Horní rám'); if(window&&sillsill){wallBox(parent,axis,fixed,a+.025,b-.025,.025,sill+.02,cap-.025,m.glass,'Zasklení').castShadow=false;if(b-a>.8)wallBox(parent,axis,fixed,(a+b)/2-.025,(a+b)/2+.025,.09,sill,cap,m.frame,'Sloupek okna');} } // Exterior doors remain open for the walkthrough; balcony doors have a light side frame. cursor=b; }solid(cursor,end); } f.cut.visible=false; } // Sloping podkroví ceilings: ruled surfaces, matching the lower roof and the stated maximum clear height. const upper=levels[2]; const lowCeiling=(x,y)=>Math.min(5.71,3.43+.492*(x+.3)-.13,3.43+.598*(y-.25)-.13,3.43+.598*(12.36-y)-.13); upper.ceiling.children.filter(o=>/2.04|2.05/.test(o.name)).forEach(o=>upper.ceiling.remove(o)); for(const r of upper.rooms.filter(r=>['2.04','2.05'].includes(r.id))){ const xs=r.polygon.map(p=>p[0]),ys=r.polygon.map(p=>p[1]),pos=[],indices=[]; for(let x=Math.min(...xs);xMath.abs(cx-a)4.5)continue; const a=o.geometry.attributes.position;for(let i=0;icap)a.setY(i,cap-o.position.y);}a.needsUpdate=true;o.geometry.computeVertexNormals();} // Stair is a schematic 17 × 200 / 275 flight with a turn at its upper end. for(let i=0;i<13;i++)block(levels[1].stairs,'Stupeň 1NP → 2NP',5.61,4.15-i*.245,1.08,.25,-.05,(i+1)*.2,m.oak); for(let i=0;i<4;i++)block(levels[1].stairs,'Zatáčka schodiště',5.3+i*.32,1.09,.34,.88,2.6,2.8+i*.2,m.oak); surface(upper.ground,'Podesta 2NP',[[5.07,4.27],[6.27,4.27],[6.27,5.57],[5.07,5.57]],3.4,m.oak); for(let i=0;i<10;i++)block(levels[0].stairs,'Stupeň 1PP → 1NP',5.57,1.8+i*.265,1,.267,-2.2,-1.9+(i+1)*.19,m.plinth); surface(levels[0].ground,'Podesta sklepa',[[5.07,1.02],[6.73,1.02],[6.73,1.72],[5.07,1.72]],-1.9,m.tile); block(levels[0].full,'Pilíř ve sklepě',8.89,1.9,.32,.32,-2.2,-.14,m.wall);block(levels[0].cut,'Pilíř ve sklepě v řezu',8.89,1.9,.32,.32,-2.2,-1.08,m.wall); block(upper.full,'Horní stěna nad napojením nižší střechy',5.36,5.685,2.28,.47,5.78,6.8,m.wall); // Balcony slab and open metal guardrail. const balcony=upper.rooms.find(r=>r.id==='2.06');surface(upper.ground,'Balkón – spodní líc',balcony.polygon,3.14,m.plinth); const rail=group(upper.full,'Balkónové zábradlí');for(let i=0;i[[x-w/2,y-d/2],[x+w/2,y-d/2],[x+w/2,y+d/2],[x-w/2,y+d/2]]; surface(roofGroup,'Severní střešní rovina',[A,B,C,D].map(v=>[v[0],v[2]]),(x,y)=>3.43+.492*(x+.3),m.roof,[hole(1.65,6.4,.72,.8),hole(1.65,8.2,.72,.8)]); surface(roofGroup,'Východní valba nižší střechy',[[A[0],A[2]],[4.72,.25],[B[0],B[2]]],(x,y)=>3.43+.598*(y-.25),m.roof,[hole(3.25,2.25,.8,.64)]); face(roofGroup,'Západní valba nižší střechy',[D,C,[4.72,3.43,12.36]],m.roof); block(roofGroup,'Komín',4.92,5.25,.65,.65,5.4,8.72,m.wall);block(roofGroup,'Hlava komína',4.92,5.25,.78,.78,8.72,8.84,m.plinth); // Roof windows are schematic; their plan positions follow the dashed openings. for(const [x,y]of [[1.65,6.4],[1.65,8.2],[3.25,2.25]]){ const w=x<2?.72:.8,d=x<2?.8:.64; const height=(a,b)=>x<2?3.43+.492*(a+.3)+.025:3.43+.598*(b-.25)+.025; surface(roofGroup,'Zasklení střešního okna',hole(x,y,w,d),height,m.glass).castShadow=false; surface(roofGroup,'Rám střešního okna',hole(x,y,w+.12,d+.12),height,m.frame,[hole(x,y,w,d)]); const outer=hole(x,y,w,d),inner=hole(x,y,w-.20,d-.20); for(let i=0;i<4;i++){const j=(i+1)%4;face(upper.ceiling,'Ostění střešního okna',[[...inner[i]],[...inner[j]],[...outer[j]],[...outer[i]]].map(([a,b],k)=>[a,k<2?lowCeiling(a,b):height(a,b),b]),m.ceiling);} } const foundation=group(houseGroup,'Sokl');for(let i=0;i{const e=s.origin[0]+s.ux[0]*x+s.uy[0]*y,n=s.origin[1]+s.ux[1]*x+s.uy[1]*y;const ix=Math.max(0,Math.min(s.width-1.001,e-s.bbox[0]-.5)),iy=Math.max(0,Math.min(s.height-1.001,s.bbox[3]-n-.5));const i=Math.floor(ix),j=Math.floor(iy),u=ix-i,v=iy-j;return (1-v)*((1-u)*s.heights[j][i]+u*s.heights[j][i+1])+v*((1-u)*s.heights[j+1][i]+u*s.heights[j+1][i+1]);}; const height=(x,y)=>raw(x,y)-datum; const terrainMeshes=[],boundaries=[]; for(const parcel of s.parcels){ const polygon=parcel.polygon.slice(0,-1),pos=[],indices=[]; const triangles=THREE.ShapeUtils.triangulateShape(polygon.map(p=>new THREE.Vector2(...p)),[]); for(const tri of triangles){const [a,b,c]=tri.map(i=>polygon[i]);const n=Math.ceil(Math.max(Math.hypot(a[0]-b[0],a[1]-b[1]),Math.hypot(a[0]-c[0],a[1]-c[1]),Math.hypot(b[0]-c[0],b[1]-c[1]))/1.2);const rows=[]; for(let i=0;i<=n;i++){rows[i]=[];for(let j=0;j<=n-i;j++){const x=a[0]+(b[0]-a[0])*i/n+(c[0]-a[0])*j/n,y=a[1]+(b[1]-a[1])*i/n+(c[1]-a[1])*j/n;rows[i][j]=pos.length/3;const [X,Z]=planPoint(x,y);pos.push(X,height(x,y),Z);}} for(let i=0;i{const [X,Z]=planPoint(x,y);return new THREE.Vector3(X,height(x,y)+.06,Z);});const line=new THREE.Line(new THREE.BufferGeometry().setFromPoints(points),new THREE.LineBasicMaterial({color:'#4d6a4b'}));line.name='Hranice '+parcel.id;boundaryGroup.add(line);boundaries.push(line); } surface(parkingGroup,'Orientační poloha ulice Kovářova',[[-8,15],[19,15],[19,19],[-8,19]],height,m.road); // Editable datum is explicit: the drawings do not tie ±0.000 to a Bpv elevation. function setDatum(value){const delta=datum-value;datum=value;groundGroup.position.y+=delta;boundaryGroup.position.y+=delta;parkingGroup.position.y+=delta;} return {setDatum,get datum(){return datum;},raw}; }