Drawing paths of objects(specifically planets)

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