Open SET001 opened 8 years ago
Get ready @SET001, because this may take a while...
For your first problem, if you want a solution without putting it in the physics loop you can set the angular factor of your physijs mesh:
this.get("mesh").object.addEventListener("ready", function(){
this.get("mesh").object.setAngularFactor(new THREE.Vector3(0, 0, 0));
});
The angular factor of an object is basically a vector determining whether or not the final angular velocities should be applied on the mesh (one number for each axis). So, if the factor is an empty vector, no force would be applied for rotation, thus eliminating rotation. This method only needs to be called once (when your object is added to the world), which is why it is put in a 'ready' event.
For your second and third problem, you're going to have to use a little more thinking. First off, you need to determine at what slope angle the character will stop climbing and start falling. I recommend a number between 40 and 45 degrees, but it is up to you. I'll use 45 degrees here. Next, for every frame, you need to:
Now that we've got our priorities in order, we can begin. You should probably create a new function dedicated to this process. Then, you need to create a THREE.Raycaster
(preferably outside of the new function) with its first parameter, ray origin, set to the character position, and its second parameter, ray direction, set to new THREE.Vector3(0, -1, 0)
, which will make the ray point directly down. In the function, make the raycaster intersect the heightmap mesh with the .intersectObject
method, explained here. After getting an array of intersections, you simply take the first intersection (which is the closest) and pass it on to the next part.
We have the intersecting triangle, but what do we do with it? Well, our next challenge is to find out how steep the triangle is. Fortunatly for us, THREE.js provides a utility function in THREE.Vector3
called .angleTo
. Just use the upwards unit vector (a.k.a. new THREE.Vector3(0, 1, 0)
) and set the function parameter to the triangle normal.
Finally, we know where the character is on the heightmap and how steep the place where it's standing. All we have to do now is take action, which can be done with a simple if
statement.
Check if the slope angle is higher than your limit, in radians (in this case 45 degrees, so about 0.707 radians I think), or if it is lower than your limit, again in radians, and disable climbing / sliding. To achieve the first situation, you can ignore all key inputs so that no code will be run. To achieve the second situation, you can set the linear factor to new THREE.Vector3(0, 0, 0)
. Feel free to make your own handler.
Some example code:
// Note: I refer to 'player' as character object and 'heightmap' to heightmap object.
// Raycaster init code
var raycaster = new THREE.Raycaster(player.position.clone(), new THREE.Vector3(0, -1, 0));
// Slope limit - the angle that tells when players start sliding down (in radians)
var SLOPE_LIMIT = 0.707; // 45 deg
// Call this every frame!
function heightmapPhysics(){
var panel, intersects;
// Always update player position to raycaster!
raycaster.ray.origin.copy(player.position);
// Find closest intersection
intersects = raycaster.intersectObject(heightmap, false);
if(intersects.length){
panel = raycaster.intersectObject(heightmap, false)[0].face;
}
// Get angle between triangle normal and upwards unit vector
var slope_ang = new THREE.Vector3(0, 1, 0).angleTo(panel.normal);
// Take appropriate action
if(slope_ang < SLOPE_LIMIT){
// Stop sliding
} else {
// Stop climbing
}
}
Looks easier in JS, right? Hope this helps.
@xprogram , thank you so much for you expanded response. Actually just using setAngularFactor
inside ready
callback solved almost all my problems - player is not sliding on small landscape angles and is not climbing on steep. However it still behaves very strange in many cases and I does not have control over exact landscape angles. All this appeared very tricky.
@SET001 I'm glad to know my solution is working! I'm just having trouble understanding your problem with control over landscape angles. Could you explain what seems tricky?
@xprogram the tricky is a whole player controls based on physijs. I have so much problems and so little progress here:
setLinearVelocity
are not smooth. I also tried applyCentralImpulse
but still they are not smooth. I'm using following approach:
moveForward(){
var box = this.component.entity.get('mesh').object;
this.velocity = Math.abs(box.getLinearVelocity().z) + Math.abs(box.getLinearVelocity().x);
if (!this.isJumping && this.velocity < this.moveSpeed){
var matrix = (new THREE.Matrix4()).makeRotationFromEuler( box.rotation );
var velocity = (new THREE.Vector3( 0, 0, -1 ).applyMatrix4(matrix)).normalize().multiplyScalar(this.moveSpeed-this.velocity);
box.setLinearVelocity(box.getLinearVelocity().add(velocity));
}
}
you can try this here http://smartass.su/terrain_movements/ (use mouse and wasd+space)
@SET001 I found a great article about FPS physics (not for JS though, but explains concept well), I think it is something you want to look at (might help with problem 2): http://oxleygamedev.blogspot.ca/2011/04/player-physics-part-2.html
For your 1st problem, a working solution with some event listeners:
// Note: I reference the scene as 'scene' and player as 'box'
var prevPos = new THREE.Vector3(0, 0, 0);
scene.addEventListener("update", function(){
prevPos.copy(box.position);
});
box.addEventListener("collision", function(other_obj){
if(box.position.distanceTo(prevPos) > 10){
box.__dirtyPosition = true;
box.position.copy(prevPos);
box.y += box.geometry.boundingBox.size().y;
box.setLinearVelocity(new THREE.Vector3(0, 0, 0));
}
});
About your 3rd problem: in the function moveForward
you are checking if the velocity is slower than the speed required in an incorrect way. Instead, try this:
this.velocity = box.getLinearVelocity().length();
if(!this.isJumping && this.velocity < this.moveSpeed){
// ...
}
@xprogram, About first problem - why it does not work without that hack you proposed? Why something sometimes keep moving after collision?
@SET001 I wonder about that myself...
With CCD (Continuous Collision Detection) objects moving at high speed should be stopped from any object in its way, regardless of velocity. As long as your swept sphere radius is less than your player size (box size) and your threshold is about player size, CCD should function normally (according to Physijs and Bullet Physics docs). I haven't really played with it, but maybe setting swept sphere radius and motion threshold for the heightmap can help too.
@xprogram, I found that falling through the ground happens if I will set __dirtyRotation
on each frame.
Here is my test case for this situation - http://smartass.su/terrain_movements/raw.html
If you only remove that line from render loop - it will never fall through the ground.
Why I need to set __dirtyRotation
on each frame is because without it my mouse controls, that binded to physijs object, behaves strange when rotating more that 180`.
Here is the demonstration of problem with mouse rotation - http://smartass.su/terrain_movements/raw_mouse.html
Eventually I found the secret formula for camera rotations:
var mesh = this.component.entity.get('mesh').object;
this.rotation.y += this.mouseAcceleration*Math.abs(foo.mouseLeft);
mesh.rotation.copy(this.rotation);
mesh.__dirtyRotation = true;
Does anyone have an example for the above code? I am looking for one. Thanks!
I've noticed reducing terrain resolution (for example going from 512x512 to 128x128 bitmap) increases chances of successful landing. With 512x512 capsule collider almost always gets stuck/falls through for me
Also reducing timeStep helps too. Switched from fixedTimeStep 1/60 to 1/180 and objects no longer fall through terrain.
I'm trying to implement players ability to move among terrain (based on heightmap) and experiencing few issues here:
Here is a simple video that demonstrate this problems: https://www.youtube.com/watch?v=15X7tLMjsPg
Are there any other ways to avoid object rotation without having to execute some code each frame call? Also I thought I can set setAngularVelocity to 0
this.get('mesh').object.setAngularVelocity(new THREE.Vector3(0, 0, 0));
but for some reason it does not helped
2, 3, I don't have any idea how to solve this.
Terrain and player materials are set to
gravity is set to
(0, -30, 0 )
Any help are highly appreciated.