Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PhysicsShapeQueryParameters.sphere_cast #2217

Closed
seadra opened this issue Jun 30, 2015 · 26 comments
Closed

PhysicsShapeQueryParameters.sphere_cast #2217

seadra opened this issue Jun 30, 2015 · 26 comments

Comments

@seadra
Copy link

seadra commented Jun 30, 2015

(forum discussion)

Unity's SphereCast is quite an elementary and useful function.

To be fair, it seems it seems (I'm not sure though) is somehow possible to achieve a similar thing using the current API, but it is overly complicated and half of it isn't even documented.

Please add a similar high-level function to godot.

@reduz
Copy link
Member

reduz commented Jun 30, 2015

It is possible and you can cast any shape, not just spheres using
directspacestate
On Jun 29, 2015 10:02 PM, "seadra" [email protected] wrote:

(forum discussion
http://www.godotengine.org/forum/viewtopic.php?t=2175&p=10282)

Unity's SphereCast
http://docs.unity3d.com/ScriptReference/Physics.SphereCast.html is
quite an elementary and useful function.

To be fair, it seems it seems (I'm not sure though) is somehow possible to
achieve a similar thing using the current API, but it is overly complicated
and half of it isn't even documented.

Please add a similar high-level function to godot.


Reply to this email directly or view it on GitHub
#2217.

@seadra
Copy link
Author

seadra commented Jun 30, 2015

Well, as I mentioned, 1) the documentation is non-existent (also here) so nobody except you knows how to do that exactly, 2) SphereCast is enough for many use cases and a nice high-level API function.

"Whenever you want to cast a sphere, prepare a a CapsuleShape in advance and then feed it into the (undocumented) PhysicsShapeQueryParameters, and now you can use intersect_shape" isn't very user-friendly for such an elementary task.

@reduz
Copy link
Member

reduz commented Jun 30, 2015

Feel free to contribute the doc
On Jun 29, 2015 10:27 PM, "seadra" [email protected] wrote:

Well, as I mentioned, 1) the documentation is non-existent
https://github.com/okamstudio/godot/wiki/class_physicsdirectspacestate
so nobody except you knows how to do that exactly, 2) SphereCast is enough
for many use cases and a nice high-level API function


Reply to this email directly or view it on GitHub
#2217 (comment).

@reduz
Copy link
Member

reduz commented Jun 30, 2015

You can ask for docs, ask how it works, ask for help if it doesnt work or
contribute yourself. Complaining will get you nowhere
On Jun 29, 2015 10:33 PM, "Juan Linietsky" [email protected] wrote:

Feel free to contribute the doc
On Jun 29, 2015 10:27 PM, "seadra" [email protected] wrote:

Well, as I mentioned, 1) the documentation is non-existent
https://github.com/okamstudio/godot/wiki/class_physicsdirectspacestate
so nobody except you knows how to do that exactly, 2) SphereCast is enough
for many use cases and a nice high-level API function


Reply to this email directly or view it on GitHub
#2217 (comment).

@seadra
Copy link
Author

seadra commented Jun 30, 2015

Uh-oh. Sorry, but I'm not to complaining. I just reiterated my point with more detail, because it seems like you missed it. I am essentially requesting a feature, along with the reason why I still think having a SphereCast function is something necessary.

How can I contribute to the doc if I don't know how it works?

Also, I would have contributed if I knew how it worked and if there was a way for ordinary users to contribute docs. I guess this system is still in the works, along with the new website.

In any case, I'm speaking/making requests from a practical point of view. I am using Unity, and now that it's coming to Linux, I am 100% happy with it (especially since we have long paid the license fees). The community is huge and alive, there is vast documentation and how-tos. It has every single feature I need and more. Along with an immense archive of reusable prefabs and content. There is no practical incentive for me use godot, or get some feature implemented.
But still, I wish to see godoc succeed and I wish to use it for real work someday in the future because godot is the only FOSS engine. That's all this is about.

I know you have your priorities, but the existence of a low-level (and undocumented) way of achieving the same effect doesn't change the fact that the current API to do a simple SphereCast (or anything similar) is more complicated than it should be, which is what I've been saying all along.

I also don't think brushing tested-and-proven Unity features, or user-friendliness, away as "complaints" will get you anywhere either.

@reduz
Copy link
Member

reduz commented Jun 30, 2015

well, if you are not using something, you have no incentive to know how
something works, which explains your attitude :) Documenting functions is
really easy, just a PR on classes.xml file.

If you want to see Godot succeed, the best you can do is contribute to it,
even with feedback from a user perspective, but your current way of just
requesting stuff for the sake of it is not useful, sorry

On Tue, Jun 30, 2015 at 2:02 AM, seadra [email protected] wrote:

Uh-oh. Sorry, but I'm not to complaining. I was requesting a feature,
along with the reason why I think it is a necessary feature.

