continuous-integration/drone/push Build is passing
- Implemented computeMikkTSpaceTangents for consistent tangent generation. - Added mergeGeometries to combine multiple BufferGeometries. - Introduced mergeAttributes for merging BufferAttributes. - Created deepCloneAttribute for cloning BufferAttributes. - Developed interleaveAttributes and deinterleaveAttribute for attribute management. - Added estimateBytesUsed to calculate memory usage of geometries. - Implemented mergeVertices to optimize vertex data. - Created toTrianglesDrawMode for converting geometries to triangle mode. - Added computeMorphedAttributes for handling morph targets in geometries. - Implemented mergeGroups to consolidate geometry groups. - Developed toCreasedNormals for generating smooth normals with crease angles.
128 lines
8.8 KiB
JavaScript
128 lines
8.8 KiB
JavaScript
// Plan coordinates in metres: px garden -> road; py west -> east.
|
||
// Areas are the printed PDF values; names (except zádveří) follow the drawn fixtures.
|
||
export const rooms = [
|
||
{id:'living',name:'Obývací pokoj + kuchyň',short:'Obývák + kuchyň',area:49.88,polygon:[[.295,.295],[7.955,.295],[7.955,6.791],[.295,6.791]],label:[4.0,3.7],spawn:[3.7,3.7],look:[1.2,1.8],floor:'wood'},
|
||
{id:'spare',name:'Místnost bez určení',short:'Místnost',area:7.05,polygon:[[8.1,.295],[11.255,.295],[11.255,2.531],[8.1,2.531]],label:[9.7,1.4],spawn:[9.7,1.4],look:[8.5,2.3],floor:'tile'},
|
||
{id:'wardrobe',name:'Šatna',short:'Šatna',area:6.15,polygon:[[8.1,2.656],[11.255,2.656],[11.255,4.605],[8.1,4.605]],label:[9.7,3.63],spawn:[8.8,3.65],look:[10.5,3.65],floor:'wood'},
|
||
{id:'entry',name:'Zádveří',short:'Zádveří',area:8.58,polygon:[[8.1,4.75],[11.255,4.75],[11.255,7.432],[8.1,7.432]],label:[9.8,6.1],spawn:[10.3,6.1],look:[8.3,5.8],floor:'tile'},
|
||
{id:'hall',name:'Chodba',short:'Chodba',area:7.59,polygon:[[8.1,7.557],[9.421,7.557],[9.421,13.406],[8.1,13.406]],label:[8.76,10.5],spawn:[8.76,8.15],look:[8.76,12.6],floor:'wood'},
|
||
{id:'technical',name:'Technická místnost',short:'Technická',area:7.10,polygon:[[9.546,7.557],[11.38,7.557],[11.38,6.821],[12.755,6.821],[12.755,9.456],[9.546,9.456]],label:[11.15,8.35],spawn:[10.5,8.4],look:[12.3,8.1],floor:'tile'},
|
||
{id:'bath',name:'Koupelna',short:'Koupelna',area:8.66,polygon:[[9.546,9.581],[12.755,9.581],[12.755,12.282],[9.546,12.282]],label:[10.6,10.9],spawn:[10.25,10.95],look:[12.15,11.0],floor:'tile'},
|
||
{id:'front',name:'Pokoj u příjezdu',short:'Pokoj',area:15.08,polygon:[[9.546,12.407],[12.755,12.407],[12.755,17.105],[9.546,17.105]],label:[11.0,14.6],spawn:[10.9,14.3],look:[12.0,16.0],floor:'wood'},
|
||
{id:'garden',name:'Pokoj u zahrady',short:'Pokoj',area:15.62,polygon:[[4.045,7.015],[7.955,7.015],[7.955,10.981],[4.045,10.981]],label:[5.7,8.8],spawn:[5.4,8.7],look:[7.0,7.5],floor:'wood'},
|
||
{id:'master',name:'Ložnice se šatnou',short:'Ložnice + šatna',area:22.35,polygon:[[4.045,11.106],[7.955,11.106],[7.955,13.425],[5.344,13.425],[5.344,13.55],[7.673,13.55],[7.673,17.105],[4.045,17.105]],label:[4.65,14.2],spawn:[4.6,14.5],look:[5.95,16.2],floor:'wood'},
|
||
{id:'ensuite',name:'Koupelna u ložnice',short:'Koupelna',area:5.68,polygon:[[7.798,13.55],[9.421,13.55],[9.421,17.105],[7.798,17.105]],label:[8.6,15.2],spawn:[8.35,14.55],look:[8.6,16.65],floor:'tile'}
|
||
];
|
||
|
||
// axis=px: fixed px, runs along py; axis=py: fixed py, runs along px.
|
||
export const partitions = [
|
||
['px',8.0275,.295,13.425,.145,[[5.19,6.09],[9.257,10.156],[11.805,12.708]]],
|
||
['py',2.5935,8.1,11.255,.125,[[8.349,9.248]]],
|
||
['py',4.6775,8.1,11.255,.145,[[8.349,9.248]]],
|
||
['py',7.4945,8.1,11.255,.125,[[8.349,9.248]]],
|
||
['py',6.903,3.775,7.955,.224,[]],
|
||
['py',11.0435,4.045,7.955,.125,[]],
|
||
['px',9.4835,7.557,13.406,.125,[[8.006,8.905],[10.656,11.457],[12.457,13.357]]],
|
||
['py',9.5185,9.546,12.755,.125,[]],
|
||
['py',12.3445,9.546,12.755,.125,[]],
|
||
['py',13.4785,8.1,9.421,.145,[]],
|
||
['py',13.4875,5.344,7.955,.125,[]],
|
||
['px',7.7355,13.55,17.105,.125,[[14.152,14.953]]],
|
||
['px',9.4835,13.406,17.105,.125,[]],
|
||
['py',10.63,11.255,12.755,.10,[]]
|
||
];
|
||
|
||
export function buildInterior({THREE,houseGroup,planPoint,box,mesh,group}) {
|
||
const root=group(houseGroup,'Interiér podle půdorysu');
|
||
const floors=group(root,'Podlahy místností'),fullWalls=group(root,'Příčky – plná výška'),cutWalls=group(root,'Příčky – řez 1,12 m');
|
||
const ceiling=group(root,'Podhled +2,600 m'),furniture=group(root,'Orientační vybavení');
|
||
const height=2.6,cutHeight=1.12,doorHead=2.1;
|
||
const wallMaterial=new THREE.MeshStandardMaterial({color:0xeeeae1,roughness:.92});
|
||
const wood=new THREE.MeshStandardMaterial({color:0xc7ac87,roughness:.85});
|
||
const tile=new THREE.MeshStandardMaterial({color:0xc8c6be,roughness:.83});
|
||
const trim=new THREE.MeshStandardMaterial({color:0xe4d5bb,roughness:.78});
|
||
const grout=new THREE.LineBasicMaterial({color:0x8c8373,transparent:true,opacity:.16});
|
||
const wallColliders=[];
|
||
function horizontal(parent,name,polygon,y,material,down=false) {
|
||
const points=polygon.map(p=>planPoint(...p));
|
||
const triangles=THREE.ShapeUtils.triangulateShape(points.map(([x,z])=>new THREE.Vector2(x,z)),[]);
|
||
const geom=new THREE.BufferGeometry();
|
||
geom.setAttribute('position',new THREE.Float32BufferAttribute(points.flatMap(([x,z])=>[x,y,z]),3));
|
||
const indices=triangles.flatMap(([a,b,c])=>{
|
||
const A=points[a],B=points[b],C=points[c];
|
||
const normalY=(B[1]-A[1])*(C[0]-A[0])-(B[0]-A[0])*(C[1]-A[1]);
|
||
return (normalY>0)!==down?[a,b,c]:[c,b,a];
|
||
});
|
||
geom.setIndex(indices);geom.computeVertexNormals();
|
||
const m=mesh(geom,material,parent,name);m.castShadow=false;return m;
|
||
}
|
||
// A continuous surface also fills thresholds, so doors have no visible cracks.
|
||
const outline=[[0,0],[11.55,0],[11.55,6.525],[13.05,6.525],[13.05,17.4],[3.75,17.4],[3.75,7.125],[0,7.125]];
|
||
horizontal(floors,'Podlaha a prahy',outline,.004,tile);
|
||
horizontal(ceiling,'Souvislý rovný podhled',outline,height,new THREE.MeshStandardMaterial({color:0xf5f3eb,roughness:1,side:THREE.DoubleSide}),true);
|
||
for(const room of rooms) {
|
||
const m=horizontal(floors,`${room.name} · ${room.area.toFixed(2)} m²`,room.polygon,.01,room.floor==='wood'?wood:tile);
|
||
m.userData.roomId=room.id;
|
||
// Thin joints: plank rhythm in dry rooms, large tiles in service rooms.
|
||
const loX=Math.min(...room.polygon.map(p=>p[0])),hiX=Math.max(...room.polygon.map(p=>p[0]));
|
||
const loY=Math.min(...room.polygon.map(p=>p[1])),hiY=Math.max(...room.polygon.map(p=>p[1]));
|
||
const pts=[];
|
||
const inside=(px,py)=>pointInPolygon(room.polygon,px,py);
|
||
function joint(a,b) { const [x,z]=planPoint(...a),[xx,zz]=planPoint(...b);pts.push(x,.012,z,xx,.012,zz); }
|
||
for(let py=Math.ceil(loY/(room.floor==='wood'?.20:.6))*(room.floor==='wood'?.20:.6);py<hiY;py+=room.floor==='wood'?.20:.6) {
|
||
let start=null;
|
||
for(let px=loX+.002;px<=hiX+.03;px+=.025) {
|
||
const ok=px<hiX&&inside(px,py);
|
||
if(ok&&start===null)start=px;
|
||
if(!ok&&start!==null){joint([start,py],[Math.min(px,hiX-.002),py]);start=null;}
|
||
}
|
||
}
|
||
if(room.floor==='tile')for(let px=Math.ceil(loX/.6)*.6;px<hiX;px+=.6){
|
||
let start=null;for(let py=loY+.002;py<=hiY+.03;py+=.025){
|
||
const ok=py<hiY&&inside(px,py);if(ok&&start===null)start=py;
|
||
if(!ok&&start!==null){joint([px,start],[px,Math.min(py,hiY-.002)]);start=null;}
|
||
}
|
||
}
|
||
const geometry=new THREE.BufferGeometry();geometry.setAttribute('position',new THREE.Float32BufferAttribute(pts,3));
|
||
const joints=new THREE.LineSegments(geometry,grout);joints.name='Spáry podlahy';floors.add(joints);
|
||
}
|
||
function wallBox(parent,axis,fixed,start,end,thickness,bottom,top,material,name) {
|
||
const px=axis==='px'?fixed:(start+end)/2,py=axis==='py'?fixed:(start+end)/2;
|
||
const [x,z]=planPoint(px,py);
|
||
return box(parent,name,x,(bottom+top)/2,z,axis==='px'?end-start:thickness,top-bottom,axis==='px'?thickness:end-start,material);
|
||
}
|
||
for(const [axis,fixed,start,end,thickness,holes] of partitions) {
|
||
let cursor=start;
|
||
const solid=(a,b)=>{
|
||
if(b-a<.001)return;
|
||
const m=wallBox(fullWalls,axis,fixed,a,b,thickness,0,height,wallMaterial,'Vnitřní příčka');
|
||
wallBox(cutWalls,axis,fixed,a,b,thickness,0,cutHeight,wallMaterial,'Vnitřní příčka v řezu');
|
||
const sx=axis==='px'?b-a:thickness,sz=axis==='px'?thickness:b-a;
|
||
wallColliders.push({minX:m.position.x-sx/2,maxX:m.position.x+sx/2,minZ:m.position.z-sz/2,maxZ:m.position.z+sz/2});
|
||
for(const parent of [fullWalls,cutWalls])wallBox(parent,axis,fixed,a,b,thickness+.016,.015,.09,trim,'Soklová lišta');
|
||
};
|
||
for(const [a,b] of holes){
|
||
solid(cursor,a);
|
||
wallBox(fullWalls,axis,fixed,a,b,thickness,doorHead,height,wallMaterial,'Nadpraží vnitřních dveří');
|
||
for(const parent of [fullWalls,cutWalls])for(const edge of [a,b])wallBox(parent,axis,fixed,edge-.018,edge+.018,thickness+.035,.01,parent===fullWalls?doorHead:cutHeight,trim,'Obložková zárubeň');
|
||
wallBox(fullWalls,axis,fixed,a-.018,b+.018,thickness+.035,doorHead-.04,doorHead,trim,'Horní zárubeň');
|
||
cursor=b;
|
||
}
|
||
solid(cursor,end);
|
||
}
|
||
// Attic access as drawn in vestibule; a flush hatch, not a freestanding object.
|
||
const [hx,hz]=planPoint(8.9,6.3);
|
||
box(ceiling,'Půdní výlez 1000 × 700',hx,2.586,hz,.70,.018,1.0,trim);
|
||
fullWalls.visible=false;ceiling.visible=false;root.visible=false;
|
||
return {root,floors,fullWalls,cutWalls,ceiling,furniture,rooms,wallColliders,wallMaterial,cutHeight,height};
|
||
}
|
||
|
||
export function pointInPolygon(polygon,x,z) {
|
||
let inside=false;
|
||
for(let i=0,j=polygon.length-1;i<polygon.length;j=i++){
|
||
const a=polygon[i],b=polygon[j];
|
||
if((a[1]>z)!==(b[1]>z)&&x<(b[0]-a[0])*(z-a[1])/(b[1]-a[1])+a[0])inside=!inside;
|
||
}
|
||
return inside;
|
||
}
|