tools/capture drives a store page's skin viewer in headless Firefox, stepping its clock one tick at a time and taking every tick from ten fixed cameras. It then fits our own rig to the frames by rendering the model (a Python port of LimbBend and PoseApplier) and matching outlines and colours, and writes the result as Emotecraft JSON. It measures pixels only and never reads the page's animation data. The README covers the steps, the checks and the limits. Co-Authored-By: Claude Opus 5.5 <[email protected]>
56 lines
2.3 KiB
Python
56 lines
2.3 KiB
Python
"""The back-to-front twin of a rig, which covers the same space with the textures turned round.
|
|
|
|
The head and torso boxes are centred on their pivots, so spinning either half a turn around its own
|
|
vertical axis leaves it covering the same space; only which face shows changes. The arms hang from
|
|
the torso's sides, so spinning the torso carries each arm to the other side. That twin (torso and
|
|
head facing backwards, arms swapped) is a separate local minimum the search can't walk out of, so
|
|
measure.py jumps to it directly, refines it, and keeps whichever scores lower. Only the colours tell
|
|
them apart.
|
|
|
|
In rig terms: the torso and head get R Ry(pi); each arm takes the other's rotation times Ry(pi) (a
|
|
half turn around its own length, which moves its off-centre box to the other side of its pivot); and
|
|
bends keep their shape with their direction turned half round, since the frame they're measured in
|
|
turned. The legs hang from the hips and don't change.
|
|
"""
|
|
import numpy as np
|
|
|
|
from model import INDEX, rotation_zyx
|
|
|
|
SPIN = np.diag([-1.0, 1.0, -1.0]) # Ry(pi)
|
|
ARMS = {"right_arm": "left_arm", "left_arm": "right_arm"}
|
|
|
|
|
|
def euler_zyx(r):
|
|
"""(pitch, yaw, roll) with rotation_zyx(pitch, yaw, roll) == r."""
|
|
yaw = np.arcsin(np.clip(-r[2, 0], -1, 1))
|
|
return np.arctan2(r[2, 1], r[2, 2]), yaw, np.arctan2(r[1, 0], r[0, 0])
|
|
|
|
|
|
def _spun(rig, source):
|
|
g = lambda c: rig[INDEX[f"{source}_{c}"]]
|
|
return euler_zyx(rotation_zyx(g("pitch"), g("yaw"), g("roll")) @ SPIN)
|
|
|
|
|
|
def flip_head(rig):
|
|
"""Just the head spun round: a cube on its pivot, so the same space facing the other way."""
|
|
out = rig.copy()
|
|
for c, v in zip(("pitch", "yaw", "roll"), _spun(rig, "head")):
|
|
out[INDEX[f"head_{c}"]] = v
|
|
return out
|
|
|
|
|
|
def twins(rig):
|
|
"""Every back-to-front twin worth trying: the torso with its arms, the head, and both."""
|
|
return [flip(rig), flip_head(rig), flip_head(flip(rig))]
|
|
|
|
|
|
def flip(rig):
|
|
out = rig.copy()
|
|
for part, source in [("body", "body"), ("head", "head"), *ARMS.items()]:
|
|
for c, v in zip(("pitch", "yaw", "roll"), _spun(rig, source)):
|
|
out[INDEX[f"{part}_{c}"]] = v
|
|
for part, source in [("body", "body"), *ARMS.items()]:
|
|
out[INDEX[f"{part}_bend"]] = rig[INDEX[f"{source}_bend"]]
|
|
out[INDEX[f"{part}_axis"]] = rig[INDEX[f"{source}_axis"]] + np.pi
|
|
return out
|