How do i rotate in 3d

Godot Version

4.5.1

Question

So I’ve set up this boid in a box made of a static body and an area 3d. At the moment the boid is a rigid body 3d. Right now I’m manually adjusting the flight path but I’d like to design it for later use when I’d like it to hit something and act on its own physics rather than my trajectory. Right now I’m having a problem orienting the ray casts I’m using to detect area 3d nodes that enter its visibility cylinder.

Originally I planned on making my ray cast function like the diagram below. Whenever an object entered the area 3d node it would be detected and a ray cast search process would begin. (this has been achieved). Essentially a hemisphere would be drawn around the origin of the boid and rotated to so that the ray casts would be at radius distance from the boid, with the central ray of the hemisphere being oriented along the boids forward direction/movement. Using its liner velocity to get its forward direction.

After smacking my head against a wall for a week, I’ve realised no matter what I do trying to find a to offset the the angles using the linear velocity even just along the 6 cardinal cases is proving to be outside my current capabilities. I imagine this problem will probably require some matrix maths but I’m not sure how to proceed with that path or even start towards it.

IDK I feel like there is an obvious solution that I am missing rn. But I can’t place my finger on it.

This is the code I’m using on the boid:

func ray_sphere( space_state, origin : Vector3, current_transform : Transform3D, radius : float):
	
	#arrays for storing new and viable
	var new_direction : Vector3
	var valid_directions = []
	
    # vel convert to direction vector
	direction = linear_velocity.normalized()
	
    #ray_cast var
	var ray_cast_x = 0.0
	var ray_cast_y = 0.0
	var ray_cast_z = 0.0
	var ray_cast_target = Vector3(ray_cast_x, ray_cast_y, ray_cast_z)
	var diff = ray_cast_target - origin

	
	#ray cast var
	var query = []
	var result_ray = []


    ## use 3 loops to look through, start from 0deg and work outwards
	# create array to store previous checks no repeats
	var deg_exceptions : Array = []
	
	# dont test if false, only check once
	var testable = false
	
			
	### offset theta and phi by direction value
	#var theta_offset = rad_to_deg((acos(direction.x)))
	#var phi_offset = rad_to_deg(acos(direction.y))

    for angle_observation in range(0.0, 100.0, 10.0):
		for theta in range(-angle_observation, angle_observation+10, 10.0):
			for phi in range(90-angle_observation, 90+angle_observation+10, 10.0):

				# convert to float
				var f_theta = float (theta)
				var f_phi = float (phi)


				# check if in expception space, if they are dont test
				for i in len(deg_exceptions)+1:
					
					if len(deg_exceptions) == 0:
						testable = true
						# add to exception to ensure no rechecks
						deg_exceptions.append(Vector2(theta, phi))
						#print('passed')
						break

					if Vector2(theta, phi) == deg_exceptions[i]: 
						testable = false
						#print(' cant test :', theta,' ', phi)
					else: 
						testable = true
						# add to exception to ensure no rechecks
						deg_exceptions.append(Vector2(theta, phi))
						#print('passed')
						break


				# if they passed the check test them
				if testable == true:
				
					
					var temp_x:float
					var temp_y:float
					var temp_z:float
					
					# do the raycast calc
					temp_x = cos(deg_to_rad(f_theta))
					temp_y = cos(deg_to_rad(f_phi))
					temp_z = sin(deg_to_rad(f_theta))
					
					
					var temp_vect = Vector3(temp_x, temp_y, temp_z)
					var temp_vect_norm = temp_vect.normalized()
					
					# use the ray casts direction * the origin to get the cast location
					ray_cast_target = origin + radius * temp_vect_norm
					diff = ray_cast_target - origin					
					
					
					# cast the ray and check if hit anything
					query = PhysicsRayQueryParameters3D.create(origin, ray_cast_target)
					query.collide_with_areas = true
					query.collide_with_bodies = true
					result_ray = space_state.intersect_ray(query)

                    # when ray is empty pass direction back
					if result_ray == {}:	
                        # make sure he solution hasnt been returned at vector.zero
						if diff.normalized().length() > .9:
							valid_directions.append(diff.normalized())
						pass
					pass
				pass
			pass		
		
		
		## check if valid direction has been found, if so break loop
		if len(valid_directions) > 0:
			break
		pass
	
	## checks are over
	# after all the checks pick the first one
	if len(valid_directions) > 0:
		var temp = valid_directions[0].normalized()

		if temp.length() > 0.9:	
			new_direction = temp

	## send back direction and use to update velocity
	return new_direction

This function does report the right direction back. I’ve spent days making sure to check every value so I know it’s not bugged elsewhere.

This is the Physics process used to call the function:

func _physics_process(delta: float) -> void:	
	
	# fetch the space server to do physics stuff
	space_server = get_world_3d().direct_space_state
	
	# get resource id
	var rid := get_rid()
	# physics state
	var phys_state := PhysicsServer3D.body_get_direct_state(rid)
		
	## if we have seen a coll update path to avoid
	
	if seen_cols:
				
		# create ray and cast from this boids position, check for collisions on rays and randomly choose the first one isnt hitting anything. Spiral out form theta = 0 phi = 90.
		var temp_vel = ray_sphere(space_server, global_position, global_transform, ray_length)
		get_tree().set_pause(true)
        
        # make sure were not loading zeros
		if temp_vel.length() > .9:
			vel = temp_vel
			vel *= speed
	pass

I’m also using this rotation function to rotate my object to face the direction of motion. The forward direction in the local space would be (1, 0, 0) I think. If I set a random motion it does rotate to match.

