UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

159 lines (146 loc) 4.87 kB
/** * 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); } }