diff --git a/assets/textures/sky_dusk.jpg b/assets/textures/sky_dusk.jpg new file mode 100644 index 0000000..73dee50 Binary files /dev/null and b/assets/textures/sky_dusk.jpg differ diff --git a/assets/textures/sky_dusk.jpg.import b/assets/textures/sky_dusk.jpg.import new file mode 100644 index 0000000..0702f5b --- /dev/null +++ b/assets/textures/sky_dusk.jpg.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bijmj1x4hi11a" +path="res://.godot/imported/sky_dusk.jpg-63e4d345e86325d5834f66bcdedd0851.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/sky_dusk.jpg" +dest_files=["res://.godot/imported/sky_dusk.jpg-63e4d345e86325d5834f66bcdedd0851.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/textures/sky_night.jpg b/assets/textures/sky_night.jpg new file mode 100644 index 0000000..6639c18 Binary files /dev/null and b/assets/textures/sky_night.jpg differ diff --git a/assets/textures/sky_night.jpg.import b/assets/textures/sky_night.jpg.import new file mode 100644 index 0000000..e629fc2 --- /dev/null +++ b/assets/textures/sky_night.jpg.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://2rackpqbvwp2" +path="res://.godot/imported/sky_night.jpg-79b8d08aa9423f4579159cfb2d221005.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/sky_night.jpg" +dest_files=["res://.godot/imported/sky_night.jpg-79b8d08aa9423f4579159cfb2d221005.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/shot_dusk.png.import b/shot_dusk.png.import new file mode 100644 index 0000000..06040c8 --- /dev/null +++ b/shot_dusk.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ckgqxm67w4wjg" +path="res://.godot/imported/shot_dusk.png-f6af1b6e4e64591ef1de1c661224bed5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://shot_dusk.png" +dest_files=["res://.godot/imported/shot_dusk.png-f6af1b6e4e64591ef1de1c661224bed5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/shot_new_bubbla_600.png.import b/shot_new_bubbla_600.png.import new file mode 100644 index 0000000..ee89fdd --- /dev/null +++ b/shot_new_bubbla_600.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ceecujmymhp7v" +path="res://.godot/imported/shot_new_bubbla_600.png-05b2db194b7a014fc14c973c912e5583.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://shot_new_bubbla_600.png" +dest_files=["res://.godot/imported/shot_new_bubbla_600.png-05b2db194b7a014fc14c973c912e5583.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/shot_new_falcodore_gtho.png.import b/shot_new_falcodore_gtho.png.import new file mode 100644 index 0000000..ae27d47 --- /dev/null +++ b/shot_new_falcodore_gtho.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://mll58atwcrp6" +path="res://.godot/imported/shot_new_falcodore_gtho.png-e66ed2ab5a9d8d094ec961c70879ba93.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://shot_new_falcodore_gtho.png" +dest_files=["res://.godot/imported/shot_new_falcodore_gtho.png-e66ed2ab5a9d8d094ec961c70879ba93.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/shot_new_hardon_waggo.png.import b/shot_new_hardon_waggo.png.import new file mode 100644 index 0000000..008c5f4 --- /dev/null +++ b/shot_new_hardon_waggo.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c7vogu04epx1b" +path="res://.godot/imported/shot_new_hardon_waggo.png-070bed85dde44ad7d2157c19c292a0a0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://shot_new_hardon_waggo.png" +dest_files=["res://.godot/imported/shot_new_hardon_waggo.png-070bed85dde44ad7d2157c19c292a0a0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/shot_new_hardon_wb_ute.png.import b/shot_new_hardon_wb_ute.png.import new file mode 100644 index 0000000..bc22849 --- /dev/null +++ b/shot_new_hardon_wb_ute.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bgjfj2d166iw" +path="res://.godot/imported/shot_new_hardon_wb_ute.png-7ed5d240147f85803b553146a1f90363.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://shot_new_hardon_wb_ute.png" +dest_files=["res://.godot/imported/shot_new_hardon_wb_ute.png-7ed5d240147f85803b553146a1f90363.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/shot_night.png.import b/shot_night.png.import new file mode 100644 index 0000000..b4a1b09 --- /dev/null +++ b/shot_night.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ssfx4eea0hio" +path="res://.godot/imported/shot_night.png-18957e7f26c9433700879ac3d7e65fdb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://shot_night.png" +dest_files=["res://.godot/imported/shot_night.png-18957e7f26c9433700879ac3d7e65fdb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/src/game.gd b/src/game.gd index 3a5f19b..744523b 100644 --- a/src/game.gd +++ b/src/game.gd @@ -10,6 +10,7 @@ extends Node3D static var car_path := "" static var level_path := "" static var mode := "cruise" +static var time_of_day := "day" # day | dusk | night static var junction: Dictionary = {} const LAPS := 3 @@ -493,11 +494,22 @@ func _spawn_smashables(level: Node) -> void: hmat.albedo_color = Color(0.86, 0.84, 0.72) hmat.emission_enabled = true hmat.emission = Color(0.9, 0.85, 0.6) - hmat.emission_energy_multiplier = 0.35 + hmat.emission_energy_multiplier = 0.35 if time_of_day != "night" else 3.5 hm.material = hmat head.mesh = hm head.position = Vector3(1.45, size.y * 0.5 - 0.36, 0) b.add_child(head) + if time_of_day == "night": + # a real light under the lantern -- and because it's parented + # to the physics body, toppling the lamp takes its pool of + # light with it, which is deeply satisfying + var ol := OmniLight3D.new() + ol.light_color = Color(1.0, 0.87, 0.55) + ol.light_energy = 1.6 + ol.omni_range = 13.0 + ol.omni_attenuation = 1.4 + ol.position = head.position - Vector3(0, 0.5, 0) + b.add_child(ol) add_child(b) var t: Transform3D = (m as Node3D).global_transform b.global_transform = Transform3D( @@ -507,12 +519,22 @@ func _spawn_smashables(level: Node) -> void: n += 1 func _build_environment() -> void: - ## Brisbane midday: hard high sun, deep blue zenith, everything a bit blown out. - ## The bare default (no tonemap, grey ambient) rendered the city flat and dead. + ## Three Brisbanes: midday (hard high sun), dusk (low orange sun under a FLUX + ## panorama) and night (moonlight, working street lamps, headlights). var sun := DirectionalLight3D.new() - sun.rotation_degrees = Vector3(-62, 38, 0) - sun.light_color = Color(1.0, 0.96, 0.88) - sun.light_energy = 1.25 + match time_of_day: + "dusk": + sun.rotation_degrees = Vector3(-11, 55, 0) + sun.light_color = Color(1.0, 0.62, 0.32) + sun.light_energy = 0.85 + "night": + sun.rotation_degrees = Vector3(-48, -20, 0) + sun.light_color = Color(0.55, 0.65, 0.90) # moon + sun.light_energy = 0.14 + _: + sun.rotation_degrees = Vector3(-62, 38, 0) + sun.light_color = Color(1.0, 0.96, 0.88) + sun.light_energy = 1.25 sun.shadow_enabled = true sun.directional_shadow_mode = DirectionalLight3D.SHADOW_PARALLEL_4_SPLITS sun.directional_shadow_max_distance = 220.0 @@ -533,14 +555,24 @@ func _build_environment() -> void: fill.shadow_enabled = false add_child(fill) - var sky_mat := ProceduralSkyMaterial.new() - sky_mat.sky_top_color = Color(0.27, 0.46, 0.74) - sky_mat.sky_horizon_color = Color(0.80, 0.86, 0.92) - sky_mat.sky_energy_multiplier = 1.0 - sky_mat.ground_bottom_color = Color(0.28, 0.28, 0.30) - sky_mat.ground_horizon_color = Color(0.66, 0.72, 0.78) - sky_mat.sun_angle_max = 4.0 - sky_mat.sun_curve = 0.08 + # dusk/night ride FLUX panoramas; day keeps the procedural gradient + var sky_mat: Material + var pano := "res://assets/textures/sky_%s.jpg" % time_of_day + if time_of_day != "day" and ResourceLoader.exists(pano): + var pm := PanoramaSkyMaterial.new() + pm.panorama = load(pano) + pm.energy_multiplier = 1.3 if time_of_day == "dusk" else 0.9 + sky_mat = pm + else: + var gm := ProceduralSkyMaterial.new() + gm.sky_top_color = Color(0.27, 0.46, 0.74) + gm.sky_horizon_color = Color(0.80, 0.86, 0.92) + gm.sky_energy_multiplier = 1.0 + gm.ground_bottom_color = Color(0.28, 0.28, 0.30) + gm.ground_horizon_color = Color(0.66, 0.72, 0.78) + gm.sun_angle_max = 4.0 + gm.sun_curve = 0.08 + sky_mat = gm var e := Environment.new() e.background_mode = Environment.BG_SKY @@ -550,8 +582,16 @@ func _build_environment() -> void: # a street canyon is lit almost entirely by ambient, so pure sky ambient # painted the whole city blue -- blend it half-and-half with neutral e.ambient_light_sky_contribution = 0.5 - e.ambient_light_color = Color(0.88, 0.87, 0.84) - e.ambient_light_energy = 0.55 + match time_of_day: + "dusk": + e.ambient_light_color = Color(0.80, 0.62, 0.55) + e.ambient_light_energy = 0.38 + "night": + e.ambient_light_color = Color(0.35, 0.40, 0.55) + e.ambient_light_energy = 0.16 + _: + e.ambient_light_color = Color(0.88, 0.87, 0.84) + e.ambient_light_energy = 0.55 e.reflected_light_source = Environment.REFLECTION_SOURCE_SKY e.tonemap_mode = Environment.TONE_MAPPER_ACES e.tonemap_exposure = 0.6 @@ -573,10 +613,28 @@ func _build_environment() -> void: e.fog_depth_end = 900.0 e.fog_depth_curve = 0.7 e.fog_sun_scatter = 0.15 + if time_of_day == "night": + e.glow_intensity = 0.8 # lamp heads and tail lights bloom + e.fog_light_color = Color(0.10, 0.11, 0.16) + elif time_of_day == "dusk": + e.fog_light_color = Color(0.55, 0.38, 0.30) var env := WorldEnvironment.new() env.environment = e add_child(env) + if time_of_day == "night": + # headlights on the player: two warm spots raking the road ahead + for sx in [-0.55, 0.55]: + var hl := SpotLight3D.new() + hl.light_color = Color(1.0, 0.94, 0.78) + hl.light_energy = 4.0 + hl.spot_range = 42.0 + hl.spot_angle = 28.0 + hl.spot_angle_attenuation = 1.6 + hl.position = Vector3(sx, 0.15, -car.stats.length * 0.48) + hl.rotation_degrees = Vector3(-4.0, 180.0, 0) + car.add_child(hl) + func _exit_tree() -> void: Engine.time_scale = 1.0 diff --git a/src/main.gd b/src/main.gd index 2020fc6..1c263d2 100644 --- a/src/main.gd +++ b/src/main.gd @@ -3,10 +3,12 @@ extends Control const MODES := [["cruise", "Cruise"], ["crash", "Crash Mode"], ["race", "Race"], ["rage", "Road Rage"], ["drag", "Drag"], ["derby", "Derby"]] +const TIMES := [["day", "Day"], ["dusk", "Dusk"], ["night", "Night"]] @onready var car_list: ItemList = $VBox/HBox/Cars @onready var level_list: ItemList = $VBox/HBox/Levels @onready var mode_list: ItemList = $VBox/HBox/Modes +@onready var time_list: ItemList = $VBox/HBox/Times var car_paths: Array = [] var level_paths: Array = [] @@ -29,6 +31,9 @@ func _ready() -> void: junction_data.append(j.data) for m in MODES: mode_list.add_item(m[1]) + for t in TIMES: + time_list.add_item(t[1]) + time_list.select(0) if not car_paths.is_empty(): car_list.select(0) if not level_paths.is_empty(): @@ -45,4 +50,6 @@ func _start() -> void: Game.junction = junction_data[li] # picking a CRASH JCT entry forces junction mode, whatever the mode column says Game.mode = "junction" if not Game.junction.is_empty() else MODES[mode_list.get_selected_items()[0]][0] + var ti := time_list.get_selected_items() + Game.time_of_day = TIMES[ti[0]][0] if not ti.is_empty() else "day" get_tree().change_scene_to_file("res://src/game.tscn") diff --git a/src/main.tscn b/src/main.tscn index c1f3c0b..5eca3d2 100644 --- a/src/main.tscn +++ b/src/main.tscn @@ -51,6 +51,10 @@ size_flags_horizontal = 3 [node name="Modes" type="ItemList" parent="VBox/HBox"] size_flags_horizontal = 3 +[node name="Times" type="ItemList" parent="VBox/HBox"] +size_flags_horizontal = 3 +size_flags_stretch_ratio = 0.5 + [node name="Start" type="Button" parent="VBox"] text = "RACE" theme_override_font_sizes/font_size = 32 diff --git a/tools/flux_local.py b/tools/flux_local.py index efd99bc..c745223 100644 --- a/tools/flux_local.py +++ b/tools/flux_local.py @@ -1,7 +1,10 @@ #!/usr/bin/env python3 """Generate a texture on the local MODELBEAST farm (operator: flux_local). -Usage: flux_local.py "" [size_px] [target_luma] +Usage: flux_local.py "" [size_px] [target_luma] [WxH] + +size_px is the output's max dimension; WxH overrides the GENERATION aspect +(e.g. 1024x512 for a 2:1 sky panorama). Output keeps the generation aspect. The fallback for tools/flux_texture.sh when Cloudflare Workers AI returns 429 -- its free tier is 10,000 neurons/day and a full texture-set regen burns through @@ -48,9 +51,12 @@ def main(): luma = float(sys.argv[4]) if len(sys.argv) > 4 else 0.0 tok = token() - gen = max(size, 768) # generate at >=768 then downscale, same as the CF path + if len(sys.argv) > 5 and "x" in sys.argv[5]: + gw, gh = (int(v) for v in sys.argv[5].split("x")) + else: + gw = gh = max(size, 768) # generate at >=768 then downscale, like the CF path job = call("/api/jobs", tok, {"operator": "flux_local", "params": { - "prompt": prompt, "width": gen, "height": gen, "steps": 6}}) + "prompt": prompt, "width": gw, "height": gh, "steps": 6}}) jid = job["id"] for _ in range(120): time.sleep(3) @@ -75,7 +81,9 @@ def main(): open(tmp, "wb").write(blob) from PIL import Image, ImageStat - im = Image.open(tmp).convert("RGB").resize((size, size), Image.LANCZOS) + im = Image.open(tmp).convert("RGB") + scale = size / max(im.size) + im = im.resize((max(1, int(im.width * scale)), max(1, int(im.height * scale))), Image.LANCZOS) if luma: mean = ImageStat.Stat(im.convert("L")).mean[0] if mean > luma: