Files
gamedev-the-steward/tools/capture_jajce_lookdev.gd
T
2026-07-26 22:26:00 +02:00

156 lines
5.1 KiB
GDScript

extends SceneTree
const DEFAULT_OUTPUT_PATH := "res://docs/baselines/jajce_lookdev_01.png"
const DEFAULT_SCENE_PATH := "res://world/jajce/JajceLookdev.tscn"
const CAPTURE_SIZE := Vector2i(1280, 720)
const WARMUP_FRAMES := 90
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
root.size = CAPTURE_SIZE
var scene_path := _get_argument_value("--scene=", DEFAULT_SCENE_PATH)
var output_path := _get_argument_value("--output=", DEFAULT_OUTPUT_PATH)
var scene := load(scene_path) as PackedScene
if scene == null:
push_error("Could not load %s" % scene_path)
quit(1)
return
var lookdev := scene.instantiate() as Node3D
root.add_child(lookdev)
await process_frame
if not _apply_resource_capture_overrides():
quit(1)
return
_stabilize_lookdev(lookdev)
for _frame in WARMUP_FRAMES:
await process_frame
var image := root.get_texture().get_image()
if image == null or image.is_empty():
push_error("Lookdev capture produced no image")
quit(1)
return
var analysis := _analyze_image(image)
if analysis.get("range", 0.0) < 0.05:
push_error("Lookdev capture is too uniform: %s" % analysis)
quit(1)
return
var error := image.save_png(output_path)
if error != OK:
push_error("Could not save %s: %s" % [output_path, error_string(error)])
quit(1)
return
print("[TOOL] Saved %s | %s" % [output_path, analysis])
quit(0)
func _stabilize_lookdev(lookdev: Node3D) -> void:
var camera := root.get_camera_3d()
if camera != null:
camera.set_process(false)
var simulation_manager := lookdev.get_node_or_null("SimulationManager")
if simulation_manager != null:
simulation_manager.set_process(false)
var day_night_cycle := lookdev.get_node_or_null("JajceWorld/DayNightCycle")
if day_night_cycle == null:
return
day_night_cycle.set_process(false)
day_night_cycle.call("_apply_light_rotation", 0.4)
day_night_cycle.call("_apply_environment", 0.4)
func _get_argument_value(prefix: String, default_value: String) -> String:
for argument in OS.get_cmdline_user_args():
if argument.begins_with(prefix):
var value := argument.trim_prefix(prefix)
if not value.is_empty():
return value
return default_value
func _apply_resource_capture_overrides() -> bool:
var hide_all_labels := OS.get_cmdline_user_args().has("--hide-debug-labels")
var hide_labels := hide_all_labels or OS.get_cmdline_user_args().has("--hide-resource-labels")
for resource in ResourceNode.get_all():
if hide_labels:
resource.set_debug_label_enabled(false)
var hide_animal_labels := (
hide_all_labels or OS.get_cmdline_user_args().has("--hide-animal-labels")
)
for animal in AnimalNode.get_all():
if hide_animal_labels:
animal.set_debug_label_enabled(false)
if hide_all_labels:
for storage in StorageNode.get_all():
storage.set_debug_label_enabled(false)
for site in ActivitySite.get_all():
site.set_debug_label_enabled(false)
for argument in OS.get_cmdline_user_args():
if argument.begins_with("--animal-hunger="):
var animal_override := argument.trim_prefix("--animal-hunger=")
var animal_parts := animal_override.split(":", false, 1)
if animal_parts.size() != 2 or not animal_parts[1].is_valid_float():
push_error("Invalid animal hunger override: %s" % animal_override)
return false
var animal := AnimalNode.get_by_id(StringName(animal_parts[0]))
if animal == null:
push_error("Unknown animal hunger override ID: %s" % animal_parts[0])
return false
var animal_state := AnimalStateRecord.create_from_node(animal)
animal_state.set_hunger(animal_parts[1].to_float())
if not animal.bind_state(animal_state):
push_error("Could not bind capture state for animal: %s" % animal_parts[0])
return false
continue
if not argument.begins_with("--resource-amount="):
continue
var override_text := argument.trim_prefix("--resource-amount=")
var parts := override_text.split(":", false, 1)
if parts.size() != 2 or not parts[1].is_valid_float():
push_error("Invalid resource amount override: %s" % override_text)
return false
var resource := ResourceNode.get_by_id(StringName(parts[0]))
if resource == null:
push_error("Unknown resource amount override ID: %s" % parts[0])
return false
var state := ResourceStateRecord.create_from_node(resource)
state.set_amount_remaining(parts[1].to_float())
if not resource.bind_state(state):
push_error("Could not bind capture state for resource: %s" % parts[0])
return false
return true
func _analyze_image(image: Image) -> Dictionary:
var min_luma := INF
var max_luma := -INF
var total_luma := 0.0
var samples := 0
var step_x = maxi(1, image.get_width() / 64)
var step_y = maxi(1, image.get_height() / 36)
for y in range(0, image.get_height(), step_y):
for x in range(0, image.get_width(), step_x):
var color := image.get_pixel(x, y)
var luma := color.r * 0.2126 + color.g * 0.7152 + color.b * 0.0722
min_luma = minf(min_luma, luma)
max_luma = maxf(max_luma, luma)
total_luma += luma
samples += 1
return {
"size": "%sx%s" % [image.get_width(), image.get_height()],
"samples": samples,
"average_luma": total_luma / samples,
"range": max_luma - min_luma,
}