Skip to content

Commit 5d17811

Browse files
JezSonicmarcinn
andcommitted
(#76) Implement tracks rendering
This is initial implementation of tracks rendering. It is not 100%-compatible with the original simulator, but still good enough for PoC. Current implementation: * adds TrackRenderingServer, which uses RenderingServer directly * TrackRenderingServer is written in GDScript instead of PoC due to potential issues with memory management, which causes segfaults * adds initial MaszynaTrack3D node for tracks placement * adds initial MaszynaSwitch3D for junctions Limitations: * tracks are defined as Curve3D with multiple points, where the original simulator uses 2-point curves only per track segment; this is incompatible with Mover physics server * junctions are defined as MaszynaSwitch3D, a separate nodes, where the original simulator uses for switches two curves per track with a common point; this is incompatible with Mover physics server * common render settings like curve quality or smoothing should be implemented as project-wide settings Co-authored-by: Marcin Nowak <marcin.j.nowak@gmail.com>
1 parent ced6326 commit 5d17811

16 files changed

Lines changed: 1614 additions & 5 deletions

AGENTS.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Planning and architectue:
2+
3+
* apply separation of concerns
4+
5+
Code generation:
6+
7+
* Keep code clean and do minimal code changes
8+
* Follow DRY and KISS principles
9+
* use english comments (if needed)
10+
* GDSCRIPT: avoid type interference, use explicit type declaration
11+
12+
General guidelines:
13+
14+
* IMPORTANT: do only what operator want, do not assume anything by yourself!
15+
* if you are not sure, ask operator for decision
16+
17+
Custom nodes and Godot Editor:
18+
19+
* place editor related code in addons/libmaszyna/editor
20+
* use libmaszyna.gd just for bootstraping and proxying to editor plugins
21+
22+
Documentation:
23+
24+
* after changes update Godot documentation in `doc_classes`
25+
26+
Build:
27+
28+
* use cmake to build c++ extension (check Makefile and compile-debug target)
29+
30+
Checks:
31+
32+
* compile c++ plugin and check result
33+
* run Godot in headless mode outside sandbox, look for parse errors

addons/libmaszyna/libmaszyna.gd

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ var maszyna_environment_node_script = preload("res://addons/libmaszyna/environme
99
var maszyna_environment_node_icon = preload("res://addons/libmaszyna/environment/maszyna_environment_node_icon.png")
1010
var e3d_model_instance_script = preload("res://addons/libmaszyna/e3d/e3d_model_instance.gd")
1111
var e3d_model_instance_icon = preload("res://addons/libmaszyna/e3d/e3d_model_instance.png")
12+
var maszyna_track_3d_script = preload("res://addons/libmaszyna/maszyna_track_3d.gd")
13+
var maszyna_switch_3d_script = preload("res://addons/libmaszyna/maszyna_switch_3d.gd")
1214

1315
# Editor plugins
1416

@@ -48,6 +50,21 @@ func _enter_tree():
4850
)
4951

5052
ResourceLoader.add_resource_format_loader(e3d_loader)
53+
54+
add_custom_type(
55+
"MaszynaTrack3D",
56+
"Path3D",
57+
maszyna_track_3d_script,
58+
null
59+
)
60+
61+
add_custom_type(
62+
"MaszynaSwitch3D",
63+
"Node3D",
64+
maszyna_switch_3d_script,
65+
null
66+
)
67+
5168
user_settings_dock = user_settings_dock_scene.instantiate()
5269
add_control_to_dock(DOCK_SLOT_RIGHT_UL, user_settings_dock)
5370

@@ -62,6 +79,8 @@ func _exit_tree():
6279

6380
remove_custom_type("E3DModelInstance")
6481
remove_custom_type("MaszynaEnvironmentNode")
82+
remove_custom_type("MaszynaTrack3D")
83+
remove_custom_type("MaszynaSwitch3D")
6584

6685
remove_autoload_singleton("AudioStreamManager")
6786
remove_autoload_singleton("E3DModelInstanceManager")
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
@tool
2+
extends Node3D
3+
class_name MaszynaSwitch3D
4+
5+
enum SwitchState { STRAIGHT, DIVERGING }
6+
7+
@export var state: SwitchState = SwitchState.STRAIGHT:
8+
set(x):
9+
state = x
10+
_update_switch()
11+
12+
@export_group("Tracks")
13+
@export var straight_track: MaszynaTrack3D:
14+
set(x):
15+
straight_track = x
16+
update_configuration_warnings()
17+
_update_switch()
18+
19+
@export var diverging_track: MaszynaTrack3D:
20+
set(x):
21+
diverging_track = x
22+
update_configuration_warnings()
23+
_update_switch()
24+
25+
@export var common_track: MaszynaTrack3D:
26+
set(x):
27+
common_track = x
28+
update_configuration_warnings()
29+
_update_switch()
30+
31+
func _ready():
32+
_update_switch()
33+
34+
func _get_configuration_warnings():
35+
var warnings = []
36+
if not straight_track:
37+
warnings.append("No straight track node assigned.")
38+
if not diverging_track:
39+
warnings.append("No diverging track node assigned.")
40+
if not common_track:
41+
warnings.append("No common track node assigned.")
42+
43+
if straight_track and diverging_track and common_track:
44+
if not straight_track.curve or not diverging_track.curve or not common_track.curve:
45+
warnings.append("One or more tracks are missing curves.")
46+
else:
47+
var inlet_pos = _get_inlet_position()
48+
var s_start = straight_track.to_global(straight_track.curve.get_point_position(0))
49+
var s_end = straight_track.to_global(straight_track.curve.get_point_position(straight_track.curve.point_count - 1))
50+
51+
if s_start.distance_to(inlet_pos) > 0.5 and s_end.distance_to(inlet_pos) > 0.5:
52+
warnings.append("Straight track does not seem to connect to the switch inlet.")
53+
54+
return warnings
55+
56+
func _get_inlet_position() -> Vector3:
57+
if common_track and common_track.curve:
58+
# inlet is at the end of common track (usually)
59+
return common_track.to_global(common_track.curve.get_point_position(common_track.curve.point_count - 1))
60+
if straight_track and straight_track.curve:
61+
# or at the start of straight track
62+
return straight_track.to_global(straight_track.curve.get_point_position(0))
63+
return global_position
64+
65+
func resolve_track(incoming_track: Node3D) -> MaszynaTrack3D:
66+
if incoming_track == common_track:
67+
return straight_track if state == SwitchState.STRAIGHT else diverging_track
68+
elif incoming_track == straight_track or incoming_track == diverging_track:
69+
return common_track
70+
return null
71+
72+
func _update_switch():
73+
if not is_inside_tree(): return
74+
update_configuration_warnings()
75+
76+
if common_track: _auto_link_track(common_track)
77+
if straight_track: _auto_link_track(straight_track)
78+
if diverging_track: _auto_link_track(diverging_track)
79+
80+
func _auto_link_track(track: MaszynaTrack3D):
81+
if not track or not track.curve: return
82+
var inlet_pos = _get_inlet_position()
83+
var p_start = track.to_global(track.curve.get_point_position(0))
84+
var p_end = track.to_global(track.curve.get_point_position(track.curve.point_count - 1))
85+
86+
# Only link the end that is actually AT the switch junction
87+
if p_start.distance_to(inlet_pos) < 0.5:
88+
if track.previous_track.is_empty():
89+
track.previous_track = track.get_path_to(self)
90+
elif p_end.distance_to(inlet_pos) < 0.5:
91+
if track.next_track.is_empty():
92+
track.next_track = track.get_path_to(self)
93+
94+
func toggle():
95+
state = SwitchState.DIVERGING if state == SwitchState.STRAIGHT else SwitchState.STRAIGHT
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://bami2gx4l6pyl

0 commit comments

Comments
 (0)