feat(assets): wardrobe wave 2 — footwear, sportswear, swimwear, sweater, cargos, thrds parka, slides (T-1089)
Twelve garments, per-body on all 11 bodies, chromakey-gated (worst clips: parka 3px, boots 54, sweater/tank 83, sneakers 100 final-geometry, slides 123, swim trunks 132, cargo 138 — all under the 150px gate), previewed: tank top, sweater (crew-neck via per-body ring-valley probe — first draft read mock-neck, fixed by measured rim circularization), track jacket (recolorable sleeve-stripe region), joggers (side-stripe region), cargo pants, swim trunks, one-piece swimsuit, sneakers (prism-sole), formal shoes, ankle boots (calf shaft), slides (open strap + sole), and the hip-length thrds parka — the first canon-branded garment (Braemar cold-weather cooperative), quilted, logo-capable. Tops now hem into real hip geometry (the natural seg_torso bottom is a 9-14cm tooth ring — the sweater established the hem-into-hips practice). Manifest merged by the lead: 24 clothing entries with region/default-tint metadata. Full modern catalogue: 21 garments across tops/bottoms/feet/ full-body x casual/formal/sport/swim/outerwear. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
extends SceneTree
|
||||
## Headless load check: verify garment GLB scenes load and their albedo
|
||||
## texture resolves. Usage:
|
||||
## godot4 --path client --headless --script res://tools/garment_preview/load_check.gd
|
||||
## Temporary tool script — safe to delete after wave-2 integration.
|
||||
|
||||
const ITEMS := [
|
||||
"res://assets/characters/clothing/jeans_modern/average_m.glb",
|
||||
"res://assets/characters/clothing/swim_trunks/average_m.glb",
|
||||
"res://assets/characters/clothing/swim_trunks/heavy_m.glb",
|
||||
]
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
for path in ITEMS:
|
||||
_check(path)
|
||||
quit(0)
|
||||
|
||||
|
||||
func _check(path: String) -> void:
|
||||
if not ResourceLoader.exists(path):
|
||||
print("loadcheck: MISSING ", path)
|
||||
return
|
||||
var ps := load(path) as PackedScene
|
||||
if ps == null:
|
||||
print("loadcheck: LOAD FAILED ", path)
|
||||
return
|
||||
var inst := ps.instantiate()
|
||||
var found := false
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D:
|
||||
var mi := n as MeshInstance3D
|
||||
if mi.mesh == null:
|
||||
continue
|
||||
for surf in range(mi.mesh.get_surface_count()):
|
||||
var m := mi.mesh.surface_get_material(surf)
|
||||
if m is BaseMaterial3D:
|
||||
var tex := (m as BaseMaterial3D).albedo_texture
|
||||
if tex != null:
|
||||
print("loadcheck: OK ", path, " albedo=", tex.get_class(),
|
||||
" ", tex.get_width(), "x", tex.get_height())
|
||||
found = true
|
||||
if not found:
|
||||
print("loadcheck: NO ALBEDO ", path)
|
||||
inst.free()
|
||||
@@ -0,0 +1,198 @@
|
||||
extends Node3D
|
||||
## boots_modern preview capture (T-1089 wave 2, footwear set) — parameterized
|
||||
## copy of preview_swim_trunks.gd for a FOOTWEAR 3-region garment: per-body
|
||||
## region mask (reference fallback), no logo, camera framed low on the feet +
|
||||
## boot shafts so the chunky sole, lace bars/eyelets and the shaft rim read.
|
||||
## Front + 3/4 Walk renders. Temporary tool scene — safe to delete after
|
||||
## captures.
|
||||
##
|
||||
## Config via env vars (all optional):
|
||||
## GARMENT_PREVIEW_ITEM default "boots_modern"
|
||||
## GARMENT_PREVIEW_BODY default "average_m"
|
||||
## GARMENT_PREVIEW_OUT default "<repo>/.cache/garment-preview/boots_modern"
|
||||
|
||||
const CLOTHING_DIR := "res://assets/characters/clothing/"
|
||||
const SHADER_PATH := "res://assets/characters/shaders/toon_garment.gdshader"
|
||||
const VIEWPORT := Vector2i(768, 1024)
|
||||
|
||||
const BODY_KEY_TO_ENUM := {
|
||||
"average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M,
|
||||
"average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F,
|
||||
"muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M,
|
||||
"muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F,
|
||||
"thin_m": CharacterVisualDescriptor.BodyType.THIN_M,
|
||||
"thin_f": CharacterVisualDescriptor.BodyType.THIN_F,
|
||||
"heavy_m": CharacterVisualDescriptor.BodyType.HEAVY_M,
|
||||
"heavy_f": CharacterVisualDescriptor.BodyType.HEAVY_F,
|
||||
"teen_m": CharacterVisualDescriptor.BodyType.TEEN_M,
|
||||
"teen_f": CharacterVisualDescriptor.BodyType.TEEN_F,
|
||||
"child": CharacterVisualDescriptor.BodyType.CHILD,
|
||||
}
|
||||
|
||||
# Brown/black work-street default (spec) — matches the manifest default_tints
|
||||
# so tinted and untinted renders agree: sole near-black rubber, upper work
|
||||
# brown, laces/eyelets dark.
|
||||
const TINT_SOLE := Color(0.16, 0.15, 0.14) # R region, near-black rubber
|
||||
const TINT_UPPER := Color(0.44, 0.29, 0.18) # G region, work brown leather
|
||||
const TINT_LACES := Color(0.22, 0.18, 0.15) # B region, dark laces
|
||||
|
||||
var _out_dir := ""
|
||||
var _cam: Camera3D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var item := _env("GARMENT_PREVIEW_ITEM", "boots_modern")
|
||||
var body := _env("GARMENT_PREVIEW_BODY", "average_m")
|
||||
_out_dir = _env("GARMENT_PREVIEW_OUT",
|
||||
"/var/mnt/data/projects/settled-reach/.cache/garment-preview/boots_modern")
|
||||
get_window().size = VIEWPORT
|
||||
_setup_stage()
|
||||
_run.call_deferred(item, body)
|
||||
|
||||
|
||||
func _env(key: String, fallback: String) -> String:
|
||||
var v := OS.get_environment(key)
|
||||
return v if not v.is_empty() else fallback
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-38, 28, 0)
|
||||
light.light_energy = 1.3
|
||||
add_child(light)
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.82, 0.82, 0.86)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.16, 0.17, 0.20)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
_cam = Camera3D.new()
|
||||
_cam.fov = 40.0
|
||||
add_child(_cam)
|
||||
# Frame low on the feet + boot shafts (rim ~0.15-0.22 m): sole slab, lace
|
||||
# panel and shaft rim must all read; slight top-down angle shows the sole
|
||||
# lip. Distance leaves room for the Walk stride toward the camera.
|
||||
_cam.position = Vector3(0.0, 0.42, 1.35)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0.0, 0.16, 0.0), Vector3.UP)
|
||||
|
||||
|
||||
func _run(item: String, body: String) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(_out_dir)
|
||||
if not BODY_KEY_TO_ENUM.has(body):
|
||||
push_error("preview: unknown body %s" % body)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var cv := CharacterVisual.new()
|
||||
add_child(cv)
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = BODY_KEY_TO_ENUM[body]
|
||||
desc.head_id = "head_001"
|
||||
desc.hair_id = "buzzed"
|
||||
desc.eyebrow_id = "regular"
|
||||
desc.skin_tone = 3
|
||||
cv.load_descriptor(desc)
|
||||
|
||||
var skel := cv.get_skeleton()
|
||||
if skel == null:
|
||||
push_error("preview: no skeleton")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var meshes := _attach_garment(skel, item, body)
|
||||
print("preview: attached %d garment meshes" % meshes.size())
|
||||
var shader := load(SHADER_PATH) as Shader
|
||||
# Per-body region mask (offset-shell route); reference fallback mirrors
|
||||
# the runtime compositor.
|
||||
var mask := _try_load(CLOTHING_DIR + "%s/%s_mask.png" % [item, body])
|
||||
if mask == null:
|
||||
mask = _try_load(CLOTHING_DIR + "%s/reference_mask.png" % item)
|
||||
for mi in meshes:
|
||||
_apply_garment_shader(mi, shader, mask)
|
||||
|
||||
# Freeze on a mid-stride Walk pose so the sole, laces and shaft rim read
|
||||
# under motion (front foot planted, back heel lifting).
|
||||
var ap := cv.get_animation_player()
|
||||
if ap != null:
|
||||
var walk := _resolve_clip(ap, "Walk")
|
||||
if not walk.is_empty():
|
||||
cv.play_animation(walk)
|
||||
ap.pause()
|
||||
ap.seek(ap.current_animation_length * 0.25, true)
|
||||
await get_tree().process_frame
|
||||
|
||||
# Front + 3/4 (task deliverable: two preview renders).
|
||||
for yaw in [0, 45]:
|
||||
cv.rotation_degrees.y = float(yaw)
|
||||
await RenderingServer.frame_post_draw
|
||||
await RenderingServer.frame_post_draw
|
||||
var fname := "%s__%s__yaw%03d.png" % [item, body, yaw]
|
||||
get_viewport().get_texture().get_image().save_png(_out_dir.path_join(fname))
|
||||
print("preview: wrote ", fname)
|
||||
|
||||
print("preview: DONE")
|
||||
get_tree().quit(0)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D, item: String, body: String) -> Array:
|
||||
var out: Array = []
|
||||
var glb := CLOTHING_DIR + "%s/%s.glb" % [item, body]
|
||||
if not ResourceLoader.exists(glb):
|
||||
push_error("preview: garment GLB missing %s" % glb)
|
||||
return out
|
||||
var inst := (load(glb) as PackedScene).instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
out.append(mi)
|
||||
inst.queue_free()
|
||||
return out
|
||||
|
||||
|
||||
func _apply_garment_shader(mi: MeshInstance3D, shader: Shader, mask: Texture2D) -> void:
|
||||
if mi.mesh == null or shader == null:
|
||||
return
|
||||
var albedo := _albedo_of(mi)
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
if albedo:
|
||||
mat.set_shader_parameter("albedo_tex", albedo)
|
||||
if mask:
|
||||
mat.set_shader_parameter("region_mask", mask)
|
||||
mat.set_shader_parameter("tint_0", TINT_SOLE)
|
||||
mat.set_shader_parameter("tint_1", TINT_UPPER)
|
||||
mat.set_shader_parameter("tint_2", TINT_LACES)
|
||||
mat.set_shader_parameter("tint_3", Color.WHITE)
|
||||
mat.set_shader_parameter("shadow_strength", 0.2)
|
||||
mi.material_override = mat
|
||||
|
||||
|
||||
func _albedo_of(mi: MeshInstance3D) -> Texture2D:
|
||||
for surf in range(mi.mesh.get_surface_count()):
|
||||
var m := mi.mesh.surface_get_material(surf)
|
||||
if m is BaseMaterial3D:
|
||||
return (m as BaseMaterial3D).albedo_texture
|
||||
return null
|
||||
|
||||
|
||||
func _try_load(path: String) -> Texture2D:
|
||||
return load(path) as Texture2D if ResourceLoader.exists(path) else null
|
||||
|
||||
|
||||
func _resolve_clip(ap: AnimationPlayer, requested: String) -> String:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
if str(a).to_lower().contains(requested.to_lower()):
|
||||
return str(a)
|
||||
return ""
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/garment_preview/preview_boots_modern.gd" id="1_prevbt"]
|
||||
|
||||
[node name="GarmentPreviewBootsModern" type="Node3D"]
|
||||
script = ExtResource("1_prevbt")
|
||||
@@ -0,0 +1,195 @@
|
||||
extends Node3D
|
||||
## cargo_pants preview capture (T-1089 wave 2) — parameterized copy of
|
||||
## preview_shorts_modern.gd for a LOWER-BODY 3-region garment: per-body region
|
||||
## mask (reference fallback), no logo, camera framed on the full leg span
|
||||
## (waistband to ankle hem) so the thigh cargo pockets and straight fit read.
|
||||
## Front + 3/4 Walk renders. Temporary tool scene — safe to delete after
|
||||
## captures.
|
||||
##
|
||||
## Config via env vars (all optional):
|
||||
## GARMENT_PREVIEW_ITEM default "cargo_pants"
|
||||
## GARMENT_PREVIEW_BODY default "average_m"
|
||||
## GARMENT_PREVIEW_OUT default "<repo>/.cache/garment-preview/cargo_pants"
|
||||
|
||||
const CLOTHING_DIR := "res://assets/characters/clothing/"
|
||||
const SHADER_PATH := "res://assets/characters/shaders/toon_garment.gdshader"
|
||||
const VIEWPORT := Vector2i(768, 1024)
|
||||
|
||||
const BODY_KEY_TO_ENUM := {
|
||||
"average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M,
|
||||
"average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F,
|
||||
"muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M,
|
||||
"muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F,
|
||||
"thin_m": CharacterVisualDescriptor.BodyType.THIN_M,
|
||||
"thin_f": CharacterVisualDescriptor.BodyType.THIN_F,
|
||||
"heavy_m": CharacterVisualDescriptor.BodyType.HEAVY_M,
|
||||
"heavy_f": CharacterVisualDescriptor.BodyType.HEAVY_F,
|
||||
"teen_m": CharacterVisualDescriptor.BodyType.TEEN_M,
|
||||
"teen_f": CharacterVisualDescriptor.BodyType.TEEN_F,
|
||||
"child": CharacterVisualDescriptor.BodyType.CHILD,
|
||||
}
|
||||
|
||||
# Utility palette: charcoal waistband, olive legs, grey-olive pockets+flaps
|
||||
# (tint_2 demonstrates the B region recolors independently). tint_3 unused.
|
||||
const TINT_WAISTBAND := Color(0.13, 0.14, 0.16) # R region
|
||||
const TINT_LEGS := Color(0.45, 0.47, 0.38) # G region
|
||||
const TINT_POCKETS := Color(0.36, 0.37, 0.33) # B region
|
||||
|
||||
var _out_dir := ""
|
||||
var _cam: Camera3D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var item := _env("GARMENT_PREVIEW_ITEM", "cargo_pants")
|
||||
var body := _env("GARMENT_PREVIEW_BODY", "average_m")
|
||||
_out_dir = _env("GARMENT_PREVIEW_OUT",
|
||||
"/var/mnt/data/projects/settled-reach/.cache/garment-preview/cargo_pants")
|
||||
get_window().size = VIEWPORT
|
||||
_setup_stage()
|
||||
_run.call_deferred(item, body)
|
||||
|
||||
|
||||
func _env(key: String, fallback: String) -> String:
|
||||
var v := OS.get_environment(key)
|
||||
return v if not v.is_empty() else fallback
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-38, 28, 0)
|
||||
light.light_energy = 1.3
|
||||
add_child(light)
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.82, 0.82, 0.86)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.16, 0.17, 0.20)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
_cam = Camera3D.new()
|
||||
_cam.fov = 45.0
|
||||
add_child(_cam)
|
||||
# Frame the full leg span (waistband ~1.03 down to the ankle hem) so the
|
||||
# thigh cargo pockets and the straight-fit silhouette both read.
|
||||
_cam.position = Vector3(0.0, 0.72, 2.05)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0.0, 0.60, 0.0), Vector3.UP)
|
||||
|
||||
|
||||
func _run(item: String, body: String) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(_out_dir)
|
||||
if not BODY_KEY_TO_ENUM.has(body):
|
||||
push_error("preview: unknown body %s" % body)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var cv := CharacterVisual.new()
|
||||
add_child(cv)
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = BODY_KEY_TO_ENUM[body]
|
||||
desc.head_id = "head_001"
|
||||
desc.hair_id = "buzzed"
|
||||
desc.eyebrow_id = "regular"
|
||||
desc.skin_tone = 3
|
||||
cv.load_descriptor(desc)
|
||||
|
||||
var skel := cv.get_skeleton()
|
||||
if skel == null:
|
||||
push_error("preview: no skeleton")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var meshes := _attach_garment(skel, item, body)
|
||||
print("preview: attached %d garment meshes" % meshes.size())
|
||||
var shader := load(SHADER_PATH) as Shader
|
||||
# Per-body region mask (offset-shell route); reference fallback mirrors
|
||||
# the runtime compositor.
|
||||
var mask := _try_load(CLOTHING_DIR + "%s/%s_mask.png" % [item, body])
|
||||
if mask == null:
|
||||
mask = _try_load(CLOTHING_DIR + "%s/reference_mask.png" % item)
|
||||
for mi in meshes:
|
||||
_apply_garment_shader(mi, shader, mask)
|
||||
|
||||
# Freeze on a mid-stride Walk pose so pockets + hem read under motion.
|
||||
var ap := cv.get_animation_player()
|
||||
if ap != null:
|
||||
var walk := _resolve_clip(ap, "Walk")
|
||||
if not walk.is_empty():
|
||||
cv.play_animation(walk)
|
||||
ap.pause()
|
||||
ap.seek(ap.current_animation_length * 0.25, true)
|
||||
await get_tree().process_frame
|
||||
|
||||
# Front + 3/4 (task deliverable: two preview renders).
|
||||
for yaw in [0, 45]:
|
||||
cv.rotation_degrees.y = float(yaw)
|
||||
await RenderingServer.frame_post_draw
|
||||
await RenderingServer.frame_post_draw
|
||||
var fname := "%s__%s__yaw%03d.png" % [item, body, yaw]
|
||||
get_viewport().get_texture().get_image().save_png(_out_dir.path_join(fname))
|
||||
print("preview: wrote ", fname)
|
||||
|
||||
print("preview: DONE")
|
||||
get_tree().quit(0)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D, item: String, body: String) -> Array:
|
||||
var out: Array = []
|
||||
var glb := CLOTHING_DIR + "%s/%s.glb" % [item, body]
|
||||
if not ResourceLoader.exists(glb):
|
||||
push_error("preview: garment GLB missing %s" % glb)
|
||||
return out
|
||||
var inst := (load(glb) as PackedScene).instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
out.append(mi)
|
||||
inst.queue_free()
|
||||
return out
|
||||
|
||||
|
||||
func _apply_garment_shader(mi: MeshInstance3D, shader: Shader, mask: Texture2D) -> void:
|
||||
if mi.mesh == null or shader == null:
|
||||
return
|
||||
var albedo := _albedo_of(mi)
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
if albedo:
|
||||
mat.set_shader_parameter("albedo_tex", albedo)
|
||||
if mask:
|
||||
mat.set_shader_parameter("region_mask", mask)
|
||||
mat.set_shader_parameter("tint_0", TINT_WAISTBAND)
|
||||
mat.set_shader_parameter("tint_1", TINT_LEGS)
|
||||
mat.set_shader_parameter("tint_2", TINT_POCKETS)
|
||||
mat.set_shader_parameter("tint_3", Color.WHITE)
|
||||
mat.set_shader_parameter("shadow_strength", 0.2)
|
||||
mi.material_override = mat
|
||||
|
||||
|
||||
func _albedo_of(mi: MeshInstance3D) -> Texture2D:
|
||||
for surf in range(mi.mesh.get_surface_count()):
|
||||
var m := mi.mesh.surface_get_material(surf)
|
||||
if m is BaseMaterial3D:
|
||||
return (m as BaseMaterial3D).albedo_texture
|
||||
return null
|
||||
|
||||
|
||||
func _try_load(path: String) -> Texture2D:
|
||||
return load(path) as Texture2D if ResourceLoader.exists(path) else null
|
||||
|
||||
|
||||
func _resolve_clip(ap: AnimationPlayer, requested: String) -> String:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
if str(a).to_lower().contains(requested.to_lower()):
|
||||
return str(a)
|
||||
return ""
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/garment_preview/preview_cargo_pants.gd" id="1_prevcargo"]
|
||||
|
||||
[node name="GarmentPreviewCargoPants" type="Node3D"]
|
||||
script = ExtResource("1_prevcargo")
|
||||
@@ -0,0 +1,193 @@
|
||||
extends Node3D
|
||||
## joggers_modern preview capture (T-1089 wave 2) — parameterized copy of
|
||||
## preview_shorts_modern.gd for a LOWER-BODY 4-region garment: per-body region
|
||||
## mask (reference fallback), no logo, camera framed waist to feet so the
|
||||
## waistband, side stripe and ankle cuffs all read, front + 3/4 Walk renders.
|
||||
## Temporary tool scene — safe to delete after captures.
|
||||
##
|
||||
## Config via env vars (all optional):
|
||||
## GARMENT_PREVIEW_ITEM default "joggers_modern"
|
||||
## GARMENT_PREVIEW_BODY default "average_m"
|
||||
## GARMENT_PREVIEW_OUT default "<repo>/.cache/garment-preview/joggers_modern"
|
||||
|
||||
const CLOTHING_DIR := "res://assets/characters/clothing/"
|
||||
const SHADER_PATH := "res://assets/characters/shaders/toon_garment.gdshader"
|
||||
const VIEWPORT := Vector2i(768, 1024)
|
||||
|
||||
const BODY_KEY_TO_ENUM := {
|
||||
"average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M,
|
||||
"average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F,
|
||||
"muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M,
|
||||
"muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F,
|
||||
"thin_m": CharacterVisualDescriptor.BodyType.THIN_M,
|
||||
"thin_f": CharacterVisualDescriptor.BodyType.THIN_F,
|
||||
"heavy_m": CharacterVisualDescriptor.BodyType.HEAVY_M,
|
||||
"heavy_f": CharacterVisualDescriptor.BodyType.HEAVY_F,
|
||||
"teen_m": CharacterVisualDescriptor.BodyType.TEEN_M,
|
||||
"teen_f": CharacterVisualDescriptor.BodyType.TEEN_F,
|
||||
"child": CharacterVisualDescriptor.BodyType.CHILD,
|
||||
}
|
||||
|
||||
# Athletic palette: charcoal band + cuffs, slate legs, white track stripe.
|
||||
const TINT_WAISTBAND := Color(0.16, 0.17, 0.19) # R region
|
||||
const TINT_LEGS := Color(0.30, 0.34, 0.42) # G region
|
||||
const TINT_STRIPE := Color(0.92, 0.93, 0.95) # B region
|
||||
const TINT_CUFFS := Color(0.16, 0.17, 0.19) # A region
|
||||
|
||||
var _out_dir := ""
|
||||
var _cam: Camera3D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var item := _env("GARMENT_PREVIEW_ITEM", "joggers_modern")
|
||||
var body := _env("GARMENT_PREVIEW_BODY", "average_m")
|
||||
_out_dir = _env("GARMENT_PREVIEW_OUT",
|
||||
"/var/mnt/data/projects/settled-reach/.cache/garment-preview/joggers_modern")
|
||||
get_window().size = VIEWPORT
|
||||
_setup_stage()
|
||||
_run.call_deferred(item, body)
|
||||
|
||||
|
||||
func _env(key: String, fallback: String) -> String:
|
||||
var v := OS.get_environment(key)
|
||||
return v if not v.is_empty() else fallback
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-38, 28, 0)
|
||||
light.light_energy = 1.3
|
||||
add_child(light)
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.82, 0.82, 0.86)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.16, 0.17, 0.20)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
_cam = Camera3D.new()
|
||||
_cam.fov = 45.0
|
||||
add_child(_cam)
|
||||
# Frame waist -> feet so waistband, stripe run and ankle cuffs all read.
|
||||
_cam.position = Vector3(0.0, 0.62, 2.2)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0.0, 0.60, 0.0), Vector3.UP)
|
||||
|
||||
|
||||
func _run(item: String, body: String) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(_out_dir)
|
||||
if not BODY_KEY_TO_ENUM.has(body):
|
||||
push_error("preview: unknown body %s" % body)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var cv := CharacterVisual.new()
|
||||
add_child(cv)
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = BODY_KEY_TO_ENUM[body]
|
||||
desc.head_id = "head_001"
|
||||
desc.hair_id = "buzzed"
|
||||
desc.eyebrow_id = "regular"
|
||||
desc.skin_tone = 3
|
||||
cv.load_descriptor(desc)
|
||||
|
||||
var skel := cv.get_skeleton()
|
||||
if skel == null:
|
||||
push_error("preview: no skeleton")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var meshes := _attach_garment(skel, item, body)
|
||||
print("preview: attached %d garment meshes" % meshes.size())
|
||||
var shader := load(SHADER_PATH) as Shader
|
||||
# Per-body region mask (offset-shell route); reference fallback mirrors
|
||||
# the runtime compositor.
|
||||
var mask := _try_load(CLOTHING_DIR + "%s/%s_mask.png" % [item, body])
|
||||
if mask == null:
|
||||
mask = _try_load(CLOTHING_DIR + "%s/reference_mask.png" % item)
|
||||
for mi in meshes:
|
||||
_apply_garment_shader(mi, shader, mask)
|
||||
|
||||
# Freeze on a mid-stride Walk pose so taper + cuffs read under motion.
|
||||
var ap := cv.get_animation_player()
|
||||
if ap != null:
|
||||
var walk := _resolve_clip(ap, "Walk")
|
||||
if not walk.is_empty():
|
||||
cv.play_animation(walk)
|
||||
ap.pause()
|
||||
ap.seek(ap.current_animation_length * 0.25, true)
|
||||
await get_tree().process_frame
|
||||
|
||||
# Front + 3/4 (task deliverable: two preview renders).
|
||||
for yaw in [0, 45]:
|
||||
cv.rotation_degrees.y = float(yaw)
|
||||
await RenderingServer.frame_post_draw
|
||||
await RenderingServer.frame_post_draw
|
||||
var fname := "%s__%s__yaw%03d.png" % [item, body, yaw]
|
||||
get_viewport().get_texture().get_image().save_png(_out_dir.path_join(fname))
|
||||
print("preview: wrote ", fname)
|
||||
|
||||
print("preview: DONE")
|
||||
get_tree().quit(0)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D, item: String, body: String) -> Array:
|
||||
var out: Array = []
|
||||
var glb := CLOTHING_DIR + "%s/%s.glb" % [item, body]
|
||||
if not ResourceLoader.exists(glb):
|
||||
push_error("preview: garment GLB missing %s" % glb)
|
||||
return out
|
||||
var inst := (load(glb) as PackedScene).instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
out.append(mi)
|
||||
inst.queue_free()
|
||||
return out
|
||||
|
||||
|
||||
func _apply_garment_shader(mi: MeshInstance3D, shader: Shader, mask: Texture2D) -> void:
|
||||
if mi.mesh == null or shader == null:
|
||||
return
|
||||
var albedo := _albedo_of(mi)
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
if albedo:
|
||||
mat.set_shader_parameter("albedo_tex", albedo)
|
||||
if mask:
|
||||
mat.set_shader_parameter("region_mask", mask)
|
||||
mat.set_shader_parameter("tint_0", TINT_WAISTBAND)
|
||||
mat.set_shader_parameter("tint_1", TINT_LEGS)
|
||||
mat.set_shader_parameter("tint_2", TINT_STRIPE)
|
||||
mat.set_shader_parameter("tint_3", TINT_CUFFS)
|
||||
mat.set_shader_parameter("shadow_strength", 0.2)
|
||||
mi.material_override = mat
|
||||
|
||||
|
||||
func _albedo_of(mi: MeshInstance3D) -> Texture2D:
|
||||
for surf in range(mi.mesh.get_surface_count()):
|
||||
var m := mi.mesh.surface_get_material(surf)
|
||||
if m is BaseMaterial3D:
|
||||
return (m as BaseMaterial3D).albedo_texture
|
||||
return null
|
||||
|
||||
|
||||
func _try_load(path: String) -> Texture2D:
|
||||
return load(path) as Texture2D if ResourceLoader.exists(path) else null
|
||||
|
||||
|
||||
func _resolve_clip(ap: AnimationPlayer, requested: String) -> String:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
if str(a).to_lower().contains(requested.to_lower()):
|
||||
return str(a)
|
||||
return ""
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/garment_preview/preview_joggers_modern.gd" id="1_prevj"]
|
||||
|
||||
[node name="GarmentPreviewJoggers" type="Node3D"]
|
||||
script = ExtResource("1_prevj")
|
||||
@@ -0,0 +1,209 @@
|
||||
extends Node3D
|
||||
## parka_thrds preview capture (T-1089 wave 2 — first canon-branded garment).
|
||||
##
|
||||
## Torso-family variant of preview_scene.gd for the hip-length thrds parka:
|
||||
## collar-to-hip camera framing, per-body region mask preference
|
||||
## (<body>_mask.png, per-body offset-shell convention) and the deep blue-grey
|
||||
## cold-weather default palette on the four recolor zones:
|
||||
## R hood roll + collar = darker blue-grey
|
||||
## G body = deep blue-grey
|
||||
## B sleeves = deep blue-grey (a shade apart)
|
||||
## A pocket / quilt trim = pale steel accent
|
||||
## The left-breast chest patch carries the thrds logo (canon brand,
|
||||
## wiki/star-systems/GJ-475 — Braemar fiber cooperative).
|
||||
##
|
||||
## Config via env vars (all optional):
|
||||
## GARMENT_PREVIEW_ITEM default "parka_thrds"
|
||||
## GARMENT_PREVIEW_BODY default "average_m"
|
||||
## GARMENT_PREVIEW_OUT default "<repo>/.cache/garment-preview/parka_thrds"
|
||||
## GARMENT_PREVIEW_LOGO default thrds
|
||||
##
|
||||
## Run: godot4 --path client --rendering-driver opengl3 \
|
||||
## res://tools/garment_preview/preview_parka_thrds.tscn
|
||||
|
||||
const CLOTHING_DIR := "res://assets/characters/clothing/"
|
||||
const LOGO_PATH := "res://assets/characters/logos/thrds.png"
|
||||
const SHADER_PATH := "res://assets/characters/shaders/toon_garment.gdshader"
|
||||
const VIEWPORT := Vector2i(768, 1024)
|
||||
|
||||
const BODY_KEY_TO_ENUM := {
|
||||
"average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M,
|
||||
"average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F,
|
||||
"muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M,
|
||||
"muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F,
|
||||
"thin_m": CharacterVisualDescriptor.BodyType.THIN_M,
|
||||
"thin_f": CharacterVisualDescriptor.BodyType.THIN_F,
|
||||
"heavy_m": CharacterVisualDescriptor.BodyType.HEAVY_M,
|
||||
"heavy_f": CharacterVisualDescriptor.BodyType.HEAVY_F,
|
||||
"teen_m": CharacterVisualDescriptor.BodyType.TEEN_M,
|
||||
"teen_f": CharacterVisualDescriptor.BodyType.TEEN_F,
|
||||
"child": CharacterVisualDescriptor.BodyType.CHILD,
|
||||
}
|
||||
|
||||
# Deep blue-grey cold-weather default palette (parka_thrds spec).
|
||||
const TINT_ROLL := Color(0.22, 0.26, 0.34) # R: hood roll + collar
|
||||
const TINT_BODY := Color(0.30, 0.35, 0.45) # G: body
|
||||
const TINT_SLEEVES := Color(0.27, 0.32, 0.41) # B: sleeves
|
||||
const TINT_TRIM := Color(0.47, 0.52, 0.60) # A: pocket / quilt trim
|
||||
|
||||
var _out_dir := ""
|
||||
var _cam: Camera3D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var item := _env("GARMENT_PREVIEW_ITEM", "parka_thrds")
|
||||
var body := _env("GARMENT_PREVIEW_BODY", "average_m")
|
||||
_out_dir = _env("GARMENT_PREVIEW_OUT",
|
||||
"/var/mnt/data/projects/settled-reach/.cache/garment-preview/parka_thrds")
|
||||
get_window().size = VIEWPORT
|
||||
_setup_stage()
|
||||
_run.call_deferred(item, body)
|
||||
|
||||
|
||||
func _env(key: String, fallback: String) -> String:
|
||||
var v := OS.get_environment(key)
|
||||
return v if not v.is_empty() else fallback
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-38, 28, 0)
|
||||
light.light_energy = 1.3
|
||||
add_child(light)
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.82, 0.82, 0.86)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.16, 0.17, 0.20)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
_cam = Camera3D.new()
|
||||
_cam.fov = 45.0
|
||||
add_child(_cam)
|
||||
# Collar-to-hip framing — the parka runs from the hood roll to the hip hem.
|
||||
_cam.position = Vector3(0.0, 1.22, 2.4)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0.0, 1.12, 0.0), Vector3.UP)
|
||||
|
||||
|
||||
func _run(item: String, body: String) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(_out_dir)
|
||||
if not BODY_KEY_TO_ENUM.has(body):
|
||||
push_error("preview: unknown body %s" % body)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var cv := CharacterVisual.new()
|
||||
add_child(cv)
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = BODY_KEY_TO_ENUM[body]
|
||||
desc.head_id = "head_001"
|
||||
desc.hair_id = "buzzed"
|
||||
desc.eyebrow_id = "regular"
|
||||
desc.skin_tone = 3
|
||||
cv.load_descriptor(desc)
|
||||
|
||||
var skel := cv.get_skeleton()
|
||||
if skel == null:
|
||||
push_error("preview: no skeleton")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var meshes := _attach_garment(skel, item, body)
|
||||
print("preview: attached %d garment meshes" % meshes.size())
|
||||
var shader := load(SHADER_PATH) as Shader
|
||||
# Per-body mask first (offset-shell per-body convention), then fallback.
|
||||
var mask := _try_load(CLOTHING_DIR + "%s/%s_mask.png" % [item, body])
|
||||
if mask == null:
|
||||
mask = _try_load(CLOTHING_DIR + "%s/reference_mask.png" % item)
|
||||
var logo_path := _env("GARMENT_PREVIEW_LOGO", LOGO_PATH)
|
||||
var logo := _try_load(logo_path)
|
||||
for mi in meshes:
|
||||
_apply_garment_shader(mi, shader, mask, logo)
|
||||
|
||||
# Freeze mid-walk so the parka reads under a real pose.
|
||||
var ap := cv.get_animation_player()
|
||||
if ap != null:
|
||||
var walk := _resolve_clip(ap, "Walk")
|
||||
if not walk.is_empty():
|
||||
cv.play_animation(walk)
|
||||
ap.pause()
|
||||
ap.seek(ap.current_animation_length * 0.25, true)
|
||||
await get_tree().process_frame
|
||||
|
||||
# Front + 3/4 — the two requested preview angles.
|
||||
for yaw in [0, 45]:
|
||||
cv.rotation_degrees.y = float(yaw)
|
||||
await RenderingServer.frame_post_draw
|
||||
await RenderingServer.frame_post_draw
|
||||
var fname := "%s__%s__yaw%03d.png" % [item, body, yaw]
|
||||
get_viewport().get_texture().get_image().save_png(_out_dir.path_join(fname))
|
||||
print("preview: wrote ", fname)
|
||||
|
||||
print("preview: DONE")
|
||||
get_tree().quit(0)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D, item: String, body: String) -> Array:
|
||||
var out: Array = []
|
||||
var glb := CLOTHING_DIR + "%s/%s.glb" % [item, body]
|
||||
if not ResourceLoader.exists(glb):
|
||||
push_error("preview: garment GLB missing %s" % glb)
|
||||
return out
|
||||
var inst := (load(glb) as PackedScene).instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
out.append(mi)
|
||||
inst.queue_free()
|
||||
return out
|
||||
|
||||
|
||||
func _apply_garment_shader(mi: MeshInstance3D, shader: Shader, mask: Texture2D, logo: Texture2D) -> void:
|
||||
if mi.mesh == null or shader == null:
|
||||
return
|
||||
var albedo := _albedo_of(mi)
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
if albedo:
|
||||
mat.set_shader_parameter("albedo_tex", albedo)
|
||||
if mask:
|
||||
mat.set_shader_parameter("region_mask", mask)
|
||||
if logo:
|
||||
mat.set_shader_parameter("logo_tex", logo)
|
||||
mat.set_shader_parameter("logo_enabled", 1.0)
|
||||
mat.set_shader_parameter("tint_0", TINT_ROLL)
|
||||
mat.set_shader_parameter("tint_1", TINT_BODY)
|
||||
mat.set_shader_parameter("tint_2", TINT_SLEEVES)
|
||||
mat.set_shader_parameter("tint_3", TINT_TRIM)
|
||||
mat.set_shader_parameter("shadow_strength", 0.2)
|
||||
mi.material_override = mat
|
||||
|
||||
|
||||
func _albedo_of(mi: MeshInstance3D) -> Texture2D:
|
||||
for surf in range(mi.mesh.get_surface_count()):
|
||||
var m := mi.mesh.surface_get_material(surf)
|
||||
if m is BaseMaterial3D:
|
||||
return (m as BaseMaterial3D).albedo_texture
|
||||
return null
|
||||
|
||||
|
||||
func _try_load(path: String) -> Texture2D:
|
||||
return load(path) as Texture2D if ResourceLoader.exists(path) else null
|
||||
|
||||
|
||||
func _resolve_clip(ap: AnimationPlayer, requested: String) -> String:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
if str(a).to_lower().contains(requested.to_lower()):
|
||||
return str(a)
|
||||
return ""
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/garment_preview/preview_parka_thrds.gd" id="1_prevp"]
|
||||
|
||||
[node name="ParkaThrdsPreview" type="Node3D"]
|
||||
script = ExtResource("1_prevp")
|
||||
@@ -0,0 +1,198 @@
|
||||
extends Node3D
|
||||
## shoes_formal preview capture (T-1089 wave 2, footwear set) — parameterized
|
||||
## copy of preview_swim_trunks.gd for a FOOTWEAR 3-region garment: per-body
|
||||
## region mask (reference fallback), no logo, camera framed low on the feet so
|
||||
## the thin flat sole, painted toe-cap line and topline stitch read. Black
|
||||
## default tints (manifest defaults) — identity carried by painted-seam luma.
|
||||
## Front + 3/4 Walk renders. Temporary tool scene — safe to delete after
|
||||
## captures.
|
||||
##
|
||||
## Config via env vars (all optional):
|
||||
## GARMENT_PREVIEW_ITEM default "shoes_formal"
|
||||
## GARMENT_PREVIEW_BODY default "average_m"
|
||||
## GARMENT_PREVIEW_OUT default "<repo>/.cache/garment-preview/shoes_formal"
|
||||
|
||||
const CLOTHING_DIR := "res://assets/characters/clothing/"
|
||||
const SHADER_PATH := "res://assets/characters/shaders/toon_garment.gdshader"
|
||||
const VIEWPORT := Vector2i(1024, 768)
|
||||
|
||||
const BODY_KEY_TO_ENUM := {
|
||||
"average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M,
|
||||
"average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F,
|
||||
"muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M,
|
||||
"muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F,
|
||||
"thin_m": CharacterVisualDescriptor.BodyType.THIN_M,
|
||||
"thin_f": CharacterVisualDescriptor.BodyType.THIN_F,
|
||||
"heavy_m": CharacterVisualDescriptor.BodyType.HEAVY_M,
|
||||
"heavy_f": CharacterVisualDescriptor.BodyType.HEAVY_F,
|
||||
"teen_m": CharacterVisualDescriptor.BodyType.TEEN_M,
|
||||
"teen_f": CharacterVisualDescriptor.BodyType.TEEN_F,
|
||||
"child": CharacterVisualDescriptor.BodyType.CHILD,
|
||||
}
|
||||
|
||||
# Black formal default (matches the manifest default_tints): sole a hair
|
||||
# lighter than the upper so the welt line reads; toe cap same as upper — the
|
||||
# painted cap-line stitch carries the boundary, not a colour step.
|
||||
const TINT_SOLE := Color(0.10, 0.10, 0.11) # R region
|
||||
const TINT_UPPER := Color(0.07, 0.07, 0.08) # G region
|
||||
const TINT_TOECAP := Color(0.07, 0.07, 0.08) # B region
|
||||
|
||||
var _out_dir := ""
|
||||
var _cam: Camera3D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var item := _env("GARMENT_PREVIEW_ITEM", "shoes_formal")
|
||||
var body := _env("GARMENT_PREVIEW_BODY", "average_m")
|
||||
_out_dir = _env("GARMENT_PREVIEW_OUT",
|
||||
"/var/mnt/data/projects/settled-reach/.cache/garment-preview/shoes_formal")
|
||||
get_window().size = VIEWPORT
|
||||
_setup_stage()
|
||||
_run.call_deferred(item, body)
|
||||
|
||||
|
||||
func _env(key: String, fallback: String) -> String:
|
||||
var v := OS.get_environment(key)
|
||||
return v if not v.is_empty() else fallback
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-38, 28, 0)
|
||||
light.light_energy = 1.3
|
||||
add_child(light)
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.82, 0.82, 0.86)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.16, 0.17, 0.20)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
_cam = Camera3D.new()
|
||||
_cam.fov = 40.0
|
||||
add_child(_cam)
|
||||
# Frame the feet + lower shin from slightly above ground level so the
|
||||
# flat sole, toe-cap line and topline opening all read.
|
||||
_cam.position = Vector3(0.0, 0.28, 0.85)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0.0, 0.09, 0.0), Vector3.UP)
|
||||
|
||||
|
||||
func _run(item: String, body: String) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(_out_dir)
|
||||
if not BODY_KEY_TO_ENUM.has(body):
|
||||
push_error("preview: unknown body %s" % body)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var cv := CharacterVisual.new()
|
||||
add_child(cv)
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = BODY_KEY_TO_ENUM[body]
|
||||
desc.head_id = "head_001"
|
||||
desc.hair_id = "buzzed"
|
||||
desc.eyebrow_id = "regular"
|
||||
desc.skin_tone = 3
|
||||
cv.load_descriptor(desc)
|
||||
|
||||
var skel := cv.get_skeleton()
|
||||
if skel == null:
|
||||
push_error("preview: no skeleton")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var meshes := _attach_garment(skel, item, body)
|
||||
print("preview: attached %d garment meshes" % meshes.size())
|
||||
var shader := load(SHADER_PATH) as Shader
|
||||
# Per-body region mask (offset-shell route); reference fallback mirrors
|
||||
# the runtime compositor.
|
||||
var mask := _try_load(CLOTHING_DIR + "%s/%s_mask.png" % [item, body])
|
||||
if mask == null:
|
||||
mask = _try_load(CLOTHING_DIR + "%s/reference_mask.png" % item)
|
||||
for mi in meshes:
|
||||
_apply_garment_shader(mi, shader, mask)
|
||||
|
||||
# Freeze on a mid-stride Walk pose so the sole lift + toe cap read under
|
||||
# motion (one foot planted, one rolling off the toes).
|
||||
var ap := cv.get_animation_player()
|
||||
if ap != null:
|
||||
var walk := _resolve_clip(ap, "Walk")
|
||||
if not walk.is_empty():
|
||||
cv.play_animation(walk)
|
||||
ap.pause()
|
||||
ap.seek(ap.current_animation_length * 0.25, true)
|
||||
await get_tree().process_frame
|
||||
|
||||
# Front + 3/4 (task deliverable: two preview renders).
|
||||
for yaw in [0, 45]:
|
||||
cv.rotation_degrees.y = float(yaw)
|
||||
await RenderingServer.frame_post_draw
|
||||
await RenderingServer.frame_post_draw
|
||||
var fname := "%s__%s__yaw%03d.png" % [item, body, yaw]
|
||||
get_viewport().get_texture().get_image().save_png(_out_dir.path_join(fname))
|
||||
print("preview: wrote ", fname)
|
||||
|
||||
print("preview: DONE")
|
||||
get_tree().quit(0)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D, item: String, body: String) -> Array:
|
||||
var out: Array = []
|
||||
var glb := CLOTHING_DIR + "%s/%s.glb" % [item, body]
|
||||
if not ResourceLoader.exists(glb):
|
||||
push_error("preview: garment GLB missing %s" % glb)
|
||||
return out
|
||||
var inst := (load(glb) as PackedScene).instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
out.append(mi)
|
||||
inst.queue_free()
|
||||
return out
|
||||
|
||||
|
||||
func _apply_garment_shader(mi: MeshInstance3D, shader: Shader, mask: Texture2D) -> void:
|
||||
if mi.mesh == null or shader == null:
|
||||
return
|
||||
var albedo := _albedo_of(mi)
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
if albedo:
|
||||
mat.set_shader_parameter("albedo_tex", albedo)
|
||||
if mask:
|
||||
mat.set_shader_parameter("region_mask", mask)
|
||||
mat.set_shader_parameter("tint_0", TINT_SOLE)
|
||||
mat.set_shader_parameter("tint_1", TINT_UPPER)
|
||||
mat.set_shader_parameter("tint_2", TINT_TOECAP)
|
||||
mat.set_shader_parameter("tint_3", Color.WHITE)
|
||||
mat.set_shader_parameter("shadow_strength", 0.2)
|
||||
mi.material_override = mat
|
||||
|
||||
|
||||
func _albedo_of(mi: MeshInstance3D) -> Texture2D:
|
||||
for surf in range(mi.mesh.get_surface_count()):
|
||||
var m := mi.mesh.surface_get_material(surf)
|
||||
if m is BaseMaterial3D:
|
||||
return (m as BaseMaterial3D).albedo_texture
|
||||
return null
|
||||
|
||||
|
||||
func _try_load(path: String) -> Texture2D:
|
||||
return load(path) as Texture2D if ResourceLoader.exists(path) else null
|
||||
|
||||
|
||||
func _resolve_clip(ap: AnimationPlayer, requested: String) -> String:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
if str(a).to_lower().contains(requested.to_lower()):
|
||||
return str(a)
|
||||
return ""
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/garment_preview/preview_shoes_formal.gd" id="1_prevsf"]
|
||||
|
||||
[node name="GarmentPreviewShoesFormal" type="Node3D"]
|
||||
script = ExtResource("1_prevsf")
|
||||
@@ -0,0 +1,194 @@
|
||||
extends Node3D
|
||||
## slides preview capture (T-1089 wave 2, swim set) — parameterized copy of
|
||||
## preview_swim_trunks.gd for the FOOTWEAR slot, 2-region garment: per-body
|
||||
## region mask (reference fallback), no logo, camera framed low on the feet so
|
||||
## the sole slab and midfoot strap band read. Front + 3/4 Walk renders.
|
||||
## Temporary tool scene — safe to delete after captures.
|
||||
##
|
||||
## Config via env vars (all optional):
|
||||
## GARMENT_PREVIEW_ITEM default "slides"
|
||||
## GARMENT_PREVIEW_BODY default "average_m"
|
||||
## GARMENT_PREVIEW_OUT default "<repo>/.cache/garment-preview/slides"
|
||||
|
||||
const CLOTHING_DIR := "res://assets/characters/clothing/"
|
||||
const SHADER_PATH := "res://assets/characters/shaders/toon_garment.gdshader"
|
||||
const VIEWPORT := Vector2i(1024, 768)
|
||||
|
||||
const BODY_KEY_TO_ENUM := {
|
||||
"average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M,
|
||||
"average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F,
|
||||
"muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M,
|
||||
"muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F,
|
||||
"thin_m": CharacterVisualDescriptor.BodyType.THIN_M,
|
||||
"thin_f": CharacterVisualDescriptor.BodyType.THIN_F,
|
||||
"heavy_m": CharacterVisualDescriptor.BodyType.HEAVY_M,
|
||||
"heavy_f": CharacterVisualDescriptor.BodyType.HEAVY_F,
|
||||
"teen_m": CharacterVisualDescriptor.BodyType.TEEN_M,
|
||||
"teen_f": CharacterVisualDescriptor.BodyType.TEEN_F,
|
||||
"child": CharacterVisualDescriptor.BodyType.CHILD,
|
||||
}
|
||||
|
||||
# Bright swim palette matching the painted albedo (sole=R off-white foam,
|
||||
# strap=G coral) so tinted and untinted renders agree.
|
||||
const TINT_SOLE := Color(0.82, 0.83, 0.85) # R region, off-white foam
|
||||
const TINT_STRAP := Color(0.93, 0.35, 0.20) # G region, bright coral
|
||||
const TINT_UNUSED := Color.WHITE
|
||||
|
||||
var _out_dir := ""
|
||||
var _cam: Camera3D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var item := _env("GARMENT_PREVIEW_ITEM", "slides")
|
||||
var body := _env("GARMENT_PREVIEW_BODY", "average_m")
|
||||
_out_dir = _env("GARMENT_PREVIEW_OUT",
|
||||
"/var/mnt/data/projects/settled-reach/.cache/garment-preview/slides")
|
||||
get_window().size = VIEWPORT
|
||||
_setup_stage()
|
||||
_run.call_deferred(item, body)
|
||||
|
||||
|
||||
func _env(key: String, fallback: String) -> String:
|
||||
var v := OS.get_environment(key)
|
||||
return v if not v.is_empty() else fallback
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-38, 28, 0)
|
||||
light.light_energy = 1.3
|
||||
add_child(light)
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.82, 0.82, 0.86)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.16, 0.17, 0.20)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
_cam = Camera3D.new()
|
||||
_cam.fov = 40.0
|
||||
add_child(_cam)
|
||||
# Low, close framing on the feet: the sole slab, the strap band and the
|
||||
# open toe/heel must all read. Slight downward angle shows the footbed.
|
||||
_cam.position = Vector3(0.0, 0.42, 1.15)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0.0, 0.10, 0.0), Vector3.UP)
|
||||
|
||||
|
||||
func _run(item: String, body: String) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(_out_dir)
|
||||
if not BODY_KEY_TO_ENUM.has(body):
|
||||
push_error("preview: unknown body %s" % body)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var cv := CharacterVisual.new()
|
||||
add_child(cv)
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = BODY_KEY_TO_ENUM[body]
|
||||
desc.head_id = "head_001"
|
||||
desc.hair_id = "buzzed"
|
||||
desc.eyebrow_id = "regular"
|
||||
desc.skin_tone = 3
|
||||
cv.load_descriptor(desc)
|
||||
|
||||
var skel := cv.get_skeleton()
|
||||
if skel == null:
|
||||
push_error("preview: no skeleton")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var meshes := _attach_garment(skel, item, body)
|
||||
print("preview: attached %d garment meshes" % meshes.size())
|
||||
var shader := load(SHADER_PATH) as Shader
|
||||
# Per-body region mask (offset-shell route); reference fallback mirrors
|
||||
# the runtime compositor.
|
||||
var mask := _try_load(CLOTHING_DIR + "%s/%s_mask.png" % [item, body])
|
||||
if mask == null:
|
||||
mask = _try_load(CLOTHING_DIR + "%s/reference_mask.png" % item)
|
||||
for mi in meshes:
|
||||
_apply_garment_shader(mi, shader, mask)
|
||||
|
||||
# Freeze on a mid-stride Walk pose so the strap and sole read under motion.
|
||||
var ap := cv.get_animation_player()
|
||||
if ap != null:
|
||||
var walk := _resolve_clip(ap, "Walk")
|
||||
if not walk.is_empty():
|
||||
cv.play_animation(walk)
|
||||
ap.pause()
|
||||
ap.seek(ap.current_animation_length * 0.25, true)
|
||||
await get_tree().process_frame
|
||||
|
||||
# Front + 3/4 (task deliverable: two preview renders).
|
||||
for yaw in [0, 45]:
|
||||
cv.rotation_degrees.y = float(yaw)
|
||||
await RenderingServer.frame_post_draw
|
||||
await RenderingServer.frame_post_draw
|
||||
var fname := "%s__%s__yaw%03d.png" % [item, body, yaw]
|
||||
get_viewport().get_texture().get_image().save_png(_out_dir.path_join(fname))
|
||||
print("preview: wrote ", fname)
|
||||
|
||||
print("preview: DONE")
|
||||
get_tree().quit(0)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D, item: String, body: String) -> Array:
|
||||
var out: Array = []
|
||||
var glb := CLOTHING_DIR + "%s/%s.glb" % [item, body]
|
||||
if not ResourceLoader.exists(glb):
|
||||
push_error("preview: garment GLB missing %s" % glb)
|
||||
return out
|
||||
var inst := (load(glb) as PackedScene).instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
out.append(mi)
|
||||
inst.queue_free()
|
||||
return out
|
||||
|
||||
|
||||
func _apply_garment_shader(mi: MeshInstance3D, shader: Shader, mask: Texture2D) -> void:
|
||||
if mi.mesh == null or shader == null:
|
||||
return
|
||||
var albedo := _albedo_of(mi)
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
if albedo:
|
||||
mat.set_shader_parameter("albedo_tex", albedo)
|
||||
if mask:
|
||||
mat.set_shader_parameter("region_mask", mask)
|
||||
mat.set_shader_parameter("tint_0", TINT_SOLE)
|
||||
mat.set_shader_parameter("tint_1", TINT_STRAP)
|
||||
mat.set_shader_parameter("tint_2", TINT_UNUSED)
|
||||
mat.set_shader_parameter("tint_3", TINT_UNUSED)
|
||||
mat.set_shader_parameter("shadow_strength", 0.2)
|
||||
mi.material_override = mat
|
||||
|
||||
|
||||
func _albedo_of(mi: MeshInstance3D) -> Texture2D:
|
||||
for surf in range(mi.mesh.get_surface_count()):
|
||||
var m := mi.mesh.surface_get_material(surf)
|
||||
if m is BaseMaterial3D:
|
||||
return (m as BaseMaterial3D).albedo_texture
|
||||
return null
|
||||
|
||||
|
||||
func _try_load(path: String) -> Texture2D:
|
||||
return load(path) as Texture2D if ResourceLoader.exists(path) else null
|
||||
|
||||
|
||||
func _resolve_clip(ap: AnimationPlayer, requested: String) -> String:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
if str(a).to_lower().contains(requested.to_lower()):
|
||||
return str(a)
|
||||
return ""
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/garment_preview/preview_slides.gd" id="1_prevsl"]
|
||||
|
||||
[node name="GarmentPreviewSlides" type="Node3D"]
|
||||
script = ExtResource("1_prevsl")
|
||||
@@ -0,0 +1,195 @@
|
||||
extends Node3D
|
||||
## sneakers_modern preview capture (T-1089 wave 2, footwear set) — parameterized
|
||||
## copy of preview_swim_trunks.gd for a FEET-slot 3-region garment (both feet in
|
||||
## one garment, peasant_shoes convention): per-body region mask (reference
|
||||
## fallback), no logo, camera framed LOW on the feet so the sole slab, painted
|
||||
## laces, toe cap and panel lines read. Front + 3/4 Walk renders.
|
||||
## Temporary tool scene — safe to delete after captures.
|
||||
##
|
||||
## Config via env vars (all optional):
|
||||
## GARMENT_PREVIEW_ITEM default "sneakers_modern"
|
||||
## GARMENT_PREVIEW_BODY default "average_m"
|
||||
## GARMENT_PREVIEW_OUT default "<repo>/.cache/garment-preview/sneakers_modern"
|
||||
|
||||
const CLOTHING_DIR := "res://assets/characters/clothing/"
|
||||
const SHADER_PATH := "res://assets/characters/shaders/toon_garment.gdshader"
|
||||
const VIEWPORT := Vector2i(1024, 768)
|
||||
|
||||
const BODY_KEY_TO_ENUM := {
|
||||
"average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M,
|
||||
"average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F,
|
||||
"muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M,
|
||||
"muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F,
|
||||
"thin_m": CharacterVisualDescriptor.BodyType.THIN_M,
|
||||
"thin_f": CharacterVisualDescriptor.BodyType.THIN_F,
|
||||
"heavy_m": CharacterVisualDescriptor.BodyType.HEAVY_M,
|
||||
"heavy_f": CharacterVisualDescriptor.BodyType.HEAVY_F,
|
||||
"teen_m": CharacterVisualDescriptor.BodyType.TEEN_M,
|
||||
"teen_f": CharacterVisualDescriptor.BodyType.TEEN_F,
|
||||
"child": CharacterVisualDescriptor.BodyType.CHILD,
|
||||
}
|
||||
|
||||
# Everyday default: white/grey trainers (spec) — matches the painted albedo
|
||||
# tones so tinted and untinted renders agree.
|
||||
const TINT_SOLE := Color(0.74, 0.75, 0.77) # R region, grey sole slab
|
||||
const TINT_UPPER := Color(0.92, 0.92, 0.93) # G region, white upper
|
||||
const TINT_TRIM := Color(0.88, 0.88, 0.90) # B region, laces + trim
|
||||
|
||||
var _out_dir := ""
|
||||
var _cam: Camera3D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var item := _env("GARMENT_PREVIEW_ITEM", "sneakers_modern")
|
||||
var body := _env("GARMENT_PREVIEW_BODY", "average_m")
|
||||
_out_dir = _env("GARMENT_PREVIEW_OUT",
|
||||
"/var/mnt/data/projects/settled-reach/.cache/garment-preview/sneakers_modern")
|
||||
get_window().size = VIEWPORT
|
||||
_setup_stage()
|
||||
_run.call_deferred(item, body)
|
||||
|
||||
|
||||
func _env(key: String, fallback: String) -> String:
|
||||
var v := OS.get_environment(key)
|
||||
return v if not v.is_empty() else fallback
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-38, 28, 0)
|
||||
light.light_energy = 1.3
|
||||
add_child(light)
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.82, 0.82, 0.86)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.16, 0.17, 0.20)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
_cam = Camera3D.new()
|
||||
_cam.fov = 40.0
|
||||
add_child(_cam)
|
||||
# Low frame on the feet: the sole slab, laces, toe box and panel lines
|
||||
# must read; mid-stride Walk spreads the feet ~0.5 m front-to-back.
|
||||
_cam.position = Vector3(0.0, 0.28, 1.05)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0.0, 0.09, 0.0), Vector3.UP)
|
||||
|
||||
|
||||
func _run(item: String, body: String) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(_out_dir)
|
||||
if not BODY_KEY_TO_ENUM.has(body):
|
||||
push_error("preview: unknown body %s" % body)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var cv := CharacterVisual.new()
|
||||
add_child(cv)
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = BODY_KEY_TO_ENUM[body]
|
||||
desc.head_id = "head_001"
|
||||
desc.hair_id = "buzzed"
|
||||
desc.eyebrow_id = "regular"
|
||||
desc.skin_tone = 3
|
||||
cv.load_descriptor(desc)
|
||||
|
||||
var skel := cv.get_skeleton()
|
||||
if skel == null:
|
||||
push_error("preview: no skeleton")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var meshes := _attach_garment(skel, item, body)
|
||||
print("preview: attached %d garment meshes" % meshes.size())
|
||||
var shader := load(SHADER_PATH) as Shader
|
||||
# Per-body region mask (offset-shell route); reference fallback mirrors
|
||||
# the runtime compositor.
|
||||
var mask := _try_load(CLOTHING_DIR + "%s/%s_mask.png" % [item, body])
|
||||
if mask == null:
|
||||
mask = _try_load(CLOTHING_DIR + "%s/reference_mask.png" % item)
|
||||
for mi in meshes:
|
||||
_apply_garment_shader(mi, shader, mask)
|
||||
|
||||
# Freeze on a mid-stride Walk pose so the sole + toe box read under motion.
|
||||
var ap := cv.get_animation_player()
|
||||
if ap != null:
|
||||
var walk := _resolve_clip(ap, "Walk")
|
||||
if not walk.is_empty():
|
||||
cv.play_animation(walk)
|
||||
ap.pause()
|
||||
ap.seek(ap.current_animation_length * 0.25, true)
|
||||
await get_tree().process_frame
|
||||
|
||||
# Front + 3/4 (task deliverable: two preview renders).
|
||||
for yaw in [0, 45]:
|
||||
cv.rotation_degrees.y = float(yaw)
|
||||
await RenderingServer.frame_post_draw
|
||||
await RenderingServer.frame_post_draw
|
||||
var fname := "%s__%s__yaw%03d.png" % [item, body, yaw]
|
||||
get_viewport().get_texture().get_image().save_png(_out_dir.path_join(fname))
|
||||
print("preview: wrote ", fname)
|
||||
|
||||
print("preview: DONE")
|
||||
get_tree().quit(0)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D, item: String, body: String) -> Array:
|
||||
var out: Array = []
|
||||
var glb := CLOTHING_DIR + "%s/%s.glb" % [item, body]
|
||||
if not ResourceLoader.exists(glb):
|
||||
push_error("preview: garment GLB missing %s" % glb)
|
||||
return out
|
||||
var inst := (load(glb) as PackedScene).instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
out.append(mi)
|
||||
inst.queue_free()
|
||||
return out
|
||||
|
||||
|
||||
func _apply_garment_shader(mi: MeshInstance3D, shader: Shader, mask: Texture2D) -> void:
|
||||
if mi.mesh == null or shader == null:
|
||||
return
|
||||
var albedo := _albedo_of(mi)
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
if albedo:
|
||||
mat.set_shader_parameter("albedo_tex", albedo)
|
||||
if mask:
|
||||
mat.set_shader_parameter("region_mask", mask)
|
||||
mat.set_shader_parameter("tint_0", TINT_SOLE)
|
||||
mat.set_shader_parameter("tint_1", TINT_UPPER)
|
||||
mat.set_shader_parameter("tint_2", TINT_TRIM)
|
||||
mat.set_shader_parameter("tint_3", Color.WHITE)
|
||||
mat.set_shader_parameter("shadow_strength", 0.2)
|
||||
mi.material_override = mat
|
||||
|
||||
|
||||
func _albedo_of(mi: MeshInstance3D) -> Texture2D:
|
||||
for surf in range(mi.mesh.get_surface_count()):
|
||||
var m := mi.mesh.surface_get_material(surf)
|
||||
if m is BaseMaterial3D:
|
||||
return (m as BaseMaterial3D).albedo_texture
|
||||
return null
|
||||
|
||||
|
||||
func _try_load(path: String) -> Texture2D:
|
||||
return load(path) as Texture2D if ResourceLoader.exists(path) else null
|
||||
|
||||
|
||||
func _resolve_clip(ap: AnimationPlayer, requested: String) -> String:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
if str(a).to_lower().contains(requested.to_lower()):
|
||||
return str(a)
|
||||
return ""
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/garment_preview/preview_sneakers_modern.gd" id="1_prevsn"]
|
||||
|
||||
[node name="GarmentPreviewSneakersModern" type="Node3D"]
|
||||
script = ExtResource("1_prevsn")
|
||||
@@ -0,0 +1,187 @@
|
||||
extends Node3D
|
||||
## Sweater preview capture (T-1089 wave 2 sweater_modern) — per-garment copy of
|
||||
## preview_scene.gd (same convention as preview_buttondown_modern.gd): warm
|
||||
## muted knit palette (deeper collar/cuffs/hem over an oat-rust body), body-
|
||||
## aware mask loading (<body>_mask.png first, mirroring runtime), no logo
|
||||
## (logo_capable OFF), front + 3/4 walk yaws only.
|
||||
##
|
||||
## Config via env vars (all optional):
|
||||
## GARMENT_PREVIEW_ITEM default "sweater_modern"
|
||||
## GARMENT_PREVIEW_BODY default "average_m"
|
||||
## GARMENT_PREVIEW_OUT default "<repo>/.cache/garment-preview"
|
||||
|
||||
const CLOTHING_DIR := "res://assets/characters/clothing/"
|
||||
const SHADER_PATH := "res://assets/characters/shaders/toon_garment.gdshader"
|
||||
const VIEWPORT := Vector2i(768, 1024)
|
||||
|
||||
const BODY_KEY_TO_ENUM := {
|
||||
"average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M,
|
||||
"average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F,
|
||||
"muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M,
|
||||
"muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F,
|
||||
"teen_m": CharacterVisualDescriptor.BodyType.TEEN_M,
|
||||
"teen_f": CharacterVisualDescriptor.BodyType.TEEN_F,
|
||||
}
|
||||
|
||||
# Warm muted knit: oat-rust body, deeper clay collar + cuffs/hem bands.
|
||||
const TINT_COLLAR := Color(0.58, 0.42, 0.31) # R region (crew collar)
|
||||
const TINT_BODY := Color(0.72, 0.57, 0.43) # G region (body knit)
|
||||
const TINT_CUFF := Color(0.58, 0.42, 0.31) # B region (cuffs + hem)
|
||||
|
||||
var _out_dir := ""
|
||||
var _cam: Camera3D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var item := _env("GARMENT_PREVIEW_ITEM", "sweater_modern")
|
||||
var body := _env("GARMENT_PREVIEW_BODY", "average_m")
|
||||
_out_dir = _env("GARMENT_PREVIEW_OUT",
|
||||
"/var/mnt/data/projects/settled-reach/.cache/garment-preview")
|
||||
get_window().size = VIEWPORT
|
||||
_setup_stage()
|
||||
_run.call_deferred(item, body)
|
||||
|
||||
|
||||
func _env(key: String, fallback: String) -> String:
|
||||
var v := OS.get_environment(key)
|
||||
return v if not v.is_empty() else fallback
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-38, 28, 0)
|
||||
light.light_energy = 1.3
|
||||
add_child(light)
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.82, 0.82, 0.86)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.16, 0.17, 0.20)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
_cam = Camera3D.new()
|
||||
_cam.fov = 45.0
|
||||
add_child(_cam)
|
||||
# Frame torso + full sleeves so collar, cuffs and the hip-length hem read.
|
||||
_cam.position = Vector3(0.0, 1.22, 1.75)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0.0, 1.12, 0.0), Vector3.UP)
|
||||
|
||||
|
||||
func _run(item: String, body: String) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(_out_dir)
|
||||
if not BODY_KEY_TO_ENUM.has(body):
|
||||
push_error("preview: unknown body %s" % body)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var cv := CharacterVisual.new()
|
||||
add_child(cv)
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = BODY_KEY_TO_ENUM[body]
|
||||
desc.head_id = "head_001"
|
||||
desc.hair_id = "buzzed"
|
||||
desc.eyebrow_id = "regular"
|
||||
desc.skin_tone = 3
|
||||
cv.load_descriptor(desc)
|
||||
|
||||
var skel := cv.get_skeleton()
|
||||
if skel == null:
|
||||
push_error("preview: no skeleton")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var meshes := _attach_garment(skel, item, body)
|
||||
print("preview: attached %d garment meshes" % meshes.size())
|
||||
var shader := load(SHADER_PATH) as Shader
|
||||
# Per-body-authored garment: prefer <body>_mask.png (runtime behaviour).
|
||||
var mask := _try_load(CLOTHING_DIR + "%s/%s_mask.png" % [item, body])
|
||||
if mask == null:
|
||||
mask = _try_load(CLOTHING_DIR + "%s/reference_mask.png" % item)
|
||||
for mi in meshes:
|
||||
_apply_garment_shader(mi, shader, mask)
|
||||
|
||||
# Freeze on a walk pose so the knit reads in motion.
|
||||
var ap := cv.get_animation_player()
|
||||
if ap != null:
|
||||
var walk := _resolve_clip(ap, "Walk")
|
||||
if not walk.is_empty():
|
||||
cv.play_animation(walk)
|
||||
ap.pause()
|
||||
ap.seek(ap.current_animation_length * 0.25, true)
|
||||
await get_tree().process_frame
|
||||
|
||||
# Front + 3/4 (task spec: two preview renders).
|
||||
for yaw in [0, 45]:
|
||||
cv.rotation_degrees.y = float(yaw)
|
||||
await RenderingServer.frame_post_draw
|
||||
await RenderingServer.frame_post_draw
|
||||
var fname := "%s__%s__yaw%03d.png" % [item, body, yaw]
|
||||
get_viewport().get_texture().get_image().save_png(_out_dir.path_join(fname))
|
||||
print("preview: wrote ", fname)
|
||||
|
||||
print("preview: DONE")
|
||||
get_tree().quit(0)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D, item: String, body: String) -> Array:
|
||||
var out: Array = []
|
||||
var glb := CLOTHING_DIR + "%s/%s.glb" % [item, body]
|
||||
if not ResourceLoader.exists(glb):
|
||||
push_error("preview: garment GLB missing %s" % glb)
|
||||
return out
|
||||
var inst := (load(glb) as PackedScene).instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
out.append(mi)
|
||||
inst.queue_free()
|
||||
return out
|
||||
|
||||
|
||||
func _apply_garment_shader(mi: MeshInstance3D, shader: Shader, mask: Texture2D) -> void:
|
||||
if mi.mesh == null or shader == null:
|
||||
return
|
||||
var albedo := _albedo_of(mi)
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
if albedo:
|
||||
mat.set_shader_parameter("albedo_tex", albedo)
|
||||
if mask:
|
||||
mat.set_shader_parameter("region_mask", mask)
|
||||
mat.set_shader_parameter("tint_0", TINT_COLLAR)
|
||||
mat.set_shader_parameter("tint_1", TINT_BODY)
|
||||
mat.set_shader_parameter("tint_2", TINT_CUFF)
|
||||
mat.set_shader_parameter("tint_3", Color.WHITE)
|
||||
mat.set_shader_parameter("logo_enabled", 0.0)
|
||||
mat.set_shader_parameter("shadow_strength", 0.2)
|
||||
mi.material_override = mat
|
||||
|
||||
|
||||
func _albedo_of(mi: MeshInstance3D) -> Texture2D:
|
||||
for surf in range(mi.mesh.get_surface_count()):
|
||||
var m := mi.mesh.surface_get_material(surf)
|
||||
if m is BaseMaterial3D:
|
||||
return (m as BaseMaterial3D).albedo_texture
|
||||
return null
|
||||
|
||||
|
||||
func _try_load(path: String) -> Texture2D:
|
||||
return load(path) as Texture2D if ResourceLoader.exists(path) else null
|
||||
|
||||
|
||||
func _resolve_clip(ap: AnimationPlayer, requested: String) -> String:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
if str(a).to_lower().contains(requested.to_lower()):
|
||||
return str(a)
|
||||
return ""
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/garment_preview/preview_sweater_modern.gd" id="1_swprev"]
|
||||
|
||||
[node name="SweaterPreview" type="Node3D"]
|
||||
script = ExtResource("1_swprev")
|
||||
@@ -0,0 +1,194 @@
|
||||
extends Node3D
|
||||
## swim_trunks preview capture (T-1089 wave 2, swim set) — parameterized copy of
|
||||
## preview_shorts_modern.gd for a LOWER-BODY 3-region garment: per-body region
|
||||
## mask (reference fallback), no logo, camera framed on hips + upper thighs so
|
||||
## the drawstring, waistband and side panel read. Front + 3/4 Walk renders.
|
||||
## Temporary tool scene — safe to delete after captures.
|
||||
##
|
||||
## Config via env vars (all optional):
|
||||
## GARMENT_PREVIEW_ITEM default "swim_trunks"
|
||||
## GARMENT_PREVIEW_BODY default "average_m"
|
||||
## GARMENT_PREVIEW_OUT default "<repo>/.cache/garment-preview/swim_trunks"
|
||||
|
||||
const CLOTHING_DIR := "res://assets/characters/clothing/"
|
||||
const SHADER_PATH := "res://assets/characters/shaders/toon_garment.gdshader"
|
||||
const VIEWPORT := Vector2i(768, 1024)
|
||||
|
||||
const BODY_KEY_TO_ENUM := {
|
||||
"average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M,
|
||||
"average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F,
|
||||
"muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M,
|
||||
"muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F,
|
||||
"thin_m": CharacterVisualDescriptor.BodyType.THIN_M,
|
||||
"thin_f": CharacterVisualDescriptor.BodyType.THIN_F,
|
||||
"heavy_m": CharacterVisualDescriptor.BodyType.HEAVY_M,
|
||||
"heavy_f": CharacterVisualDescriptor.BodyType.HEAVY_F,
|
||||
"teen_m": CharacterVisualDescriptor.BodyType.TEEN_M,
|
||||
"teen_f": CharacterVisualDescriptor.BodyType.TEEN_F,
|
||||
"child": CharacterVisualDescriptor.BodyType.CHILD,
|
||||
}
|
||||
|
||||
# Bright saturated swim palette (spec: coral/teal default) — matches the
|
||||
# painted albedo hues so tinted and untinted renders agree.
|
||||
const TINT_WAISTBAND := Color(0.87, 0.40, 0.31) # R region, deep coral
|
||||
const TINT_BODY := Color(0.95, 0.45, 0.35) # G region, coral
|
||||
const TINT_PANEL := Color(0.13, 0.68, 0.64) # B region, teal
|
||||
|
||||
var _out_dir := ""
|
||||
var _cam: Camera3D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var item := _env("GARMENT_PREVIEW_ITEM", "swim_trunks")
|
||||
var body := _env("GARMENT_PREVIEW_BODY", "average_m")
|
||||
_out_dir = _env("GARMENT_PREVIEW_OUT",
|
||||
"/var/mnt/data/projects/settled-reach/.cache/garment-preview/swim_trunks")
|
||||
get_window().size = VIEWPORT
|
||||
_setup_stage()
|
||||
_run.call_deferred(item, body)
|
||||
|
||||
|
||||
func _env(key: String, fallback: String) -> String:
|
||||
var v := OS.get_environment(key)
|
||||
return v if not v.is_empty() else fallback
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-38, 28, 0)
|
||||
light.light_energy = 1.3
|
||||
add_child(light)
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.82, 0.82, 0.86)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.16, 0.17, 0.20)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
_cam = Camera3D.new()
|
||||
_cam.fov = 45.0
|
||||
add_child(_cam)
|
||||
# Frame hips + upper thighs so the drawstring, waistband and the
|
||||
# above-the-knee hem read clearly.
|
||||
_cam.position = Vector3(0.0, 0.90, 1.5)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0.0, 0.80, 0.0), Vector3.UP)
|
||||
|
||||
|
||||
func _run(item: String, body: String) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(_out_dir)
|
||||
if not BODY_KEY_TO_ENUM.has(body):
|
||||
push_error("preview: unknown body %s" % body)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var cv := CharacterVisual.new()
|
||||
add_child(cv)
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = BODY_KEY_TO_ENUM[body]
|
||||
desc.head_id = "head_001"
|
||||
desc.hair_id = "buzzed"
|
||||
desc.eyebrow_id = "regular"
|
||||
desc.skin_tone = 3
|
||||
cv.load_descriptor(desc)
|
||||
|
||||
var skel := cv.get_skeleton()
|
||||
if skel == null:
|
||||
push_error("preview: no skeleton")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var meshes := _attach_garment(skel, item, body)
|
||||
print("preview: attached %d garment meshes" % meshes.size())
|
||||
var shader := load(SHADER_PATH) as Shader
|
||||
# Per-body region mask (offset-shell route); reference fallback mirrors
|
||||
# the runtime compositor.
|
||||
var mask := _try_load(CLOTHING_DIR + "%s/%s_mask.png" % [item, body])
|
||||
if mask == null:
|
||||
mask = _try_load(CLOTHING_DIR + "%s/reference_mask.png" % item)
|
||||
for mi in meshes:
|
||||
_apply_garment_shader(mi, shader, mask)
|
||||
|
||||
# Freeze on a mid-stride Walk pose so hem + drawstring read under motion.
|
||||
var ap := cv.get_animation_player()
|
||||
if ap != null:
|
||||
var walk := _resolve_clip(ap, "Walk")
|
||||
if not walk.is_empty():
|
||||
cv.play_animation(walk)
|
||||
ap.pause()
|
||||
ap.seek(ap.current_animation_length * 0.25, true)
|
||||
await get_tree().process_frame
|
||||
|
||||
# Front + 3/4 (task deliverable: two preview renders).
|
||||
for yaw in [0, 45]:
|
||||
cv.rotation_degrees.y = float(yaw)
|
||||
await RenderingServer.frame_post_draw
|
||||
await RenderingServer.frame_post_draw
|
||||
var fname := "%s__%s__yaw%03d.png" % [item, body, yaw]
|
||||
get_viewport().get_texture().get_image().save_png(_out_dir.path_join(fname))
|
||||
print("preview: wrote ", fname)
|
||||
|
||||
print("preview: DONE")
|
||||
get_tree().quit(0)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D, item: String, body: String) -> Array:
|
||||
var out: Array = []
|
||||
var glb := CLOTHING_DIR + "%s/%s.glb" % [item, body]
|
||||
if not ResourceLoader.exists(glb):
|
||||
push_error("preview: garment GLB missing %s" % glb)
|
||||
return out
|
||||
var inst := (load(glb) as PackedScene).instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
out.append(mi)
|
||||
inst.queue_free()
|
||||
return out
|
||||
|
||||
|
||||
func _apply_garment_shader(mi: MeshInstance3D, shader: Shader, mask: Texture2D) -> void:
|
||||
if mi.mesh == null or shader == null:
|
||||
return
|
||||
var albedo := _albedo_of(mi)
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
if albedo:
|
||||
mat.set_shader_parameter("albedo_tex", albedo)
|
||||
if mask:
|
||||
mat.set_shader_parameter("region_mask", mask)
|
||||
mat.set_shader_parameter("tint_0", TINT_WAISTBAND)
|
||||
mat.set_shader_parameter("tint_1", TINT_BODY)
|
||||
mat.set_shader_parameter("tint_2", TINT_PANEL)
|
||||
mat.set_shader_parameter("tint_3", Color.WHITE)
|
||||
mat.set_shader_parameter("shadow_strength", 0.2)
|
||||
mi.material_override = mat
|
||||
|
||||
|
||||
func _albedo_of(mi: MeshInstance3D) -> Texture2D:
|
||||
for surf in range(mi.mesh.get_surface_count()):
|
||||
var m := mi.mesh.surface_get_material(surf)
|
||||
if m is BaseMaterial3D:
|
||||
return (m as BaseMaterial3D).albedo_texture
|
||||
return null
|
||||
|
||||
|
||||
func _try_load(path: String) -> Texture2D:
|
||||
return load(path) as Texture2D if ResourceLoader.exists(path) else null
|
||||
|
||||
|
||||
func _resolve_clip(ap: AnimationPlayer, requested: String) -> String:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
if str(a).to_lower().contains(requested.to_lower()):
|
||||
return str(a)
|
||||
return ""
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/garment_preview/preview_swim_trunks.gd" id="1_prevsw"]
|
||||
|
||||
[node name="GarmentPreviewSwimTrunks" type="Node3D"]
|
||||
script = ExtResource("1_prevsw")
|
||||
@@ -0,0 +1,198 @@
|
||||
extends Node3D
|
||||
## One-piece swimsuit preview capture (T-1089 wave 2, swimsuit_onepiece) —
|
||||
## per-garment copy of preview_scene.gd (same convention as the other wave
|
||||
## companions): saturated colour-block palette (off-white straps/trim over a
|
||||
## vivid coral body with deep-teal side blocks, matching the manifest
|
||||
## default_tints), body-aware mask loading (<body>_mask.png first, mirroring
|
||||
## runtime), NO logo (garment is not logo-capable — UV2 ships fully parked),
|
||||
## front + 3/4 walk yaws. Default body is average_f; override via env.
|
||||
##
|
||||
## Config via env vars (all optional):
|
||||
## GARMENT_PREVIEW_ITEM default "swimsuit_onepiece"
|
||||
## GARMENT_PREVIEW_BODY default "average_f"
|
||||
## GARMENT_PREVIEW_OUT default "<repo>/.cache/garment-preview"
|
||||
|
||||
const CLOTHING_DIR := "res://assets/characters/clothing/"
|
||||
const SHADER_PATH := "res://assets/characters/shaders/toon_garment.gdshader"
|
||||
const VIEWPORT := Vector2i(768, 1024)
|
||||
|
||||
const BODY_KEY_TO_ENUM := {
|
||||
"average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M,
|
||||
"average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F,
|
||||
"muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M,
|
||||
"muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F,
|
||||
"thin_m": CharacterVisualDescriptor.BodyType.THIN_M,
|
||||
"thin_f": CharacterVisualDescriptor.BodyType.THIN_F,
|
||||
"heavy_m": CharacterVisualDescriptor.BodyType.HEAVY_M,
|
||||
"heavy_f": CharacterVisualDescriptor.BodyType.HEAVY_F,
|
||||
"teen_m": CharacterVisualDescriptor.BodyType.TEEN_M,
|
||||
"teen_f": CharacterVisualDescriptor.BodyType.TEEN_F,
|
||||
"child": CharacterVisualDescriptor.BodyType.CHILD,
|
||||
}
|
||||
|
||||
# Saturated colour-block default (matches the manifest default_tints):
|
||||
# off-white straps + opening trim, vivid coral body, deep teal side blocks.
|
||||
const TINT_TRIM := Color(0.95, 0.94, 0.91) # R region (straps + edge trim)
|
||||
const TINT_BODY := Color(0.89, 0.29, 0.20) # G region (main body)
|
||||
const TINT_BLOCK := Color(0.12, 0.48, 0.55) # B region (side colour-block)
|
||||
|
||||
var _out_dir := ""
|
||||
var _cam: Camera3D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var item := _env("GARMENT_PREVIEW_ITEM", "swimsuit_onepiece")
|
||||
var body := _env("GARMENT_PREVIEW_BODY", "average_f")
|
||||
_out_dir = _env("GARMENT_PREVIEW_OUT",
|
||||
"/var/mnt/data/projects/settled-reach/.cache/garment-preview")
|
||||
get_window().size = VIEWPORT
|
||||
_setup_stage()
|
||||
_run.call_deferred(item, body)
|
||||
|
||||
|
||||
func _env(key: String, fallback: String) -> String:
|
||||
var v := OS.get_environment(key)
|
||||
return v if not v.is_empty() else fallback
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-38, 28, 0)
|
||||
light.light_energy = 1.3
|
||||
add_child(light)
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.82, 0.82, 0.86)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.16, 0.17, 0.20)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
_cam = Camera3D.new()
|
||||
_cam.fov = 45.0
|
||||
add_child(_cam)
|
||||
# Frame the whole garment span (shoulder straps down past the high-cut
|
||||
# hips) with bare limbs in view — the suit reads by its openings.
|
||||
_cam.position = Vector3(0.0, 1.02, 2.6)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0.0, 0.95, 0.0), Vector3.UP)
|
||||
|
||||
|
||||
func _run(item: String, body: String) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(_out_dir)
|
||||
if not BODY_KEY_TO_ENUM.has(body):
|
||||
push_error("preview: unknown body %s" % body)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var cv := CharacterVisual.new()
|
||||
add_child(cv)
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = BODY_KEY_TO_ENUM[body]
|
||||
desc.head_id = "head_001"
|
||||
# The male buzzed/regular combo renders mottled on female bodies at
|
||||
# preview camera range; pick female variants there (cosmetic only).
|
||||
var fem := body.ends_with("_f")
|
||||
desc.hair_id = "bob" if fem else "buzzed"
|
||||
desc.eyebrow_id = "female" if fem else "regular"
|
||||
desc.skin_tone = 3
|
||||
cv.load_descriptor(desc)
|
||||
|
||||
var skel := cv.get_skeleton()
|
||||
if skel == null:
|
||||
push_error("preview: no skeleton")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var meshes := _attach_garment(skel, item, body)
|
||||
print("preview: attached %d garment meshes" % meshes.size())
|
||||
var shader := load(SHADER_PATH) as Shader
|
||||
# Per-body-authored garment: prefer <body>_mask.png (runtime behaviour).
|
||||
var mask := _try_load(CLOTHING_DIR + "%s/%s_mask.png" % [item, body])
|
||||
if mask == null:
|
||||
mask = _try_load(CLOTHING_DIR + "%s/reference_mask.png" % item)
|
||||
for mi in meshes:
|
||||
_apply_garment_shader(mi, shader, mask)
|
||||
|
||||
# Freeze on a walk pose so the suit reads in motion (bare limbs swinging).
|
||||
var ap := cv.get_animation_player()
|
||||
if ap != null:
|
||||
var walk := _resolve_clip(ap, "Walk")
|
||||
if not walk.is_empty():
|
||||
cv.play_animation(walk)
|
||||
ap.pause()
|
||||
ap.seek(ap.current_animation_length * 0.25, true)
|
||||
await get_tree().process_frame
|
||||
|
||||
# Front + 3/4 (task spec: two preview renders).
|
||||
for yaw in [0, 45]:
|
||||
cv.rotation_degrees.y = float(yaw)
|
||||
await RenderingServer.frame_post_draw
|
||||
await RenderingServer.frame_post_draw
|
||||
var fname := "%s__%s__yaw%03d.png" % [item, body, yaw]
|
||||
get_viewport().get_texture().get_image().save_png(_out_dir.path_join(fname))
|
||||
print("preview: wrote ", fname)
|
||||
|
||||
print("preview: DONE")
|
||||
get_tree().quit(0)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D, item: String, body: String) -> Array:
|
||||
var out: Array = []
|
||||
var glb := CLOTHING_DIR + "%s/%s.glb" % [item, body]
|
||||
if not ResourceLoader.exists(glb):
|
||||
push_error("preview: garment GLB missing %s" % glb)
|
||||
return out
|
||||
var inst := (load(glb) as PackedScene).instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
out.append(mi)
|
||||
inst.queue_free()
|
||||
return out
|
||||
|
||||
|
||||
func _apply_garment_shader(mi: MeshInstance3D, shader: Shader, mask: Texture2D) -> void:
|
||||
if mi.mesh == null or shader == null:
|
||||
return
|
||||
var albedo := _albedo_of(mi)
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
if albedo:
|
||||
mat.set_shader_parameter("albedo_tex", albedo)
|
||||
if mask:
|
||||
mat.set_shader_parameter("region_mask", mask)
|
||||
mat.set_shader_parameter("tint_0", TINT_TRIM)
|
||||
mat.set_shader_parameter("tint_1", TINT_BODY)
|
||||
mat.set_shader_parameter("tint_2", TINT_BLOCK)
|
||||
mat.set_shader_parameter("tint_3", Color.WHITE)
|
||||
mat.set_shader_parameter("shadow_strength", 0.2)
|
||||
mi.material_override = mat
|
||||
|
||||
|
||||
func _albedo_of(mi: MeshInstance3D) -> Texture2D:
|
||||
for surf in range(mi.mesh.get_surface_count()):
|
||||
var m := mi.mesh.surface_get_material(surf)
|
||||
if m is BaseMaterial3D:
|
||||
return (m as BaseMaterial3D).albedo_texture
|
||||
return null
|
||||
|
||||
|
||||
func _try_load(path: String) -> Texture2D:
|
||||
return load(path) as Texture2D if ResourceLoader.exists(path) else null
|
||||
|
||||
|
||||
func _resolve_clip(ap: AnimationPlayer, requested: String) -> String:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
if str(a).to_lower().contains(requested.to_lower()):
|
||||
return str(a)
|
||||
return ""
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/garment_preview/preview_swimsuit_onepiece.gd" id="1_swsprev"]
|
||||
|
||||
[node name="SwimsuitOnepiecePreview" type="Node3D"]
|
||||
script = ExtResource("1_swsprev")
|
||||
@@ -0,0 +1,199 @@
|
||||
extends Node3D
|
||||
## Tank top preview capture (T-1089 wave 2, tank_top) — per-garment copy of
|
||||
## preview_scene.gd (same convention as the other wave companions): bright
|
||||
## sport palette (off-white collar/armhole bindings over bright orange body,
|
||||
## matching the manifest default_tints), body-aware mask loading
|
||||
## (<body>_mask.png first, mirroring runtime), thrds chest logo ON
|
||||
## (logo_capable garment — proves the UV2 channel), front + 3/4 walk yaws.
|
||||
##
|
||||
## Config via env vars (all optional):
|
||||
## GARMENT_PREVIEW_ITEM default "tank_top"
|
||||
## GARMENT_PREVIEW_BODY default "average_m"
|
||||
## GARMENT_PREVIEW_OUT default "<repo>/.cache/garment-preview"
|
||||
## GARMENT_PREVIEW_LOGO default thrds.png
|
||||
|
||||
const CLOTHING_DIR := "res://assets/characters/clothing/"
|
||||
const LOGO_PATH := "res://assets/characters/logos/thrds.png"
|
||||
const SHADER_PATH := "res://assets/characters/shaders/toon_garment.gdshader"
|
||||
const VIEWPORT := Vector2i(768, 1024)
|
||||
|
||||
const BODY_KEY_TO_ENUM := {
|
||||
"average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M,
|
||||
"average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F,
|
||||
"muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M,
|
||||
"muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F,
|
||||
"thin_m": CharacterVisualDescriptor.BodyType.THIN_M,
|
||||
"thin_f": CharacterVisualDescriptor.BodyType.THIN_F,
|
||||
"heavy_m": CharacterVisualDescriptor.BodyType.HEAVY_M,
|
||||
"heavy_f": CharacterVisualDescriptor.BodyType.HEAVY_F,
|
||||
"teen_m": CharacterVisualDescriptor.BodyType.TEEN_M,
|
||||
"teen_f": CharacterVisualDescriptor.BodyType.TEEN_F,
|
||||
"child": CharacterVisualDescriptor.BodyType.CHILD,
|
||||
}
|
||||
|
||||
# Bright sport default (matches the manifest default_tints for tank_top):
|
||||
# off-white bindings, bright orange body.
|
||||
const TINT_COLLAR := Color(0.96, 0.95, 0.92) # R region (collar band)
|
||||
const TINT_BODY := Color(0.91, 0.41, 0.23) # G region (body)
|
||||
const TINT_ARMHOLE := Color(0.96, 0.95, 0.92) # B region (armhole trim)
|
||||
|
||||
var _out_dir := ""
|
||||
var _cam: Camera3D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var item := _env("GARMENT_PREVIEW_ITEM", "tank_top")
|
||||
var body := _env("GARMENT_PREVIEW_BODY", "average_m")
|
||||
_out_dir = _env("GARMENT_PREVIEW_OUT",
|
||||
"/var/mnt/data/projects/settled-reach/.cache/garment-preview")
|
||||
get_window().size = VIEWPORT
|
||||
_setup_stage()
|
||||
_run.call_deferred(item, body)
|
||||
|
||||
|
||||
func _env(key: String, fallback: String) -> String:
|
||||
var v := OS.get_environment(key)
|
||||
return v if not v.is_empty() else fallback
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-38, 28, 0)
|
||||
light.light_energy = 1.3
|
||||
add_child(light)
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.82, 0.82, 0.86)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.16, 0.17, 0.20)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
_cam = Camera3D.new()
|
||||
_cam.fov = 45.0
|
||||
add_child(_cam)
|
||||
# Frame the upper body so scoop/straps/armholes + chest logo read clearly.
|
||||
_cam.position = Vector3(0.0, 1.25, 1.7)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0.0, 1.15, 0.0), Vector3.UP)
|
||||
|
||||
|
||||
func _run(item: String, body: String) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(_out_dir)
|
||||
if not BODY_KEY_TO_ENUM.has(body):
|
||||
push_error("preview: unknown body %s" % body)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var cv := CharacterVisual.new()
|
||||
add_child(cv)
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = BODY_KEY_TO_ENUM[body]
|
||||
desc.head_id = "head_001"
|
||||
desc.hair_id = "buzzed"
|
||||
desc.eyebrow_id = "regular"
|
||||
desc.skin_tone = 3
|
||||
cv.load_descriptor(desc)
|
||||
|
||||
var skel := cv.get_skeleton()
|
||||
if skel == null:
|
||||
push_error("preview: no skeleton")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var meshes := _attach_garment(skel, item, body)
|
||||
print("preview: attached %d garment meshes" % meshes.size())
|
||||
var shader := load(SHADER_PATH) as Shader
|
||||
# Per-body-authored garment: prefer <body>_mask.png (runtime behaviour).
|
||||
var mask := _try_load(CLOTHING_DIR + "%s/%s_mask.png" % [item, body])
|
||||
if mask == null:
|
||||
mask = _try_load(CLOTHING_DIR + "%s/reference_mask.png" % item)
|
||||
var logo := _try_load(_env("GARMENT_PREVIEW_LOGO", LOGO_PATH))
|
||||
for mi in meshes:
|
||||
_apply_garment_shader(mi, shader, mask, logo)
|
||||
|
||||
# Freeze on a walk pose so the tank reads in motion (bare arms swinging).
|
||||
var ap := cv.get_animation_player()
|
||||
if ap != null:
|
||||
var walk := _resolve_clip(ap, "Walk")
|
||||
if not walk.is_empty():
|
||||
cv.play_animation(walk)
|
||||
ap.pause()
|
||||
ap.seek(ap.current_animation_length * 0.25, true)
|
||||
await get_tree().process_frame
|
||||
|
||||
# Front + 3/4 (task spec: two preview renders).
|
||||
for yaw in [0, 45]:
|
||||
cv.rotation_degrees.y = float(yaw)
|
||||
await RenderingServer.frame_post_draw
|
||||
await RenderingServer.frame_post_draw
|
||||
var fname := "%s__%s__yaw%03d.png" % [item, body, yaw]
|
||||
get_viewport().get_texture().get_image().save_png(_out_dir.path_join(fname))
|
||||
print("preview: wrote ", fname)
|
||||
|
||||
print("preview: DONE")
|
||||
get_tree().quit(0)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D, item: String, body: String) -> Array:
|
||||
var out: Array = []
|
||||
var glb := CLOTHING_DIR + "%s/%s.glb" % [item, body]
|
||||
if not ResourceLoader.exists(glb):
|
||||
push_error("preview: garment GLB missing %s" % glb)
|
||||
return out
|
||||
var inst := (load(glb) as PackedScene).instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
out.append(mi)
|
||||
inst.queue_free()
|
||||
return out
|
||||
|
||||
|
||||
func _apply_garment_shader(mi: MeshInstance3D, shader: Shader, mask: Texture2D, logo: Texture2D) -> void:
|
||||
if mi.mesh == null or shader == null:
|
||||
return
|
||||
var albedo := _albedo_of(mi)
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
if albedo:
|
||||
mat.set_shader_parameter("albedo_tex", albedo)
|
||||
if mask:
|
||||
mat.set_shader_parameter("region_mask", mask)
|
||||
if logo:
|
||||
mat.set_shader_parameter("logo_tex", logo)
|
||||
mat.set_shader_parameter("logo_enabled", 1.0)
|
||||
mat.set_shader_parameter("tint_0", TINT_COLLAR)
|
||||
mat.set_shader_parameter("tint_1", TINT_BODY)
|
||||
mat.set_shader_parameter("tint_2", TINT_ARMHOLE)
|
||||
mat.set_shader_parameter("tint_3", Color.WHITE)
|
||||
mat.set_shader_parameter("shadow_strength", 0.2)
|
||||
mi.material_override = mat
|
||||
|
||||
|
||||
func _albedo_of(mi: MeshInstance3D) -> Texture2D:
|
||||
for surf in range(mi.mesh.get_surface_count()):
|
||||
var m := mi.mesh.surface_get_material(surf)
|
||||
if m is BaseMaterial3D:
|
||||
return (m as BaseMaterial3D).albedo_texture
|
||||
return null
|
||||
|
||||
|
||||
func _try_load(path: String) -> Texture2D:
|
||||
return load(path) as Texture2D if ResourceLoader.exists(path) else null
|
||||
|
||||
|
||||
func _resolve_clip(ap: AnimationPlayer, requested: String) -> String:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
if str(a).to_lower().contains(requested.to_lower()):
|
||||
return str(a)
|
||||
return ""
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/garment_preview/preview_tank_top.gd" id="1_tankprev"]
|
||||
|
||||
[node name="TankTopPreview" type="Node3D"]
|
||||
script = ExtResource("1_tankprev")
|
||||
@@ -0,0 +1,194 @@
|
||||
extends Node3D
|
||||
## Track jacket preview capture (T-1089 wave 2 — track_jacket) — per-garment
|
||||
## copy of preview_scene.gd (same convention as preview_buttondown_modern.gd):
|
||||
## team palette (white stand collar + white sleeve stripes over royal-blue
|
||||
## body, navy cuff/hem trim), body-aware mask loading (<body>_mask.png first,
|
||||
## mirroring runtime), thrds chest logo (logo_capable ON, left-breast patch),
|
||||
## front + 3/4 walk yaws only.
|
||||
##
|
||||
## Config via env vars (all optional):
|
||||
## GARMENT_PREVIEW_ITEM default "track_jacket"
|
||||
## GARMENT_PREVIEW_BODY default "average_m"
|
||||
## GARMENT_PREVIEW_OUT default "<repo>/.cache/garment-preview"
|
||||
## GARMENT_PREVIEW_LOGO default thrds.png
|
||||
|
||||
const CLOTHING_DIR := "res://assets/characters/clothing/"
|
||||
const LOGO_PATH := "res://assets/characters/logos/thrds.png"
|
||||
const SHADER_PATH := "res://assets/characters/shaders/toon_garment.gdshader"
|
||||
const VIEWPORT := Vector2i(768, 1024)
|
||||
|
||||
const BODY_KEY_TO_ENUM := {
|
||||
"average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M,
|
||||
"average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F,
|
||||
"muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M,
|
||||
"muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F,
|
||||
"teen_m": CharacterVisualDescriptor.BodyType.TEEN_M,
|
||||
"teen_f": CharacterVisualDescriptor.BodyType.TEEN_F,
|
||||
}
|
||||
|
||||
# Team kit: white stand collar, royal-blue body, white stripes, navy trim.
|
||||
const TINT_COLLAR := Color(0.92, 0.93, 0.95) # R region
|
||||
const TINT_BODY := Color(0.16, 0.30, 0.62) # G region
|
||||
const TINT_STRIPE := Color(0.94, 0.95, 0.97) # B region (team stripe)
|
||||
const TINT_TRIM := Color(0.10, 0.15, 0.32) # A region (cuffs + hem band)
|
||||
|
||||
var _out_dir := ""
|
||||
var _cam: Camera3D = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var item := _env("GARMENT_PREVIEW_ITEM", "track_jacket")
|
||||
var body := _env("GARMENT_PREVIEW_BODY", "average_m")
|
||||
_out_dir = _env("GARMENT_PREVIEW_OUT",
|
||||
"/var/mnt/data/projects/settled-reach/.cache/garment-preview")
|
||||
get_window().size = VIEWPORT
|
||||
_setup_stage()
|
||||
_run.call_deferred(item, body)
|
||||
|
||||
|
||||
func _env(key: String, fallback: String) -> String:
|
||||
var v := OS.get_environment(key)
|
||||
return v if not v.is_empty() else fallback
|
||||
|
||||
|
||||
func _setup_stage() -> void:
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-38, 28, 0)
|
||||
light.light_energy = 1.3
|
||||
add_child(light)
|
||||
var env := Environment.new()
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.82, 0.82, 0.86)
|
||||
env.ambient_light_energy = 0.9
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.16, 0.17, 0.20)
|
||||
var we := WorldEnvironment.new()
|
||||
we.environment = env
|
||||
add_child(we)
|
||||
_cam = Camera3D.new()
|
||||
_cam.fov = 45.0
|
||||
add_child(_cam)
|
||||
# Frame the upper body so collar/zip/stripes/logo read clearly.
|
||||
_cam.position = Vector3(0.0, 1.25, 1.7)
|
||||
_cam.look_at_from_position(_cam.position, Vector3(0.0, 1.15, 0.0), Vector3.UP)
|
||||
|
||||
|
||||
func _run(item: String, body: String) -> void:
|
||||
DirAccess.make_dir_recursive_absolute(_out_dir)
|
||||
if not BODY_KEY_TO_ENUM.has(body):
|
||||
push_error("preview: unknown body %s" % body)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var cv := CharacterVisual.new()
|
||||
add_child(cv)
|
||||
var desc := CharacterVisualDescriptor.new()
|
||||
desc.body_type = BODY_KEY_TO_ENUM[body]
|
||||
desc.head_id = "head_001"
|
||||
desc.hair_id = "buzzed"
|
||||
desc.eyebrow_id = "regular"
|
||||
desc.skin_tone = 3
|
||||
cv.load_descriptor(desc)
|
||||
|
||||
var skel := cv.get_skeleton()
|
||||
if skel == null:
|
||||
push_error("preview: no skeleton")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
|
||||
var meshes := _attach_garment(skel, item, body)
|
||||
print("preview: attached %d garment meshes" % meshes.size())
|
||||
var shader := load(SHADER_PATH) as Shader
|
||||
# Per-body-authored garment: prefer <body>_mask.png (runtime behaviour).
|
||||
var mask := _try_load(CLOTHING_DIR + "%s/%s_mask.png" % [item, body])
|
||||
if mask == null:
|
||||
mask = _try_load(CLOTHING_DIR + "%s/reference_mask.png" % item)
|
||||
var logo := _try_load(_env("GARMENT_PREVIEW_LOGO", LOGO_PATH))
|
||||
for mi in meshes:
|
||||
_apply_garment_shader(mi, shader, mask, logo)
|
||||
|
||||
# Freeze on a walk pose so the jacket reads in motion.
|
||||
var ap := cv.get_animation_player()
|
||||
if ap != null:
|
||||
var walk := _resolve_clip(ap, "Walk")
|
||||
if not walk.is_empty():
|
||||
cv.play_animation(walk)
|
||||
ap.pause()
|
||||
ap.seek(ap.current_animation_length * 0.25, true)
|
||||
await get_tree().process_frame
|
||||
|
||||
# Front + 3/4 (task spec: two preview renders).
|
||||
for yaw in [0, 45]:
|
||||
cv.rotation_degrees.y = float(yaw)
|
||||
await RenderingServer.frame_post_draw
|
||||
await RenderingServer.frame_post_draw
|
||||
var fname := "%s__%s__yaw%03d.png" % [item, body, yaw]
|
||||
get_viewport().get_texture().get_image().save_png(_out_dir.path_join(fname))
|
||||
print("preview: wrote ", fname)
|
||||
|
||||
print("preview: DONE")
|
||||
get_tree().quit(0)
|
||||
|
||||
|
||||
func _attach_garment(skel: Skeleton3D, item: String, body: String) -> Array:
|
||||
var out: Array = []
|
||||
var glb := CLOTHING_DIR + "%s/%s.glb" % [item, body]
|
||||
if not ResourceLoader.exists(glb):
|
||||
push_error("preview: garment GLB missing %s" % glb)
|
||||
return out
|
||||
var inst := (load(glb) as PackedScene).instantiate()
|
||||
var stack: Array[Node] = [inst]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
if n is MeshInstance3D and (n as MeshInstance3D).skin != null:
|
||||
var mi := n as MeshInstance3D
|
||||
mi.get_parent().remove_child(mi)
|
||||
mi.owner = null
|
||||
skel.add_child(mi)
|
||||
out.append(mi)
|
||||
inst.queue_free()
|
||||
return out
|
||||
|
||||
|
||||
func _apply_garment_shader(mi: MeshInstance3D, shader: Shader, mask: Texture2D, logo: Texture2D) -> void:
|
||||
if mi.mesh == null or shader == null:
|
||||
return
|
||||
var albedo := _albedo_of(mi)
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = shader
|
||||
if albedo:
|
||||
mat.set_shader_parameter("albedo_tex", albedo)
|
||||
if mask:
|
||||
mat.set_shader_parameter("region_mask", mask)
|
||||
if logo:
|
||||
mat.set_shader_parameter("logo_tex", logo)
|
||||
mat.set_shader_parameter("logo_enabled", 1.0)
|
||||
mat.set_shader_parameter("tint_0", TINT_COLLAR)
|
||||
mat.set_shader_parameter("tint_1", TINT_BODY)
|
||||
mat.set_shader_parameter("tint_2", TINT_STRIPE)
|
||||
mat.set_shader_parameter("tint_3", TINT_TRIM)
|
||||
mat.set_shader_parameter("shadow_strength", 0.2)
|
||||
mi.material_override = mat
|
||||
|
||||
|
||||
func _albedo_of(mi: MeshInstance3D) -> Texture2D:
|
||||
for surf in range(mi.mesh.get_surface_count()):
|
||||
var m := mi.mesh.surface_get_material(surf)
|
||||
if m is BaseMaterial3D:
|
||||
return (m as BaseMaterial3D).albedo_texture
|
||||
return null
|
||||
|
||||
|
||||
func _try_load(path: String) -> Texture2D:
|
||||
return load(path) as Texture2D if ResourceLoader.exists(path) else null
|
||||
|
||||
|
||||
func _resolve_clip(ap: AnimationPlayer, requested: String) -> String:
|
||||
for lib_name in ap.get_animation_library_list():
|
||||
var lib := ap.get_animation_library(lib_name)
|
||||
for a in lib.get_animation_list():
|
||||
if str(a).to_lower().contains(requested.to_lower()):
|
||||
return str(a)
|
||||
return ""
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tools/garment_preview/preview_track_jacket.gd" id="1_tjprev"]
|
||||
|
||||
[node name="TrackJacketPreview" type="Node3D"]
|
||||
script = ExtResource("1_tjprev")
|
||||
Reference in New Issue
Block a user