extends SceneTree ## Generates the placeholder avatar asset pack (GLB files) referenced by ## assets/avatars/catalog.json. ## ## These are deliberately crude primitive stand-ins so the avatar loader / ## customization / in-world rendering can be built and tested now; real art ## swaps in later as a pure content change (catalog paths are unchanged). ## ## The base body is a Roblox-classic (R6) style blocky figure: six chunky box ## parts (head, torso, two arms, two legs) skinned one-part-per-bone onto the ## 24-bone canonical rig, arms hanging at the sides, plus a painted-on face. ## ## Run: godot --headless --path . --script res://scripts/tools/gen_placeholder_avatars.gd ## Output: res://assets/avatars/*.glb (13 attachments + 1 rigged base body) const OUT_DIR := "res://assets/avatars/" # Canonical rig -- must match avatar_loader.gd CANONICAL_BONES. Each entry: # name -> [parent_name, global_rest_position] (A-pose, arms down, Y-up, metres) # The character faces -Z (Godot's node "forward"), so the face, toes and # front-mounted attachments live at negative Z. const BONES := { "Root": ["", Vector3(0.00, 0.00, 0.00)], "Hips": ["Root", Vector3(0.00, 0.76, 0.00)], "Spine": ["Hips", Vector3(0.00, 1.00, 0.00)], "Chest": ["Spine", Vector3(0.00, 1.30, 0.00)], "Neck": ["Chest", Vector3(0.00, 1.50, 0.00)], # Head bone sits at the visual head's centre and HeadTop just above its # crown, so hats / hair / helmets mount where you'd expect. (The body mesh # is rigidly bound, so moving these bones doesn't move the mesh.) "Head": ["Neck", Vector3(0.00, 1.74, 0.00)], "HeadTop": ["Head", Vector3(0.00, 2.06, 0.00)], "Jaw": ["Head", Vector3(0.00, 1.66, -0.22)], "LeftEye": ["Head", Vector3(0.12, 1.80, -0.28)], "RightEye": ["Head", Vector3(-0.12, 1.80, -0.28)], "LeftShoulder": ["Chest", Vector3(0.42, 1.50, 0.00)], "LeftUpperArm": ["LeftShoulder", Vector3(0.60, 1.44, 0.00)], "LeftLowerArm": ["LeftUpperArm", Vector3(0.60, 1.14, 0.00)], "LeftHand": ["LeftLowerArm", Vector3(0.60, 0.82, 0.00)], "RightShoulder": ["Chest", Vector3(-0.42, 1.50, 0.00)], "RightUpperArm": ["RightShoulder", Vector3(-0.60, 1.44, 0.00)], "RightLowerArm": ["RightUpperArm", Vector3(-0.60, 1.14, 0.00)], "RightHand": ["RightLowerArm", Vector3(-0.60, 0.82, 0.00)], "LeftUpperLeg": ["Hips", Vector3(0.21, 0.74, 0.00)], "LeftLowerLeg": ["LeftUpperLeg", Vector3(0.21, 0.38, 0.00)], "LeftFoot": ["LeftLowerLeg", Vector3(0.21, 0.05, -0.06)], "RightUpperLeg": ["Hips", Vector3(-0.21, 0.74, 0.00)], "RightLowerLeg": ["RightUpperLeg", Vector3(-0.21, 0.38, 0.00)], "RightFoot": ["RightLowerLeg", Vector3(-0.21, 0.05, -0.06)], } # Order matters: parents before children. const BONE_ORDER := [ "Root", "Hips", "Spine", "Chest", "Neck", "Head", "HeadTop", "Jaw", "LeftEye", "RightEye", "LeftShoulder", "LeftUpperArm", "LeftLowerArm", "LeftHand", "RightShoulder", "RightUpperArm", "RightLowerArm", "RightHand", "LeftUpperLeg", "LeftLowerLeg", "LeftFoot", "RightUpperLeg", "RightLowerLeg", "RightFoot", ] # Roblox-classic colour blocks. const C_SKIN := Color(0.96, 0.80, 0.19) # bright yellow (head + arms) const C_SHIRT := Color(0.05, 0.41, 0.67) # bright blue (torso) const C_PANTS := Color(0.18, 0.49, 0.30) # bright green (legs) const C_FACE := Color(0.05, 0.05, 0.06) # near-black (eyes + smile) # Body parts: [bone, centre, size, colour]. Each part is one rigid box, fully # weighted to its bone -- so the rig still drives it, but it reads as a clean # solid block (no banding, no capsule taper). const PARTS := [ # Torso, arms and legs all share the same 0.46 depth so a limb always fully # covers the slice of torso behind it -- otherwise a brightly-lit sliver of # the torso side peeks past the arm and reads as a cyan seam. ["Head", Vector3(0.00, 1.74, 0.00), Vector3(0.58, 0.56, 0.58), C_SKIN], ["Spine", Vector3(0.00, 1.14, 0.00), Vector3(0.90, 0.80, 0.46), C_SHIRT], ["LeftUpperArm", Vector3(0.57, 1.14, 0.00), Vector3(0.34, 0.78, 0.46), C_SKIN], ["RightUpperArm", Vector3(-0.57, 1.14, 0.00), Vector3(0.34, 0.78, 0.46), C_SKIN], ["LeftUpperLeg", Vector3(0.21, 0.38, 0.00), Vector3(0.40, 0.78, 0.46), C_PANTS], ["RightUpperLeg", Vector3(-0.21, 0.38, 0.00), Vector3(0.40, 0.78, 0.46), C_PANTS], # painted-on face -- thin plates on the front of the head (-Z), rigged to Head ["Head", Vector3(0.13, 1.78, -0.30), Vector3(0.09, 0.12, 0.02), C_FACE], ["Head", Vector3(-0.13, 1.78, -0.30), Vector3(0.09, 0.12, 0.02), C_FACE], ["Head", Vector3(0.00, 1.64, -0.30), Vector3(0.32, 0.05, 0.02), C_FACE], ] # Attachments: id -> [Mesh, local_offset_from_mount_bone, colour] # Mesh is built in _attachment_mesh(). const ATTACHMENTS := { "head_cyber_01": ["head", Vector3(0, 0.12, 0.0), Color(0.20, 0.85, 0.95)], "hair_mohawk_01": ["mohawk", Vector3(0, 0.10, 0.0), Color(0.90, 0.20, 0.35)], "hair_ponytail_01": ["ponytail", Vector3(0, -0.22, 0.24), Color(0.45, 0.30, 0.15)], "face_glasses_01": ["glasses", Vector3(0, 0.12, -0.07), Color(0.10, 0.10, 0.12)], "back_backpack_01": ["box", Vector3(0, -0.06, 0.28), Color(0.30, 0.35, 0.25)], "accessory_necklace_01": ["necklace", Vector3(0, 1.34, -0.10), Color(0.85, 0.75, 0.20)], } var _fail := 0 func _initialize() -> void: var abs_dir := ProjectSettings.globalize_path(OUT_DIR) DirAccess.make_dir_recursive_absolute(abs_dir) _gen_base_body() for id in ATTACHMENTS: var spec: Array = ATTACHMENTS[id] _gen_attachment(id, spec[0], spec[1], spec[2]) _gen_chest_bandit() _gen_pants_cargo() _gen_glove("left_hand_glove_01", 1.0) _gen_glove("right_hand_glove_01", -1.0) _gen_wristband("left_hand_wristband_01", 1.0) _gen_wristband("right_hand_wristband_01", -1.0) _gen_boot("left_foot_boot_01", 1.0) _gen_boot("right_foot_boot_01", -1.0) _gen_sneaker("left_foot_sneaker_01", 1.0) _gen_sneaker("right_foot_sneaker_01", -1.0) _gen_belt() _gen_water_buffalo_hat() print("\n%s" % ("DONE — all placeholder avatar GLBs written" if _fail == 0 else "FAILED (%d errors)" % _fail)) quit(_fail) # --------------------------------------------------------------------------- # Base body -- rigged, skinned # --------------------------------------------------------------------------- func _global_rest(bone: String) -> Transform3D: return Transform3D(Basis.IDENTITY, BONES[bone][1]) func _gen_base_body() -> void: var root := Node3D.new() root.name = "AvatarRoot" var skel := Skeleton3D.new() skel.name = "Skeleton3D" root.add_child(skel) var idx := {} for bone in BONE_ORDER: var i := skel.add_bone(bone) idx[bone] = i for bone in BONE_ORDER: var parent: String = BONES[bone][0] var g := _global_rest(bone) if parent == "": skel.set_bone_rest(idx[bone], g) else: skel.set_bone_parent(idx[bone], idx[parent]) skel.set_bone_rest(idx[bone], _global_rest(parent).affine_inverse() * g) for bone in BONE_ORDER: skel.set_bone_pose_position(idx[bone], skel.get_bone_rest(idx[bone]).origin) # Skin: one bind per bone, bind pose = inverse global rest. var skin := Skin.new() for bone in BONE_ORDER: skin.add_named_bind(bone, _global_rest(bone).affine_inverse()) var bind_index := {} for b in range(BONE_ORDER.size()): bind_index[BONE_ORDER[b]] = b # One surface per distinct colour so parts stay flat-shaded solids. var groups: Dictionary = {} # Color (as key string) -> {color, verts, normals, bones, weights} for part in PARTS: var bone: String = part[0] var centre: Vector3 = part[1] var size: Vector3 = part[2] var col: Color = part[3] var key := col.to_html() if not groups.has(key): groups[key] = { "color": col, "verts": PackedVector3Array(), "normals": PackedVector3Array(), "bones": PackedInt32Array(), "weights": PackedFloat32Array(), } var gg = groups[key] _append_box(gg.verts, gg.normals, gg.bones, gg.weights, centre, size, bind_index[bone]) var mesh := ArrayMesh.new() var surf := 0 for key in groups: var gg = groups[key] var arr := [] arr.resize(Mesh.ARRAY_MAX) arr[Mesh.ARRAY_VERTEX] = gg.verts arr[Mesh.ARRAY_NORMAL] = gg.normals arr[Mesh.ARRAY_BONES] = gg.bones arr[Mesh.ARRAY_WEIGHTS] = gg.weights mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arr) var mat := StandardMaterial3D.new() mat.albedo_color = gg.color mat.roughness = 0.9 mesh.surface_set_material(surf, mat) surf += 1 var mi := MeshInstance3D.new() mi.name = "Body" mi.mesh = mesh mi.skin = skin skel.add_child(mi) mi.skeleton = mi.get_path_to(skel) _write_glb(root, "base_human_male_01") root.free() # Append an axis-aligned box (centre, size) with every vert weighted 1.0 to bone_idx. func _append_box(verts: PackedVector3Array, normals: PackedVector3Array, bones: PackedInt32Array, weights: PackedFloat32Array, centre: Vector3, size: Vector3, bone_idx: int) -> void: var h := size * 0.5 # 8 corners var c: Array[Vector3] = [ centre + Vector3(-h.x, -h.y, -h.z), centre + Vector3(h.x, -h.y, -h.z), centre + Vector3(h.x, -h.y, h.z), centre + Vector3(-h.x, -h.y, h.z), centre + Vector3(-h.x, h.y, -h.z), centre + Vector3(h.x, h.y, -h.z), centre + Vector3(h.x, h.y, h.z), centre + Vector3(-h.x, h.y, h.z), ] # Vertex order per face is CCW seen from outside -> the computed normal # points outward. (Getting this backwards renders the whole mesh as an # inside-out black shell: every face is lit from behind.) var faces := [ [3, 2, 6, 7], # +Z front [1, 0, 4, 5], # -Z back [7, 6, 5, 4], # +Y top [0, 1, 2, 3], # -Y bottom [2, 1, 5, 6], # +X right [3, 7, 4, 0], # -X left ] for f in faces: var q: Array[Vector3] = [c[f[0]], c[f[1]], c[f[2]], c[f[3]]] var n: Vector3 = (q[1] - q[0]).cross(q[2] - q[0]).normalized() for tri in [[0, 2, 1], [0, 3, 2]]: for vi in tri: verts.append(q[vi]) normals.append(n) bones.append_array([bone_idx, 0, 0, 0]) weights.append_array([1.0, 0.0, 0.0, 0.0]) # --------------------------------------------------------------------------- # Attachments -- plain static meshes # --------------------------------------------------------------------------- func _gen_attachment(id: String, kind: String, offset: Vector3, col: Color) -> void: var root := Node3D.new() root.name = "AttachmentRoot" var mi := MeshInstance3D.new() mi.name = id mi.mesh = _attachment_mesh(kind) mi.position = offset var mat := StandardMaterial3D.new() mat.albedo_color = col mat.roughness = 0.9 mi.material_override = mat root.add_child(mi) _write_glb(root, id) root.free() func _attachment_mesh(kind: String) -> Mesh: match kind: "head": # A cyber visor band across the upper half of the ~0.58 head box. var m := BoxMesh.new(); m.size = Vector3(0.64, 0.30, 0.62); return m "mohawk": var m := PrismMesh.new(); m.size = Vector3(0.10, 0.24, 0.50); return m "ponytail": var m := BoxMesh.new(); m.size = Vector3(0.16, 0.36, 0.20); return m "glasses": var m := BoxMesh.new(); m.size = Vector3(0.48, 0.11, 0.05); return m "torso": var m := BoxMesh.new(); m.size = Vector3(0.80, 0.66, 0.42); return m "box": var m := BoxMesh.new(); m.size = Vector3(0.34, 0.40, 0.16); return m "hips": var m := BoxMesh.new(); m.size = Vector3(0.66, 0.34, 0.40); return m "blob": var m := BoxMesh.new(); m.size = Vector3(0.30, 0.24, 0.34); return m "boot": var m := BoxMesh.new(); m.size = Vector3(0.34, 0.16, 0.42); return m "sneaker": var m := BoxMesh.new(); m.size = Vector3(0.32, 0.14, 0.40); return m "band": var m := TorusMesh.new(); m.inner_radius = 0.10; m.outer_radius = 0.16; return m "belt": var m := TorusMesh.new(); m.inner_radius = 0.22; m.outer_radius = 0.30; return m "necklace": var m := TorusMesh.new(); m.inner_radius = 0.10; m.outer_radius = 0.14; return m _: return BoxMesh.new() func _gen_water_buffalo_hat() -> void: var root := Node3D.new() root.name = "AttachmentRoot" var cap_mat := StandardMaterial3D.new() cap_mat.albedo_color = Color(0.13, 0.20, 0.58) # Flintstones Water Buffalo blue cap_mat.roughness = 0.95 # 1. Hat Cap Base (twice as tall cylindrical body) var cap := MeshInstance3D.new() cap.name = "HatCap" var cap_mesh := CylinderMesh.new() cap_mesh.top_radius = 0.29 cap_mesh.bottom_radius = 0.32 cap_mesh.height = 0.60 cap.mesh = cap_mesh cap.position = Vector3(0.0, 0.30, 0.0) cap.material_override = cap_mat root.add_child(cap) # 1b. Rounded Dome Top (tall rounded crown) var dome := MeshInstance3D.new() dome.name = "HatDome" var dome_mesh := SphereMesh.new() dome_mesh.radius = 0.29 dome_mesh.height = 0.36 dome.mesh = dome_mesh dome.position = Vector3(0.0, 0.56, 0.0) dome.material_override = cap_mat root.add_child(dome) # 2. Fur Brim / Rim at bottom edge of cap var brim := MeshInstance3D.new() brim.name = "FurBrim" var brim_mesh := TorusMesh.new() brim_mesh.inner_radius = 0.28 brim_mesh.outer_radius = 0.36 brim.mesh = brim_mesh brim.position = Vector3(0.0, 0.01, 0.0) var brim_mat := StandardMaterial3D.new() brim_mat.albedo_color = Color(0.24, 0.18, 0.13) # Fur trim brim_mat.roughness = 1.0 brim.material_override = brim_mat root.add_child(brim) # 3. Lodge Gold Medallion on front (-Z is forward) var medal := MeshInstance3D.new() medal.name = "LodgeBadge" var medal_mesh := BoxMesh.new() medal_mesh.size = Vector3(0.12, 0.10, 0.04) medal.mesh = medal_mesh medal.position = Vector3(0.0, 0.22, -0.32) var medal_mat := StandardMaterial3D.new() medal_mat.albedo_color = Color(0.96, 0.82, 0.18) # Gold lodge crest medal_mat.metallic = 0.7 medal_mat.roughness = 0.3 medal.material_override = medal_mat root.add_child(medal) # Horn material: first part is ivory/white, tip is brown (no middle piece) var horn_mat := StandardMaterial3D.new() horn_mat.albedo_color = Color(0.93, 0.90, 0.82) # Bone/ivory white horn_mat.roughness = 0.45 var tip_mat := StandardMaterial3D.new() tip_mat.albedo_color = Color(0.28, 0.18, 0.12) # Buffalo horn brown tip_mat.roughness = 0.55 # 4. Short Water Buffalo Horns: white base + brown tip only for side in [-1.0, 1.0]: var side_name := "Right" if side < 0.0 else "Left" # First part: white base segment projecting outward from side of hat var h_base := MeshInstance3D.new() h_base.name = "Horn_%s_Base" % side_name var base_mesh := CylinderMesh.new() base_mesh.bottom_radius = 0.08 base_mesh.top_radius = 0.055 base_mesh.height = 0.17 h_base.mesh = base_mesh h_base.position = Vector3(side * 0.34, 0.22, 0.01) h_base.rotation_degrees = Vector3(5.0, side * -10.0, side * -60.0) h_base.material_override = horn_mat root.add_child(h_base) # Second part: brown tip segment curving upward (middle piece removed) var h_tip := MeshInstance3D.new() h_tip.name = "Horn_%s_Tip" % side_name var tip_mesh := CylinderMesh.new() tip_mesh.bottom_radius = 0.055 tip_mesh.top_radius = 0.005 tip_mesh.height = 0.15 h_tip.mesh = tip_mesh h_tip.position = Vector3(side * 0.46, 0.32, -0.01) h_tip.rotation_degrees = Vector3(12.0, 0.0, side * 10.0) h_tip.material_override = tip_mat root.add_child(h_tip) _write_glb(root, "hair_waterbuffalo_01") root.free() func _gen_chest_bandit() -> void: var root := Node3D.new() root.name = "AttachmentRoot" # Main vest/shirt covering torso (torso is 0.90 x 0.80 x 0.46) var vest := MeshInstance3D.new() vest.name = "Vest" var vest_mesh := BoxMesh.new() vest_mesh.size = Vector3(0.94, 0.82, 0.50) vest.mesh = vest_mesh vest.position = Vector3(0.0, -0.16, 0.0) # Relative to Chest bone (Y=1.30, torso center Y=1.14) var vest_mat := StandardMaterial3D.new() vest_mat.albedo_color = Color(0.55, 0.20, 0.20) # Bandit crimson vest_mat.roughness = 0.85 vest.material_override = vest_mat root.add_child(vest) # Collar trim at top var collar := MeshInstance3D.new() collar.name = "Collar" var collar_mesh := BoxMesh.new() collar_mesh.size = Vector3(0.96, 0.12, 0.52) collar.mesh = collar_mesh collar.position = Vector3(0.0, 0.20, 0.0) var trim_mat := StandardMaterial3D.new() trim_mat.albedo_color = Color(0.22, 0.16, 0.12) # Dark leather trim trim_mat.roughness = 0.8 collar.material_override = trim_mat root.add_child(collar) # Bottom hem trim var hem := MeshInstance3D.new() hem.name = "Hem" var hem_mesh := BoxMesh.new() hem_mesh.size = Vector3(0.96, 0.08, 0.52) hem.mesh = hem_mesh hem.position = Vector3(0.0, -0.53, 0.0) hem.material_override = trim_mat root.add_child(hem) _write_glb(root, "chest_bandit_01") root.free() func _gen_pants_cargo() -> void: var root := Node3D.new() root.name = "AttachmentRoot" var pants_mat := StandardMaterial3D.new() pants_mat.albedo_color = Color(0.28, 0.34, 0.44) # Cargo slate blue pants_mat.roughness = 0.9 var pocket_mat := StandardMaterial3D.new() pocket_mat.albedo_color = Color(0.20, 0.24, 0.32) # Dark pocket trim pocket_mat.roughness = 0.9 # 1. Waist mounted to Hips bone (Y=0.76) var attach_hips := Node3D.new() attach_hips.name = "Attach_Hips" root.add_child(attach_hips) var waist := MeshInstance3D.new() waist.name = "Waist" var waist_mesh := BoxMesh.new() waist_mesh.size = Vector3(0.88, 0.22, 0.52) waist.mesh = waist_mesh waist.position = Vector3(0.0, -0.05, 0.0) # Relative to Hips (Y=0.76), covers world Y 0.60 to 0.82 waist.material_override = pants_mat attach_hips.add_child(waist) # 2. Left and Right Pant Legs mounted to LeftUpperLeg and RightUpperLeg for side in [-1.0, 1.0]: var sname := "Right" if side < 0.0 else "Left" var attach_leg := Node3D.new() attach_leg.name = "Attach_%sUpperLeg" % sname root.add_child(attach_leg) var leg := MeshInstance3D.new() leg.name = "PantLeg_%s" % sname var leg_mesh := BoxMesh.new() # Encases leg from hip joint down to ankle/sole. # Relative to UpperLeg bone (Y=0.74), center Y=-0.37, height 0.76 covers Y=-0.75 to +0.01 (world Y=-0.01 to 0.75). # Width 0.44 encloses 0.40 leg, Depth 0.50 encloses 0.46 leg. leg_mesh.size = Vector3(0.44, 0.76, 0.50) leg.mesh = leg_mesh leg.position = Vector3(0.0, -0.37, 0.0) leg.material_override = pants_mat attach_leg.add_child(leg) # Cargo pocket on outer side var pocket := MeshInstance3D.new() pocket.name = "Pocket_%s" % sname var pocket_mesh := BoxMesh.new() pocket_mesh.size = Vector3(0.08, 0.20, 0.24) pocket.mesh = pocket_mesh pocket.position = Vector3(side * 0.23, -0.32, 0.0) pocket.material_override = pocket_mat attach_leg.add_child(pocket) _write_glb(root, "pants_cargo_01") root.free() func _gen_glove(id: String, side: float) -> void: var root := Node3D.new() root.name = "AttachmentRoot" # Arm is width 0.34, depth 0.46. Bone X is at side * 0.60, arm center is at side * 0.57. var x_off := -0.03 * side var glove := MeshInstance3D.new() glove.name = "GloveBody" var glove_mesh := BoxMesh.new() glove_mesh.size = Vector3(0.38, 0.28, 0.50) glove.mesh = glove_mesh glove.position = Vector3(x_off, -0.06, 0.0) var glove_mat := StandardMaterial3D.new() glove_mat.albedo_color = Color(0.15, 0.15, 0.18) glove_mat.roughness = 0.85 glove.material_override = glove_mat root.add_child(glove) # Glove cuff at top var cuff := MeshInstance3D.new() cuff.name = "GloveCuff" var cuff_mesh := BoxMesh.new() cuff_mesh.size = Vector3(0.40, 0.06, 0.52) cuff.mesh = cuff_mesh cuff.position = Vector3(x_off, 0.08, 0.0) var cuff_mat := StandardMaterial3D.new() cuff_mat.albedo_color = Color(0.25, 0.24, 0.28) cuff_mat.roughness = 0.7 cuff.material_override = cuff_mat root.add_child(cuff) _write_glb(root, id) root.free() func _gen_wristband(id: String, side: float) -> void: var root := Node3D.new() root.name = "AttachmentRoot" var x_off := -0.03 * side var band := MeshInstance3D.new() band.name = "Wristband" var band_mesh := BoxMesh.new() band_mesh.size = Vector3(0.38, 0.12, 0.50) band.mesh = band_mesh band.position = Vector3(x_off, 0.02, 0.0) var band_mat := StandardMaterial3D.new() band_mat.albedo_color = Color(0.96, 0.82, 0.18) band_mat.metallic = 0.8 band_mat.roughness = 0.25 band.material_override = band_mat root.add_child(band) _write_glb(root, id) root.free() func _gen_boot(id: String, side: float) -> void: var root := Node3D.new() root.name = "AttachmentRoot" # Boot Upper (encases lower leg and extends forward to toe) var boot := MeshInstance3D.new() boot.name = "BootUpper" var boot_mesh := BoxMesh.new() boot_mesh.size = Vector3(0.44, 0.22, 0.58) boot.mesh = boot_mesh boot.position = Vector3(0.0, 0.06, 0.02) var boot_mat := StandardMaterial3D.new() boot_mat.albedo_color = Color(0.14, 0.11, 0.10) # Dark boot leather boot_mat.roughness = 0.8 boot.material_override = boot_mat root.add_child(boot) # Boot Sole var sole := MeshInstance3D.new() sole.name = "BootSole" var sole_mesh := BoxMesh.new() sole_mesh.size = Vector3(0.46, 0.06, 0.60) sole.mesh = sole_mesh sole.position = Vector3(0.0, -0.03, 0.02) var sole_mat := StandardMaterial3D.new() sole_mat.albedo_color = Color(0.07, 0.07, 0.08) # Black rubber tread sole_mat.roughness = 0.95 sole.material_override = sole_mat root.add_child(sole) _write_glb(root, id) root.free() func _gen_sneaker(id: String, side: float) -> void: var root := Node3D.new() root.name = "AttachmentRoot" var sneaker_mat := StandardMaterial3D.new() sneaker_mat.albedo_color = Color(0.85, 0.25, 0.25) # Sneaker red sneaker_mat.roughness = 0.8 var white_mat := StandardMaterial3D.new() white_mat.albedo_color = Color(0.92, 0.92, 0.92) # White rubber white_mat.roughness = 0.85 # Sneaker Upper var upper := MeshInstance3D.new() upper.name = "SneakerUpper" var upper_mesh := BoxMesh.new() upper_mesh.size = Vector3(0.44, 0.18, 0.58) upper.mesh = upper_mesh upper.position = Vector3(0.0, 0.04, 0.02) upper.material_override = sneaker_mat root.add_child(upper) # White Rubber Sole var sole := MeshInstance3D.new() sole.name = "SneakerSole" var sole_mesh := BoxMesh.new() sole_mesh.size = Vector3(0.46, 0.06, 0.60) sole.mesh = sole_mesh sole.position = Vector3(0.0, -0.03, 0.02) sole.material_override = white_mat root.add_child(sole) # White Rubber Toe Cap var toe := MeshInstance3D.new() toe.name = "ToeCap" var toe_mesh := BoxMesh.new() toe_mesh.size = Vector3(0.44, 0.08, 0.14) toe.mesh = toe_mesh toe.position = Vector3(0.0, 0.01, -0.21) toe.material_override = white_mat root.add_child(toe) _write_glb(root, id) root.free() func _gen_belt() -> void: var root := Node3D.new() root.name = "AttachmentRoot" var belt := MeshInstance3D.new() belt.name = "BeltStrap" var belt_mesh := BoxMesh.new() belt_mesh.size = Vector3(0.94, 0.10, 0.50) belt.mesh = belt_mesh belt.position = Vector3(0.0, 0.74, 0.0) var belt_mat := StandardMaterial3D.new() belt_mat.albedo_color = Color(0.20, 0.16, 0.12) belt_mat.roughness = 0.8 belt.material_override = belt_mat root.add_child(belt) var buckle := MeshInstance3D.new() buckle.name = "BeltBuckle" var buckle_mesh := BoxMesh.new() buckle_mesh.size = Vector3(0.14, 0.12, 0.04) buckle.mesh = buckle_mesh buckle.position = Vector3(0.0, 0.74, -0.26) var buckle_mat := StandardMaterial3D.new() buckle_mat.albedo_color = Color(0.96, 0.82, 0.18) buckle_mat.metallic = 0.8 buckle_mat.roughness = 0.25 buckle.material_override = buckle_mat root.add_child(buckle) _write_glb(root, "accessory_belt_01") root.free() # --------------------------------------------------------------------------- # GLB export # --------------------------------------------------------------------------- func _write_glb(scene_root: Node, basename: String) -> void: # append_from_scene needs the node in a tree. get_root().add_child(scene_root) var doc := GLTFDocument.new() var state := GLTFState.new() var err := doc.append_from_scene(scene_root, state) if err != OK: push_error("append_from_scene failed for %s: %s" % [basename, error_string(err)]) _fail += 1 get_root().remove_child(scene_root) return var out_path := ProjectSettings.globalize_path("%s%s.glb" % [OUT_DIR, basename]) err = doc.write_to_filesystem(state, out_path) get_root().remove_child(scene_root) if err != OK: push_error("write_to_filesystem failed for %s: %s" % [basename, error_string(err)]) _fail += 1 return print(" wrote %s.glb (%d bytes)" % [basename, FileAccess.get_file_as_bytes(out_path).size()])