145 lines
4.8 KiB
GDScript
145 lines
4.8 KiB
GDScript
|
|
# Measurements for the full-body Lena bake: bone heights, leg UV overlap,
|
||
|
|
# torso half-width, and fabric (bra/underwear) color stats.
|
||
|
|
extends SceneTree
|
||
|
|
|
||
|
|
const GLB_PATH := "C:/Users/CAN/tinqs-ltd/ariki-game/assets/quaternius/derived-bodies/Ariki_Female_QuatSkin.glb"
|
||
|
|
const GRID := 256
|
||
|
|
|
||
|
|
func _init() -> void:
|
||
|
|
var doc := GLTFDocument.new()
|
||
|
|
var state := GLTFState.new()
|
||
|
|
if doc.append_from_file(GLB_PATH, state) != OK:
|
||
|
|
quit(1)
|
||
|
|
return
|
||
|
|
var scene := doc.generate_scene(state)
|
||
|
|
var skel: Skeleton3D = null
|
||
|
|
var body: Node = null
|
||
|
|
var stack: Array = [scene]
|
||
|
|
while not stack.is_empty():
|
||
|
|
var n: Node = stack.pop_back()
|
||
|
|
if n is Skeleton3D and skel == null:
|
||
|
|
skel = n
|
||
|
|
if (n is MeshInstance3D or n.get_class() == "ImporterMeshInstance3D") \
|
||
|
|
and String(n.name).to_lower().contains("lena"):
|
||
|
|
body = n
|
||
|
|
for c in n.get_children():
|
||
|
|
stack.push_back(c)
|
||
|
|
|
||
|
|
for bn in ["neck_01", "spine_03", "spine_02", "spine_01", "pelvis",
|
||
|
|
"thigh_r", "calf_r", "foot_r", "clavicle_r"]:
|
||
|
|
print("bone %s: %s" % [bn, skel.get_bone_global_rest(skel.find_bone(bn)).origin])
|
||
|
|
|
||
|
|
var mesh = body.mesh
|
||
|
|
if mesh is ImporterMesh:
|
||
|
|
mesh = mesh.get_mesh()
|
||
|
|
var arrays: Array = mesh.surface_get_arrays(0)
|
||
|
|
var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
|
||
|
|
var uvs: PackedVector2Array = arrays[Mesh.ARRAY_TEX_UV]
|
||
|
|
var normals: PackedVector3Array = arrays[Mesh.ARRAY_NORMAL]
|
||
|
|
var vbones: PackedInt32Array = arrays[Mesh.ARRAY_BONES]
|
||
|
|
var vweights = arrays[Mesh.ARRAY_WEIGHTS]
|
||
|
|
var nverts := verts.size()
|
||
|
|
var influences := int(float(vbones.size()) / float(nverts))
|
||
|
|
|
||
|
|
var skin: Skin = body.skin
|
||
|
|
var bind_bone := PackedInt32Array()
|
||
|
|
for i in skin.get_bind_count():
|
||
|
|
var b := skin.get_bind_bone(i)
|
||
|
|
if b < 0:
|
||
|
|
b = skel.find_bone(skin.get_bind_name(i))
|
||
|
|
bind_bone.append(b)
|
||
|
|
|
||
|
|
# leg UV overlap (same method that showed 100% for arms)
|
||
|
|
var sets := {}
|
||
|
|
for side in [["r", ["thigh_r", "calf_r", "foot_r"]], ["l", ["thigh_l", "calf_l", "foot_l"]]]:
|
||
|
|
var ids := {}
|
||
|
|
for bn in side[1]:
|
||
|
|
ids[skel.find_bone(bn)] = true
|
||
|
|
var binds := {}
|
||
|
|
for i in bind_bone.size():
|
||
|
|
if ids.has(bind_bone[i]):
|
||
|
|
binds[i] = true
|
||
|
|
var mask := PackedByteArray()
|
||
|
|
mask.resize(GRID * GRID)
|
||
|
|
for v in nverts:
|
||
|
|
var w := 0.0
|
||
|
|
for k in influences:
|
||
|
|
if binds.has(vbones[v * influences + k]):
|
||
|
|
w += vweights[v * influences + k]
|
||
|
|
if w > 0.3:
|
||
|
|
var gx := clampi(int(uvs[v].x * GRID), 0, GRID - 1)
|
||
|
|
var gy := clampi(int(uvs[v].y * GRID), 0, GRID - 1)
|
||
|
|
mask[gy * GRID + gx] = 1
|
||
|
|
sets[side[0]] = mask
|
||
|
|
var r: PackedByteArray = sets["r"]
|
||
|
|
var l: PackedByteArray = sets["l"]
|
||
|
|
var cr := 0
|
||
|
|
var ov := 0
|
||
|
|
for i in GRID * GRID:
|
||
|
|
if r[i] == 1:
|
||
|
|
cr += 1
|
||
|
|
if l[i] == 1:
|
||
|
|
ov += 1
|
||
|
|
print("LEG UV overlap: %d/%d cells (%.0f%%)" % [ov, cr, 100.0 * ov / maxf(cr, 1)])
|
||
|
|
|
||
|
|
# arm weights (to exclude arms when measuring torso width)
|
||
|
|
var arm_ids := {}
|
||
|
|
for bn in ["upperarm_l", "lowerarm_l", "hand_l", "upperarm_r", "lowerarm_r", "hand_r"]:
|
||
|
|
arm_ids[skel.find_bone(bn)] = true
|
||
|
|
var arm_binds := {}
|
||
|
|
for i in bind_bone.size():
|
||
|
|
if arm_ids.has(bind_bone[i]):
|
||
|
|
arm_binds[i] = true
|
||
|
|
|
||
|
|
# torso extents + fabric stats: front-facing verts in the chest band
|
||
|
|
var mat: Material = mesh.surface_get_material(0)
|
||
|
|
var img: Image = (mat as BaseMaterial3D).albedo_texture.get_image()
|
||
|
|
img.clear_mipmaps()
|
||
|
|
img.convert(Image.FORMAT_RGBA8)
|
||
|
|
var tw := img.get_width()
|
||
|
|
var th := img.get_height()
|
||
|
|
var neck_y: float = skel.get_bone_global_rest(skel.find_bone("neck_01")).origin.y
|
||
|
|
var pelvis_y: float = skel.get_bone_global_rest(skel.find_bone("pelvis")).origin.y
|
||
|
|
var maxx_front := 0.0
|
||
|
|
var maxx_back := 0.0
|
||
|
|
var n_fab := 0
|
||
|
|
var n_skin := 0
|
||
|
|
var sat_fab := 0.0
|
||
|
|
var sat_skin := 0.0
|
||
|
|
var lum_fab := 0.0
|
||
|
|
var lum_skin := 0.0
|
||
|
|
for v in nverts:
|
||
|
|
var aw := 0.0
|
||
|
|
for k in influences:
|
||
|
|
if arm_binds.has(vbones[v * influences + k]):
|
||
|
|
aw += vweights[v * influences + k]
|
||
|
|
if aw > 0.3:
|
||
|
|
continue
|
||
|
|
var p := verts[v]
|
||
|
|
if p.y < pelvis_y or p.y > neck_y:
|
||
|
|
continue
|
||
|
|
if normals[v].z > 0.25:
|
||
|
|
maxx_front = maxf(maxx_front, absf(p.x))
|
||
|
|
elif normals[v].z < -0.25:
|
||
|
|
maxx_back = maxf(maxx_back, absf(p.x))
|
||
|
|
var px := clampi(int(uvs[v].x * tw), 0, tw - 1)
|
||
|
|
var py := clampi(int(uvs[v].y * th), 0, th - 1)
|
||
|
|
var c := img.get_pixel(px, py)
|
||
|
|
var mx := maxf(c.r, maxf(c.g, c.b))
|
||
|
|
var mn := minf(c.r, minf(c.g, c.b))
|
||
|
|
var sat := (mx - mn) / maxf(mx, 0.001)
|
||
|
|
var lum := (c.r + c.g + c.b) / 3.0
|
||
|
|
# crude split: bra/underwear are pale + desaturated
|
||
|
|
if sat < 0.15 and lum > 0.55:
|
||
|
|
n_fab += 1
|
||
|
|
sat_fab += sat
|
||
|
|
lum_fab += lum
|
||
|
|
else:
|
||
|
|
n_skin += 1
|
||
|
|
sat_skin += sat
|
||
|
|
lum_skin += lum
|
||
|
|
print("torso band y %.2f..%.2f half-width front=%.3f back=%.3f" % [pelvis_y, neck_y, maxx_front, maxx_back])
|
||
|
|
print("fabric-ish verts: %d (avg sat %.3f lum %.3f)" % [n_fab, sat_fab / maxf(n_fab, 1), lum_fab / maxf(n_fab, 1)])
|
||
|
|
print("skin-ish verts: %d (avg sat %.3f lum %.3f)" % [n_skin, sat_skin / maxf(n_skin, 1), lum_skin / maxf(n_skin, 1)])
|
||
|
|
quit()
|