How can I contribute to doc if I don't know how it works?
Also, I would have contributed if I knew how it worked and if there was a
way for ordinary users to contribute docs. I guess this system is still in
the works, along with the new website.

In any case, I'm speaking/making requests from a practical point of view.
I am using Unity, and now that it's coming to Linux, I am 100% happy with
it (especially since I have paid the license fee). There is no practical
incentive for me use godot, or get some feature implemented.
But still, I wish to see godoc succeed and I wish to use it for real work
because godot is the only free engine. That's all this is about.


Reply to this email directly or view it on GitHub
#2217 (comment).

@seadra
Copy link
Author

seadra commented Jun 30, 2015

I surely would have done it had I know how it worked.
But I think I can send PR for some other functions. Are these changes automatically reflected in the wiki?

OK, so you don't think having a few higher-level functions such as SphereCast would be better, like, at all?

@reduz
Copy link
Member

reduz commented Jun 30, 2015

there are high level functions like that, in fact for 2D there is
test-motion which is very similar (and needs to be ported to 3D)
also yes, changes to classes.xml are automatically reflected in the wiki
and the built-in help

On Tue, Jun 30, 2015 at 12:01 PM, seadra [email protected] wrote:

I surely would have done it had I know how it worked.
But I think I can send PR for some other functions. Are these changes
automatically reflected in the wiki?

OK, so you don't think having a few higher-level functions such as
SphereCast would be better, like, at all?


Reply to this email directly or view it on GitHub
#2217 (comment).

@seadra
Copy link
Author

seadra commented Jul 1, 2015

