How to get YUKA.js working in node environment

Hi All,

I know this is not 100% related to Babylon.js, but I need help and I dont know where else to find it. As some of you know, I’m working on a top down RPG with babylon and colyseus and I’m trying to integrate YUKA.js to my node server as I need need all the feature is has (lightweight, pathfinding, wandering, ai behaviour, navmesh, etc…) and there is not many other JS alternatives.

I have no problem running my babylon client, loading the navmesh, and preventing client to go outside the navmesh area, but I’m trying to get this to work on the server.

My current issue is that i’m just not managing to get it work on the node server as explained on this github issue: How to correctly export a NavMesh from Blender 2.8 · Issue #9 · Mugen87/yuka · GitHub currently awaiting answer from creator

Maybe one of you guys can pinpoint what I’m doing wrong:

// LOADING NAVMESH (loadNavMeshFromFile.ts)
function loadNavMeshFromFile(fileNameNavMesh: string) {
    const data = await fs.readFileSync( path.join( __dirname, '../../../public/models/navmesh/'+fileNameNavMesh+'.glb' ) );
    const loader = new YUKA.NavMeshLoader(); 
    return await loader.parse( data.buffer, "",{ mergeConvexRegions: false })
}

// INITIALIZING ON SERVER (GameRoom.ts)
const navMesh = await loadNavMeshFromFile(options.location);

// CHECKING VALID POSITION ON SERVER (PlayerState.ts)
let check1 = new Vector3(this.x, this.y, this.z); // current pos
let check2 = new Vector3(newX, newY, newZ); // new calculated pos
const foundPath: any = navMesh.findPath(check1, check2); // check if in navmesh
if (foundPath && foundPath.length > 0) {
Logger.info('Valid position for '+this.name+': ( x: '+this.x+', y: '+this.y+', z: '+this.z+', rot: '+this.rot);
}else{
Logger.warning('Invalid position for '+this.name+': ( x: '+this.x+', y: '+this.y+', z: '+this.z+', rot: '+this.rot);
}

Many thanks,

SERVER CODE HERE: t5c/src/server at main · oriongunning/t5c · GitHub

There have been some discussions and realizations with Yuka in recent months, for eg:

Maybe it can help?

2 Likes

what errors are you getting?

first thing i see is that loadNavMeshFromFile is using await but its not an async function

If thats not it, my first guess is that yuka is esm format and node is running in commonjs mode. 2 ways to accommodate this easily. 1: copy/paste yuka.js into your project and let your current build setup handle the transpiling. 2: use ts-node sucrase-node babel-node, esbuild-register, etc instead of node. this basically transpiles the entire app, including everything in node_modules

3 Likes

@Evgeni_Popov Thanks, I’ll look into that post.

@jeremy-coleman I will look into both of those points, thanks!

Even if I dont think there is any issue loading the navmesh, as I have can echo all of the regions, centroid, etc… The issue is that from what I can see, is that the navmesh is all wrong and is not where it’s suppose to be.

For example, in all teh test below, you can see the navmesh is return weird numbers: the y axis should be at zero, no? as my navmesh is flat, it should only have 2 axis?

----------- TEST 1---------------
`let test = this._gameroom.navMesh.getRandomRegion(); console.log(test);`

gives more sensible results, but the Y axis should not be higher than 1?:

centroid: Vector3 {
x: -11.88888963063558,
y: 5.9604641222676946e-8,
z: 0.8888889948527018
},
centroid: Vector3 {
x: 7.222222328186035,
y: 5.9604641222676946e-8,
z: -9.777779261271158
},

-------------- TEST 2 -------------
Another debug test, after creating a vehicule on the server
`
this.entityManager = new EntityManager();
this.time = new Time()

let id = nanoid();
const vehicle = new Vehicle()
vehicle._uuid = id;
vehicle.name = "rat";
vehicle.rotation.fromEuler(0, 2 * Math.PI * Math.random(), 0);
vehicle.position.x = 1;
vehicle.position.y = 1;
vehicle.position.z = 1;
const wanderBehavior = new WanderBehavior()
vehicle.steering.add(wanderBehavior)
this.entityManager.add(vehicle)`

and then in my update function
`const delta = this.time.update().getDelta(); this.entityManager.update(delta);`

with a console.log, my vehicle position is way out of the bounds.
`position: Vector3 { x: -1647608072.1801252, y: 1, z: 277497351.3185711 }, rotation: Quaternion { x: 0, y: 0.4561747218779746, z: 0, w: 0.8898902309383739 },`
----------TEST 3--------------
console log results of navmesh.regions[x].centroid

