/
counterbugtech
/
KontrBugCAD
Обзор
Документация
Войти
/
counterbugtech
/
KontrBugCAD
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
js/utils/GeometryUtils.js
1 строка
9 KB
counterbugtech
Обновление до 0.9.44
28 май 2026, 06:20
Верифицирован
28 май 2026, 06:20
cef7548
Код
Авторство
О чём код?
import*as THREE from"three";import*as BufferGeometryUtils from"three/addons/utils/BufferGeometryUtils.js";export class GeometryUtils{static NORMAL_THRESHOLD=.995;static PLANE_DISTANCE_THRESHOLD=.05;static POINT_EPSILON=.001;static EDGE_PRECISION=6;static toVector3(t){if(t)return t instanceof THREE.Vector3?t.clone():Array.isArray(t)&&2==t.length?new THREE.Vector3(t[0],t[1],0):Array.isArray(t)&&3==t.length?new THREE.Vector3(t[0],t[1],t[2]):"number"==typeof t.x?new THREE.Vector3(t.x,t.y,t.z||0):void 0}static calculatePolygonCenter(t,e=1e-6){if(!t||0===t.length)return null;let r=t;if(t.length>=2&&t[0].distanceTo(t[t.length-1])<=e&&t.length>1&&(r=t.slice(0,-1)),0===r.length)return null;const n=new THREE.Vector3;for(const t of r)n.add(t);return n.divideScalar(r.length),n}static getEdgeKey(t,e){const r=Math.pow(10,this.EDGE_PRECISION),n=`${Math.round(t.x*r)/r},${Math.round(t.y*r)/r},${Math.round(t.z*r)/r}`,o=`${Math.round(e.x*r)/r},${Math.round(e.y*r)/r},${Math.round(e.z*r)/r}`;return n<o?`${n}|${o}`:`${o}|${n}`}static getVertexKey(t){const e=Math.pow(10,this.EDGE_PRECISION);return`${Math.round(t.x*e)/e},${Math.round(t.y*e)/e},${Math.round(t.z*e)/e}`}static ensureManifoldGeometry(t,e=.01){console.log("[ensureManifoldGeometry] ",e);let r=t.clone();if(!r.index){const t=r.attributes.position.count,e=new Uint32Array(t);for(let r=0;r<t;r++)e[r]=r;r.setIndex(new THREE.BufferAttribute(e,1))}return BufferGeometryUtils&&BufferGeometryUtils.mergeVertices?r=BufferGeometryUtils.mergeVertices(r,e):console.warn("BufferGeometryUtils.mergeVertices недоступен"),r}static ensureIndexed(t){let e=t.clone();if(!e.index){const t=e.attributes.position,r=new Uint32Array(t.count);for(let e=0;e<t.count;e++)r[e]=e;e.setIndex(new THREE.BufferAttribute(r,1))}return e}static pointsEqual(t,e,r=this.POINT_EPSILON){return Math.abs(t.x-e.x)<r&&Math.abs(t.y-e.y)<r&&Math.abs(t.z-e.z)<r}static getTriangleNormal(t,e,r){const n=e.clone().sub(t),o=r.clone().sub(t);return(new THREE.Vector3).crossVectors(n,o).normalize()}static getTriangleCenter(t,e,r){return(new THREE.Vector3).add(t).add(e).add(r).divideScalar(3)}static trianglesCoplanar(t,e,r,n,o,a,s=this.PLANE_DISTANCE_THRESHOLD){const i=this.getTriangleNormal(t,e,r),c=i.dot(t),u=t=>Math.abs(i.dot(t)-c)<s;return u(n)&&u(o)&&u(a)}static buildEdgeToTrianglesMap(t){const e=new Map,r=t.attributes.position,n=t.index;if(!r)return e;const o=n?n.count/3:r.count/3;for(let t=0;t<o;t++){let o,a,s;n?(o=n.array[3*t],a=n.array[3*t+1],s=n.array[3*t+2]):(o=3*t,a=3*t+1,s=3*t+2);const i=(new THREE.Vector3).fromBufferAttribute(r,o),c=(new THREE.Vector3).fromBufferAttribute(r,a),u=(new THREE.Vector3).fromBufferAttribute(r,s),l=(r,n)=>{const o=this.getEdgeKey(r,n);e.has(o)||e.set(o,[]),e.get(o).push(t)};l(i,c),l(c,u),l(u,i)}return e}static getTrianglesOfFace(t,e,r=null){let n=t;n.index||(n=this.ensureIndexed(n));const o=t.attributes.position,a=t.index;if(!o)return[e];let s,i,c;a?(s=a.array[3*e],i=a.array[3*e+1],c=a.array[3*e+2]):(s=3*e,i=3*e+1,c=3*e+2);const u=(new THREE.Vector3).fromBufferAttribute(o,s),l=(new THREE.Vector3).fromBufferAttribute(o,i),f=(new THREE.Vector3).fromBufferAttribute(o,c),E=this.getTriangleNormal(u,l,f),h=E.dot(u),g=r||this.buildEdgeToTrianglesMap(t),d=new Set,y=[e];for(d.add(e);y.length;){const t=y.shift();let e,r,n;a?(e=a.array[3*t],r=a.array[3*t+1],n=a.array[3*t+2]):(e=3*t,r=3*t+1,n=3*t+2);const s=(new THREE.Vector3).fromBufferAttribute(o,e),i=(new THREE.Vector3).fromBufferAttribute(o,r),c=(new THREE.Vector3).fromBufferAttribute(o,n),u=[this.getEdgeKey(s,i),this.getEdgeKey(i,c),this.getEdgeKey(c,s)];for(const e of u){const r=g.get(e)||[];for(const e of r){if(e===t)continue;if(d.has(e))continue;let r,n,s;a?(r=a.array[3*e],n=a.array[3*e+1],s=a.array[3*e+2]):(r=3*e,n=3*e+1,s=3*e+2);const i=(new THREE.Vector3).fromBufferAttribute(o,r),c=(new THREE.Vector3).fromBufferAttribute(o,n),u=(new THREE.Vector3).fromBufferAttribute(o,s),l=this.getTriangleNormal(i,c,u);Math.abs(E.dot(l))>this.NORMAL_THRESHOLD&&Math.abs(E.dot(i)-h)<this.PLANE_DISTANCE_THRESHOLD&&Math.abs(E.dot(c)-h)<this.PLANE_DISTANCE_THRESHOLD&&Math.abs(E.dot(u)-h)<this.PLANE_DISTANCE_THRESHOLD&&(d.add(e),y.push(e))}}}return Array.from(d)}static collectFaceEdges(t,e,r){const n=t.attributes.position,o=t.index,a=new Map;for(const t of r){let r,s,i;o?(r=o.array[3*t],s=o.array[3*t+1],i=o.array[3*t+2]):(r=3*t,s=3*t+1,i=3*t+2);const c=(new THREE.Vector3).fromBufferAttribute(n,r),u=(new THREE.Vector3).fromBufferAttribute(n,s),l=(new THREE.Vector3).fromBufferAttribute(n,i);c.applyMatrix4(e),u.applyMatrix4(e),l.applyMatrix4(e);const f=[{a:c,b:u},{a:u,b:l},{a:l,b:c}];for(const t of f){const e=this.getEdgeKey(t.a,t.b);a.has(e)||a.set(e,{v1:t.a,v2:t.b,count:0}),a.get(e).count++}}return a}static buildContoursFromEdges(t){const e=[];for(const r of t.values())1===r.count&&e.push(r);if(0===e.length)return[];const r=new Map,n=t=>this.getVertexKey(t);for(const t of e){const e=n(t.v1),o=n(t.v2);r.has(e)||r.set(e,[]),r.has(o)||r.set(o,[]),r.get(e).push(t),r.get(o).push(t)}const o=new Set,a=[];for(const t of e){const s=n(t.v1);if(o.has(s))continue;const i=[];let c=t.v1.clone(),u=null;do{i.push(c.clone());const t=n(c);o.add(t);const e=r.get(t)||[];let a=null;for(const t of e){const e=this.getEdgeKey(t.v1,t.v2);if(!o.has(e)&&t!==u){a=t,o.add(e);break}}if(!a)break;u=a,c=a.v1.equals(c)?a.v2.clone():a.v1.clone()}while(!c.equals(t.v1)&&i.length<e.length);i.length>=3&&(i[0].equals(i[i.length-1])||i.push(i[0].clone()),a.push(i))}return a}static calculatePolygonArea2D(t){if(t.length<3)return 0;let e=0;for(let r=0;r<t.length;r++){const n=(r+1)%t.length;e+=t[r].x*t[n].y,e-=t[n].x*t[r].y}return Math.abs(e)/2}static isClockwise2D(t){let e=0;for(let r=0;r<t.length;r++){const n=(r+1)%t.length;e+=(t[n].x-t[r].x)*(t[n].y+t[r].y)}return e<0}static pointInPolygon2D(t,e){let r=!1;for(let n=0,o=e.length-1;n<e.length;o=n++){const a=e[n].x,s=e[n].y,i=e[o].x,c=e[o].y;s>t.y!=c>t.y&&t.x<(i-a)*(t.y-s)/(c-s)+a&&(r=!r)}return r}static extractOuterAndHoles2D(t){if(0===t.length)return{outer:null,holes:[]};if(1===t.length)return{outer:t[0],holes:[]};const e=t.map(t=>({contour:t,area:this.calculatePolygonArea2D(t),isClockwise:this.isClockwise2D(t)}));e.sort((t,e)=>e.area-t.area);const r=e[0].contour,n=[];for(let t=1;t<e.length;t++)this.pointInPolygon2D(e[t].contour[0],r),n.push(e[t].contour);return{outer:r,holes:n}}static projectContourToPlane(t,e){return t.map(t=>{const r=e.worldToLocal(t.clone());return new THREE.Vector2(r.x,r.y)})}static createPlaneFromPoints(t,e,r){const n=(new THREE.Vector3).add(t).add(e).add(r).divideScalar(3),o=this.getTriangleNormal(t,e,r),a=new THREE.Object3D;a.position.copy(n);const s=(new THREE.Quaternion).setFromUnitVectors(new THREE.Vector3(0,0,1),o);return a.quaternion.copy(s),a}static getFaceNormal(t,e){const r=t.geometry,n=r.attributes.position,o=r.index;let a,s,i;o?(a=o.array[3*e],s=o.array[3*e+1],i=o.array[3*e+2]):(a=3*e,s=3*e+1,i=3*e+2);const c=(new THREE.Vector3).fromBufferAttribute(n,a),u=(new THREE.Vector3).fromBufferAttribute(n,s),l=(new THREE.Vector3).fromBufferAttribute(n,i),f=this.getTriangleNormal(c,u,l);return f.applyQuaternion(t.quaternion),f}static calculatePolygonArea3D(t){if(t.length<3)return 0;let e=0;for(let r=0;r<t.length;r++){const n=(r+1)%t.length;e+=t[r].x*t[n].y,e-=t[n].x*t[r].y}return Math.abs(e)/2}static getContourCenter2D(t){if(0===t.length)return new THREE.Vector2(0,0);let e=0,r=0;return t.forEach(t=>{e+=t.x,r+=t.y}),new THREE.Vector2(e/t.length,r/t.length)}static calculateSignedPolygonArea(t){if(t.length<3)return 0;let e=0;for(let r=0;r<t.length;r++){const n=(r+1)%t.length;e+=t[r].x*t[n].y,e-=t[n].x*t[r].y}return e/2}static mergeVerticesCustom(t,e=.01,r=!1){const n=t.attributes.position,o=t.index;if(!o)return t;let a=e;if(r){t.computeBoundingBox();const e=t.boundingBox;if(e){const t=e.max.distanceTo(e.min);a=Math.max(.001*t,1e-6)}}const s=[];for(let t=0;t<n.count;t++)s.push((new THREE.Vector3).fromBufferAttribute(n,t));const i=new Map,c=[],u=new Array(s.length).fill(-1);for(let t=0;t<s.length;t++){const e=s[t],r=`${Math.round(e.x/a)*a},${Math.round(e.y/a)*a},${Math.round(e.z/a)*a}`;i.has(r)||(i.set(r,c.length),c.push(e.clone())),u[t]=i.get(r)}const l=o.array,f=[];for(let t=0;t<l.length;t+=3){const e=u[l[t]],r=u[l[t+1]],n=u[l[t+2]];e!==r&&r!==n&&n!==e&&f.push(e,r,n)}const E=new THREE.BufferGeometry,h=new Float32Array(3*c.length);return c.forEach((t,e)=>{h[3*e]=t.x,h[3*e+1]=t.y,h[3*e+2]=t.z}),E.setAttribute("position",new THREE.BufferAttribute(h,3)),E.setIndex(f),t.attributes.normal&&E.computeVertexNormals(),E}static centerGeometry(t){t.computeBoundingBox();const e=t.boundingBox;if(!e)return new THREE.Vector3(0,0,0);const r=e.getCenter(new THREE.Vector3),n=t.attributes.position;if(!n)return r;const o=n.array;for(let t=0;t<o.length;t+=3)o[t]-=r.x,o[t+1]-=r.y,o[t+2]-=r.z;return n.needsUpdate=!0,t.computeBoundingBox(),t.computeVertexNormals(),r}static computeCenter(t){const e=t.attributes.position;if(!e)return new THREE.Vector3(0,0,0);const r=e.count;let n=0,o=0,a=0;for(let t=0;t<r;t++)n+=e.getX(t),o+=e.getY(t),a+=e.getZ(t);return 0===r?new THREE.Vector3(0,0,0):new THREE.Vector3(n/r,o/r,a/r)}}