func rotation_calc(state : PhysicsDirectBodyState3D, current_transform : Transform3D, target_direction : Vector3 ):
	
	var forward_local_axis : Vector3 = Vector3(1.0, 0.0, 0.0)
	var forward_direction : Vector3 = ( current_transform.basis * forward_local_axis).normalized()
	
	
	var local_speed : float = clampf( rot_vel, 0, PI)	
	
	
	if forward_direction.dot(target_direction) >= 1e-4 or forward_direction.dot(target_direction) <= -1e-4:
		state.angular_velocity = (local_speed * forward_direction.cross(target_direction)) / state.step
	
	# its parallel, nudge it out a bit so the other condition can update correctly
	if forward_direction.dot(target_direction) < -.99 or (forward_direction.dot(target_direction) < 1e-4 and forward_direction.dot(target_direction) > -1e-4):
		state.angular_velocity = (local_speed * Vector3(0.0, 0.0, .01)) / state.step
	
		pass
	pass
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:

	#update linear velocity
	set_linear_velocity(vel)
	#update roatation
	rotation_calc(state, global_transform, linear_velocity.normalized())
	pass

I appreciate you having a look. If the solution is really simple for a beginner and I’ve just over looked something obvious, please just hint at it. Its been almost 6 years since I’ve done anything with trig or matrices. I really need to work on it more.

I’m having a little trouble understanding your goal and problem. How exactly do you want your angles to be offset by the objects linear velocity?

temp_vect_norm should be normalized linear velocity.

The red dot is the centre of the ray cast hemisphere, that should be along the direction of the linear velocity

temp_vect_norm is the direction of the ray cast, If I did that all the ray casts would only be along the linear velocity direction, direction is already the linear velocity

Isn’t that what you wanted? What should be the direction of the raycast?

see the red dot on the hemisphere, I want to offset my angles so that red dot is along the direction of the linear velocity, the hemisphere is the range of the current theta and phi arrays. The range the ray cast spreads out over

Not really clear. You want multiple raycasts distributed in some way around the velocity direction? What’s the purpose of those raycasts? Why not just use an area?

yep thats what I want, I want to look for directions where there isn’t an object to avoid colliding with, but only limited to that direction. I need the direction of the rays to pick a new path

So you want to find a direction inside a cone (with forward being the central axis of the cone), where a ray doesn’t hit anything?

not a cone a hemisphere, but otherwise yes

How many potential targets can be in the range? Tens, hundreds?
Do you need to get this direction every frame or just from time to time?

currently I’m using an area 3d node with 10 collision limit. I’m not expecting alot of objects right now.

If you loook at the script, in physics_process there is an array seen_cols, this is updated with the name of every object detected by the area 3d.

If you look at the ray cast function my angle range is already defined by theta and phi using the angle of observation to control the range. It goes from -90 → 90 deg in 10 deg steps on theta, and phi does similarly from 0 deg → 180 deg.

That’s a good question, how often do I need to fetch it. I programmed for it to be fetched whenever physics process runs a frame and a coll_seen was found. Assuming the updated ray cast movement from the last time was sufficient to move so its no longer in its detected area. Might be worth adding a cool down of some kind after the rotation problem is addressed.

I want the ray cast to be flexible, while a fixed amount could be added using the ray cast node, I just wanted to learn how to use them from the script

Maybe it’s not the most imaginative but if you can make the boid face the direction of travel, then you can use its basis.

You have the direction of a raycast going forward from the normalized velocity and/or negative basis.z, which by convention is forward in godot.

You can sweep in a hemisphere by taking that forward direction and rotating it by the boids x/y basis. Eg up to ± PI/2 radians

I wouldn’t use physics raycasts at all for this.

It can be done in several ways. I’d first implement a stupid brute force solution that may be sufficient for a small number of obstacles. In any case I would do it every frame. Or if it needs to be done as often as possible I’d put it in a thread.

  1. Get a list of all obstacles in vicinity
  2. Generate a random direction vector inside the hemisphere, draw an imaginary line along it
  3. Iterate through the obstacle list and calculate obstacle distances to the line. Find the smallest distance
  4. If the smallest distance is larger than some threshold - you’ve find a safe direction. Otherwise repeat from 2

Alternatively you can use the angular distance (dot product) instead of linear distance. It’s computationally less expensive and may be more fitting, depending on your use case

You can get more sophisticated by using uniformly distributed direction samples instead of just picking randoms. In that case you can even find a direction that’s most distant from all obstacles, ensuring the most spacious corridor. But more on that if the described approach proves to be inadequate for your use case.

I have already rotated the rigid body to face the direction of motion. My problem is I cant figure out how to rotate the hemisphere to point in the direction of motion.

This idea has the same problem my current set up does, I’d have to define the hemisphere and rotate it to face the direction of motion. Literally the same issue I’m currently stuck on.

Using the dot product might be better but if the ray isnt cast in the right direction the results would be useless.

The directions I’m using art random. My for loops start at the red dot then move outwards by 10 degree. It already is a uniform distribution with 10 degree steps, none of it is random

Rotating in steps doesn’t produce an uniform distribution on the surface of the sphere. Samples will pinch as you move towards poles. But let’s deal with your main confusion first.

You need to operate in a basis that has your velocity direction as one of the 3 axes. By convention, negative z axis is considered forward in Godot.

How do you currently align to the velocity? The simplest way would be to use look_at(). Once you align the transform like this the coordinate system axes you’re working with will be global_basis.x, global_basis.y and global_basis.z. Everything you do needs to happen within this coordinate system. So in order to cover the hemisphere, take -global_basis.z and simply axis/angle rotate it around global_basis.x and global_basis.z. That’s all there is to it.

the code is already in the post, rotation_calc()

its not aligned to the velocity and i cant figure out how, thats what the post is about

explain how to rotate it. The basis rotation depend on a matrix transform. my set up with theta and phi cant be transformed because its not a matrix