diff --git a/assets/art/keyart.jpg b/assets/art/keyart.jpg new file mode 100644 index 0000000..1127f95 Binary files /dev/null and b/assets/art/keyart.jpg differ diff --git a/assets/art/keyart.jpg.import b/assets/art/keyart.jpg.import new file mode 100644 index 0000000..41d7da6 --- /dev/null +++ b/assets/art/keyart.jpg.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://drqh8c73yn7c3" +path="res://.godot/imported/keyart.jpg-a17068a646e54c982c021f2d854b8d99.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/keyart.jpg" +dest_files=["res://.godot/imported/keyart.jpg-a17068a646e54c982c021f2d854b8d99.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/art/sleeves_a.jpg b/assets/art/sleeves_a.jpg new file mode 100644 index 0000000..1217e72 Binary files /dev/null and b/assets/art/sleeves_a.jpg differ diff --git a/assets/art/sleeves_a.jpg.import b/assets/art/sleeves_a.jpg.import new file mode 100644 index 0000000..6abd784 --- /dev/null +++ b/assets/art/sleeves_a.jpg.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dkqsrlw0qbq8q" +path="res://.godot/imported/sleeves_a.jpg-c3eb7828a71203698e54664c9ba6c8ac.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/sleeves_a.jpg" +dest_files=["res://.godot/imported/sleeves_a.jpg-c3eb7828a71203698e54664c9ba6c8ac.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/art/sleeves_b.jpg b/assets/art/sleeves_b.jpg new file mode 100644 index 0000000..4dd5a0a Binary files /dev/null and b/assets/art/sleeves_b.jpg differ diff --git a/assets/art/sleeves_b.jpg.import b/assets/art/sleeves_b.jpg.import new file mode 100644 index 0000000..50e82ac --- /dev/null +++ b/assets/art/sleeves_b.jpg.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxq5sjamvdegc" +path="res://.godot/imported/sleeves_b.jpg-5f25abc3a10a57693e8df03cba6dbd6d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/sleeves_b.jpg" +dest_files=["res://.godot/imported/sleeves_b.jpg-5f25abc3a10a57693e8df03cba6dbd6d.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/scripts/Main.gd b/scripts/Main.gd index 0800c65..f7897ba 100644 --- a/scripts/Main.gd +++ b/scripts/Main.gd @@ -82,6 +82,16 @@ const RACK_GLB := "res://assets/store/rack.glb" const CRATE_GLB := "res://assets/store/crate.glb" const RECORD_GLB := "res://assets/store/record.glb" +# Generated sleeve art (Nano Banana): each file is a 3x3 sheet of nine original +# fictional album covers. A record picks one tile at random via uv1 scale/offset — +# one texture in VRAM, eighteen different-looking records on the floor. +const SLEEVE_ATLASES: Array[String] = [ + "res://assets/art/sleeves_a.jpg", + "res://assets/art/sleeves_b.jpg", +] +var _sleeve_textures: Array[Texture2D] = [] +var _records_skinned := 0 + func _build_rack(origin: Vector3) -> void: # two racks form a smashable back wall of the booth _glb_piece(RACK_GLB, "wood", Vector2(origin.x - 0.95, origin.z - 0.7), true) @@ -101,6 +111,7 @@ func _build_rack(origin: Vector3) -> void: var jitter := Vector2(randf_range(-0.06, 0.06), randf_range(-0.06, 0.06)) var rec := _glb_piece(RECORD_GLB, "vinyl", xz + jitter, false, crate_top + float(i) * 0.03) (crate["piece"] as Smashable).supports.append(rec["piece"]) + print("[art] records skinned with sleeve art: ", _records_skinned) ## Instance a GLB as the VISUAL of a Smashable, give it a box collider auto-sized to ## the mesh AABB, and drop it so its bottom rests at height `sit_on`. Returns @@ -112,6 +123,8 @@ func _glb_piece(path: String, kind: String, xz: Vector2, frozen: bool, sit_on := var packed := load(path) as PackedScene var vis: Node3D = packed.instantiate() s.add_child(vis) + if kind == "vinyl": + _skin_record(vis) # random sleeve art on the jacket faces _world.add_child(s) # in-tree so transforms/AABBs resolve var ab := _body_local_aabb(s, vis) if ab.size == Vector3.ZERO: @@ -126,6 +139,43 @@ func _glb_piece(path: String, kind: String, xz: Vector2, frozen: bool, sit_on := s.smashed.connect(_on_smashed) return {"piece": s, "top": sit_on + ab.size.y} +## Give a record's sleeve faces a random cover from the generated 3x3 sheets. +## The record.glb sleeve cube carries M_Sleeve_Front / M_Sleeve_Back materials +## with real UVs — we duplicate them and window one tile of the atlas via +## uv1_scale/uv1_offset (no image slicing, one texture serves nine covers). +func _skin_record(vis: Node3D) -> void: + if _sleeve_textures.is_empty(): + for p in SLEEVE_ATLASES: + var t := load(p) as Texture2D + if t != null: + _sleeve_textures.append(t) + if _sleeve_textures.is_empty(): + return # art not imported yet — records stay plain + var tex: Texture2D = _sleeve_textures[randi() % _sleeve_textures.size()] + var tile_col := randi() % 3 + var tile_row := randi() % 3 + var mis: Array[MeshInstance3D] = [] + _collect_meshes(vis, mis) + var did := false + for mi in mis: + if mi.mesh == null: + continue + for si in mi.mesh.get_surface_count(): + var mat := mi.mesh.surface_get_material(si) + if mat == null or not (mat is BaseMaterial3D): + continue + var nm: String = mat.resource_name + if nm.contains("Sleeve_Front") or nm.contains("Sleeve_Back"): + var dup := mat.duplicate() as BaseMaterial3D + dup.albedo_color = Color.WHITE + dup.albedo_texture = tex + dup.uv1_scale = Vector3(1.0 / 3.0, 1.0 / 3.0, 1.0) + dup.uv1_offset = Vector3(tile_col / 3.0, tile_row / 3.0, 0.0) + mi.set_surface_override_material(si, dup) + did = true + if did: + _records_skinned += 1 + ## Combined AABB of every MeshInstance3D under `visual_root`, in `body`'s local space ## (independent of where `body` currently sits in the world). func _body_local_aabb(body: Node3D, visual_root: Node3D) -> AABB: