/**
* @module Intersections
* @description Creates an intersection lines for graphic objects.
* @author [Andrej Hristoliubov]{@link https://anhr.github.io/AboutMe/}
*
* @copyright 2011 Data Arts Team, Google Creative Lab
*
* @license under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* @see [How to detect collision in three.js?]{@link https://newbedev.com/how-to-detect-collision-in-three-js}
* @see [Collision detection example]{@link http://stemkoski.github.io/Three.js/Collision-Detection.html}
* @see [Three JS - Find all points where a mesh intersects a plane]{@link https://stackoverflow.com/questions/42348495/three-js-find-all-points-where-a-mesh-intersects-a-plane}
*/
import three from '../three.js'
//import { SpriteText } from '../SpriteText/SpriteText.js'
//import { getWorldPosition } from '../getPosition.js';
//медленно работает
//import clearThree from '../clearThree.js';
class Intersections {
/**
* Creates an intersection lines for graphic objects.
* @param {THREE.Mesh} object [graphic object]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh} for which the intersection lines with other objects will be obtained.
* @param {THREE.Mesh|array} intersectMeshList <b>THREE.Mesh</b> - [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh}, which intersects with <b>object</b>.
* <pre>
* <b>array</b> - array of intersect graphic objects. Every item is:
* <b>THREE.Mesh</b> - [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh}, which intersects with <b>object</b>.
* or <b>object</b> - object keys is:
* <b>mesh</b> - [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh}, which intersects with <b>object</b>.
* <b>color</b> - intersection lines [color]{@link https://en.wikipedia.org/wiki/Web_colors#Hex_triplet}.
* Examples: <b>0xffffff</b>, <b>'yellow'</b>. Default is white.
* <pre>
* @param {object} [settings] the following settings are available:
* @param {THREE.Scene} [settings.scene] [THREE.Scene]{@link https://threejs.org/docs/index.html?q=sce#api/en/scenes/Scene}.
* @param {THREE.WebGLRenderer} [settings.renderer] [THREE.WebGLRenderer]{@link https://threejs.org/docs/index.html?q=WebGLRenderer#api/en/renderers/WebGLRenderer}.
* @param {function} [settings.onReady] Callback function that called if intersection lines is ready and that take as input an array of all intersect lines.
* <pre>
* <b>function( intersectionLines )</b>
* <b>intersectionLines</b> - array of all intersect lines.
* Every item of array is object. Object keys is:
* <b>mesh</b> - [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh}, which intersects with <b>object</b>.
* <b>points</b> - array of [points]{@link https://threejs.org/docs/index.html?q=Vect#api/en/math/Vector3} of intersection line.
* <b>color</b> - intersection line color.
* Default is white.
* </pre>
*/
constructor( object, intersectMeshList, settings = {} ) {
const THREE = three.THREE, options = three.options || {}, scene = settings.scene || three.group;
if ( object instanceof THREE.Mesh === false ) object = object.mesh;
const positions = object.geometry.attributes.position;
if ( !Array.isArray( intersectMeshList ) ) intersectMeshList = [intersectMeshList];
const collidableMeshList = [], arrayIntersectLinesColor = [];
intersectMeshList.forEach( function ( item ) {
if ( item instanceof THREE.Mesh ) {
collidableMeshList.push( item );
arrayIntersectLinesColor.push( 0xffffff );//white
} else {
collidableMeshList.push( item.mesh );
arrayIntersectLinesColor.push( item.color || 0xffffff );
}
} );
/*
//debug
if ( typeof SpriteText !== "undefined" ) {
collidableMeshList.forEach( function ( mesh ) {
for ( var i = 0; i < mesh.geometry.attributes.position.count; i++ )
mesh.add( new SpriteText( i, new THREE.Vector3().fromBufferAttribute( mesh.geometry.attributes.position, i ) ) );
} );
}
*/
//У некоторых геометрических фигур нет object.geometry.index. Например THREE.TetrahedronGeometry
if ( !object.geometry.index ) {
const array = [];
for ( var i = 0; i < positions.count; i++ ) array.push( i );
object.geometry.index = new THREE.Uint16BufferAttribute( array, 1 );
}
//debug
var currentdate = new Date();
//console.log( ( ( new Date().getTime() - currentdate.getTime() ) / 1000 ) + ' ищщем точки, которые совпадают' )
//ищщем точки, которые совпадают или почти совпадают с небольшой погрешностью
//что бы у них был одинаковый индекс
for ( let i = 1; i < object.geometry.index.count; i++ ) {
const point1 = new THREE.Vector3().fromBufferAttribute( positions, object.geometry.index.array[i] );
//console.log( 'index: ' + object.geometry.index.array[i] );
//console.log( point1 );
for ( let j = i - 1; j >= 0; j-- ) {
const point2 = new THREE.Vector3().fromBufferAttribute( positions, object.geometry.index.array[j] );
// @returns
// 0 координаты совпадают
// 1 коодинаты с маленьким отклонением
// 4 коодинаты с большим отклонением
function Delta( a, b ) {
const d = Math.abs( a - b )
if ( d === 0 ) return 0;
// if ( ( d > 0 ) && ( d <= 7.347880586115415e-16 ) ) return 1;
// if ( ( d > 0 ) && ( d <= 3.1840817637818772e-15 ) ) return 1;
// if ( ( d > 0 ) && ( d <= 3.394278283598952e-15 ) ) return 1;//use THREE.SphereGeometry for testing
if ( ( d > 0 ) && ( d <= 4e-15 ) ) return 1;//use THREE.SphereGeometry for testing
// if ( ( d > 0 ) && ( d <= 3.e-10 ) ) return 1;
return 4;
}
const res = Delta( point2.x, point1.x ) + Delta( point2.y, point1.y ) + Delta( point2.z, point1.z );
//0 координаты совпадают
//1,2,3 одна, две или три коодинаты с маленьким отклонением. Остальные совпадают.
//4 есть хоть одна коодината с большим отклонением
//if ( ( res > 0 ) && ( res < 4 ) )
if ( res < 4 ) {
object.geometry.index.array[i] = object.geometry.index.array[j];
break;
}
}
}
function arrayIntersectionsPush( intersection, array ) {
const point1 = intersection.point;
for ( var i = 0; i < array.length; i++ ) {
function isSameAxis( axis1, axis2 ) { return axis1 === axis2 }
const point2 = array[i].point;
if ( isSameAxis( point1.x, point2.x ) && isSameAxis( point1.y, point2.y ) && isSameAxis( point1.z, point2.z ) )
return;//не надо добавлять точки с одинаковым положением
}
array.push( intersection );
}
//точки пересечения одного тела с другим могут образовывать несколько замкнутых линий ( Loops ).
//Например пересечение тора с плоскостью.
//Здесь перечислены все обнруженные Loops точек пересечения.
const intersectionLines = [],
//Список граней, имеющих линии пересечения. Нужен что бы во врнмя построения линий пересечения не искать по всем граням.
arrayIntersectFaces = [];
const edges = [];//список ребер
// edges2 = [];//список ребер, кторый сспользуется при содании faces. Этот списоу уменьшается по мере создания faces. Это позволяетс сократить время создания faces
//Заполнить список ребер
for ( var index = 0; index < object.geometry.index.count; index += 3 ) {
class Edge {
constructor( index, index2 ) {
function Vertex( index ) {
//debug
if ( typeof SpriteText !== "undefined" ) var spriteText;
return {
get index() {
if ( index >= object.geometry.index.array.length )
index = 0;//сюда попадает когда ищу индекс второй точки ребра edge.vertex2
//и когда ребро последнее в объекте.
//Я предполагаю что вторая точка ребра edge.vertex2 это первая точка в object.geometry.index
//Для проверки использовать const geometry = new THREE.BufferGeometry();
const i = object.geometry.index.array[index];
if ( i === undefined )
console.error( 'Intersections.createIntersections.Edge.Vertex: i = ' + i );
return i;
// return index;
},
get pointLocal() {
const vertex = positions.itemSize === 3 ? new THREE.Vector3() : positions.itemSize === 4 ? new THREE.Vector3() : undefined;
return vertex.fromBufferAttribute( positions, this.index );
},
get point() {
const point = this.pointLocal.applyMatrix4( object.matrix );
// const point = getWorldPosition( object, this.pointLocal );
//debug
if ( typeof SpriteText !== "undefined" && !spriteText ) {
spriteText = new SpriteText( this.index, this.pointLocal );
object.add( spriteText );
}
return point;
},
//debug
set update( a ) { if ( typeof SpriteText !== "undefined" ) spriteText = a; },
}
};
var vertex1, vertex2, collisionResultsOriginPoint, intersectionObject;
//индексы точек пересечения, положение которых совпадает с положением точек пересечения на соседних ребрах грани.
//Эти точи пересечения надо игнорировать, что бы количество точек пересечения на грани было четным.
//Подробности в Face.intersections
const arraySpliceIntersection = [],
array = [];//только точки пересечения, которые нужно учитывать
this.spliceIntersection = function ( index, uuid ) {
arraySpliceIntersection.push( { index: index, uuid: uuid, } );
array.length = 0;
}
//debug
if ( typeof SpriteText !== "undefined" ) { var groupSpriteText; }
Object.defineProperties( this, {
intersection: {
get: function () {
if ( !collisionResultsOriginPoint ) {
//if ( ( this.vertex1.index === 22 ) || ( this.vertex2.index === 22 ) )
// console.log( 'this.vertex.index === 22' );
const direction = this.vertex2.point.clone().sub( this.vertex1.point ).clone().normalize(),
rayOriginPoint = new THREE.Raycaster( this.vertex1.point, direction, 0,
this.vertex2.point.distanceTo( this.vertex1.point ) );
//console.log( ' origin: ' + this.vertex1.point.x + ', ' + this.vertex1.point.y + ', ' + this.vertex1.point.z +
// ' vertex2: ' + this.vertex2.point.x + ', ' + this.vertex2.point.y + ', ' + this.vertex2.point.z )
// ' direction: ' + direction.x + ', ' + direction.y + ', ' + direction.z )
//array - список точек пересечения, возвращенный THREE.Raycaster.intersectObjects
//collisionResultsOriginPoint.length === 0 пересечений не обнаружено
//undefined - точка пересечения еще не вычислялась
//collisionResultsOriginPoint[i] = false - уже добавлен в arrayIntersectLine
collisionResultsOriginPoint = rayOriginPoint.intersectObjects( collidableMeshList );//, false );//recursive = false на тот случай когда для отладки в пересекаемые объекты добавляю текст с номером вершины
if ( !this.faces )
console.error( 'edge ' + this.vertex1.index + ' ' + this.vertex2.index + ' intersects ' + collisionResultsOriginPoint.length )
}
var res;
if ( this.intersectionObject ) {
if ( array.length === 0 ) {
//Надо вывести только точки пересечения с объектом this.intersectionObject
//и если в начале или конце соседнего ребра нет точки пересечения с такми же положением
const intersectionObject = this.intersectionObject;
collisionResultsOriginPoint.forEach( function ( intersection, index ) {
if ( intersection.object.uuid === intersectionObject.uuid ) {
/*
//не добавлять точку пересечения в array если в другом ребре грани есть точка пересечения с тем же положением
var boSpliceIntersection = false;
for ( var i = 0; i < arraySpliceIntersection.length; i++ ) {
if ( arraySpliceIntersection[i] === index ) {
boSpliceIntersection = true;
break;
}
}
if ( !boSpliceIntersection )
*/
arrayIntersectionsPush( intersection, array );
}
} );
//не добавлять точку пересечения в array если в другом ребре грани есть точка пересечения с тем же положением
if ( arraySpliceIntersection.length > 1 )
console.error( 'under construction' );//надо рассотировать arraySpliceIntersection так что бы из array сначала удалялись элементы с наибольшим индексом. Иначе не те элементы будут удаляться
for ( var i = 0; i < arraySpliceIntersection.length; i++ ) {
if ( arraySpliceIntersection[i].uuid === intersectionObject.uuid )
array.splice( arraySpliceIntersection[i].index, 1 );
}
}
res = array;
} else res = collisionResultsOriginPoint;
/*
//debug
if ( typeof SpriteText !== "undefined" ) {
if ( !groupSpriteText ) {
groupSpriteText = new THREE.Group();
object.parent.add( groupSpriteText );
}
groupSpriteText.children.forEach( function ( item ) { groupSpriteText.remove( item ); } );
res.forEach( function ( intersection, index ) { groupSpriteText.add( new SpriteText( 'i' + index, intersection.point ) ); } );
}
*/
return res;
},
set: function ( intersection ) {
//debug
this.vertex1.update = undefined;
this.vertex2.update = undefined;
collisionResultsOriginPoint = intersection;
},
},
intersectionObject: {
get: function () { return intersectionObject; },
set: function ( intersectionObjectNew ) {
intersectionObject = intersectionObjectNew;
arraySpliceIntersection.length = 0;
array.length = 0;
},
},
vertex1: {
get: function () {
if ( !vertex1 )
vertex1 = Vertex( index );
return vertex1;
}
},
vertex2: {
get: function () {
if ( !vertex2 )
vertex2 = Vertex( index2 );
return vertex2;
}
},
} );
/*
//debug
const index1 = object.geometry.index.array[this.vertex1.index], index2 = object.geometry.index.array[this.vertex2.index],
edgeIndex1 = 9, edgeIndex2 = 18;
//console.log( 'index1 = ' + index1 + ' index2 = ' + index2 );
if (
( ( index1 === edgeIndex1 ) && ( index2 === edgeIndex2 ) ) ||
( ( index1 === edgeIndex2 ) && ( index2 === edgeIndex1 ) )
)
console.log( edgeIndex1 + ',' + edgeIndex2 );
*/
this.isCollision = function () { return collisionResultsOriginPoint.length > 0; }
this.isSame = function ( edge ) {
//Если сделать const boSame
//то появится ошибка
//[!] (babel plugin) SyntaxError: D:/My documents/MyProjects/webgl/three.js/GitHub/commonNodeJS/master/Intersections/Intersections.js: "boSame" is read-only
//во время создания файлов myThree\build
//npm run build
var boSame = ( ( this.vertex1.index === edge.vertex1.index ) && ( this.vertex2.index === edge.vertex2.index ) ) ||
( ( this.vertex1.index === edge.vertex2.index ) && ( this.vertex2.index === edge.vertex1.index ) );
if ( boSame === undefined ) boSame = false;
return boSame;
}
}
}
var edge = new Edge( index, index + 1 );
edges.push( edge );
// edges2.push( edge );
edge = new Edge( index + 1, index + 2 )
edges.push( edge );
// edges2.push( edge );
//Если поменять порядок индексов, то в edges появятся одинаковые ребра,
//которые будут отличатся только переставленными edge.vector1 и edge.vector2
//а это приведет к тому что в некоторых случаях не будет пересечения с объектом
//если ребро пересекается с объектом одним из концов непонятно почему.
//Как результат, могут появиться разрывы в линии пересечения.
//Для проверки в примере установить plane( 'plane', new THREE.PlaneGeometry( 30, 30 ) ).rotation.y = Math.PI / 2;
//и const objGeom = new THREE.DodecahedronGeometry( 10, 0 );
//и obj.position.z = 8.9;
edge = new Edge( index, index + 2 );
edges.push( edge );
// edges2.push( edge );
}
//список граней
const faces = [];
//Progress window
const renderer = options.renderer || settings.renderer;
var elProgress, cProgress;
if ( renderer ) {
const elCanvas = renderer.domElement, elContainer = elCanvas.parentElement;
if ( elContainer.tagName !== "DIV" ) {
console.error( 'Intersections: elContainer.tagName = ' + elContainer.tagName );
return;
}
const container = "container";
if ( !elContainer.classList.contains( container ) ) elContainer.classList.add( container );
elProgress = document.createElement( 'div' );
cProgress = document.createElement( 'input' );
const elTitle = document.createElement( 'div' );
elProgress.style.position = 'absolute';
elProgress.style.top = 0;
elProgress.style.left = 0;
elProgress.style.backgroundColor = 'white';
elProgress.style.margin = '2px';
elProgress.style.padding = '2px';
const lang = { progressTitle: 'Intersections preparing.<br>Wait please...', };
switch ( options.getLanguageCode() ) {
case 'ru'://Russian language
lang.progressTitle = 'Подготовка пересечений.<br>Пожалуйста подождите...';
break;
}
elTitle.innerHTML = lang.progressTitle;
elProgress.appendChild( elTitle );
cProgress.min = "0";
cProgress.max = object.geometry.index.count;
cProgress.type = "range";
cProgress.disabled = true;
elProgress.appendChild( cProgress );
elContainer.appendChild( elProgress );
}
//Заполнить список граней
index = 0;
/*
const skip = parseInt( object.geometry.index.count / 100 );//время для TorusGeometry сократилось с 54 до 47 секунд
var skipCur = 0;
*/
function step( timestamp ) {
if ( cProgress )
cProgress.value = index;
if ( index >= object.geometry.index.count ) {
if ( elProgress )
elProgress.remove();
boCreateIntersections = true;
setTimeout( function () { createIntersections(); }, 0 );//Таймаут нужен что бы установился matrixWorld объектов из collidableMeshList.
return;
}
//console.log( ( ( new Date().getTime() - currentdate.getTime() ) / 1000 ) + ' geometry index = ' + index )
class Face {
/* *
* [Face]{@link https://threejs.org/docs/index.html?q=Fa#examples/en/math/convexhull/Face}
* @param {number} index index of vertices of the face <b>from object.geometry.index</b>
* @param {number} id identifier of the face in the <b>faces</b> array.
*/
constructor( index/*, id*/ ) {
const vectorIndex = new THREE.Vector3(),
arrayIntersectLines = [];//линии пересечения грани. каждый элемент содержит ребро и индекс начала и конца линии пересечения
vectorIndex.fromBufferAttribute( object.geometry.index, index );
Object.defineProperties( this, {
faceEdges: { get: function () { return faceEdges; } },//прилегающие к грани ребра
id: { get: function () { return vectorIndex; /*return id;*/ } },
//for debugging
name: { get: function () { /*return 'Face ' + id;*/ return 'Face ' + vectorIndex.x + ', ' + vectorIndex.y + ', ' + vectorIndex.z; }
},
vertices: {
get: function () {
return {
vertex1: faceEdges.edge1.vertex1,
vertex2: faceEdges.edge1.vertex2,
get vertex3() {
if ( !faceEdges.edge3 ) {
console.error( 'faceEdges.edge3 = ' + faceEdges.edge3 );
if ( faceEdges.edge1.vertex1.index === faceEdges.edge2.vertex1.index )
return faceEdges.edge2.vertex2;
if ( faceEdges.edge1.vertex1.index === faceEdges.edge2.vertex2.index )
return faceEdges.edge2.vertex1;
if ( faceEdges.edge1.vertex2.index === faceEdges.edge2.vertex1.index )
return faceEdges.edge2.vertex2;
if ( faceEdges.edge1.vertex2.index === faceEdges.edge2.vertex2.index )
return faceEdges.edge2.vertex1;
return;
}
return ( faceEdges.edge1.vertex1.index !== faceEdges.edge3.vertex1.index ) &&
( faceEdges.edge1.vertex2.index !== faceEdges.edge3.vertex1.index ) ?
faceEdges.edge3.vertex1 : faceEdges.edge3.vertex2;
},
}
}
}
} );
//прилегающие к грани ребра
function emtyIntersection() {
return {
intersection: [],
isSame: function () { return true; },
}
}
const intersectionEdges = {};//список ребер, имеющих пересечения
//список всех ребер грани
const faceEdges = {
set edge1( edge1 ) { intersectionEdges.edge1 = edge1; },
get edge1() {
if ( intersectionEdges.edge1 ) return intersectionEdges.edge1;
return emtyIntersection();
},
set edge2( edge2 ) { intersectionEdges.edge2 = edge2; },
get edge2() {
if ( intersectionEdges.edge2 ) return intersectionEdges.edge2;
return emtyIntersection();
},
set edge3( edge3 ) { intersectionEdges.edge3 = edge3; },
get edge3() {
if ( intersectionEdges.edge3 ) return intersectionEdges.edge3;
return emtyIntersection();
},
get intersectionObjects() {
const intersectionObjects = [];
function getObects( intersections ) {
intersections.forEach( function ( intersection ) {
var boAdded = false;
for ( var i = 0; i < intersectionObjects.length; i++ ) {
if ( intersection.object.uuid === intersectionObjects[i].uuid ) {
boAdded = true;
break;
}
}
if ( !boAdded ) intersectionObjects.push( intersection.object );
} );
}
getObects( faceEdges.edge1.intersection );
getObects( faceEdges.edge2.intersection );
getObects( faceEdges.edge3.intersection );
return intersectionObjects;
},
set intersectionObject( intersectionObject ) {
faceEdges.edge1.intersectionObject = intersectionObject;
faceEdges.edge2.intersectionObject = intersectionObject;
faceEdges.edge3.intersectionObject = intersectionObject;
},
get intersectionObject() { return faceEdges.edge1.intersectionObject; },
};
//Каждый face имеет три вершины vectorIndex
//в этом цикле из списка всех ребер edges, полученных из object.geometry.index,
//для face ищем три ребра faceEdges.edge1, faceEdges.edge2, faceEdges.edge3
//и для каждого ребра ищем два face, которым принадлежит это ребро edge.faces.face1 и edge.faces.face2
for ( var i = edges.length - 1; i >= 0; i-- ) {
const edge = edges[i],
vertex1Index = edge.vertex1.index,//object.geometry.index.array[edge.vertex1.index],
vertex2Index = edge.vertex2.index;//object.geometry.index.array[edge.vertex2.index];
function setFace( face ) {
if ( !edge.faces ) edge.faces = {}
if ( !edge.faces.face1 ) edge.faces.face1 = face;
else if ( !edge.faces.face2 ) {
edge.faces.face2 = face;
//edges2.pop();
} else console.error( 'Face: too many edge.faces' );
}
if (
( vectorIndex.x === vertex1Index ) && ( vectorIndex.y === vertex2Index ) ||
( vectorIndex.x === vertex2Index ) && ( vectorIndex.y === vertex1Index )
) {
if ( !faceEdges.edge1.isSame( edge ) ) console.error( 'Face: duplicate faceEdges.edge1' );
else {
faceEdges.edge1 = edge;
setFace( this );
}
} else if (
( vectorIndex.z === vertex1Index ) && ( vectorIndex.y === vertex2Index ) ||
( vectorIndex.z === vertex2Index ) && ( vectorIndex.y === vertex1Index )
) {
const boSame = faceEdges.edge2.isSame( edge );
if ( !boSame ) console.error( 'Face: duplicate faceEdges.edge2' );
else {
faceEdges.edge2 = edge;
setFace( this );
}
} else if (
( vectorIndex.z === vertex1Index ) && ( vectorIndex.x === vertex2Index ) ||
( vectorIndex.z === vertex2Index ) && ( vectorIndex.x === vertex1Index )
) {
if ( !faceEdges.edge3.isSame( edge ) ) console.error( 'Face: duplicate faceEdges.edge3' );
else {
faceEdges.edge3 = edge;
setFace( this );
}
}
}
this.isCollision = function () {
return faceEdges.edge1.intersection.length || faceEdges.edge2.intersection.length || faceEdges.edge3.intersection.length;
}
//debug
if ( typeof SpriteText !== "undefined" ) { var groupSpriteText; }
this.nextIntersectPoint = function ( point ) {
for ( var i = 0; i < arrayIntersectLines.length; i++ ) {
function returnPoint( point ) {
//if ( vectorIndex.equals( new THREE.Vector3( 22, 13, 10 ) ) )
// console.log( '111' );
arrayIntersectLines.splice( i, 1 );
return point;
}
const intersectLine = arrayIntersectLines[i],
uuid = point.uuid;
if (
point.edge.isSame( intersectLine.point1.edge ) &&
( point.intersectionIndex === intersectLine.point1.intersectionIndex ) &&
( uuid === intersectLine.point1.uuid )
)
return returnPoint( intersectLine.point2 );
else if (
point.edge.isSame( intersectLine.point2.edge ) &&
( point.intersectionIndex === intersectLine.point2.intersectionIndex ) &&
( uuid === intersectLine.point2.uuid )
)
return returnPoint( intersectLine.point1 );
}
}
this.intersectLines = function () { return arrayIntersectLines; }
//console.log( this.name + ' intersections: edge1 ' + faceEdges.edge1.intersection.length + ' edge2 ' + faceEdges.edge2.intersection.length + ' edge3 ' + faceEdges.edge3.intersection.length )
//если эта грань имеет пересечения с объектом, то строим линии пересечения грани с объектом
this.intersections = function () {
/*
//debug
if ( typeof SpriteText !== "undefined" ) {
object.add( new SpriteText( 'e1v1 ' + faceEdges.edge1.vertex1.index, faceEdges.edge1.vertex1.pointLocal ) );
object.add( new SpriteText( 'e1v2 ' + faceEdges.edge1.vertex2.index, faceEdges.edge1.vertex2.pointLocal ) );
object.add( new SpriteText( 'e2v1 ' + faceEdges.edge2.vertex1.index, faceEdges.edge2.vertex1.pointLocal, { center: { x: 0, y: 0, } } ) );
object.add( new SpriteText( 'e2v2 ' + faceEdges.edge2.vertex2.index, faceEdges.edge2.vertex2.pointLocal, { center: { x: 0, y: 0, } } ) );
object.add( new SpriteText( 'e3v1 ' + faceEdges.edge3.vertex1.index, faceEdges.edge3.vertex1.pointLocal, { center: { x: 1, y: 0, } } ) );
object.add( new SpriteText( 'e3v2 ' + faceEdges.edge3.vertex2.index, faceEdges.edge3.vertex2.pointLocal, { center: { x: 1, y: 0, } } ) );
}
*/
//if ( vectorIndex.equals( new THREE.Vector3( 18, 10, 9 ) ) )
// console.log( vectorIndex );
//список объектов пересечения
//Внимание! Нельзя удадять intersectionObjects потому что размер массива faceEdges.intersectionObjects меняется при
//изменении faceEdges.intersectionObject и тогда могут быть пропущены некторые линии пересечения грани с объектом
const intersectionObjects = faceEdges.intersectionObjects;
//Для каждого объекта пересечения делаем отдельные линии пересечения
for ( var iIntersectionObject = 0; iIntersectionObject < intersectionObjects.length; iIntersectionObject++ ) {
//Линии переаечения начинаем строить с faceEdges.edge1.vertex1.
faceEdges.intersectionObject = intersectionObjects[iIntersectionObject];
if ( !this.isCollision() )
continue;//не пересекается
//Надо убрать точки пересечения с одинаковыми координатами но в разных ребрах.
//Это может произойти если начало или конец ребра пересекается с объектом
//Тогда одна и таже точка будет в начале одного ребра и в конце другого
//и количество точек будет нечетным. А это означает что грань вроде бы попала на край объекта
//а я не рисую линию пересечения на краю объекта потому что не знаю как это делать
for ( var i = faceEdges.edge1.intersection.length - 1; i >= 0; i-- ) {
for ( var j = faceEdges.edge2.intersection.length - 1; j >= 0; j-- ) {
if ( ( faceEdges.edge1.intersection.length > i ) && ( faceEdges.edge2.intersection.length > j ) &&
equals( faceEdges.edge1.intersection[i].point, faceEdges.edge2.intersection[j].point ) )
faceEdges.edge2.spliceIntersection( j, faceEdges.intersectionObject.uuid );
for ( var k = faceEdges.edge3.intersection.length - 1; k >= 0; k-- ) {
if ( ( faceEdges.edge1.intersection.length > i ) && ( faceEdges.edge3.intersection.length > k ) &&
equals( faceEdges.edge1.intersection[i].point, faceEdges.edge3.intersection[k].point ) )
faceEdges.edge3.spliceIntersection( k, faceEdges.intersectionObject.uuid );
if ( ( faceEdges.edge2.intersection.length > j ) && ( faceEdges.edge3.intersection.length > k ) &&
equals( faceEdges.edge2.intersection[j].point, faceEdges.edge3.intersection[k].point ) )
faceEdges.edge3.spliceIntersection( k, faceEdges.intersectionObject.uuid );
}
}
}
function isOdd( num ) { return num % 2; }
function isOddOrZero( num ) {
// return num === 0 ? true : isOdd( num );
return isOdd( num );
}
function arrayIntersectionsPushEdge( vertexIndex, edge ) {
switch ( vertexIndex ) {
case edge.vertex1.index:
for ( var i = 0; i < edge.intersection.length; i++ )
arrayPushEdge( edge, i );
// arrayIntersectionsPush( edge.intersection[i], arrayIntersections );
break;
case edge.vertex2.index:
for ( var i = edge.intersection.length - 1; i >= 0; i-- )
arrayPushEdge( edge, i );
// arrayIntersectionsPush( edge.intersection[i], arrayIntersections );
break;
default: console.error( 'Face.intersections: arrayIntersections push failed!' );
}
}
function arrayIntersectionsPushEdge3( vertexIndex ) { arrayIntersectionsPushEdge( vertexIndex, faceEdges.edge3 ); }
function arrayIntersectionsPushEdge2( vertexIndex ) { arrayIntersectionsPushEdge( vertexIndex, faceEdges.edge2 ); }
const arrayIntersections = [];//Все точки пересечения, начиная с faceEdges.edge1
function arrayPushEdge( edge, intersectionIndex )
{
const intersection = edge.intersection[intersectionIndex];
arrayIntersections.push( {
get edge() { return edge; },
get intersectionIndex() { return intersectionIndex; },
get uuid() { return intersection.object.uuid; },
get faces() { return edge.faces; },
get point() { return intersection.point; },
get intersection() { return intersection; },
} );
}
var lastEdge;//ребро, которое имеет общую точку с faceEdges.edge1.vertex1
for ( var i = 0; i < faceEdges.edge1.intersection.length; i++ )
arrayPushEdge( faceEdges.edge1, i );
// arrayIntersectionsPush( faceEdges.edge1.intersection[i], arrayIntersections );
switch ( faceEdges.edge1.vertex2.index ) {
case faceEdges.edge2.vertex1.index:
lastEdge = faceEdges.edge3;
//первая точка пересечения в faceEdges.edge2.intersection находтся ближе к faceEdges.edge1.vertex2
for ( var i = 0; i < faceEdges.edge2.intersection.length; i++ )
arrayPushEdge( faceEdges.edge2, i );
// arrayIntersectionsPush( faceEdges.edge2.intersection[i], arrayIntersections );
arrayIntersectionsPushEdge3( faceEdges.edge2.vertex2.index );
break;
case faceEdges.edge2.vertex2.index:
lastEdge = faceEdges.edge3;
//первая точка пересечения в faceEdges.edge2.intersection находтся дальше от faceEdges.edge1.vertex2
for ( var i = faceEdges.edge2.intersection.length - 1; i >= 0; i-- )
arrayPushEdge( faceEdges.edge2, i );
// arrayIntersectionsPush( faceEdges.edge2.intersection[i], arrayIntersections );
arrayIntersectionsPushEdge3( faceEdges.edge2.vertex1.index );
break;
case faceEdges.edge3.vertex1.index:
lastEdge = faceEdges.edge2;
//первая точка пересечения в faceEdges.edge3.intersection находтся ближе к faceEdges.edge1.vertex2
for ( var i = 0; i < faceEdges.edge3.intersection.length; i++ )
arrayPushEdge( faceEdges.edge3, i );
// arrayIntersectionsPush( faceEdges.edge3.intersection[i], arrayIntersections );
arrayIntersectionsPushEdge2( faceEdges.edge3.vertex2.index );
break;
case faceEdges.edge3.vertex2.index:
lastEdge = faceEdges.edge2;
//первая точка пересечения в faceEdges.edge3.intersection находтся дальше от faceEdges.edge1.vertex2
for ( var i = faceEdges.edge3.intersection.length - 1; i >= 0; i-- )
arrayPushEdge( faceEdges.edge3, i );
// arrayIntersectionsPush( faceEdges.edge3.intersection[i], arrayIntersections );
arrayIntersectionsPushEdge2( faceEdges.edge3.vertex1.index );
break;
default: console.error( 'Face.intersections: arrayIntersections push failed!' );
}
/*
//debug
if ( typeof SpriteText !== "undefined" ) {
if ( !groupSpriteText ) {
groupSpriteText = new THREE.Group();
object.parent.add( groupSpriteText );
}
for ( var i = groupSpriteText.children.length - 1; i >= 0; i-- )
groupSpriteText.remove( groupSpriteText.children[i] );
arrayIntersections.forEach( function ( intersection, index ) { groupSpriteText.add( new SpriteText( index, intersection.point, { center: { x: 0, y: 0, } } ) ); } );
}
*/
const intersectionCount = arrayIntersections.length;
if ( isOdd( intersectionCount ) ) {
//console.log( 'Нечетное количество точек пересечения. Край объекта' );
continue;
}
function addIntersectLine( point1, point2 ) { arrayIntersectLines.push( { point1: point1, point2: point2 } ); }
/*
function createIntersectLineSegment( points ) {
const arrayIntersectLoop = [];//список точек, которые образуют линию пересечения объектов
arrayIntersectLoops.push( arrayIntersectLoop );
arrayIntersectLoop.intersectLine = new THREE.Line( new THREE.BufferGeometry().setFromPoints( points ),
new THREE.LineBasicMaterial( { color: 0xffffff } ) );
scene.add( arrayIntersectLoop.intersectLine );
if ( options.guiSelectPoint ) {
arrayIntersectLoop.intersectLine.name = 'line segment ' + arrayIntersectLoops.length;
options.guiSelectPoint.addMesh( arrayIntersectLoop.intersectLine );
}
}
*/
//Найти в списке граней, имеющих линии пересечения список граней для текущего объекта пересечения faceEdges.intersectionObject
let arrayMesh;
for ( var i = 0; i < arrayIntersectFaces.length; i++ ) {
if ( arrayIntersectFaces[i].mesh.uuid === faceEdges.intersectionObject.uuid ) {
arrayMesh = arrayIntersectFaces[i];
break;
}
}
//Если этого списка нет, то добавить его
if ( !arrayMesh ) {
arrayMesh = [];
arrayMesh.mesh = faceEdges.intersectionObject;
arrayIntersectFaces.push( arrayMesh );
}
arrayMesh.push( this );
if ( intersectionCount === 2 )
addIntersectLine( arrayIntersections[0], arrayIntersections[1] );
// createIntersectLineSegment( [arrayIntersections[0].point, arrayIntersections[1].point] );
else if ( !isOddOrZero( faceEdges.edge1.intersection.length ) || !isOddOrZero( lastEdge.intersection.length ) ) {
//faceEdges.edge1.vertex1 нахоится снаружи объекта.
//Возможно грань персекается с вершиной объекта в которой сходится 3 грани.
//Другими словами грань как бы надета на вершину пирамиды
var boDetected = false;
if ( intersectionCount === 6 ) {
function equalFaces( face1, face2 ) { return ( face1.a === face2.a ) && ( face1.b === face2.b ) && ( face1.c === face2.c ); }
if (
equalFaces( arrayIntersections[0].intersection.face, arrayIntersections[5].intersection.face ) &&
equalFaces( arrayIntersections[1].intersection.face, arrayIntersections[2].intersection.face ) &&
equalFaces( arrayIntersections[3].intersection.face, arrayIntersections[4].intersection.face )
) {
addIntersectLine( arrayIntersections[0], arrayIntersections[5] );
addIntersectLine( arrayIntersections[1], arrayIntersections[2] );
addIntersectLine( arrayIntersections[3], arrayIntersections[4] );
boDetected = true;
} else if (
equalFaces( arrayIntersections[0].intersection.face, arrayIntersections[1].intersection.face ) &&
equalFaces( arrayIntersections[2].intersection.face, arrayIntersections[3].intersection.face ) &&
equalFaces( arrayIntersections[4].intersection.face, arrayIntersections[5].intersection.face )
) {
console.error( 'under constraction' );
addIntersectLine( arrayIntersections[0], arrayIntersections[1] );
addIntersectLine( arrayIntersections[2], arrayIntersections[3] );
addIntersectLine( arrayIntersections[4], arrayIntersections[5] );
boDetected = true;
}
}
if ( !boDetected ) {
//Тогда первую линию пересечения проводим через ближайшие к faceEdges.edge1.vertex1 точки пересечения
//console.log( faceEdges.intersectionObject.name + ' faceEdges.edge1.vertex1 нахоится снаружи объекта.' );
for ( var i = 0; i < intersectionCount / 2; i++ )
addIntersectLine( arrayIntersections[i], arrayIntersections[intersectionCount - 1 - i] );
// createIntersectLineSegment( [arrayIntersections[i].point, arrayIntersections[intersectionCount - 1 - i].point] );
}
} else {
//faceEdges.edge1.vertex1 нахоится внутри объекта.
//Тогда сегметы линий пересечения делаем последовательно из точек arrayIntersections
//console.log( faceEdges.intersectionObject.name + ' faceEdges.edge1.vertex1 нахоится внутри объекта.' );
for ( var i = 0; i < intersectionCount; i += 2 )
addIntersectLine( arrayIntersections[i], arrayIntersections[i + 1] );
// createIntersectLineSegment( [arrayIntersections[i].point, arrayIntersections[i + 1].point] );
}
}
faceEdges.intersectionObject = undefined;
}
}
}
faces.push( new Face( index, faces.length ) );
index += 3;
setTimeout( function () { step(); }, 0 );//это работает побыстрей
/*
skipCur += 3;
if ( skipCur < skip )
setTimeout( function () { step(); }, 0 );//это работает побыстрей
else {
skipCur = 0;
window.requestAnimationFrame( step );
}
*/
}
window.requestAnimationFrame( step );
//console.log( 'faces' )
function equals( point1, point2 ) {
return point1.distanceTo( point2 ) <= 9.0e-10;//8.881784197001252e-16;
}
var boCreateIntersections = false;
function createIntersections() {
if ( !boCreateIntersections ) return;
//Во время отладки у объекта могут быть дочение SpriteText с индксами вершин
while ( object.children.length > 0 ) { object.remove( object.children[0] ); };
arrayIntersectFaces.length = 0;
//Удалить линии пересечения
for ( var i = intersectionLines.length - 1; i >= 0; i-- ) {
//https://stackoverflow.com/a/68004442/5175935
function removeObject3D( object3D ) {
if ( !( object3D instanceof THREE.Object3D ) ) return false;
// for better memory management and performance
object3D.geometry.dispose();
if ( object3D.material instanceof Array ) {
// for better memory management and performance
object3D.material.forEach( material => material.dispose() );
} else {
// for better memory management and performance
object3D.material.dispose();
}
object3D.removeFromParent(); // the parent might be the scene or another Object3D, but it is sure to be removed this way
return true;
}
const arrayIntersectLine = intersectionLines[i];
if ( arrayIntersectLine.intersectLine ) {
removeObject3D( arrayIntersectLine.intersectLine );
// clearThree( arrayIntersectLine.intersectLine );
if ( options.guiSelectPoint ) options.guiSelectPoint.removeMesh( arrayIntersectLine.intersectLine );
arrayIntersectLine.intersectLine = null;
}
if ( arrayIntersectLine.intersectPoints ) {
removeObject3D( arrayIntersectLine.intersectPoints );
// clearThree( arrayIntersectLine.intersectPoints );
if ( options.guiSelectPoint ) options.guiSelectPoint.removeMesh( arrayIntersectLine.intersectPoints );
arrayIntersectLine.intersectPoints = null;
}
intersectionLines.pop();
}
//ищем точки пересечения
edges.forEach( function ( edge ) {
edge.intersection = undefined;
edge.intersection;
} );
//Строим линии пересечения с гранью
faces.forEach( function ( face ) {
face.intersections();
} );
//строим линию пересечения с объектом
arrayIntersectFaces.forEach( function ( meshLines ) {
meshLines.forEach( function ( face ) {
const arrayIntersectLines = face.intersectLines();
for ( var i = arrayIntersectLines.length - 1; i >= 0; i-- ) {
const line = arrayIntersectLines[i];
if ( !line )
continue;
if ( meshLines.mesh.uuid !== line.point1.uuid )
continue;
arrayIntersectLines.splice( i, 1 );
const points = [line.point1.point];
var point = line.point2, faceNext = face,
boPush = true;//false - добавлять новую точку в начало points
while ( true ) {
if ( boPush ) {
if ( !equals( points[points.length - 1], point.point ) )//не добавлять две одинаковые точки подряд
points.push( point.point );
} else {
if ( !equals( points[0], point.point ) )//не добавлять две одинаковые точки подряд
points.unshift( point.point );
}
const faces = point.faces;
if ( faceNext.id.equals( faces.face1.id ) )
faceNext = faces.face2;
else if ( faceNext.id.equals( faces.face2.id ) )
faceNext = faces.face1;
else console.error( 'Intersections.createIntersections: get twin face failed' );
if ( !faceNext )
break;
var pointEnd = point;
point = faceNext.nextIntersectPoint( point );
if ( !point ) {
const vertexId = equals( pointEnd.point, pointEnd.edge.vertex1.point ) ?
pointEnd.edge.vertex1.index : equals( pointEnd.point, pointEnd.edge.vertex2.point ) ?
pointEnd.edge.vertex2.index : undefined;
faceNext = undefined;
if ( vertexId ) {
//Текущая точка линии пересечения не находится на соседней грани
//и совпадает с одной из вершин ребра pointEnd.edge.
//Поэтому продолжение линии пересечения надо искать на гранях, имеющих общие вершины с pointEnd.edge
for ( var iIntersectFaces = 0; iIntersectFaces < arrayIntersectFaces.length; iIntersectFaces++ ) {
if ( pointEnd.uuid !== arrayIntersectFaces[iIntersectFaces].mesh.uuid )
continue;
for ( var jIntersectFaces = 0; jIntersectFaces < arrayIntersectFaces[iIntersectFaces].length; jIntersectFaces++ ) {
const face = arrayIntersectFaces[iIntersectFaces][jIntersectFaces];
if (
( face.faceEdges.edge1.vertex1.index === vertexId ) ||
( face.faceEdges.edge1.vertex2.index === vertexId ) ||
( face.faceEdges.edge2.vertex1.index === vertexId ) ||
( face.faceEdges.edge2.vertex2.index === vertexId ) ||
( face.faceEdges.edge3.vertex1.index === vertexId ) ||
( face.faceEdges.edge3.vertex2.index === vertexId )
) {
const intersectLines = face.intersectLines();
for ( var k = 0; k < intersectLines.length; k++ ) {
const line2 = intersectLines[k];
if ( equals( pointEnd.point, line2.point1.point ) )
point = line2.point1;
else if ( equals( pointEnd.point, line2.point2.point ) )
point = line2.point2;
if ( point ) {
faceNext = face;
point = faceNext.nextIntersectPoint( point );
break;
}
}
if ( faceNext ) break;//обнаружена грань, имеющая общую вершину с pointEnd.edge
}
}
if ( faceNext ) break;
}
}
if ( !faceNext ) {
//не нашел ни одной грани у которой точка пересечения проходит через вершину и равна последней точке в points
//Может быть линию персечения нужно достроить с другого конца первой точки?
pointEnd = line.point1;
const face1IntersectLines = pointEnd.faces.face1.intersectLines(),
// face2IntersectLines = line.point1.faces.face2.intersectLines(),
face = face1IntersectLines.length ? pointEnd.faces.face1 : pointEnd.faces.face2,
intersectLines = face.intersectLines();
for ( var iIntersectLines = 0; iIntersectLines < intersectLines.length; iIntersectLines++ ) {
const intersectLine = intersectLines[iIntersectLines];
if (
( intersectLine.point1.uuid === pointEnd.uuid ) &&
intersectLine.point1.edge.isSame( pointEnd.edge ) &&
( intersectLine.point1.intersectionIndex === pointEnd.intersectionIndex )
)
point = intersectLine.point1;
else if (
( intersectLine.point2.uuid === pointEnd.uuid ) &&
intersectLine.point2.edge.isSame( pointEnd.edge ) &&
( intersectLine.point2.intersectionIndex === pointEnd.intersectionIndex )
)
point = intersectLine.point2;
if ( point ) {
//обнаружена грань, имеющая линию пересечения, которая является продолжением первой точки линии пересечения
faceNext = face;
point = faceNext.nextIntersectPoint( point );
boPush = false;
break;
}
}
if ( !faceNext ) break;
}
}
}
if ( points.length > 1 ) {//не добавлять линию с количеством точек меньше 2
var color = 0xffffff;
for ( var i = 0; i < collidableMeshList.length; i++ ) {
if ( collidableMeshList[i].uuid === meshLines.mesh.uuid ) {
color = arrayIntersectLinesColor[i];
break;
}
}
const arrayIntersectLine = {
intersectLine: new THREE.Line( new THREE.BufferGeometry().setFromPoints( points ),
new THREE.LineBasicMaterial( { color: color } ) ),
mesh: meshLines.mesh,//с этим объектом пересекается
color: color,
points: points,
}
intersectionLines.push( arrayIntersectLine );
scene.add( arrayIntersectLine.intersectLine );
if ( options.guiSelectPoint ) {
arrayIntersectLine.intersectLine.name =
( arrayIntersectLine.mesh.name === '' ? intersectionLines.length : arrayIntersectLine.mesh.name ) +
'-' + ( object.name === '' ? 'intersection' : object.name );
options.guiSelectPoint.addMesh( arrayIntersectLine.intersectLine );
}
}
}
} );
} );
if ( settings.onReady ) settings.onReady( intersectionLines );
}
// setTimeout( function () { createIntersections(); }, 0 );//Таймаут нужен что бы установился matrixWorld объектов из collidableMeshList.
//Иначе линии пересечения будут строиться без учета реального положения, повррота и масштаба объектов из collidableMeshList.
//список всех объектов, которые могут перемещаться и сталкиваться
const arrayMovingObjects = [object];
collidableMeshList.forEach( function ( object ) { arrayMovingObjects.push( object ); } );
arrayMovingObjects.forEach( function ( object ) {
object.userData.position = object.position.clone();
object.userData.rotation = object.rotation.clone();
object.userData.scale = object.scale.clone();
} );
if ( !options.intersections ) options.intersections = [];
options.intersections.push( function () {
for ( var i = 0; i < arrayMovingObjects.length; i++ ) {
const object = arrayMovingObjects[i];
if (
!object.userData.position.equals( object.position ) ||
!object.userData.rotation.equals( object.rotation ) ||
!object.userData.scale.equals( object.scale )
) {
setTimeout( function () {
object.userData.position = object.position.clone();
object.userData.rotation = object.rotation.clone();
object.userData.scale = object.scale.clone();
createIntersections();
}, 0 );
return;
}
}
} );
}
}
export default Intersections;