Drawing paths of objects(specifically planets)

Godot Version

4.3

Question

I’m working on some very simple planet simulations - in which planets orbit a star using a modified gravitational formula.
(example image below shows two planets orbiting a star)

I want to draw their orbit paths/trajectories but i’m not sure how to do that. If there’s a way to graphically draw a path that would be nice(ie. I input a projected position formula for the x and y axis to see where it will be going)

1 Like

There’s nothing built-in for that task, and it’s actually not that simple to do if you want to have an accurate physics-calculated orbit. You’d need to simulate tens or hundreds of physics frames each actual frame and mark position of the planet to form the orbit.
If your planets interact only with the sun, then that may be doable (although I failed at trying to make anything useful and accurate), but if you want your planets to interact with each other too, then I’d have to say it’s super difficult and have very expensive simulation calculations.
It’s much easier to do draw an orbit retrospectively.

I have thought about it and it should be possible since I can only update the path when needed (ie draw the path of orbit to an extent, and then not update it until something is changed)
Issue is I don’t know the means to draw this sort of stuff

I was playing around with this idea and came up with this demo:

Here’s a scene structure:
obraz

Here’s the script on the Planet node:

class_name Planet
extends Node3D

class PlanetState:
	var velocity: Vector3
	var position: Vector3

var sun_position: Vector3 = Vector3.ZERO
var gravitational_force: float = 5.0
var future_states: Array[PlanetState]
var simulation_steps_per_frame: int = 200
var max_states: int = 2000
var lines: Array[MeshInstance3D]
var margin: float = 0.3
var min_states_for_margin: int = 50
var max_speed: float

func _physics_process(delta: float) -> void:
	simulate_future_positions(delta)
	move()

func calculate_forces(from_position: Vector3 = global_position) -> Vector3:
	var direction: Vector3 = sun_position - from_position
	var direction_normalized: Vector3 = direction.normalized()
	var distance: float = direction.length()
	return direction_normalized * (gravitational_force / pow(distance, 2))

func simulate_future_positions(delta: float) -> void:
	if future_states.is_empty():
		var initial_velocity = 5.0 * Vector3.ONE * transform.basis.z
		var new_state: PlanetState = PlanetState.new()
		new_state.position = global_position
		new_state.velocity = initial_velocity
		future_states.append(new_state)
	
	if future_states.size() > max_states:
		return
	
	for step in simulation_steps_per_frame:
		var first_state: PlanetState = future_states[0]
		var last_state: PlanetState = future_states[-1]
		var new_state: PlanetState = PlanetState.new()
		new_state.position = last_state.position + last_state.velocity * delta
		new_state.velocity = last_state.velocity + calculate_forces(new_state.position)
		
		if future_states.size() > min_states_for_margin \
		and first_state.position.distance_to(last_state.position) <= margin:
			return
		
		future_states.append(new_state)
		if new_state.velocity.length_squared() > max_speed:
			max_speed = new_state.velocity.length_squared()
		
		var new_line: MeshInstance3D = draw_line(last_state.position, new_state.position, Color.WHITE * (last_state.velocity.length_squared() / max_speed), 0.0)
		lines.append(new_line)

func move() -> void:
	global_position = (future_states.pop_front() as PlanetState).position
	(lines.pop_front() as MeshInstance3D).queue_free()

func draw_line(pos1: Vector3, pos2: Vector3, color = Color.WHITE_SMOKE, persist_ms = 0) -> MeshInstance3D:
	var mesh_instance := MeshInstance3D.new()
	var immediate_mesh := ImmediateMesh.new()
	var material := ORMMaterial3D.new()

	mesh_instance.mesh = immediate_mesh
	mesh_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF

	immediate_mesh.surface_begin(Mesh.PRIMITIVE_LINES, material)
	immediate_mesh.surface_add_vertex(pos1)
	immediate_mesh.surface_add_vertex(pos2)
	immediate_mesh.surface_end()

	material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
	material.albedo_color = color

	get_tree().get_root().add_child(mesh_instance)
	return mesh_instance

(for the draw_line() method credit goes to Line-and-Sphere-Drawing/Draw3D.gd at main · Ryan-Mirch/Line-and-Sphere-Drawing · GitHub, I just slightly modified it)

It’s definitely not ideal, but maybe will get you started in your own direction.
You could use the same principle to simulate multi-body physics (currently the planets interact only with the Sun’s gravity).
Let me know if you have any issues implementing this in your project!

1 Like

lovely work! I’ll see if I can try this out when I get the chance.

1 Like

lol I realize you did this in 3d, i’ll see if I can convert it to 2d

1 Like

Oh yours is 2D? I assumed it’s 3D from that screenshot. 2D would be 10x easier :smiley:

yup! It uses some shaders for the planets which make it look cleaner, I haven’t yet implemented what you mentioned though as i’ve run into some other issues lmao

1 Like