Skip to content

Commit

Permalink
Merge branch 'feature-path-finder' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
tsengyushiang committed May 1, 2024
2 parents 2d3cc99 + c13d515 commit b2ac499
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 4 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"three": "^0.164.1"
"three": "^0.164.1",
"three-pathfinding": "^1.2.0"
},
"devDependencies": {
"@types/react": "^18.2.66",
Expand Down
Binary file added public/demo.nav.glb
Binary file not shown.
29 changes: 29 additions & 0 deletions src/PathFinder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { Pathfinding } from "three-pathfinding";

function PathFinder(navPath) {
const pathfinding = new Pathfinding();
const ZONE = "level1";

const loader = new GLTFLoader();
loader.load(navPath, ({ scene }) => {
let navmesh = null;
scene.traverse((node) => {
if (node.isMesh) navmesh = node;
});

// Create level.
pathfinding.setZoneData(ZONE, Pathfinding.createZone(navmesh.geometry));
});

// Find path from A to B.
const getPathFromA2B = (a, b) => {
const groupID = pathfinding.getGroup(ZONE, a);
const path = pathfinding.findPath(a, b, ZONE, groupID);
return path;
};

return { getPathFromA2B };
}

export default PathFinder;
128 changes: 125 additions & 3 deletions src/ThreeApp.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,152 @@
import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import PathFinder from "./PathFinder";

function interpolatePoints(pointsArray, numPoints) {
var interpolatedPoints = [];

// Calculate the number of segments
var numSegments = pointsArray.length - 1;

// Calculate the increment for each segment
var segmentIncrement = 1 / numSegments;

// Interpolate between each pair of consecutive points
for (var i = 0; i < numSegments; i++) {
var start = pointsArray[i];
var end = pointsArray[i + 1];

for (var t = 0; t < numPoints * segmentIncrement; t++) {
var point = start.clone().lerp(end, t / (numPoints * segmentIncrement));
interpolatedPoints.push(point);
}
}

// Add the last point
interpolatedPoints.push(pointsArray[pointsArray.length - 1]);

return interpolatedPoints;
}

const MODEL = "/triangle-localization-simulator/demo.glb";
const NAV_MODE = "/triangle-localization-simulator/demo.nav.glb";
function ThreeApp(canvas) {
const renderer = new THREE.WebGLRenderer({ canvas, alpha: true });
const scene = new THREE.Scene();
const pathFinder = new PathFinder(NAV_MODE);
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);

const setPath = (() => {
let group = new THREE.Group();
scene.add(group);

const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff });
const boxGeometry = new THREE.BoxGeometry(0.1, 0.1, 0.1);
const lineGeometry = new THREE.BufferGeometry();

return (points) => {
scene.remove(group);
const newGroup = new THREE.Group();
scene.add(newGroup);
group = newGroup;

lineGeometry.setFromPoints(points);

const line = new THREE.Line(lineGeometry, lineMaterial);
group.add(line);

points.forEach(function (point) {
const box = new THREE.Mesh(boxGeometry, boxMaterial);
box.position.copy(point);
group.add(box);
});
};
})();

const { setCamera, firstPersonViewCamera } = (() => {
const firstPersonViewCamera = new THREE.PerspectiveCamera(
30,
window.innerWidth / window.innerHeight,
0.1,
3
);
const helper = new THREE.CameraHelper(firstPersonViewCamera);
scene.add(firstPersonViewCamera);
scene.add(helper);
const setCamera = ([x, y, z], [lookAtX, lookAtY, lookAtZ]) => {
firstPersonViewCamera.position.set(x, y, z);
firstPersonViewCamera.lookAt(lookAtX, lookAtY, lookAtZ);
};
return { setCamera, firstPersonViewCamera };
})();

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7.5);
scene.add(directionalLight);

const controls = new OrbitControls(camera, renderer.domElement);
camera.position.set(0, 20, 100);
camera.position.set(0, 10, 10);
controls.update();

const gridHelper = new THREE.GridHelper(20, 20);
scene.add(gridHelper);

const loader = new GLTFLoader();
loader.load("/ap-simulator/public/demo.glb", (model) => {
loader.load(MODEL, (model) => {
scene.add(model.scene);
});

let navMesh;
loader.load(NAV_MODE, (model) => {
model.scene.traverse((node) => {
if (node.isMesh) {
node.material.color = new THREE.Color(0xff0000);
node.material.opacity = 0.3;
node.material.transparent = true;
navMesh = node;
}
});
scene.add(model.scene);
});

camera.position.z = 5;
const raycaster = new THREE.Raycaster();
const onFindPath = (event) => {
const mouse = new THREE.Vector2();
const rect = canvas.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

// Set the raycaster position based on the mouse coordinates
raycaster.setFromCamera(mouse, camera);

// Check for intersections
const intersects = raycaster.intersectObjects(scene.children, true);

if (intersects.length > 0) {
// Get the coordinates of the first intersection point
const { point } = intersects[0];
const path = pathFinder.getPathFromA2B(
firstPersonViewCamera.position,
point
);
if (!path) return null;

const refinePath = [
firstPersonViewCamera.position,
...path.map(({ x, y, z }) => new THREE.Vector3(x, y + 1, z)),
];

return interpolatePoints(refinePath, 20);
}
return null;
};

const animate = () => {
requestAnimationFrame(animate);
Expand All @@ -52,6 +171,9 @@ function ThreeApp(canvas) {

return {
resizeCanvas,
onFindPath,
setCamera,
setPath,
};
}

Expand Down
12 changes: 12 additions & 0 deletions src/ThreeCanvas.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,22 @@ const FullScreenCanvas = () => {
const canvas = canvasRef.current;
const core = new ThreeApp(canvas);

core.setCamera([-3, 1, 5], [-3, 1, 4]);

const onPointerUp = (e) => {
const path = core.onFindPath(e);

if (path) {
core.setPath(path);
}
};

window.addEventListener("resize", core.resizeCanvas);
canvas.addEventListener("pointerup", onPointerUp);

return () => {
window.removeEventListener("resize", core.resizeCanvas);
canvas.removeEventListener("pointerup", onPointerUp);
};
}, []);

Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2173,6 +2173,11 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==

three-pathfinding@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/three-pathfinding/-/three-pathfinding-1.2.0.tgz#83b936104e22bc23b34bcf9d15574bbba21257ae"
integrity sha512-TlIfy+mLOdP7OhQG4Mn1dNONYbhu2Xe+AD7jL8SbdKdAHxH/TF68svpk2c8UrSfR/3YWQ87Tq/lp5HQO/QuaMw==

three@^0.164.1:
version "0.164.1"
resolved "https://registry.yarnpkg.com/three/-/three-0.164.1.tgz#b742f76bd8dfd3736ba0d86a12dfddb73c5cdcc0"
Expand Down

0 comments on commit b2ac499

Please sign in to comment.