519 lines
17 KiB
GDScript
519 lines
17 KiB
GDScript
|
|
# Re-UVs the QuatSkin Lena with a Quaternius-style semantic atlas and transfers
|
||
|
|
# her skin texture into it (v2).
|
||
|
|
# v1 used planar +-Z for head/torso: side/down-facing surfaces (cheeks by the
|
||
|
|
# ears, under the jaw, armpit side strips) were edge-on to the projection ->
|
||
|
|
# texel starvation -> smear streaks. v2 unwraps head and torso as CYLINDERS
|
||
|
|
# around the vertical axis (u = angle x local radius, v = height) with small
|
||
|
|
# planar caps (crown, under-jaw, shoulder tops), and smooths the per-triangle
|
||
|
|
# region assignment so the arm/torso border stops jittering in the armpit.
|
||
|
|
# arms/legs cylinder around the bone chain (unchanged from v1)
|
||
|
|
# torso/head cylinder around +Y, seam at center back, + planar caps
|
||
|
|
# hands/feet planar +-Y (unchanged)
|
||
|
|
# Run headless:
|
||
|
|
# tinqs.console.exe --headless --path tattoo-test -s res://reuv_lena.gd
|
||
|
|
extends SceneTree
|
||
|
|
|
||
|
|
const GLB_IN := "C:/Users/CAN/tinqs-ltd/ariki-game/assets/quaternius/derived-bodies/Ariki_Female_QuatSkin.glb"
|
||
|
|
const GLB_OUT := "C:/Users/CAN/tinqs-ltd/tattoo-test/lena_clean_uv.glb"
|
||
|
|
const TEX_OUT := "C:/Users/CAN/tinqs-ltd/tattoo-test/lena_clean_uv_base.png"
|
||
|
|
const ATLAS := 4096
|
||
|
|
const PAD_PX := 24
|
||
|
|
const JB := 0.03
|
||
|
|
|
||
|
|
var skel: Skeleton3D
|
||
|
|
var verts: PackedVector3Array
|
||
|
|
var norms: PackedVector3Array
|
||
|
|
var uvs_old: PackedVector2Array
|
||
|
|
var vbones: PackedInt32Array
|
||
|
|
var vweights: PackedFloat32Array
|
||
|
|
var indices: PackedInt32Array
|
||
|
|
var nverts: int
|
||
|
|
var influences: int
|
||
|
|
var region_w := {}
|
||
|
|
|
||
|
|
const ISLAND_ORDER := [
|
||
|
|
"head_cyl", "head_top", "head_bot",
|
||
|
|
"torso_cyl", "torso_top", "torso_bot",
|
||
|
|
"arm_r", "arm_l", "leg_r", "leg_l",
|
||
|
|
"hand_r_back", "hand_r_palm", "hand_l_back", "hand_l_palm",
|
||
|
|
"foot_r_top", "foot_r_sole", "foot_l_top", "foot_l_sole",
|
||
|
|
]
|
||
|
|
|
||
|
|
func _init() -> void:
|
||
|
|
var t_start := Time.get_ticks_msec()
|
||
|
|
var doc := GLTFDocument.new()
|
||
|
|
var state := GLTFState.new()
|
||
|
|
if doc.append_from_file(GLB_IN, state) != OK:
|
||
|
|
push_error("GLB load failed")
|
||
|
|
quit(1)
|
||
|
|
return
|
||
|
|
var scene := doc.generate_scene(state)
|
||
|
|
|
||
|
|
var body: MeshInstance3D = 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 and String(n.name).to_lower().contains("lena"):
|
||
|
|
body = n
|
||
|
|
for c in n.get_children():
|
||
|
|
stack.push_back(c)
|
||
|
|
|
||
|
|
var mesh: ArrayMesh = body.mesh
|
||
|
|
var arrays: Array = mesh.surface_get_arrays(0)
|
||
|
|
verts = arrays[Mesh.ARRAY_VERTEX]
|
||
|
|
norms = arrays[Mesh.ARRAY_NORMAL]
|
||
|
|
uvs_old = arrays[Mesh.ARRAY_TEX_UV]
|
||
|
|
vbones = arrays[Mesh.ARRAY_BONES]
|
||
|
|
vweights = arrays[Mesh.ARRAY_WEIGHTS]
|
||
|
|
indices = arrays[Mesh.ARRAY_INDEX]
|
||
|
|
nverts = verts.size()
|
||
|
|
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)
|
||
|
|
|
||
|
|
# ── region weights ──────────────────────────────────────────────────────
|
||
|
|
var fingers_l := []
|
||
|
|
var fingers_r := []
|
||
|
|
for f in ["index", "middle", "pinky", "ring", "thumb"]:
|
||
|
|
for seg in ["01", "02", "03", "04_leaf"]:
|
||
|
|
fingers_l.append("%s_%s_l" % [f, seg])
|
||
|
|
fingers_r.append("%s_%s_r" % [f, seg])
|
||
|
|
var regions := {
|
||
|
|
"head": ["Head", "neck_01"],
|
||
|
|
"torso": ["pelvis", "spine_01", "spine_02", "spine_03", "clavicle_l", "clavicle_r"],
|
||
|
|
"arm_l": ["upperarm_l", "lowerarm_l"],
|
||
|
|
"arm_r": ["upperarm_r", "lowerarm_r"],
|
||
|
|
"hand_l": ["hand_l"] + fingers_l,
|
||
|
|
"hand_r": ["hand_r"] + fingers_r,
|
||
|
|
"leg_l": ["thigh_l", "calf_l"],
|
||
|
|
"leg_r": ["thigh_r", "calf_r"],
|
||
|
|
"foot_l": ["foot_l", "ball_l", "ball_leaf_l"],
|
||
|
|
"foot_r": ["foot_r", "ball_r", "ball_leaf_r"],
|
||
|
|
}
|
||
|
|
for key in regions:
|
||
|
|
var binds := _bind_set(bind_bone, regions[key])
|
||
|
|
var w := PackedFloat32Array()
|
||
|
|
w.resize(nverts)
|
||
|
|
for v in nverts:
|
||
|
|
for k in influences:
|
||
|
|
if binds.has(vbones[v * influences + k]):
|
||
|
|
w[v] += vweights[v * influences + k]
|
||
|
|
region_w[key] = w
|
||
|
|
|
||
|
|
# ── per-triangle region (argmax) + adjacency smoothing ──────────────────
|
||
|
|
var ntris := indices.size() / 3
|
||
|
|
var tri_region := PackedStringArray()
|
||
|
|
tri_region.resize(ntris)
|
||
|
|
for t in ntris:
|
||
|
|
var best := "torso"
|
||
|
|
var best_s := -1.0
|
||
|
|
for key in region_w:
|
||
|
|
var w: PackedFloat32Array = region_w[key]
|
||
|
|
var s: float = w[indices[t * 3]] + w[indices[t * 3 + 1]] + w[indices[t * 3 + 2]]
|
||
|
|
if s > best_s:
|
||
|
|
best_s = s
|
||
|
|
best = key
|
||
|
|
tri_region[t] = best
|
||
|
|
|
||
|
|
# edge adjacency
|
||
|
|
var edge_tri := {}
|
||
|
|
var tri_nbrs := []
|
||
|
|
tri_nbrs.resize(ntris)
|
||
|
|
for t in ntris:
|
||
|
|
tri_nbrs[t] = []
|
||
|
|
for t in ntris:
|
||
|
|
for j in 3:
|
||
|
|
var a := indices[t * 3 + j]
|
||
|
|
var b := indices[t * 3 + ((j + 1) % 3)]
|
||
|
|
var key := "%d_%d" % [mini(a, b), maxi(a, b)]
|
||
|
|
if edge_tri.has(key):
|
||
|
|
var o: int = edge_tri[key]
|
||
|
|
tri_nbrs[t].append(o)
|
||
|
|
tri_nbrs[o].append(t)
|
||
|
|
else:
|
||
|
|
edge_tri[key] = t
|
||
|
|
# majority smoothing: kills 1-triangle islands at region borders (armpit!)
|
||
|
|
for pass_i in 3:
|
||
|
|
var changed := 0
|
||
|
|
var next := tri_region.duplicate()
|
||
|
|
for t in ntris:
|
||
|
|
var counts := {}
|
||
|
|
counts[tri_region[t]] = 1
|
||
|
|
for o in tri_nbrs[t]:
|
||
|
|
var r: String = tri_region[o]
|
||
|
|
counts[r] = counts.get(r, 0) + 1
|
||
|
|
var best := tri_region[t]
|
||
|
|
var best_c: int = counts[best]
|
||
|
|
for r in counts:
|
||
|
|
if counts[r] > best_c:
|
||
|
|
best_c = counts[r]
|
||
|
|
best = r
|
||
|
|
if best != tri_region[t]:
|
||
|
|
next[t] = best
|
||
|
|
changed += 1
|
||
|
|
tri_region = next
|
||
|
|
print("region smoothing pass %d: %d flips" % [pass_i, changed])
|
||
|
|
|
||
|
|
# ── axes for the vertical cylinders ──────────────────────────────────────
|
||
|
|
var head_pos := _bone_pos("Head")
|
||
|
|
var head_ax := Vector2(head_pos.x, head_pos.z)
|
||
|
|
var tz := 0.0
|
||
|
|
for bn in ["pelvis", "spine_01", "spine_02", "spine_03"]:
|
||
|
|
tz += _bone_pos(bn).z
|
||
|
|
var torso_ax := Vector2(0.0, tz / 4.0)
|
||
|
|
|
||
|
|
# ── limb chains (unchanged) ──────────────────────────────────────────────
|
||
|
|
var chains := {
|
||
|
|
"arm_l": ["upperarm_l", "lowerarm_l", "hand_l", "spine_03"],
|
||
|
|
"arm_r": ["upperarm_r", "lowerarm_r", "hand_r", "spine_03"],
|
||
|
|
"leg_l": ["thigh_l", "calf_l", "foot_l", "pelvis"],
|
||
|
|
"leg_r": ["thigh_r", "calf_r", "foot_r", "pelvis"],
|
||
|
|
}
|
||
|
|
var limb := {}
|
||
|
|
for key in chains:
|
||
|
|
var c: Array = chains[key]
|
||
|
|
var p0 := _bone_pos(c[0])
|
||
|
|
var p1 := _bone_pos(c[1])
|
||
|
|
var p2 := _bone_pos(c[2])
|
||
|
|
var center := _bone_pos(c[3])
|
||
|
|
var axis := (p2 - p0).normalized()
|
||
|
|
var outward := (p0 - center).normalized()
|
||
|
|
var uref := (outward - axis * outward.dot(axis)).normalized()
|
||
|
|
limb[key] = {"p0": p0, "p1": p1, "p2": p2,
|
||
|
|
"len1": p0.distance_to(p1), "len2": p1.distance_to(p2),
|
||
|
|
"uref": uref, "vref": axis.cross(uref)}
|
||
|
|
|
||
|
|
# ── per-triangle island id (with normal-based caps) + smoothing ─────────
|
||
|
|
var tri_island := PackedStringArray()
|
||
|
|
tri_island.resize(ntris)
|
||
|
|
for t in ntris:
|
||
|
|
var reg := tri_region[t]
|
||
|
|
var i0 := indices[t * 3]
|
||
|
|
var an := (norms[i0] + norms[indices[t * 3 + 1]] + norms[indices[t * 3 + 2]]).normalized()
|
||
|
|
var island := reg
|
||
|
|
if reg == "head":
|
||
|
|
island = "head_top" if an.y > 0.55 else ("head_bot" if an.y < -0.60 else "head_cyl")
|
||
|
|
elif reg == "torso":
|
||
|
|
island = "torso_top" if an.y > 0.65 else ("torso_bot" if an.y < -0.75 else "torso_cyl")
|
||
|
|
elif reg.begins_with("hand"):
|
||
|
|
island = reg + ("_back" if an.y >= 0.0 else "_palm")
|
||
|
|
elif reg.begins_with("foot"):
|
||
|
|
island = reg + ("_top" if an.y >= 0.0 else "_sole")
|
||
|
|
tri_island[t] = island
|
||
|
|
for pass_i in 2:
|
||
|
|
var next := tri_island.duplicate()
|
||
|
|
for t in ntris:
|
||
|
|
var counts := {}
|
||
|
|
counts[tri_island[t]] = 1
|
||
|
|
for o in tri_nbrs[t]:
|
||
|
|
if tri_region[o] == tri_region[t]:
|
||
|
|
counts[tri_island[o]] = counts.get(tri_island[o], 0) + 1
|
||
|
|
var best := tri_island[t]
|
||
|
|
var best_c: int = counts[best]
|
||
|
|
for r in counts:
|
||
|
|
if counts[r] > best_c:
|
||
|
|
best_c = counts[r]
|
||
|
|
best = r
|
||
|
|
next[t] = best
|
||
|
|
tri_island = next
|
||
|
|
|
||
|
|
# ── per-corner meter coords ──────────────────────────────────────────────
|
||
|
|
var corner_um := PackedFloat32Array()
|
||
|
|
corner_um.resize(indices.size())
|
||
|
|
var corner_vm := PackedFloat32Array()
|
||
|
|
corner_vm.resize(indices.size())
|
||
|
|
for t in ntris:
|
||
|
|
var island := tri_island[t]
|
||
|
|
for j in 3:
|
||
|
|
var ci := t * 3 + j
|
||
|
|
var p := verts[indices[ci]]
|
||
|
|
match island:
|
||
|
|
"head_top", "torso_top":
|
||
|
|
corner_um[ci] = p.x
|
||
|
|
corner_vm[ci] = p.z
|
||
|
|
"head_bot", "torso_bot":
|
||
|
|
corner_um[ci] = p.x
|
||
|
|
corner_vm[ci] = -p.z
|
||
|
|
"head_cyl":
|
||
|
|
corner_um[ci] = atan2(p.x - head_ax.x, p.z - head_ax.y)
|
||
|
|
corner_vm[ci] = -p.y
|
||
|
|
"torso_cyl":
|
||
|
|
corner_um[ci] = atan2(p.x - torso_ax.x, p.z - torso_ax.y)
|
||
|
|
corner_vm[ci] = -p.y
|
||
|
|
"hand_l_back", "hand_r_back":
|
||
|
|
corner_um[ci] = p.z
|
||
|
|
corner_vm[ci] = absf(p.x)
|
||
|
|
"hand_l_palm", "hand_r_palm":
|
||
|
|
corner_um[ci] = -p.z
|
||
|
|
corner_vm[ci] = absf(p.x)
|
||
|
|
"foot_l_top", "foot_r_top":
|
||
|
|
corner_um[ci] = p.x
|
||
|
|
corner_vm[ci] = p.z
|
||
|
|
"foot_l_sole", "foot_r_sole":
|
||
|
|
corner_um[ci] = -p.x
|
||
|
|
corner_vm[ci] = p.z
|
||
|
|
_:
|
||
|
|
var L: Dictionary = limb[island]
|
||
|
|
var res := _limb_coords(p, L)
|
||
|
|
corner_um[ci] = res.x
|
||
|
|
corner_vm[ci] = res.y
|
||
|
|
# cylinder wrap: one angular branch per triangle, then angle -> arc meters
|
||
|
|
var is_cyl := island.begins_with("arm") or island.begins_with("leg") \
|
||
|
|
or island == "head_cyl" or island == "torso_cyl"
|
||
|
|
if is_cyl:
|
||
|
|
var a0 := corner_um[t * 3]
|
||
|
|
var a1 := corner_um[t * 3 + 1]
|
||
|
|
var a2 := corner_um[t * 3 + 2]
|
||
|
|
if maxf(a0, maxf(a1, a2)) - minf(a0, minf(a1, a2)) > PI:
|
||
|
|
for j in 3:
|
||
|
|
if corner_um[t * 3 + j] < 0.0:
|
||
|
|
corner_um[t * 3 + j] += TAU
|
||
|
|
for j in 3:
|
||
|
|
var ci2 := t * 3 + j
|
||
|
|
var p2 := verts[indices[ci2]]
|
||
|
|
var r := 0.015
|
||
|
|
if island == "head_cyl":
|
||
|
|
r = maxf(Vector2(p2.x - head_ax.x, p2.z - head_ax.y).length(), 0.015)
|
||
|
|
elif island == "torso_cyl":
|
||
|
|
r = maxf(Vector2(p2.x - torso_ax.x, p2.z - torso_ax.y).length(), 0.015)
|
||
|
|
else:
|
||
|
|
r = _limb_radius(p2, limb[island])
|
||
|
|
corner_um[ci2] = corner_um[ci2] * r
|
||
|
|
|
||
|
|
# ── island bboxes, pack, weld, transfer, export ──────────────────────────
|
||
|
|
var isl := {}
|
||
|
|
for name in ISLAND_ORDER:
|
||
|
|
isl[name] = {"mn": Vector2(1e9, 1e9), "mx": Vector2(-1e9, -1e9)}
|
||
|
|
for t in ntris:
|
||
|
|
for j in 3:
|
||
|
|
var ci := t * 3 + j
|
||
|
|
var m: Dictionary = isl[tri_island[t]]
|
||
|
|
var q := Vector2(corner_um[ci], corner_vm[ci])
|
||
|
|
m["mn"] = (m["mn"] as Vector2).min(q)
|
||
|
|
m["mx"] = (m["mx"] as Vector2).max(q)
|
||
|
|
|
||
|
|
var lo := 100.0
|
||
|
|
var hi := 4000.0
|
||
|
|
for it in 24:
|
||
|
|
var mid := (lo + hi) * 0.5
|
||
|
|
if _try_pack(isl, mid, false):
|
||
|
|
lo = mid
|
||
|
|
else:
|
||
|
|
hi = mid
|
||
|
|
var scale := lo
|
||
|
|
_try_pack(isl, scale, true)
|
||
|
|
print("atlas scale: %.0f px/m" % scale)
|
||
|
|
for name in ISLAND_ORDER:
|
||
|
|
var m: Dictionary = isl[name]
|
||
|
|
print(" %s -> (%d,%d) %dx%d px" % [name, m["x"], m["y"], m["w"], m["h"]])
|
||
|
|
|
||
|
|
var new_verts := PackedVector3Array()
|
||
|
|
var new_norms := PackedVector3Array()
|
||
|
|
var new_uvs := PackedVector2Array()
|
||
|
|
var new_uvs_old := PackedVector2Array()
|
||
|
|
var new_bones := PackedInt32Array()
|
||
|
|
var new_weights := PackedFloat32Array()
|
||
|
|
var new_indices := PackedInt32Array()
|
||
|
|
new_indices.resize(indices.size())
|
||
|
|
var weld := {}
|
||
|
|
for t in ntris:
|
||
|
|
for j in 3:
|
||
|
|
var ci := t * 3 + j
|
||
|
|
var v := indices[ci]
|
||
|
|
var m: Dictionary = isl[tri_island[t]]
|
||
|
|
var px: float = m["x"] + (corner_um[ci] - (m["mn"] as Vector2).x) * scale
|
||
|
|
var py: float = m["y"] + (corner_vm[ci] - (m["mn"] as Vector2).y) * scale
|
||
|
|
var uv := Vector2(px / ATLAS, py / ATLAS)
|
||
|
|
var key := "%d_%d_%d" % [v, int(round(px * 4.0)), int(round(py * 4.0))]
|
||
|
|
if not weld.has(key):
|
||
|
|
var nv := new_verts.size()
|
||
|
|
new_verts.append(verts[v])
|
||
|
|
new_norms.append(norms[v])
|
||
|
|
new_uvs.append(uv)
|
||
|
|
new_uvs_old.append(uvs_old[v])
|
||
|
|
for k in influences:
|
||
|
|
new_bones.append(vbones[v * influences + k])
|
||
|
|
new_weights.append(vweights[v * influences + k])
|
||
|
|
weld[key] = nv
|
||
|
|
new_indices[ci] = weld[key]
|
||
|
|
print("verts %d -> %d" % [nverts, new_verts.size()])
|
||
|
|
|
||
|
|
var mat: BaseMaterial3D = mesh.surface_get_material(0)
|
||
|
|
var src: Image = mat.albedo_texture.get_image()
|
||
|
|
src.clear_mipmaps()
|
||
|
|
src.convert(Image.FORMAT_RGBA8)
|
||
|
|
var avg := Color(0, 0, 0)
|
||
|
|
var n_avg := 0
|
||
|
|
for sy in range(0, src.get_height(), 61):
|
||
|
|
for sx in range(0, src.get_width(), 61):
|
||
|
|
var c := src.get_pixel(sx, sy)
|
||
|
|
if (c.r + c.g + c.b) / 3.0 > 0.2:
|
||
|
|
avg += c
|
||
|
|
n_avg += 1
|
||
|
|
avg = Color(avg.r / n_avg, avg.g / n_avg, avg.b / n_avg)
|
||
|
|
var dst := Image.create(ATLAS, ATLAS, false, Image.FORMAT_RGBA8)
|
||
|
|
dst.fill(avg)
|
||
|
|
|
||
|
|
var painted := 0
|
||
|
|
for t in ntris:
|
||
|
|
painted += _transfer_tri(dst, src,
|
||
|
|
new_indices[t * 3], new_indices[t * 3 + 1], new_indices[t * 3 + 2],
|
||
|
|
new_uvs, new_uvs_old)
|
||
|
|
print("transferred px: ", painted)
|
||
|
|
dst.save_png(TEX_OUT)
|
||
|
|
|
||
|
|
var new_arrays := []
|
||
|
|
new_arrays.resize(Mesh.ARRAY_MAX)
|
||
|
|
new_arrays[Mesh.ARRAY_VERTEX] = new_verts
|
||
|
|
new_arrays[Mesh.ARRAY_NORMAL] = new_norms
|
||
|
|
new_arrays[Mesh.ARRAY_TEX_UV] = new_uvs
|
||
|
|
new_arrays[Mesh.ARRAY_BONES] = new_bones
|
||
|
|
new_arrays[Mesh.ARRAY_WEIGHTS] = new_weights
|
||
|
|
new_arrays[Mesh.ARRAY_INDEX] = new_indices
|
||
|
|
var new_mesh := ArrayMesh.new()
|
||
|
|
new_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, new_arrays)
|
||
|
|
mat.albedo_texture = ImageTexture.create_from_image(dst)
|
||
|
|
new_mesh.surface_set_material(0, mat)
|
||
|
|
body.mesh = new_mesh
|
||
|
|
|
||
|
|
var out_doc := GLTFDocument.new()
|
||
|
|
var out_state := GLTFState.new()
|
||
|
|
if out_doc.append_from_scene(scene, out_state) != OK:
|
||
|
|
push_error("export append failed")
|
||
|
|
quit(1)
|
||
|
|
return
|
||
|
|
if out_doc.write_to_filesystem(out_state, GLB_OUT) != OK:
|
||
|
|
push_error("export write failed")
|
||
|
|
quit(1)
|
||
|
|
return
|
||
|
|
print("saved: %s + %s" % [GLB_OUT, TEX_OUT])
|
||
|
|
print("took %d ms" % (Time.get_ticks_msec() - t_start))
|
||
|
|
quit()
|
||
|
|
|
||
|
|
func _limb_coords(pos: Vector3, L: Dictionary) -> Vector2:
|
||
|
|
var p0: Vector3 = L["p0"]
|
||
|
|
var p1: Vector3 = L["p1"]
|
||
|
|
var p2: Vector3 = L["p2"]
|
||
|
|
var len1: float = L["len1"]
|
||
|
|
var len2: float = L["len2"]
|
||
|
|
var ax1 := (p1 - p0) / len1
|
||
|
|
var ax2 := (p2 - p1) / len2
|
||
|
|
var s1 := clampf((pos - p0).dot(ax1), 0.0, len1)
|
||
|
|
var cp1 := p0 + ax1 * s1
|
||
|
|
var s2 := clampf((pos - p1).dot(ax2), 0.0, len2)
|
||
|
|
var cp2 := p1 + ax2 * s2
|
||
|
|
var wb := smoothstep(-JB, JB, pos.distance_to(cp1) - pos.distance_to(cp2))
|
||
|
|
var t_m := lerpf(s1, len1 + s2, wb)
|
||
|
|
var dv: Vector3 = pos - cp1.lerp(cp2, wb)
|
||
|
|
var ang := atan2(dv.dot(L["vref"]), dv.dot(L["uref"]))
|
||
|
|
return Vector2(ang, t_m)
|
||
|
|
|
||
|
|
func _limb_radius(pos: Vector3, L: Dictionary) -> float:
|
||
|
|
var p0: Vector3 = L["p0"]
|
||
|
|
var p1: Vector3 = L["p1"]
|
||
|
|
var p2: Vector3 = L["p2"]
|
||
|
|
var len1: float = L["len1"]
|
||
|
|
var len2: float = L["len2"]
|
||
|
|
var ax1 := (p1 - p0) / len1
|
||
|
|
var ax2 := (p2 - p1) / len2
|
||
|
|
var s1 := clampf((pos - p0).dot(ax1), 0.0, len1)
|
||
|
|
var cp1 := p0 + ax1 * s1
|
||
|
|
var s2 := clampf((pos - p1).dot(ax2), 0.0, len2)
|
||
|
|
var cp2 := p1 + ax2 * s2
|
||
|
|
var wb := smoothstep(-JB, JB, pos.distance_to(cp1) - pos.distance_to(cp2))
|
||
|
|
return maxf(pos.distance_to(cp1.lerp(cp2, wb)), 0.015)
|
||
|
|
|
||
|
|
func _try_pack(isl: Dictionary, scale: float, commit: bool) -> bool:
|
||
|
|
var cx := PAD_PX
|
||
|
|
var cy := PAD_PX
|
||
|
|
var row_h := 0
|
||
|
|
for name in ISLAND_ORDER:
|
||
|
|
var m: Dictionary = isl[name]
|
||
|
|
var w := int(((m["mx"] as Vector2).x - (m["mn"] as Vector2).x) * scale) + 1
|
||
|
|
var h := int(((m["mx"] as Vector2).y - (m["mn"] as Vector2).y) * scale) + 1
|
||
|
|
if w > ATLAS - 2 * PAD_PX:
|
||
|
|
return false
|
||
|
|
if cx + w >= ATLAS - PAD_PX:
|
||
|
|
cx = PAD_PX
|
||
|
|
cy += row_h + PAD_PX
|
||
|
|
row_h = 0
|
||
|
|
if cy + h >= ATLAS - PAD_PX:
|
||
|
|
return false
|
||
|
|
if commit:
|
||
|
|
m["x"] = cx
|
||
|
|
m["y"] = cy
|
||
|
|
m["w"] = w
|
||
|
|
m["h"] = h
|
||
|
|
cx += w + PAD_PX
|
||
|
|
row_h = maxi(row_h, h)
|
||
|
|
return true
|
||
|
|
|
||
|
|
func _transfer_tri(dst: Image, src: Image, i0: int, i1: int, i2: int,
|
||
|
|
nuv: PackedVector2Array, ouv: PackedVector2Array) -> int:
|
||
|
|
var p0 := nuv[i0] * ATLAS
|
||
|
|
var p1 := nuv[i1] * ATLAS
|
||
|
|
var p2 := nuv[i2] * ATLAS
|
||
|
|
var denom := (p1.y - p2.y) * (p0.x - p2.x) + (p2.x - p1.x) * (p0.y - p2.y)
|
||
|
|
if absf(denom) < 1e-9:
|
||
|
|
return 0
|
||
|
|
var minx := clampi(int(floor(min(p0.x, min(p1.x, p2.x)))) - 1, 0, ATLAS - 1)
|
||
|
|
var maxx := clampi(int(ceil(max(p0.x, max(p1.x, p2.x)))) + 1, 0, ATLAS - 1)
|
||
|
|
var miny := clampi(int(floor(min(p0.y, min(p1.y, p2.y)))) - 1, 0, ATLAS - 1)
|
||
|
|
var maxy := clampi(int(ceil(max(p0.y, max(p1.y, p2.y)))) + 1, 0, ATLAS - 1)
|
||
|
|
var painted := 0
|
||
|
|
for py in range(miny, maxy + 1):
|
||
|
|
for px in range(minx, maxx + 1):
|
||
|
|
var fx := px + 0.5
|
||
|
|
var fy := py + 0.5
|
||
|
|
var b0 := ((p1.y - p2.y) * (fx - p2.x) + (p2.x - p1.x) * (fy - p2.y)) / denom
|
||
|
|
var b1 := ((p2.y - p0.y) * (fx - p2.x) + (p0.x - p2.x) * (fy - p2.y)) / denom
|
||
|
|
var b2 := 1.0 - b0 - b1
|
||
|
|
if b0 < -0.06 or b1 < -0.06 or b2 < -0.06:
|
||
|
|
continue
|
||
|
|
b0 = clampf(b0, 0.0, 1.0)
|
||
|
|
b1 = clampf(b1, 0.0, 1.0)
|
||
|
|
b2 = clampf(b2, 0.0, 1.0)
|
||
|
|
var s := b0 + b1 + b2
|
||
|
|
var ou: Vector2 = (ouv[i0] * b0 + ouv[i1] * b1 + ouv[i2] * b2) / s
|
||
|
|
dst.set_pixel(px, py, _sample_bilinear(src, ou.x, ou.y))
|
||
|
|
painted += 1
|
||
|
|
return painted
|
||
|
|
|
||
|
|
func _bind_set(bind_bone: PackedInt32Array, names: Array) -> Dictionary:
|
||
|
|
var bone_ids := {}
|
||
|
|
for bn in names:
|
||
|
|
var idx := skel.find_bone(bn)
|
||
|
|
if idx < 0:
|
||
|
|
push_error("bone not found: " + str(bn))
|
||
|
|
bone_ids[idx] = true
|
||
|
|
var out := {}
|
||
|
|
for i in bind_bone.size():
|
||
|
|
if bone_ids.has(bind_bone[i]):
|
||
|
|
out[i] = true
|
||
|
|
return out
|
||
|
|
|
||
|
|
func _bone_pos(bone: String) -> Vector3:
|
||
|
|
return skel.get_bone_global_rest(skel.find_bone(bone)).origin
|
||
|
|
|
||
|
|
func _sample_bilinear(img: Image, u: float, v: float) -> Color:
|
||
|
|
var w := img.get_width()
|
||
|
|
var h := img.get_height()
|
||
|
|
var x := clampf(u * w - 0.5, 0.0, w - 1.001)
|
||
|
|
var y := clampf(v * h - 0.5, 0.0, h - 1.001)
|
||
|
|
var x0 := int(x)
|
||
|
|
var y0 := int(y)
|
||
|
|
var x1 := mini(x0 + 1, w - 1)
|
||
|
|
var y1 := mini(y0 + 1, h - 1)
|
||
|
|
var fx := x - x0
|
||
|
|
var fy := y - y0
|
||
|
|
return img.get_pixel(x0, y0).lerp(img.get_pixel(x1, y0), fx) \
|
||
|
|
.lerp(img.get_pixel(x0, y1).lerp(img.get_pixel(x1, y1), fx), fy)
|