Thanks! I will definitely contribute to the documentation (as long as I know what I'm writing about).

So, for now, we can write a SphereCast polyfill something like this?

func sphere_cast(origin, radius, direction, hit_info, max_distance, exclude):
    var shape = SphereShape()
    shape.set_radius(radius)

    var params = PhysicsShapeQueryParameters()
    params.set_shape(shape)
    params.set_transform(get_transform().translated(origin)) # same transform as parent, just translate
    params.set_motion(direction*max_distance) # is "motion" the sweep distance?
    if exclude != null:
        param.set_exclude(exclude) # here exclude is an array of... RID??
    var res = PhysicsServer.intersect_shape(params, 1)
    if hit_info != null: # and extends Object
        hit_info = res # res is an array containing...?
    return res.size() > 0

Does this look correct?
I'm not sure about set_motion (is it the sweep distance?) and set_exclude (do we pass an array of objects/RIDs?) though; do you remember what they do?

By the way, is it possible to add new method to external classes in GDScript?

@seadra
Copy link
Author

seadra commented Jul 1, 2015

What does PhysicsServer.cast_motion do?
I tried to check the C++ code, but I'm not sure what it is.

@reduz
Copy link
Member

reduz commented Jul 1, 2015

to get the same functionality in Godot as the one you mentioned in Unity,
you have to combine cast_motion with rest_info.
cast motion will return two values, the safe and the unsafe distance you
can advance in a given motion vector (safe warrants non collision and
unsafe warrants collision). If you get a collision, you can then call
rest_info with the unsafe advance to obtain collision point, normal,
collider, etc.

On Wed, Jul 1, 2015 at 1:03 AM, seadra [email protected] wrote:

What does PhysicsServer.cast_motion do?
I tried to check the C++ code
https://github.com/okamstudio/godot/blob/89300b70e7217feaf0be15cfe395763f555bbafa/servers/physics/space_sw.cpp#L195,
but I'm not sure what it is.


Reply to this email directly or view it on GitHub
#2217 (comment).

@seadra
Copy link
Author

seadra commented Jul 1, 2015

I've been tinkering about that. Would it work it I used a CapsuleShape rather than a SphereShape and set its height to max_distance? After all, the volume swept by a sphere going in a straight line is a capsule, and unless we are interested in the normal vector, it should give the same result (using cast_motion and rest_info [get_rest_info?] sounds a bit complicated & everything you mentioned is completely undocumented so I have no idea how to do that).

BTW I suppose I shouldn't call set_motion (no such function exist for PhysicsShapeQueryParameters). And I think the line params.set_transform(get_transform().translated(origin)) is definitely wrong; it should be a transformation that corresponds to the same position as the main body, but looking at to direction, and translate such that the center of the "initial sphere" (top part of the capsule) corresponds to the point origin; something like this?

get_transform().looking_at(direction, Vector3(1,0,0)).translated(origin+direction*max_distance/2) # up shouldn't matter since capsule is axially symmetric

Something like this:

func sphere_cast(origin, radius, direction, hit_info, max_distance, exclude):
    var shape = CapsuleShape()
    shape.set_radius(radius)
    shape.set_height(max_distance)

    var params = PhysicsShapeQueryParameters()
    params.set_shape(shape)
    params.set_transform(get_transform().looking_at(direction, Vector3(1,0,0)).translated(origin+direction*max_distance/2)) # up shouldn't matter since capsule is axially symmetric
    if exclude != null:
        param.set_exclude(exclude) # here exclude is an array of... RID??
    var res = PhysicsServer.intersect_shape(params, 1)
    if hit_info != null: # and extends Object
        hit_info = res # res is an array containing...?
    return res.size() > 0

@seadra
Copy link
Author

seadra commented Jul 1, 2015

OK, after several hours trying to figure this out, I give up.

Could you write a SphereCast polyfill?

@seadra
Copy link
Author

seadra commented Jul 1, 2015

Pretty please?

@seadra
Copy link
Author

seadra commented Sep 10, 2015

I'm pushing it now, but well, hope springs eternal :)

@seadra
Copy link
Author

seadra commented Sep 10, 2015

Nobody (other than you) knows how to do it in godot (assuming that it can be done).

@reduz
Copy link
Member

reduz commented Sep 10, 2015

just leave this issue open and will eventually fix/add this

On Thu, Sep 10, 2015 at 2:09 PM, seadra [email protected] wrote:

Nobody (other than you) knows how to do it in godot (assuming that it can
be done).


Reply to this email directly or view it on GitHub
#2217 (comment).

@leonkrause leonkrause changed the title Function request: PhysicsShapeQueryParameters.sphere_cast PhysicsShapeQueryParameters.sphere_cast Oct 31, 2015
@silverweed
Copy link

Bump.
I also find this feature extremely convenient, moreso since no documentation for PhysicsShapeQueryParameters exists at all.

@bitbutter
Copy link

New Godot user here. I've been looking for how to do the equivalent of unity's spherecast too. Ideally returning collision point(s) and normal(s). It'd be great if there was a convenient method for this.

@CptPotato
Copy link
Contributor

CptPotato commented Aug 21, 2019

This is currently preventing me from implementing my 3D character properly. Any developments on this would be great, as I agree that it's quite an essential feature.

Edit: I managed to get it somewhat working but I still don't know how to get back the CollisionShape node that has collided with the sphere cast.

class SphereCastResult:
	var hit_distance: float
	var hit_position: Vector3
	var hit_normal: Vector3

func sphere_cast(origin: Vector3, offset: Vector3, radius: float):# -> SphereCastResult: (type hint breaks GitHub syntax highlighting)
	var space: PhysicsDirectSpaceState = get_world().direct_space_state as PhysicsDirectSpaceState
	
	var shape: = SphereShape.new()
	shape.radius = radius
	
	var params: = PhysicsShapeQueryParameters.new()
	params.set_shape(shape)
	params.transform = Transform.IDENTITY
	params.transform.origin = origin
	
	var castResult = space.cast_motion(params, offset)
	
	var result: = SphereCastResult.new()
	
	result.hit_distance = castResult[0] * offset.length()
	result.hit_position = origin + offset * castResult[0] # needs null check in godot physics
	
	params.transform.origin += offset * castResult[1]
	var collision = space.get_rest_info(params)
	
	result.hit_normal = collision.get("normal", Vector3.ZERO)
	
	return result

@Xrayez
Copy link
Contributor

Xrayez commented Feb 17, 2020

I think the API should more or less closely represent RayCast(2D) one (I envision this to be a node am I right?). See also godotengine/godot-proposals#72. As reduz suggested it doesn't have to be restricted to one particular shape type either, just assigning any shape via inspector or code should be enough.

This feature seems to be useful for people working on 3D projects. I only use 2D-related physics queries though. The proposal/issue is 5 years old so I might as well experiment with this? If I do end up implementing this for 2D specifically, I'm locked by the 3D side to be able to contribute, but might be easier done than said. 🙂

@akien-mga
Copy link
Member

Feature and improvement proposals for the Godot Engine are now being discussed and reviewed in a dedicated Godot Improvement Proposals (GIP) (godotengine/godot-proposals) issue tracker. The GIP tracker has a detailed issue template designed so that proposals include all the relevant information to start a productive discussion and help the community assess the validity of the proposal for the engine.

The main (godotengine/godot) tracker is now solely dedicated to bug reports and Pull Requests, enabling contributors to have a better focus on bug fixing work. Therefore, we are now closing all older feature proposals on the main issue tracker.

If you are interested in this feature proposal, please open a new proposal on the GIP tracker following the given issue template (after checking that it doesn't exist already). Be sure to reference this closed issue if it includes any relevant discussion (which you are also encouraged to summarize in the new proposal). Thanks in advance!

@elvisish
Copy link

elvisish commented Feb 17, 2022

Super late, but I want to thank you for this code, it's really helped me understand how to do these queries. You might be able to do:

class SphereCastResult:
	var hit_distance: float
	var hit_position: Vector3
	var hit_normal: Vector3
	var hit_collider

func sphere_cast(origin: Vector3, offset: Vector3, radius: float):# -> SphereCastResult: (type hint breaks GitHub syntax highlighting)
	var space: PhysicsDirectSpaceState = get_world().direct_space_state as PhysicsDirectSpaceState
	
	var shape: = SphereShape.new()
	shape.radius = radius
	
	var params: = PhysicsShapeQueryParameters.new()
	params.set_shape(shape)
	params.transform = Transform.IDENTITY
	params.transform.origin = origin
	
	var castResult = space.cast_motion(params, offset)
	
	var result: = SphereCastResult.new()
	
	result.hit_distance = castResult[0] * offset.length()
	result.hit_position = origin + offset * castResult[0] # needs null check in godot physics
	
	params.transform.origin += offset * castResult[1]

	var collision = space.get_rest_info(params)
	result.hit_normal = collision.get("normal", Vector3.ZERO)

	collision = space.intersect_shape(params, 1)
	result.hit_collider = collision[0].get("collider")
	
	return result

@Zireael07
Copy link
Contributor

Can this please get documented somewhere?

@elvisish
Copy link

elvisish commented Feb 17, 2022

Can this please get documented somewhere?

I'd like to, but I've never written documentation before and I'm not sure how to get started. For bonus info, here's how I've managed to get a shapecast from the center of the screen forwards (as far as the far clipping):

	export(NodePath) onready var cam = get_node(cam) as Camera
	onready var distance = cam.get_zfar()

	var sphere: = SphereShape.new()
	sphere.radius = 0.5
	var vp_size = get_viewport().size
	var from = cam.project_ray_origin(vp_size * 0.5)
	var to = cam.project_position(vp_size * 0.5, distance)
	var space_state : PhysicsDirectSpaceState = get_world().direct_space_state as PhysicsDirectSpaceState
	var param := PhysicsShapeQueryParameters.new()
	param.collision_mask = player.collision_mask
	param.set_shape( sphere )
	param.transform = Transform.IDENTITY
	param.transform.origin = from
	param.margin = sphere.margin
	param.exclude = [ self, player ]
	var motion = to - from
	var trace := space_state.cast_motion( param, motion )
	if not (trace[0] == 1.0 and trace[1] == 1.0):
		param.transform.origin += motion * trace[1]
		var collision := space_state.intersect_shape(param, 1)
		if collision != null:
			var collider = collision[0].get("collider")

Also, here's how to shapecast to an enemy (monster), first with a collide_shape to test already overlapping, then a shapecast (only on the enemy layer), then finally a confirmation raycast back to the player (all layers):

func fire_shape():
	var monster_mask = 1 << 3 #| 1 << 7
	var sphere: = SphereShape.new()
	var vp_size = get_viewport().size
	var from = cam.project_ray_origin(vp_size * 0.5)
	var to = cam.project_position(vp_size * 0.5, distance)
	var space_state : PhysicsDirectSpaceState = get_world().direct_space_state as PhysicsDirectSpaceState
	var param := PhysicsShapeQueryParameters.new()
	param.collision_mask = monster_mask
	param.set_shape( sphere )
	param.transform = Transform.IDENTITY
	param.transform.origin = from
	param.margin = sphere.margin
	param.exclude = [ self, player ]
	
	sphere.radius = 0.2#sphere trace first
	param.transform.origin = cam.project_position(vp_size * 0.5, 1.0) # 1.0 is slightly forward
	var sphere_trace := space_state.intersect_shape(param,1) 
	if not sphere_trace.empty():
		var collider = sphere_trace[0].get("collider")
		if collider.is_in_group("monster"):
			collider.get_parent().mesh.get_material().albedo_color = Color(randf(),randf(),randf())
	
	sphere.radius = 1.0 # then sphere cast
	var motion = to - from
	var trace := space_state.cast_motion( param, motion )
	if not (trace[0] == 1.0 and trace[1] == 1.0):
		param.transform.origin += motion * trace[1]
		var monster_point = space_state.get_rest_info(param).get("point")
		var collision := space_state.intersect_shape(param, 1) #then final intersect at end
		if not collision.empty():
			var collider = collision[0].get("collider")
			if collider.is_in_group("monster"): #final raycast back
				var result = space_state.intersect_ray(monster_point, from, [ collider ], 2147483647, true, true )
				if not result.empty():
					var player_back = result.get("collider")
					if player_back.is_in_group("Player"):
						collider.get_parent().mesh.get_material().albedo_color = Color(randf(),randf(),randf())

@Calinou
Copy link
Member

Calinou commented Feb 17, 2022

See also #54803 which implemented this feature in 2D, and #56470 which is implementing it in 3D.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests