Hello there!
I search a lot on internet and youtube videos about camera collision, and im experiencing a little problem when converting the code logical into my jme3 application.
I was trying to extend ChaseCamera and check for collision before super.update()
I have a QuadTerrain attached to my root, and my player character is attached to terrain.
The way im checking collision is, raycast the camera target to camera position.
Then set maxZoom to the distance between target and hitpoint
But i dont have a cool result⌠it does zoom so much!!
Has any body an example of chase camera with collision?
Still did not work =(
I got an idea⌠check for collision with terrain forward, backward, left and right from camera.
If collide stop rotating and zooming
I began working on rotation collision, but if you move the mouse fast, it will bug =(
Anyone can help?
I believe you also want the ray origin to be the location of the target that is being chased/orbitted by the camera. This way, you will just be casting ray backwards from the player aiming at the camera, and can then place the camera at the collided point if thereâs any collision before within the max distance.
And then I donât think you would need to do a left/right/forward collision either, only one collision back from the player to the camera.
Iâve done the same thing in my own project to confirm the concept works, but another user @capdevon has coincidentally just posted a great video displaying the same thing to the monthly WIP thread with nice debugging arrows to show what exactly is going on, hopefully the video in his post can help as well.
I see your point, this also was my 1st approach.
The problem in this approach is that my base terrain is a height map, with curves, valleys. And when i cast a ray from target to the cam location direction, it always hit a very close point because of height map curves. Maybe this approach, works well in a flat floor scene.
After many tests, i was wondering only prevening camera getting inside heightmap âcurvesâ, like testing collision when rotation and zoom int/out.
But if you guys have a sample or maybe have you ever see this camera colusion with heightmap would be very nice to know!
Are you casting the ray from the targets feet or from their head? If you cast from the head then it should work.
My code is actually almost identical to yours and it works with terrains (although Iâm on my phone atm so I canât check but will soon). The only difference is that i call a getHeadLoc() method for my Agent class that returns the location of a node thatâs at the Agents eye level.
The default height of the camera in relation to the player is important as well. In my case I donât use chase cam, but I have the camera floating at eye level following behind the player, then looking down raises the camera, and looking up lowers he camera, and this helps the camsra angle stay adjusted well so that the back-ray check for collisions doesnât clip too close to the player.
1st of all, thanks for helping me!
I tried to cast a ray from player eyes and it got better results at all!!!
But i think i will need to rotate the camera, can you confirm that?
Because when i set the zoom, it goes trough âwallsâ
And another thing i need to do, is to reset zoom position when has no collision.
I am extending chase camera and it is a little complicate to keep things working hehe
This is caused from the camera being positioned exactly at the contact point - so to fix it you could add a tiny offset to the contact point with the inverse value of the back-facing ray, then the camera should sit on top of the terrain, rather than within the terrain (or you could use contact normal as well, I think both work but havenât experimented with which is better)
So something like
Vector3f offsetVec = ray.getDirection().negate();
//or
offsetVec = collisionResults.getClosestCollision().getContactNormal();
offsetVec = offsetVec.normalize();
offsetVec.multLocal(offsetLength); //offsetLength should be a tiny value, like between 0.5 - 2.0, depending on the scale of your world
I canât see what happens in the zoomCamera method to see if thatâs working as iâd expect, and my way might be a bit different since I donât use chase cam and instead manage the camera location manually.
But the way I do it is to just force the camera location to the maximum zoom value when there is not a collision detected any closer, so something like
So I think part of my suggestion might be way over-complicating things, since I donât use chase cam to know how it works.
But I think you could also just fix the issue of the camera floating through walls by adding a small value to the value you send into the zoomCamera method, and that will act as a small offset and would be a lot easier than doing it how I suggested in first half of my last post. The code in my last post might be irrelevent if ChaseCam doesnât let you force-set the camera location and instead relies on the zoomCamera mehod, but the idea is still the same.
zoomCamera(-distance + 0.5f);
And I think my suggestion of doing this when thereâs no collision:
if Iâm understanding how chase camera works, of course. My apologies if Iâm getting anything wrong since i donât use that class myself to know for sure.
I got working the âresetâ zoom, thanks a lot again!
The last is not working is the floating through walls, i got the idea of adding/subtracting offset
But it still allows crazy rotation like this picture
The distance value needs calculated from the location where the ray was castfrom. As it is with this code, distance will be a value representing the difference between the collision point and the cameraâs last location, but what you really want is the distance between the collision, and the location where the ray was cast from / the target player.
I got your point, but in this case i think the distance is corrected because zoomCamera() method is expecting a value to ADD to the current camera distance,
So if my camera is 17 unit distance from contact point, I should add -17 to get closer
Oh I see, I was mistakenly thinking it wanted the new total zoom value.
In this case, I would try storing the totalZoom value as a variable and updating it every frame, and then you can compare this to the new total zoom distance to get the increment value that the method wants.
float distance = ray.getOrigin().distance(contactPoint);
// third attemp fixing through walls
//distance += 0.5f;
float incrimentDistance = lastDistance - distance; // might need subtracted in opposite order
lastDistance = distance;
zoomCamera(incrimentDistance );
The reason that checking the distance between the camera and collision point wonât work is because it is giving you a value greater than zero when you rotate the camera, which alters the zoom even though no zooming has occurred. So it is important to not mistake the camera being rotated for a change in zoom distance. Changing the rotation could end up causing a change in zoom, but only if it is due to the back-facing ray catching a collision at a closer point after the rotation has occurred.
Sorry the late, I got some problem and could not open my personal notebook these days.
I agree with you, if rotate, the zoom might change.
The zoom âresetâ is working, and the problem is that zoom keep trough âwallsâ yet, even multiplying by 1.1 or 0.9 to get 90% or 110%
(If I zoom in manually and slowly, without any customization in update method, it goes exactly like the last print screen - seems like impossible)
I also tried your suggestion of incrementDistance, but maybe i did not got the point
this is my full code (if you prefer, I can share with you guys the full project -it is small)
public class TerrainChaseCamera extends ChaseCamera {
private static final Vector3f TARGET_HEAD_OFFSET = new Vector3f(0, 1.5f, 0);
private final TerrainQuad terrain;
private final Ray ray;
private final CollisionResults collisionResults;
private Float realDistance;
public TerrainChaseCamera(Camera camera, Spatial target, InputManager inputManager, TerrainQuad terrain) {
super(camera, target, inputManager);
setMinDistance(3);
this.maxDistance = getMaxDistance();
this.terrain = terrain;
this.ray = new Ray();
this.collisionResults = new CollisionResults();
}
@Override
public void update(float tpf) {
if (checkCollision()) {
if (Objects.isNull(realDistance)) {
realDistance = getDistanceToTarget();
}
Vector3f contactPoint = collisionResults.getClosestCollision().getContactPoint();
// zoom keep trough "walls" yet, even multiplying by 1.1 or 0.9 to get 90% or 110%
float distance = cam.getLocation().distance(contactPoint);
zoomCamera(-distance);
/*
* also tried your suggestion of incrementDistance, but maybe i did not got the point
*
* incrementDistance = lastDistance - distance
*
* lastDistance = distance;
*
* zoomCamera(incrementDistance);
*/
} else if (Objects.nonNull(realDistance)) {
// reset zoom to original before collision -- its working
zoomCamera(realDistance - getDistanceToTarget());
realDistance = null;
}
super.update(tpf);
}
private boolean checkCollision() {
collisionResults.clear();
ray.setOrigin(targetLocation.add(TARGET_HEAD_OFFSET));
ray.setDirection(cam.getLocation().subtract(targetLocation).normalize());
terrain.collideWith(ray, collisionResults);
return collisionResults.size() != 0;
}
}
I think that this will not work for the chase camera actually, because (at your current angle in the screenshot) the back facing ray is almost parallell to the ground⌠which means no matter how much you multiply that by to get an offset, it will always have a y value close to 0, and will never raise off the ground to prevent the clipping.
So I think the only way to fix this is to use the contactNormal as the offset value instead of using the zoomCamera method
However I do not know how this would be compatible with chase camera, because it looks like you can only change the cameraâs location by calling the zoomCamera() method or by rotating the camera. Am I correct to make this assumption? Or is it possible to force-set the camera location without breaking the chase camera functionality?
Contact Point is the location of the point that has been collided with, and Contact Normal is the direction that point is facing in world space - so it is a directional vector, unlike the contact point that is a locational vector. This value is important for lighting calculation, but is also useful to use as an offset value in situations like this.
I checked my code, and I actually donât do this type of offset yet - I left a comment in my code saying to do it eventually if it becomes a bigger issue, but for me it hasnât seemed to be as noticeable. I do get a small amount of clipping through the terrain (and other models) but its only barely noticeable, and it occurs when the camera collides on the left/right side for me, rather than the bottom of the screen like your screenshot.
Two other things to note about your screenshot that stood out to me:
First is that the head location seems like it might still be too low to the ground, and the cameraâs default position should also be above and behind the head
And second is that I notice you donât aim the camera back up at the head when it is lowered to the ground; doing this seems to be a big reason as to why I donât experience similar clipping when the player looks upwards, and the camera is consequentially lowered to the ground like in your screenshot.
I actually just set the camera location to the collision point in my code, and thatâs the only extra thing I do if thereâs a camera collision.
I have intention to create height mountains with heightmap, not only a curve terrain. So eventually I will get this case of terrain height > head location, right?
You mean, maybe if fix the camera at backward head location, should decrease this problem, like using camera node example?
The reason I choose chase camera was that because the project was to do a mmorpg and some of requirements was the ability to see the front player.
The server is ok, but a 3d front-end is really new for me =S
Do you think there is a solution or another kind of camera, i can implement?
Maybe i need to lock the camera vertical rotation to be always up the head location?
Yes I have scenarios like this but donât seem to get clipping as bad. I still do get some clipping on the left/right side (which is a lot less common and less noticeable), and I could benefit from adding the contactNormal as an offset to prevent it.
But having good camera positioning and making the camera always look at the playerâs head seems to prevent me from getting the clipping at the bottom of the screen when the cameras on the ground.
In my code I have the camera set up so that moving the mouse up/down changes the vertical direction the camera is looking, but also alters the height of the cameraâs position.
So looking up makes the camera position lower to a point below and behind their head (effectively making it look up at the player from the ground, avoiding clipping because its not looking parallel to the ground), or the camera gets raised up slightly above the height of the playerâs head when they are looking down.
Left/right rotation would be completely unrelated to that, so you could still let the player spin around to look at the front of the model like you mentioned.
The clipping I get is very similar but on the left/right, although I had to put in a bit of effort to reproduce it since it doesnât seem to happen to me when Iâm playing/testing normally and not thinking about it.
I posted the edit with a video at literally the same second as your post