Files
martin.fencl f0a6480c86
continuous-integration/drone/push Build is passing
feat: add BufferGeometryUtils for advanced geometry manipulation
- 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.
2026-11-02 13:38:01 +01:00

36 lines
3.4 KiB
JavaScript

import assert from 'node:assert/strict';
import * as THREE from '../vendor/three/build/three.module.js';
import {buildInterior,pointInPolygon} from '../js/interior-model.js';
import {furnishInterior} from '../js/interior-furniture.js';
const houseGroup=new THREE.Group();
const planPoint=(px,py)=>[8.7-py,px-6.525];
const group=(parent,name)=>{const g=new THREE.Group();g.name=name;parent.add(g);return g;};
const mesh=(geometry,material,parent,name)=>{const m=new THREE.Mesh(geometry,material);m.name=name;parent.add(m);return m;};
const box=(parent,name,x,y,z,sx,sy,sz,material,rotationY=0)=>{const m=mesh(new THREE.BoxGeometry(sx,sy,sz),material,parent,name);m.position.set(x,y,z);m.rotation.y=rotationY;return m;};
const ctx={THREE,houseGroup,planPoint,group,mesh,box};
const data=buildInterior(ctx),furniture=furnishInterior({...ctx,parent:data.furniture});
assert.equal(data.rooms.length,11);assert.ok(Math.abs(data.rooms.reduce((a,r)=>a+r.area,0)-153.74)<1e-6);
const footprint=[[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]].map(p=>planPoint(...p));
const distance=(x,z,a,b)=>{const dx=b[0]-a[0],dz=b[1]-a[1],t=Math.max(0,Math.min(1,((x-a[0])*dx+(z-a[1])*dz)/(dx*dx+dz*dz)));return Math.hypot(x-a[0]-t*dx,z-a[1]-t*dz);};
function canStand(x,z,withFurniture){
if(!pointInPolygon(footprint,x,z))return false;
for(let i=0;i<footprint.length;i++)if(distance(x,z,footprint[i],footprint[(i+1)%footprint.length])<.455)return false;
for(const b of [...data.wallColliders,...(withFurniture?furniture.colliders:[])])if(x>b.minX-.16&&x<b.maxX+.16&&z>b.minZ-.16&&z<b.maxZ+.16)return false;
return true;
}
// Flood fill uses a person of radius 160 mm, not a point; all room spawns must be reachable from the foyer.
for(const withFurniture of [false,true]){
const step=.08,w=221,h=170,x0=-8.8,z0=-6.6,walkable=new Uint8Array(w*h),visited=new Uint8Array(w*h);
for(let j=0;j<h;j++)for(let i=0;i<w;i++)walkable[j*w+i]=canStand(x0+i*step,z0+j*step,withFurniture)?1:0;
const nearest=([x,z])=>Math.round((z-z0)/step)*w+Math.round((x-x0)/step);
const first=nearest(planPoint(...data.rooms.find(r=>r.id==='entry').spawn));assert.equal(walkable[first],1);
const q=[first];visited[first]=1;
for(let n=0;n<q.length;n++){const v=q[n];for(const next of [v-1,v+1,v-w,v+w])if(next>=0&&next<w*h&&!visited[next]&&walkable[next]){visited[next]=1;q.push(next);}}
for(const room of data.rooms){const p=planPoint(...room.spawn);assert.ok(canStand(...p,withFurniture),'Spawn collides: '+room.id);assert.equal(visited[nearest(p)],1,'Room unreachable from entry: '+room.id+' furniture='+withFurniture);}
console.log('All 11 rooms reachable with furniture '+withFurniture+'; walkable cells '+q.length);
}
let count=0,triangles=0;houseGroup.traverse(o=>{if(!o.isMesh&&!o.isLineSegments)return;count++;const p=o.geometry.getAttribute('position');for(const n of p.array)assert.ok(Number.isFinite(n));if(o.isMesh)triangles+=(o.geometry.index?.count??p.count)/3;});
const floor=data.floors.children.find(o=>o.isMesh);assert.ok(floor.geometry.getAttribute('normal').getY(0)>.99,'Floor must face up');
const report={rooms:data.rooms.length,areaM2:153.74,objects:count,triangles,wallColliders:data.wallColliders.length,furnitureColliders:furniture.colliders.length,connectivity:'PASS in furnished and empty modes',geometry:'PASS finite coordinates and upward floor normals'};
console.log(JSON.stringify(report,null,2));