@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
159 lines (146 loc) • 4.87 kB
text/typescript
/**
* Creates a line representing the shortest path between two points.
*
*
*/
import {Vector3, Mesh, Object3D, BufferGeometry, Float32BufferAttribute} from 'three';
import {TypedSopNode} from './_Base';
import {CoreGroup} from '../../../core/geometry/Group';
import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
import {InputCloneMode} from '../../poly/InputCloneMode';
import {CoreMask} from '../../../core/geometry/Mask';
import {SopType} from '../../poly/registers/nodes/types/Sop';
import {Node, Edge, Graph, AStar, HeuristicPolicy} from '../../../core/thirdParty/yuka/yuka';
import {Attribute} from '../../../core/geometry/Attribute';
import {Number2} from '../../../types/GlobalTypes';
import {ObjectType} from '../../../core/geometry/Constant';
const _v0 = new Vector3();
const _v1 = new Vector3();
const EDGES: [Number2, Number2, Number2] = [
[0, 1],
[1, 2],
[2, 0],
];
function getDist(graph: Graph, source: number, target: number) {
const sourceNode = graph.getNode(source) as PositionNode | undefined;
const targetNode = graph.getNode(target) as PositionNode | undefined;
if (!(sourceNode && targetNode)) {
return 0;
}
sourceNode.position(_v0);
targetNode.position(_v1);
return _v0.distanceToSquared(_v1);
}
const heuristicPolicy: HeuristicPolicy = {
calculate: getDist,
};
class PositionNode extends Node {
constructor(public readonly index: number, public readonly positionArray: ArrayLike<number>) {
super(index);
}
position(target: Vector3) {
return target.fromArray(this.positionArray, this.index * 3);
}
}
class ShortestPathSopParamsConfig extends NodeParamsConfig {
/** @param objects to find paths in */
group = ParamConfig.STRING('', {
objectMask: true,
});
/** @param index of start point */
pt0 = ParamConfig.INTEGER(0, {
range: [0, 100],
rangeLocked: [true, false],
});
/** @param index of end point */
pt1 = ParamConfig.INTEGER(1, {
range: [0, 100],
rangeLocked: [true, false],
});
}
const ParamsConfig = new ShortestPathSopParamsConfig();
export class ShortestPathSopNode extends TypedSopNode<ShortestPathSopParamsConfig> {
override paramsConfig = ParamsConfig;
static override type() {
return SopType.SHORTEST_PATH;
}
override initializeNode() {
this.io.inputs.setCount(1);
this.io.inputs.initInputsClonedState(InputCloneMode.NEVER);
}
override cook(inputCoreGroups: CoreGroup[]) {
const inputCoreGroup = inputCoreGroups[0];
const selectedObjects = CoreMask.filterThreejsObjects(inputCoreGroup, this.pv);
const newObjects: Object3D[] = [];
for (const object of selectedObjects) {
this._createShortestPath(object, newObjects);
}
this.setObjects(newObjects);
}
private _createShortestPath(object: Object3D, newObjects: Object3D[]) {
const geometry = (object as Mesh).geometry;
if (!geometry) {
return;
}
const positionAttribute = geometry.getAttribute(Attribute.POSITION);
const index = geometry.getIndex();
if (!(positionAttribute && index)) {
return;
}
const pointsCount = positionAttribute.count;
const facesCount = index.count / 3;
const positions = positionAttribute.array;
const indices = index.array;
const endPtBySrcPt: Map<number, Set<number>> = new Map();
const graph = new Graph();
for (let i = 0; i < pointsCount; i++) {
const node = new PositionNode(i, positions);
graph.addNode(node);
}
for (let i = 0; i < facesCount; i++) {
for (const edgeIndices of EDGES) {
const i0 = indices[i * 3 + edgeIndices[0]];
const i1 = indices[i * 3 + edgeIndices[1]];
let endPts0 = endPtBySrcPt.get(i0);
let endPts1 = endPtBySrcPt.get(i1);
if (!endPts0) {
endPts0 = new Set();
endPtBySrcPt.set(i0, endPts0);
}
if (!endPts1) {
endPts1 = new Set();
endPtBySrcPt.set(i1, endPts1);
}
if (!endPts0.has(i1)) {
const edge = new Edge(i0, i1, 0);
graph.addEdge(edge);
endPts0.add(i1);
endPts1.add(i0);
}
}
}
const solver = new AStar(graph, this.pv.pt0, this.pv.pt1);
solver.heuristic = heuristicPolicy;
solver.search();
const path = solver.getPath();
this._buildLine(path, graph, newObjects);
}
private _buildLine(path: number[], graph: Graph, newObjects: Object3D[]) {
const pointsCount = path.length;
const positions: number[] = new Array(pointsCount * 3);
const indices: number[] = new Array(pointsCount);
for (let i = 0; i < pointsCount; i++) {
const node = graph.getNode(path[i]) as PositionNode;
node.position(_v0).toArray(positions, i * 3);
if (i > 0) {
indices[(i - 1) * 2] = i - 1;
indices[(i - 1) * 2 + 1] = i;
}
}
const geometry = new BufferGeometry();
geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
geometry.setIndex(indices);
const object = this.createObject(geometry, ObjectType.LINE_SEGMENTS);
newObjects.push(object);
}
}