godot icon indicating copy to clipboard operation
godot copied to clipboard

SpringArm3D with child Camera3D clips through geometry

Open Lippanon opened this issue 2 years ago • 20 comments
trafficstars

Godot version

v4.0.beta.custom_build [204715ae9]

System information

Windows 10

Issue description

A SpringArm3D with a child Camera3D incorrectly goes through geometry at certain angles (not at all extreme).

Example from MRP:

If one increases the 'spring_length' of SpringArm3D to, for example, 3, it's easy to see it ignores the collision entirely at some angles, pic example: image

According to https://github.com/godotengine/godot/pull/53354 , the camera's pyramid shape should be used for the cast, if no shape is supplied, which I think is indeed happening. Perhaps if the pyramid shape is already inside geometry it doesn't detect it? In that case there's no way to set something like 'hit_from_inside' of PhysicsRayQueryParameters3D to 'true'.

Steps to reproduce

Run the MRP and use the mouse to move as shown in vid.

Minimal reproduction project

SpringArm3D clipping.zip

Lippanon avatar Dec 08 '22 18:12 Lippanon

I'm not sure if it's the same bug, but I noticed this new SpringArm3D with a Camera3D clips through a wall a couple frame before its position is fixed. I played around with the margin and the shape's margin and it doesn't seem to do anything.

https://user-images.githubusercontent.com/106078/229328652-ec2f8ad1-475b-41a0-add4-94cf09cd6834.mp4

wagnerfs avatar Apr 02 '23 02:04 wagnerfs

Has this issue been fixed yet? I noticed that a related issue #76220 has been addressed and the latest source code uses Camera3D's pyramid shape.

Vivraan avatar May 30 '23 11:05 Vivraan

Why do you say that the related issue has been addressed? It's still open

Zireael07 avatar May 30 '23 12:05 Zireael07

Primarily because the source code seems to have been modified to solve the problem, but a solution has not been accepted.

Vivraan avatar May 30 '23 12:05 Vivraan

Where? Can you provide the PR that did this?

AThousandShips avatar May 30 '23 12:05 AThousandShips

This is the commit that has been cited in #76220: https://github.com/godotengine/godot/commit/b11bb595d137c82bbf30340ef7d8fdf6d6dc08de.

Not sure myself since I was still encountering issues with geometry clipping with the spring arm node.

Vivraan avatar May 30 '23 14:05 Vivraan

No that's what is the cause of this, as in what made the margin not be used

AThousandShips avatar May 30 '23 14:05 AThousandShips

Okay, so it's definitely not a solution. I'll investigate this problem if I can. I have been trying to teach a student about basic camera stuff and it would appear I have to ask him to roll his own spring arm for now.

Vivraan avatar May 30 '23 14:05 Vivraan

Just to add up here, I completely dropped the SpringArm3D solution for my code since it's broken in a way, and did a normal spherecast collision check with a camera attached at the end and it does the job better. Apparently setting a sphere shape to the springarm doesn't work either so yeah.

wagnerfs avatar May 30 '23 14:05 wagnerfs

The current SpringArm3D brings some features that can be used with Cameras, but for a smooth experience without clipping or "hard movements" you end up needing to implement something to overcome SpringArm3D issues.

ghost avatar May 30 '23 17:05 ghost

I think I'm having the same issue on Godot 4.0.3

https://github.com/godotengine/godot/assets/13070158/13c6961d-6998-4e3c-bda6-f94360a3bbdd

From the angles when I'm rotating the camera clockwise it works, when I'm rotating the camera anti-clockwise it's like the wall collision isn't even there.

eh-jogos avatar Jun 07 '23 15:06 eh-jogos

To give an update on OP's issue, after some testing I'm fairly confident the issue lies with cast_motion. BoxShape3D and SphereShape3D also go through geometry when used in this matter, not just the camera's pyramid shape. When the SpringArm3D is forced to use intersect_ray (when shape.is_null() and no camera as a direct child) it doesn't tunnel but because it doesn't take into account the camera's near plane, you can still see through geometry. I've gone with a custom implementation as a work-around, as others have mentioned.

Lippanon avatar Jun 20 '23 21:06 Lippanon

https://github.com/godotengine/godot/assets/52212525/1bd4dfde-aac1-4fc1-aa00-e4a3fc8affbe

I think this is a basic function, but it can hardly work properly

