Records wear real sleeves: Nano Banana art atlases + UV-tile skinning
- assets/art/sleeves_{a,b}.jpg: two 3x3 sheets of ORIGINAL fictional album
covers (18 total — dub, techno, metal, jazz, punk, synthwave, hip-hop,
psych, ambient, northern soul, krautrock, disco, white-label, bossa,
UKG, country, noise) generated with Gemini gemini-3-pro-image. No real
artists/albums — copyright-clean destructible content.
- assets/art/keyart.jpg: DESTROYULATOR title key art ("CLOCK OUT. SMASH
EVERYTHING.") for a future title screen.
- Main.gd _skin_record(): records duplicate their M_Sleeve_Front/Back
materials and window one random atlas tile via uv1_scale/uv1_offset —
one texture in VRAM serves nine covers, every record looks different.
- Verified headless: boots clean, "[art] records skinned: 15" (all of
them). The two dummy-renderer "material is null" prints are headless-
only (override materials have no RID in the dummy backend). Eyeball a
sleeve in the editor — if the art renders rotated, flip uv1 here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7c88e8371d
commit
b78ea04183
BIN
assets/art/keyart.jpg
Normal file
BIN
assets/art/keyart.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 766 KiB |
40
assets/art/keyart.jpg.import
Normal file
40
assets/art/keyart.jpg.import
Normal file
@ -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
|
||||
BIN
assets/art/sleeves_a.jpg
Normal file
BIN
assets/art/sleeves_a.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
40
assets/art/sleeves_a.jpg.import
Normal file
40
assets/art/sleeves_a.jpg.import
Normal file
@ -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
|
||||
BIN
assets/art/sleeves_b.jpg
Normal file
BIN
assets/art/sleeves_b.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
40
assets/art/sleeves_b.jpg.import
Normal file
40
assets/art/sleeves_b.jpg.import
Normal file
@ -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
|
||||
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user