it's weird, I would have assumed only two axis would be used....?
Vector3 {
x: 12.055556615193685,
y: 5.9604641222676946e-8,
z: 3.888888676961263
}
Vector3 {
x: 11.277779897054037,
y: 5.9604641222676946e-8,
z: 3.888888676961263
}
Vector3 {
x: 12.055553436279297,
y: 5.9604641222676946e-8,
z: 7.277777671813965
}
Vector3 {
x: 12.277776718139648,
y: 5.9604641222676946e-8,
z: 6.722222010294597
}
Vector3 {
x: 6.44444465637207,
y: 5.9604641222676946e-8,
z: 7.722222010294597
}
Vector3 {
x: 11.833333333333334,
y: 5.9604641222676946e-8,
z: 6.05555534362793
}
Vector3 {
x: 11.944443384806315,
y: 5.9604641222676946e-8,
z: 7.722222010294597
}
Vector3 {
x: 10.722223281860352,
y: 5.9604641222676946e-8,
z: 4.05555534362793
}
Vector3 {
x: 7.333333333333333,
y: 5.9604641222676946e-8,
z: 2.1111110051472983
}
Vector3 {
x: 3.1666666666666665,
y: 5.9604641222676946e-8,
z: 4.722222010294597
}
Vector3 {
x: 6.333333333333333,
y: 5.9604641222676946e-8,
z: 7.222222010294597
}
Vector3 {
x: -16.722222646077473,
y: 5.9604641222676946e-8,
z: 0.5555556615193685
}
Vector3 {
x: -11.88888963063558,
y: 5.9604641222676946e-8,
z: 0.8888889948527018
}
Vector3 {
x: -11.555556297302246,
y: 5.9604641222676946e-8,
z: 1.4444446563720703
}
Vector3 {
x: -16.38888931274414,
y: 5.9604641222676946e-8,
z: 3.722222328186035
}
Vector3 {
x: -9.222222646077475,
y: 5.9604641222676946e-8,
z: 5.777777671813965
}
Vector3 {
x: -3.722222646077474,
y: 5.9604641222676946e-8,
z: 3.722222328186035
}
Vector3 {
x: -2.722222646077474,
y: 5.9604641222676946e-8,
z: 0.3333333333333333
}
Vector3 {
x: -3.055555979410807,
y: 5.9604641222676946e-8,
z: 0.8888889948527018
}
Vector3 {
x: -1.6666666666666667,
y: 5.9604641222676946e-8,
z: 3.1666666666666665
}
Vector3 {
x: 11.388888994852701,
y: 5.9604641222676946e-8,
z: -4.333334287007649
}
Vector3 {
x: 10.166666984558105,
y: 5.9604641222676946e-8,
z: -4.444445292154948
}
Vector3 {
x: 7.111111323038737,
y: 5.9604641222676946e-8,
z: -5.555556615193685
}
Vector3 {
x: 4.166666666666667,
y: 5.9604641222676946e-8,
z: -3.27777894337972
}
Vector3 {
x: 7.222222328186035,
y: 5.9604641222676946e-8,
z: -9.777779261271158
}
Vector3 {
x: 6.0000003178914385,
y: 5.9604641222676946e-8,
z: -9.611112594604492
}
Vector3 {
x: 5.888889312744141,
y: 5.9604641222676946e-8,
z: -8.666667938232422
}
Vector3 {
x: -12.333333333333334,
y: 5.9604641222676946e-8,
z: -9.666668256123861
}
Vector3 {
x: -3.722222646077474,
y: 5.9604641222676946e-8,
z: -4.27777894337972
}
Vector3 {
x: -8.111112594604492,
y: 5.9604641222676946e-8,
z: -6.888890266418457
}
Vector3 {
x: -14.166669209798178,
y: 5.9604641222676946e-8,
z: -5.944445610046387
}
Vector3 {
x: -19.444446563720703,
y: 5.9604641222676946e-8,
z: -4.944445292154948
}
Vector3 {
x: -15.277779261271158,
y: 5.9604641222676946e-8,
z: -3.33333428700765
}
Vector3 {
x: -11.555556297302246,
y: 5.9604641222676946e-8,
z: -1.277778645356496
}
Vector3 {
x: -21.22222328186035,
y: 5.9604641222676946e-8,
z: -4.611112276713054
}
Vector3 {
x: -20.72222328186035,
y: 5.9604641222676946e-8,
z: -4.611112276713054
}
Vector3 {
x: -16.722222646077473,
y: 5.9604641222676946e-8,
z: -0.5000009536743164
}
Vector3 {
x: -11.88888963063558,
y: 5.9604641222676946e-8,
z: -0.7777786453564962
}
Vector3 {
x: -20.055556615193684,
y: 5.9604641222676946e-8,
z: -4.666667620340983
}
Vector3 {
x: -2.722222646077474,
y: 5.9604641222676946e-8,
z: -0.27777864535649616
}
Vector3 {
x: -3.055555979410807,
y: 5.9604641222676946e-8,
z: -0.7777786453564962
}
Vector3 {
x: -1.6666666666666667,
y: 5.9604641222676946e-8,
z: -3.77777894337972
}

Sorry, I’ve been meaning to comment on this. I think this is relevant; check this out:

I wrote a script to automate splitting ground-map meshes into grids, if you’re interested I’ll go dig it up. For me that fixed the issues you’re seeing

3 Likes

@arcman7 mate, thanks, I appreciate the help, as I’m at a loss to finding a solution yet.

I’ll look into that this weekend and see if I can make sense of it. If you do have a script, I’d appreciate you digging it up?

Here is the navmesh file if you’re interested: lh_dungeon_01.zip (1.3 KB)

1 Like

I should have some more time this weekend, with the fam in Kona right now. I’ll take a look

Afternoon @arcman7, did you have time to check? :slight_smile: I’m blocked until I find a solution :frowning:

1 Like

Literally just got back lol. Just a minute.

Alright check this script out:

You’ll need to wait a few minutes for the example map to completely load. There are very slight texture stitching artifacts that I wasn’t able to get rid of, but given that you are only generating a navmesh, that won’t matter (you don’t use or need textures for those). Give it a go and let me know how that goes for you; if you’re still having trouble I’ll take a look with you.

For context, the main thing you’re interested in is the portion of the script that bins each of the meshes vertices into grids squares that you can specify as MxN. That way, if your mesh has weird partitioning such as what you saw in that GitHub issue I linked, it will be fixed.

3 Likes

Hi @arcman7 many thanks for sending this however it is way out of my league to understand and I’m not sure how I could use part of that code to resolve my issue.

I’ve remove all server checks, and I’m trying to get it to work on the client first: https://t5c.onrender.com/ . As you can see, the mesh is loaded in the right orientation, the graph helpers are there, but no collision are detected. If you check the console log, it shows the start pos, the next pos, and the results from the findPath() function, but it doesnt seem like it ever detects anything and always find 2 valid positions.

The y-values that you are expecting to be zero are in fact approximately zero; 5.9604641222676946e-8 is a very, very small value that is very close to zero. For all purposes in your tests you can consider those y-values to be zero.

The navmesh you provided does not appear to match the geometry of the map in your demo game. For starters can you explain why that is? In addition, I really need to know exactly what the behavior you’re expecting to happen is. I say this because from what I can tell atm the yuka library is performing exactly as it should be and the pathing appears correct.

Anyways, night for now!

2 Likes

Damn, thanks for confirming that, I had no idea that 5.9604641222676946e-8 is close to zero.

My navmesh should be valid, despite being slightly out of sync with the level, why do you say it does not match the geometry of the level?

What I’m expecting on the image above, is the results returned by the yuka findPath to be Array(0) because no valid paths were found on the navmesh.

// do not want to mixup babylon vector 3 and yuka vector3
import { Vector3 as Vector3Y } from "yuka";

const foundPath: any = this._navMesh.findPath(new Vector3Y( oldX, oldY, oldZ), new Vector3Y(newX, newY, newZ));
if (foundPath){
  this.nextPosition.x = newX;
  this.nextPosition.y = newY;
  this.nextPosition.z = newZ;
  this.nextRotation.y = this.nextRotation.y + (newRot - this.nextRotation.y);
  console.log('VALID position for', new Vector3Y( oldX, oldY, oldZ), new Vector3Y(newX, newY, newZ), foundPath);
}else{
  console.error('INVALID position for', new Vector3Y( oldX, oldY, oldZ), new Vector3Y(newX, newY, newZ, foundPath));
}

The glb file you linked is for the basement level that you access through the stairway, can you supply one for the starting area? I’d like to try running my script on it.

Sure, everything is in the github : t5c/public/models/navmesh at main · oriongunning/t5c · GitHub

1 Like

For starters, you should remove this floating line

Stuff like that can really throw off the yuka library as it’s digesting the mesh and grouping it into centroids.

1 Like

Thanks, I’ve regenerated my navmesh and updated github, it’s much tidier now. But still no collision detected? Must be some sort of orientation thing?

Quick update, I’ve started a new github issue in the YUKA repo (previously in a closed issue): YUKA findPath is not detecting any collisions in Babylon.js · Issue #77 · Mugen87/yuka · GitHub

Hopefully, @Mugen87 will be able to figure out what is going wrong.

Ok, little breakthrough finally, I feel a little dumb…

YUKA findPath function will always return a position on the navmesh even if the position is outside of the navmesh so by simply adding a return false; as shown on the image, everything works as I was want…

So I created a quick function to achieve that, and everything is working beautifully!

/**
* Returns false if from or to position is outside of the navmesh
*
* @param {Vector3} from - The start/source position.
* @param {Vector3} to - The end/destination position.
* @return {bool} true = on navmesh, false = outside of navmesh
*/
checkPath( from, to ) {
	let fromRegion = this.getRegionForPoint( from, this.epsilonContainsTest );
	let toRegion = this.getRegionForPoint( to, this.epsilonContainsTest );
	if ( fromRegion === null || toRegion === null ) {
		// if source or target are outside the navmesh, return false
		return false;
	}
	return true;
}

That’s great, but this also shows me I’m probably not using YUKA as intended, and I’ll have to do some more digging, but finally I think I can start moving forward!

Morality: Never assumes anything! :grinning:

4 Likes

I’ve been really busy, was finally about to jump back on this, glad you made some progress!

What is the issue you’re currently experiencing now?

One thing I noticed in your demo that last time I tried it was that it was repeatedly calling the nav function multiple times, causing the path array to only ever have 2 elements.

2 Likes