|
|
|
@@ -6,14 +6,15 @@ extends Control
|
|
|
|
|
|
|
|
|
|
signal body_selected(body: Dictionary)
|
|
|
|
|
|
|
|
|
|
const ORBITAL_CENTER_FRACTION := Vector2(0.5, 0.55)
|
|
|
|
|
const STAR_RADIUS: float = 12.0
|
|
|
|
|
const ORBITAL_RING_BASE: float = 65.0
|
|
|
|
|
const ORBITAL_RING_STEP: float = 58.0
|
|
|
|
|
const MOON_ORBIT_RADIUS: float = 24.0
|
|
|
|
|
const STATION_SIZE: float = 6.0
|
|
|
|
|
const BODY_HIT_RADIUS: float = 16.0
|
|
|
|
|
const LABEL_OFFSET: float = 11.0
|
|
|
|
|
const STAR_RADIUS: float = 140.0
|
|
|
|
|
const STAR_X_OFFSET: float = 0.0
|
|
|
|
|
const BODY_LEFT_MARGIN: float = 300.0
|
|
|
|
|
const BODY_RIGHT_MARGIN: float = 60.0
|
|
|
|
|
const MOON_CLEARANCE: float = 12.0
|
|
|
|
|
const STATION_SIZE: float = 8.0
|
|
|
|
|
const STATION_OFFSET_Y: float = -26.0
|
|
|
|
|
const BODY_HIT_RADIUS: float = 20.0
|
|
|
|
|
const LABEL_OFFSET: float = 14.0
|
|
|
|
|
const PANEL_WIDTH: float = 320.0
|
|
|
|
|
const PANEL_MARGIN: float = 16.0
|
|
|
|
|
|
|
|
|
@@ -35,6 +36,7 @@ var _orbital_bodies: Array = []
|
|
|
|
|
var _orbital_stations: Array = []
|
|
|
|
|
var _body_positions: Dictionary = {}
|
|
|
|
|
var _station_positions: Dictionary = {}
|
|
|
|
|
var _body_textures: Dictionary = {} # body_id -> ImageTexture
|
|
|
|
|
var _hovered_body: String = ""
|
|
|
|
|
var _hovered_station: String = ""
|
|
|
|
|
var _selected_station: Dictionary = {}
|
|
|
|
@@ -116,6 +118,7 @@ func load_and_show_orbital() -> void:
|
|
|
|
|
_hovered_body = ""
|
|
|
|
|
_hovered_station = ""
|
|
|
|
|
_selected_station = {}
|
|
|
|
|
_load_body_textures(sys.get("system_id", ""))
|
|
|
|
|
_compute_body_positions()
|
|
|
|
|
if _picker_panel:
|
|
|
|
|
_picker_panel.visible = false
|
|
|
|
@@ -157,27 +160,30 @@ func _draw() -> void:
|
|
|
|
|
|
|
|
|
|
func _draw_orbital() -> void:
|
|
|
|
|
var sz: Vector2 = get_rect().size
|
|
|
|
|
var center: Vector2 = sz * ORBITAL_CENTER_FRACTION
|
|
|
|
|
var mid_y: float = sz.y * 0.5
|
|
|
|
|
var star_center := Vector2(STAR_X_OFFSET, mid_y)
|
|
|
|
|
|
|
|
|
|
draw_rect(Rect2(Vector2.ZERO, sz), COLOR_BG)
|
|
|
|
|
_draw_orbit_rings(center)
|
|
|
|
|
draw_circle(center, STAR_RADIUS + 4.0, Color(COLOR_STAR.r, COLOR_STAR.g, COLOR_STAR.b, 0.18))
|
|
|
|
|
draw_circle(center, STAR_RADIUS, COLOR_STAR)
|
|
|
|
|
draw_circle(star_center, STAR_RADIUS + 40.0, Color(COLOR_STAR.r, COLOR_STAR.g, COLOR_STAR.b, 0.04))
|
|
|
|
|
draw_circle(star_center, STAR_RADIUS + 20.0, Color(COLOR_STAR.r, COLOR_STAR.g, COLOR_STAR.b, 0.08))
|
|
|
|
|
draw_circle(star_center, STAR_RADIUS, COLOR_STAR)
|
|
|
|
|
_draw_orbital_arcs(star_center)
|
|
|
|
|
_draw_stations()
|
|
|
|
|
_draw_bodies()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _draw_orbit_rings(center: Vector2) -> void:
|
|
|
|
|
var seen_orbits: Dictionary = {}
|
|
|
|
|
func _draw_orbital_arcs(star_center: Vector2) -> void:
|
|
|
|
|
for b: Dictionary in _orbital_bodies:
|
|
|
|
|
if b.get("parent_body_id") != null:
|
|
|
|
|
continue
|
|
|
|
|
var idx: int = int(b.get("orbit_index", 0))
|
|
|
|
|
if seen_orbits.has(idx):
|
|
|
|
|
var bid: String = str(b.get("body_id", ""))
|
|
|
|
|
if not _body_positions.has(bid):
|
|
|
|
|
continue
|
|
|
|
|
seen_orbits[idx] = true
|
|
|
|
|
var r: float = ORBITAL_RING_BASE + (idx - 1) * ORBITAL_RING_STEP
|
|
|
|
|
draw_arc(center, r, 0.0, TAU, 64, COLOR_RING, 0.5, true)
|
|
|
|
|
var body_pos: Vector2 = _body_positions[bid]
|
|
|
|
|
var orbit_r: float = body_pos.x - star_center.x
|
|
|
|
|
if orbit_r <= 0.0:
|
|
|
|
|
continue
|
|
|
|
|
draw_arc(star_center, orbit_r, -PI * 0.7, PI * 0.7, 64, COLOR_RING, 0.5, true)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _draw_bodies() -> void:
|
|
|
|
@@ -188,36 +194,31 @@ func _draw_bodies() -> void:
|
|
|
|
|
continue
|
|
|
|
|
var pos: Vector2 = _body_positions[bid]
|
|
|
|
|
var body_type: String = b.get("body_type", "")
|
|
|
|
|
var atmo: String = b.get("atmosphere", "none")
|
|
|
|
|
var inhabited: bool = bool(b.get("inhabited", false))
|
|
|
|
|
|
|
|
|
|
var color: Color
|
|
|
|
|
var radius: float
|
|
|
|
|
match body_type:
|
|
|
|
|
"moon":
|
|
|
|
|
color = COLOR_MOON
|
|
|
|
|
radius = 3.5
|
|
|
|
|
"oort_cloud":
|
|
|
|
|
color = COLOR_OORT
|
|
|
|
|
radius = 2.0
|
|
|
|
|
_:
|
|
|
|
|
if inhabited:
|
|
|
|
|
color = COLOR_PLANET_INHABITED
|
|
|
|
|
radius = 6.0
|
|
|
|
|
elif atmo in ["breathable", "standard"]:
|
|
|
|
|
color = COLOR_PLANET_HABITABLE
|
|
|
|
|
radius = 5.5
|
|
|
|
|
else:
|
|
|
|
|
color = COLOR_PLANET_BARE
|
|
|
|
|
radius = 4.5
|
|
|
|
|
var radius_km: float = float(b.get("body_radius_km", 0))
|
|
|
|
|
var radius: float = _km_to_draw_radius(radius_km, body_type)
|
|
|
|
|
|
|
|
|
|
if bid == _hovered_body:
|
|
|
|
|
draw_arc(pos, radius + 5.0, 0.0, TAU, 20, Color(1.0, 1.0, 1.0, 0.25), 1.0, true)
|
|
|
|
|
draw_arc(pos, radius + 5.0, 0.0, TAU, 20, Color(1.0, 1.0, 1.0, 0.25), 1.5, true)
|
|
|
|
|
|
|
|
|
|
draw_circle(pos, radius, color)
|
|
|
|
|
var tex: ImageTexture = _body_textures.get(bid)
|
|
|
|
|
if tex:
|
|
|
|
|
var draw_size := Vector2(radius * 2.0, radius * 2.0)
|
|
|
|
|
draw_texture_rect(tex, Rect2(pos - draw_size / 2.0, draw_size), false)
|
|
|
|
|
else:
|
|
|
|
|
var atmo: String = b.get("atmosphere", "none")
|
|
|
|
|
var inhabited: bool = bool(b.get("inhabited", false))
|
|
|
|
|
var color: Color
|
|
|
|
|
match body_type:
|
|
|
|
|
"moon": color = COLOR_MOON
|
|
|
|
|
"oort_cloud": color = COLOR_OORT
|
|
|
|
|
_:
|
|
|
|
|
if inhabited: color = COLOR_PLANET_INHABITED
|
|
|
|
|
elif atmo in ["breathable", "standard"]: color = COLOR_PLANET_HABITABLE
|
|
|
|
|
else: color = COLOR_PLANET_BARE
|
|
|
|
|
draw_circle(pos, radius, color)
|
|
|
|
|
|
|
|
|
|
var label: String = b.get("proper_name", bid) if b.get("proper_name") else bid
|
|
|
|
|
var show_label: bool = inhabited or bid == _hovered_body
|
|
|
|
|
var show_label: bool = body_type != "oort_cloud" or bid == _hovered_body
|
|
|
|
|
if show_label:
|
|
|
|
|
var lcolor: Color = COLOR_TEXT if bid == _hovered_body else COLOR_TEXT_DIM
|
|
|
|
|
var lsz: Vector2 = font.get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, 9)
|
|
|
|
@@ -269,7 +270,8 @@ func _compute_body_positions() -> void:
|
|
|
|
|
var sz: Vector2 = get_rect().size
|
|
|
|
|
if sz == Vector2.ZERO:
|
|
|
|
|
sz = Vector2(1280.0, 720.0)
|
|
|
|
|
var center: Vector2 = sz * ORBITAL_CENTER_FRACTION
|
|
|
|
|
var mid_y: float = sz.y * 0.5
|
|
|
|
|
var usable_width: float = sz.x - BODY_LEFT_MARGIN - BODY_RIGHT_MARGIN
|
|
|
|
|
|
|
|
|
|
var top_bodies: Array = []
|
|
|
|
|
for b: Dictionary in _orbital_bodies:
|
|
|
|
@@ -280,30 +282,16 @@ func _compute_body_positions() -> void:
|
|
|
|
|
return int(a.get("orbit_index", 0)) < int(b.get("orbit_index", 0))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var by_orbit: Dictionary = {}
|
|
|
|
|
for b: Dictionary in top_bodies:
|
|
|
|
|
var idx: int = int(b.get("orbit_index", 0))
|
|
|
|
|
if not by_orbit.has(idx):
|
|
|
|
|
by_orbit[idx] = []
|
|
|
|
|
by_orbit[idx].append(b)
|
|
|
|
|
var count: int = top_bodies.size()
|
|
|
|
|
for i: int in range(count):
|
|
|
|
|
var b: Dictionary = top_bodies[i]
|
|
|
|
|
var bid: String = str(b.get("body_id", ""))
|
|
|
|
|
if bid.is_empty():
|
|
|
|
|
continue
|
|
|
|
|
var t: float = float(i) / float(maxi(count - 1, 1))
|
|
|
|
|
var x: float = BODY_LEFT_MARGIN + t * usable_width
|
|
|
|
|
_body_positions[bid] = Vector2(x, mid_y)
|
|
|
|
|
|
|
|
|
|
for orbit_idx: int in by_orbit:
|
|
|
|
|
var ring_bodies: Array = by_orbit[orbit_idx]
|
|
|
|
|
var ring_r: float = ORBITAL_RING_BASE + (orbit_idx - 1) * ORBITAL_RING_STEP
|
|
|
|
|
var count: int = ring_bodies.size()
|
|
|
|
|
for i: int in range(count):
|
|
|
|
|
var b: Dictionary = ring_bodies[i]
|
|
|
|
|
var bid: String = str(b.get("body_id", ""))
|
|
|
|
|
if bid.is_empty():
|
|
|
|
|
continue
|
|
|
|
|
var angle: float
|
|
|
|
|
if count == 1:
|
|
|
|
|
angle = -PI / 2.0
|
|
|
|
|
else:
|
|
|
|
|
angle = -PI / 2.0 + TAU * float(i) / float(count)
|
|
|
|
|
_body_positions[bid] = center + Vector2(cos(angle), sin(angle)) * ring_r
|
|
|
|
|
|
|
|
|
|
# Moons per parent count drives spacing — hard-coded divisor caused overlap on gas giants
|
|
|
|
|
var moons_by_parent: Dictionary = {}
|
|
|
|
|
for b: Dictionary in _orbital_bodies:
|
|
|
|
|
var parent_id: Variant = b.get("parent_body_id")
|
|
|
|
@@ -313,6 +301,10 @@ func _compute_body_positions() -> void:
|
|
|
|
|
if not moons_by_parent.has(key):
|
|
|
|
|
moons_by_parent[key] = []
|
|
|
|
|
moons_by_parent[key].append(b)
|
|
|
|
|
var body_dict_by_id: Dictionary = {}
|
|
|
|
|
for b: Dictionary in _orbital_bodies:
|
|
|
|
|
body_dict_by_id[str(b.get("body_id", ""))] = b
|
|
|
|
|
|
|
|
|
|
for parent_key: String in moons_by_parent:
|
|
|
|
|
if not _body_positions.has(parent_key):
|
|
|
|
|
continue
|
|
|
|
@@ -322,16 +314,23 @@ func _compute_body_positions() -> void:
|
|
|
|
|
return int(a.get("orbit_index", 0)) < int(b.get("orbit_index", 0))
|
|
|
|
|
)
|
|
|
|
|
var parent_pos: Vector2 = _body_positions[parent_key]
|
|
|
|
|
var count: int = siblings.size()
|
|
|
|
|
for i: int in range(count):
|
|
|
|
|
var moon: Dictionary = siblings[i]
|
|
|
|
|
var parent_data: Dictionary = body_dict_by_id.get(parent_key, {})
|
|
|
|
|
var parent_draw_r: float = _km_to_draw_radius(
|
|
|
|
|
float(parent_data.get("body_radius_km", 0)),
|
|
|
|
|
parent_data.get("body_type", "")
|
|
|
|
|
)
|
|
|
|
|
var moon_count: int = siblings.size()
|
|
|
|
|
for j: int in range(moon_count):
|
|
|
|
|
var moon: Dictionary = siblings[j]
|
|
|
|
|
var moon_id: String = str(moon.get("body_id", ""))
|
|
|
|
|
if moon_id.is_empty():
|
|
|
|
|
continue
|
|
|
|
|
var angle: float = -PI / 2.0 + TAU * float(i) / float(count)
|
|
|
|
|
_body_positions[moon_id] = (
|
|
|
|
|
parent_pos + Vector2(cos(angle), sin(angle)) * MOON_ORBIT_RADIUS
|
|
|
|
|
var moon_draw_r: float = _km_to_draw_radius(
|
|
|
|
|
float(moon.get("body_radius_km", 0)), "moon"
|
|
|
|
|
)
|
|
|
|
|
var label_space: float = LABEL_OFFSET + 12.0
|
|
|
|
|
var y_off: float = parent_draw_r + label_space + MOON_CLEARANCE + (moon_draw_r * 2.0 + MOON_CLEARANCE) * float(j)
|
|
|
|
|
_body_positions[moon_id] = parent_pos + Vector2(0.0, y_off)
|
|
|
|
|
|
|
|
|
|
for s: Dictionary in _orbital_stations:
|
|
|
|
|
var station_id: String = str(s.get("station_id", ""))
|
|
|
|
@@ -342,8 +341,8 @@ func _compute_body_positions() -> void:
|
|
|
|
|
if parent_id != null and _body_positions.has(str(parent_id)):
|
|
|
|
|
station_parent_pos = _body_positions[str(parent_id)]
|
|
|
|
|
else:
|
|
|
|
|
station_parent_pos = center
|
|
|
|
|
_station_positions[station_id] = station_parent_pos + Vector2(20.0, -10.0)
|
|
|
|
|
station_parent_pos = Vector2(sz.x - BODY_RIGHT_MARGIN, mid_y)
|
|
|
|
|
_station_positions[station_id] = station_parent_pos + Vector2(0.0, STATION_OFFSET_Y)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
@@ -415,6 +414,53 @@ func _find_nearest_station(pos: Vector2) -> String:
|
|
|
|
|
return best_id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Globe textures
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _load_body_textures(system_id: String) -> void:
|
|
|
|
|
_body_textures.clear()
|
|
|
|
|
var sys_dir: String = system_id.replace(" ", "-")
|
|
|
|
|
var wiki_root: String = ProjectSettings.globalize_path("res://").get_base_dir().get_base_dir() + "/wiki/star-systems"
|
|
|
|
|
for b: Dictionary in _orbital_bodies:
|
|
|
|
|
var bid: String = str(b.get("body_id", ""))
|
|
|
|
|
if bid.is_empty():
|
|
|
|
|
continue
|
|
|
|
|
var bt: String = b.get("body_type", "")
|
|
|
|
|
var globe_path: String
|
|
|
|
|
if bt in ["oort_cloud", "asteroid_belt"]:
|
|
|
|
|
globe_path = wiki_root + "/_generics/" + bt + "/globe.png"
|
|
|
|
|
else:
|
|
|
|
|
globe_path = wiki_root + "/" + sys_dir + "/bodies/" + bid + "/globe.png"
|
|
|
|
|
if not FileAccess.file_exists(globe_path):
|
|
|
|
|
continue
|
|
|
|
|
var img := Image.load_from_file(globe_path)
|
|
|
|
|
if img == null:
|
|
|
|
|
continue
|
|
|
|
|
var tex := ImageTexture.create_from_image(img)
|
|
|
|
|
_body_textures[bid] = tex
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Sizing
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DRAW_RADIUS_MIN: float = 4.0
|
|
|
|
|
const DRAW_RADIUS_MAX: float = 48.0
|
|
|
|
|
const DRAW_RADIUS_REF_KM: float = 6371.0
|
|
|
|
|
const DRAW_RADIUS_REF_PX: float = 24.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static func _km_to_draw_radius(radius_km: float, _body_type: String) -> float:
|
|
|
|
|
if radius_km <= 0.0:
|
|
|
|
|
return DRAW_RADIUS_MIN
|
|
|
|
|
var ratio: float = radius_km / DRAW_RADIUS_REF_KM
|
|
|
|
|
var px: float = DRAW_RADIUS_REF_PX * pow(ratio, 0.5)
|
|
|
|
|
return maxf(px, DRAW_RADIUS_MIN)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Panels
|
|
|
|
|
# =============================================================================
|
|
|
|
|