schteppe / cannon.js

A lightweight 3D physics engine written in JavaScript.
http://schteppe.github.com/cannon.js
MIT License
4.64k stars 701 forks source link

Move a sphere on trimesh like a character #404

Open Zbluu opened 5 years ago

Zbluu commented 5 years ago

Hello,

This is not an issue, just a question. I have a 3D terrain (using trimesh) generated by a noise, and I want to control a sphere with my keyboard like a character walking on the terrain.

image

In a plane, this is simple, we just change the x and y position of the sphere, but in a 3D terrain, we need to adjust the Z position according to the trimesh, and I have no idea how to do that.

I only want to use cannon.js, not three.js because I only use Cannon to calculate collisions on server side without rendering.

Any suggestions ?

schteppe commented 5 years ago

did you check the raycast demo? You could raycast straight down and get the z position for the raycast hit.. this is probably the easiest approach.

Or, you could use the .getHeightAt() method on the heightfield shape directly. Just keep in mind that the input position for it should be in local space of the heightfield

swift502 commented 5 years ago

First of all, thanks for asking. While trying to answer your question I realised my code was broken and I was able to fix it. So thanks. πŸ˜„

If you have a simple movements then yes, @schteppe 's methods should work perfectly for you.

If you wanted a bit more complex behavior which accounts for velocities, I could also help you with that. The boxes represent all the different vectors I'm working with.

Although my method requires cross product of vectors and rotating a vector around a specific axis, for which I'm using THREE functions. Anyways, let me know if you want me to elaborate further... πŸ™‚

Zbluu commented 5 years ago

@schteppe Yeah, but is not that simple because my terrain isn't a 2D heigthmap projected in 3D, it's a fully 3D shape with caves and floating islands (I use Trimesh, not Heightfield), like this :

image

So at a Z position, there can be multiple possibilities, so the raycast doesn't solve my problem.

@swift502 What a nice demo ! Yes, I'm very interested by your method because I need my character to be able to jump, rotate, fall down (like yours) and slide on slopes that are too steep. Do you think that your method will work in a complex terrain like mine ? Is your source code in public access somewhere?

swift502 commented 5 years ago

It should definitely work on any terrain. The project is here. I'm just afraid that my implementation is way too complex for what you need. You'll need to pick out very specific things.

Raycasting is in character's physics pre step callback. Sticking to ground is in character's physics post step callback. The logic is divided into callbacks because of frame skipping when rendering is too slow, which is again, something you probably don't care about.

If you were confused by my setup, I could definitely give you simplified overview of what I do, with an extract of the relevant code. In a few days. I'm a bit busy right now...

Zbluu commented 5 years ago

This was definitively the help I needed. I understand the basics, and I'll try to implement this in my code. I'll let you know about my progress, thank you for taking the time to help me.

Zbluu commented 5 years ago

Um, it's harder than I thought. Your offer to give me a simple overview of how it works would be welcome. I'm a bit lost in your code :(

EDIT : After a day of work, I have finally succeeded to implement a simple character controller in my project (thanks to your very well documented work @swift502) . but I have some issues with the raycast : The raycast do not return result when it crosses a too small triangle of a trimesh. For example, in my terrain, my character falls into the void in theses places because the raycast detects nothing :

image

Maybe @schteppe can help me on this ?

swift502 commented 5 years ago

Hey @Zbluu,

I tackled the "transforming local velocity into global according to surface's tilt" problem two more times, because each time it turned out there were problems. If you had problems with that, I think I finally found the correct and quite elegant solution.

The only problem is, it's using even more THREE. And also it's using a 4D matrix, which as far as I'm concerned is basically magic. πŸ˜„ Just wanted to let you know, because the solution I had few days ago wasn't really sufficient for me.

 // If we're hitting the ground, stick to ground
 if(rayHasHit) {

     // Flatten velocity
     newVelocity.y = 0;

     // Measure the rotation from global "up" vector to the surface normal
     let up = new THREE.Vector3(0, 1, 0);
     let normal = new THREE.Vector3().copy(rayResult.hitNormalWorld);
     let q = new THREE.Quaternion().setFromUnitVectors(up, normal);

     // Rotate an identity matrix
     let matrix = new THREE.Matrix4().makeRotationFromQuaternion(q);

     // Apply the matrix to appropriately rotate the velocity vector
     newVelocity.applyMatrix4(matrix);

     // Apply velocity
     this.velocity.copy(newVelocity);
 }

My previous solution required character orientation, this doesn't require that... So it could be simpler for you...

Zbluu commented 5 years ago

Wow man, you rock. Before this, I used to apply a constant velocity on X and Z axis to move my character, which gave the impression that he was moving faster on a slope than on the flat. But this code resolves my problem, thanks ! πŸ˜„

And have you ever had any detection problems with the Raycast ? Like I said in my previous post, when the triangle is too small, my character passes through the ground. I don't understand why you're setting up the Raycast length to 0.61, maybe I'm missing something ?

It seems to me that it's more a Trimesh collision bug than a Raycast bug, but you don't use Trimesh so you probably didn't have it.

@schteppe, do you know anything about the bug that I described in my previous comment ? Maybe it would be better if I open a new issue with some reproduct cases ?

schteppe commented 5 years ago

@Zbluu I think I’ve seen this before... could you double check if it’s fixed in the most recent version of cannon? (Run grunt to rebuild the repo)

swift502 commented 5 years ago

@zbluu yeah I haven't used trimesh yet, only primitives.

0.60 is about half the height of my character, it can be anything really. 0.01 is a safe offset to make sure the ray doesn't miss. I found that if your character is exactly 0.5 above the ground, raycasting exactly 0.5 down can occasionally miss. I found it better to add the 0.01 and then compensate for it when positioning the character, just to make sure the ray always hits on a flat surface. The safe offset can be higher depending on your needs.

Zbluu commented 5 years ago

@swift502 Oh, ok. Thanks for your clarifications !

@schteppe I just tested with the most recent version of cannon recompiled by me with Grunt, and the problem is the same :(

EDIT : I tested with the Raycaster of THREE.js, and I haven't this bug.

swift502 commented 5 years ago

@Zbluu I finally got some geometry to test, and everything seems to work fine. Although trimesh seems to generally have a quite big performance impact. (Not that I didn't expect it)

Demo is here: http://jblaha.art

The movement angle still looks a bit off when moving at a large slope in a diagonal direction (relative to the slope), but you don't normally notice it...

dirkk0 commented 5 years ago

Off topic but awesome demo, congrats.

Zbluu commented 5 years ago

@swift502 Nice demo again. I had issues only with very small triangles that is not the case for you, but I implemented my own raycast and it's seems to work correctly now.