Az-qianchen avatar Jul 14 '23 01:07 Az-qianchen

Sorry to bump this thread again, but can we propose a change to the current implementation in C++ based on the custom implementation some of you guys have made?

Vivraan avatar Jul 30 '23 10:07 Vivraan

Anyone who wants can open a PR with the fix, no approval is needed for thar, just someone needs to do the work and open a PR, which will be reviewed

AThousandShips avatar Jul 30 '23 11:07 AThousandShips

Can confirm the problem still persists in 4.2.beta2. Seems we cant have basic camera control that's not breaking the immersion of the gameplay. :(

https://github.com/godotengine/godot/assets/80409979/cee33116-0ea8-40cf-83c2-54b50160a733

andzejsp avatar Oct 21 '23 13:10 andzejsp

Guys, i think ive got it. Not sure how it works and why, but you need to add shape to the spring arm image

Then it does not clip anymore.

I wonder why we dont get warning or errors when we dont have collision shape in spring arms but we get them when we add staticbody node.

Without collisionshape inside the spring arm https://streamable.com/7ll7h5

I have placed pinkish mesh under the floor so that you can see when its clipping

With collision shape: image

https://streamable.com/bofo57

andzejsp avatar Dec 05 '23 10:12 andzejsp

Guys, i think ive got it. Not sure how it works and why, but you need to add shape to the spring arm ...

Please read https://github.com/godotengine/godot/issues/69771#issuecomment-1599605464. Adding a SphereShape such as in your screenshot does not fix the tunneling/clipping. You can try it in the MRP (and probably should have) to see this. By default when no shape is provided the SpringArm should be using the camera's pyramid shape which also goes through. As I've said above, only when specifically setting the shape to null will it not tunnel, due to using a raycast, but that has other problems.

Lippanon avatar Dec 05 '23 11:12 Lippanon

Problem still not fixed in Godot 4.2 stable. Adding a shape also doesn't work.

https://github.com/godotengine/godot/assets/34932077/8f8f9570-505a-44ae-ab6e-2f329d665787

ketanp1204 avatar Jul 10 '24 19:07 ketanp1204

any news on this issue ? I still have it on godot 4.3 stable

Dorifor avatar Oct 06 '24 11:10 Dorifor

Deleted my previous comment since it was mostly irrelevant to this actual issue 🄲

After messing with the spring arm code in the engine for 2 days, I have a pretty good understanding of it, and I think some of the issues are a bit of misunderstanding of it due to really bad documentation and very surprising behavior.

Issue 1: Collider inside geometry

The MRP at the top of this issue says it's "not at all extreme" but I'm not sure I agree... if you move the camera rig just 0.054 units over on the x axis to 0.28, then it no longer repros šŸ™ˆ

As mentioned in the comment:

Perhaps if the pyramid shape is already inside geometry it doesn't detect it?

This is definitely the case, as specified in the PhysicsDirectSpaceState3D's cast_motion docs:

Note: Any Shape3Ds that the shape is already colliding with e.g. inside of, will be ignored. Use collide_shape to determine the Shape3Ds that the shape is already colliding with.

I think what's happening is that the spring arm's collision shape starts inside of the wall, so cast_motion doesn't detect it.

I tried to change the implementation so that it first does a collide_shape and then changes where the detection is done based on that, but it seems like a bit of a dead end. If you're interested in chatting about that more or want to check out where I ended up with the code, I started a thread in the contributors chat

Workaround for now: make sure that the spring arm's origin point itself doesn't get too close to an object (I know, this isn't great 😢 )

Issue 2: Spring arm with offset

Another issue brought up in this thread, for example https://github.com/godotengine/godot/issues/69771#issuecomment-1581060216, is that if the spring arm has an offset it will be placed completely inside walls.

For example, if you have this hierarchy:

  • Node3D ("following")
    • Node3D ("pivot point")
      • SpringArm3D
        • Camera3D

If the offset is on the SpringArm3D (i.e. its transform is -1.5,0,0) then when the "following" node is up against a wall, the spring arm will be placed inside that wall and that wall will be ignored by the shape cast. This sort of makes sense because the origin of the spring arm it being controlled by the following / privot point and the shape case just goes from the spring arm origin out towards its length.

I think a solution here is to do your own shape cast out in the direction that the camera is offset (i.e. a box shape in the X direction) and then change the camera's offset based on where that collides. Not sure how well that will work, but I need to do this in the game I'm working on, so I'll be trying it out.

Issue 3: Camera as a direct child, margin ignored

The SpringArm3D node has some very unexpected behavior that I haven't seen anywhere else in Godot.

  • If given a shape, then that shape will always be used as the shape cast
  • If it has a direct Camera3D child, then it will use that camera's "pyramid shape"
    • This is a convex polygon with the tip at the camera's origin and four points at the camera's near plane
    • In testing, this has the best outcome for keeping a spring arm camera from clipping through walls.
  • If no shape is present and there's no direct Camera3D child, the spring arm falls back to a ray cast
    • This does NOT do a good job at detecting collisions for a camera!!! Avoid at all costs for cameras.

To add to that, the margin value that's provided to the SpringArm3D is only looked at when the raycast fallback is used (see https://github.com/godotengine/godot/issues/76220) so messing with the margin will not save you from this bug.

Issue 3a: Phantom Camera

If you're using Phantom Camera's third-person follow functionality, it does not make a Camera3D a direct child of the SpringArm3D (see https://github.com/ramokz/phantom-camera/issues/410) so it will always fall back to the ray cast unless you explicitly set a shape! You can add a simple sphere shape to get around this, or manually get the pyramid shape of the camera on load:

func _ready():
	var pyramid_shape_data = PhysicsServer3D.shape_get_data(
		get_viewport().get_camera_3d().get_pyramid_shape_rid()
	)

	var shape = ConvexPolygonShape3D.new()
	shape.points = pyramid_shape_data

	phantom_camera.shape = shape

ā˜ļø note that this shape will need to be re-created any time the camera's pyramid shape would change (i.e. changing near plane, FOV, etc.)

TranquilMarmot avatar Oct 27 '24 18:10 TranquilMarmot

Issue 1: Collider inside geometry

If we're inside the geometry can we gently spring out of it?

fire avatar Oct 27 '24 22:10 fire

At least in my case, part of the issue of camera intermittently clipping into flat walls/floor was caused by the camera being moved/rotated in _process rather than _physics_process function, and from screenshots/clips in this issue I see that at least a couple of people made the same mistake as me.

As far as I can tell, SpringArm uses a physics ray cast, and considering that collisions are updated in physics step, it makes sense that adjusting the camera outside of that step would cause the camera to be moved using the SpringArm's old offset, which results in brief clipping until the next physics update.

None of the fixes proposed in this issue resolved this problem, but moving my player input logic to _physics_process did, with the following setup:

  • Node3D (pivot - only this node is translated/rotated by player input)
    • SpringArm3D
      • Camera3D

If processing all player input in _physics_process is a deal breaker for you for some reason, then you may want to put the camera in root hierarchy, so that it is not part of the player character, and only update its transform and rotation in _physics_process.


That said, even with this change, there's still the issue of the camera clipping into CSG nodes at specific angles, as shown in the first post. This clipping is stable (once you find the spot and stop adjusting the camera, it stays clipped - physics update doesn't fix it) and consistently reproducible.

This happens even with simple/primitive geometry: a single 3x6x3 CSG box aligned to grid, not rotated or scaled in any way.
It also seems to be somehow related to the node's position: I could only reproduce this clipping against the box's negative X face if it had negative X position, and positive X face if it had positive X position. If the X position was 0, I could clip against either face, though it was much more difficult to do, and not as consistent.

I couldn't reproduce it with StaticBody3Ds, so maybe this issue is specific to CSG's. As such, the problem will possibly disappear once you replace CSG nodes with final world geometry imported from Blender (I haven't tried that). If that is still a problem, you can work around it by "shielding" the problematic spot with StaticBody3D + CollisionShape3D with a simple shape.

Godot 4.4.stable

kartoFlane avatar Mar 13 '25 00:03 kartoFlane

The csg case is interesting..

Let's try to reproduce with csg and mesh instance of the same thing exported.


Older guesses.

According to the literature:

We'd need to code a whiskers system and a way to lift the camera up when backed in a corner.

https://www.youtube.com/watch?v=C7307qRmlMI

fire avatar Mar 13 '25 00:03 fire

I'm playing around in 4.5 with https://github.com/Jeh3no/Godot-State-Machine-Third-Person-Controller/tree/main and the Springarm3D default camera does not avoid clipping through the geometry at all, by default it just clips through every surface. I converted the default CSG geometry and it still clips, wdid Springarm3D work in a previous version of Godot?

elvisish avatar May 03 '25 12:05 elvisish

Issue 2: Spring arm with offset

Another issue brought up in this thread, for example #69771 (comment), is that if the spring arm has an offset it will be placed completely inside walls.

For example, if you have this hierarchy:

* `Node3D` ("following")
  
  * `Node3D` ("pivot point")
    
    * `SpringArm3D`
      
      * `Camera3D`

If the offset is on the SpringArm3D (i.e. its transform is -1.5,0,0) then when the "following" node is up against a wall, the spring arm will be placed inside that wall and that wall will be ignored by the shape cast. This sort of makes sense because the origin of the spring arm it being controlled by the following / privot point and the shape case just goes from the spring arm origin out towards its length.

I think a solution here is to do your own shape cast out in the direction that the camera is offset (i.e. a box shape in the X direction) and then change the camera's offset based on where that collides. Not sure how well that will work, but I need to do this in the game I'm working on, so I'll be trying it out.

Hi, I've tried second approach on adding shapecast3D to camera as a child and offsetting not cameras position (because it will be overwritten by SpringArm3D anyway), but springarm3d spring_length itself, based on get_closest_collision_safe_fraction() function value on collision contact. Overall it solves most clipping issues for vertical walls and helps with offset SpringArm3D clipping, but still does clip camera on Diagonal ceiling\wall surfaces. I might say it's better than having camera node clipping all over the place, but still needs some work to be a solution until bug is fixed.

EDIT: It is also possible to reduce wall and diagonal surface clipping by using double SpringArms3D nodes instead of coding shape cast implementation and working out edge cases. This method may not be a solid solution for those working with a single SpringArm3D. However, utilizing double SpringArms might significantly reduce wall clipping for the camera. To implement this, position one arm horizontally (rotated 90 degrees) and make the second arm its child. For both shape casts within the SpringArms, it's better to use box colliders rather than spheres. This will help prevent further clipping and edge clipping, which can occur when no collision shapes are utilized. You can adjust the horizontal arm offset according to your preferences in the code. Honestly, having that extra SpringArm, which effectively acts as a shape caster on the horizontal axis, greatly aids in offsetting the main SpringArm. This minimizes clipping issues I encountered while navigating through an obstacle course with character control. I’m not sure if this workaround will be effective for anyone else who has also offset their SpringArm for over-the-shoulder aiming, but I wanted to share it with you anyway.

ndcrd avatar Oct 30 '25 13:10 ndcrd

Using 4.5.1 Stable Official with the following hierarchy:

  • Node3D (This get's rotated by my controller inside _process, not _physics_process)
    • SpringArm3D (Offset on X by 0.7m with no shape)
      • Camera3D

Edit: I can confirm there are no issues when the SpringArm3D Node has no offset under it's Transform->Position

https://github.com/user-attachments/assets/dd6844c2-6413-4512-8669-f50d354bc874

Ethenski avatar Nov 14 '25 21:11 Ethenski

@Ethenski try this out:

  • CharacterBody3D
    • Node3D (I call this CameraXPivotPoint): Position (0, 2, 0)
      • SpringArm3D: Position (0, 0, -0.25), Rotation (0, -145, 0)
        • Camera3D: Rotation: (0, -35, 0)

The spring arm Y rotation and camera Y rotation added together must equal -180. When moving the SpringArm3D further to the right on the Z axis, you may want to change the rotations of the spring arm and the camera.

When you want to rotate the camera:

  • Rotate the CharacterBody3D on the Y axis
  • Rotate the CameraXPivotPoint on the X axis

Here's the idea behind it:

Image

In this screenshot you can see the grey line that shows where the spring arm is pointed to; the camera is placed at the end of that line:

Image

Another tip: Do NOT set a shape on the SpringArm3D!!! Leave it as <empty>; there's a special case where if a Camera3D is a direct child of a SpringArm3D, then the SpringArm3D takes on a pyramid shape that exactly matches the camera's viewing frustum


Check out the docs for the spring arm 3D that I wrote for more details about how spring arm works: https://docs.godotengine.org/en/latest/tutorials/3d/spring_arm.html ā˜ļø when I have time, I'll try and add over-the-shoulder instructions to that page since it's so tricky to set up

TranquilMarmot avatar Nov 16 '25 02:11 TranquilMarmot