@rig-c/parts 1.30.0 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.ts +40 -7
- package/docs/AUTHORING.md +5 -3
- package/docs/STABILITY.md +4 -3
- package/package.json +1 -1
- package/src/build.ts +214 -6
- package/src/check.ts +6 -4
- package/src/cloth.ts +60 -8
- package/src/config.ts +85 -16
- package/src/parts.ts +53 -12
- package/src/propose.ts +52 -27
- package/src/rig.ts +118 -2
package/src/rig.ts
CHANGED
|
@@ -166,7 +166,7 @@ import { type ContourChainRibs, type ContourReport, contourMesh, type ContourReg
|
|
|
166
166
|
import { type LocalInfluence, localInfluences } from './localweights.ts';
|
|
167
167
|
import { artCoverage, ART_ALPHA, latticeMesh, latticeStray, ONE_LOOP_PASSES } from './mesh.ts';
|
|
168
168
|
import { bindFaceSurfaceDeform, BLINK, blinkHoldMisses, blinkSpan, CONTROL_SUFFIX, controlledBones, IDLE_FPS, idleMotion, type MotionSpec, moveKeysToControls } from './motion.ts';
|
|
169
|
-
import { PAINTING_RUN, type PartsFile, readFrom, tagOf } from './parts.ts';
|
|
169
|
+
import { type AlternateSetMap, PAINTING_RUN, type PartsFile, readFrom, tagOf } from './parts.ts';
|
|
170
170
|
import { EYE_GROUP_TAGS } from './propose.ts';
|
|
171
171
|
import { alphaAbove, crop, pad, type Raster } from './raster/index.ts';
|
|
172
172
|
import { pyRound } from './round.ts';
|
|
@@ -896,6 +896,20 @@ export interface AutoMeshReport {
|
|
|
896
896
|
motion_report?: unknown;
|
|
897
897
|
}
|
|
898
898
|
|
|
899
|
+
export const SETS_SPEC = 'rig-parts-sets/1';
|
|
900
|
+
export const SETS_FILE = 'sets.json';
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* `sets.json` (issue #454): the machine-readable map from each alternate
|
|
904
|
+
* painting set to its `<set>_<part>` attachment on each slot it covers and
|
|
905
|
+
* the slots it hides. Written beside `rig.json` only when `parts.json` carries
|
|
906
|
+
* `alternates`.
|
|
907
|
+
*/
|
|
908
|
+
export interface AttachmentSetsFile {
|
|
909
|
+
spec: typeof SETS_SPEC;
|
|
910
|
+
sets: Record<string, AlternateSetMap>;
|
|
911
|
+
}
|
|
912
|
+
|
|
899
913
|
export interface RigOutput {
|
|
900
914
|
rig: RigSpec;
|
|
901
915
|
motion: MotionSpec;
|
|
@@ -915,6 +929,8 @@ export interface RigOutput {
|
|
|
915
929
|
faceAxis: FaceAxis;
|
|
916
930
|
/** The turn control's parts (issue #251), in parts.json order; null when the config has no `turn`. */
|
|
917
931
|
turn: TurnRow[] | null;
|
|
932
|
+
/** `sets.json` (issue #454), present only when `parts.alternates` is non-empty. */
|
|
933
|
+
sets?: AttachmentSetsFile;
|
|
918
934
|
}
|
|
919
935
|
|
|
920
936
|
/**
|
|
@@ -1966,6 +1982,93 @@ export function buildRig(
|
|
|
1966
1982
|
});
|
|
1967
1983
|
}
|
|
1968
1984
|
refuseIfAny(problems);
|
|
1985
|
+
// ---- alternate painting sets (issue #454) -------------------------------
|
|
1986
|
+
// Only when `parts.alternates` is present: each alternate set's `<set>_<part>`
|
|
1987
|
+
// attachments are added to `skins.default[<part>]` on the existing bones and
|
|
1988
|
+
// slots before `applyStops` and `applyTurn`, so stopped links and turn twins
|
|
1989
|
+
// carry the alternate attachments alongside the base ones.
|
|
1990
|
+
let sets: AttachmentSetsFile | undefined;
|
|
1991
|
+
if (parts.alternates !== undefined && parts.alternates.length > 0) {
|
|
1992
|
+
const setEntries: Record<string, AlternateSetMap> = {};
|
|
1993
|
+
for (const alt of parts.alternates) {
|
|
1994
|
+
for (const p of alt.records) {
|
|
1995
|
+
const attName = `${alt.name}_${p.name}`;
|
|
1996
|
+
const rawImg = images.get(attName);
|
|
1997
|
+
if (rawImg === undefined) continue;
|
|
1998
|
+
const img = pad(rawImg, PAD, PAD, PAD, PAD, [0, 0, 0, 0]);
|
|
1999
|
+
const file = `${attName}.png`;
|
|
2000
|
+
outImages.push([file, img]);
|
|
2001
|
+
const mesh = cfg.meshes[p.name];
|
|
2002
|
+
if (mesh === undefined) {
|
|
2003
|
+
const bone = B.get(cfg.regions[p.name]) as Bone;
|
|
2004
|
+
skin[p.name][attName] = region(file, bone.name, p.x + p.w / 2, p.y + p.h / 2);
|
|
2005
|
+
continue;
|
|
2006
|
+
}
|
|
2007
|
+
const fullSegs = meshSegments.get(p.name) as Segment[];
|
|
2008
|
+
const baseAtt = skin[p.name]?.[p.name];
|
|
2009
|
+
const baseBones = baseAtt !== undefined && 'type' in baseAtt ? new Set(baseAtt.weights.flatMap((v) => v.map((e) => e.bone))) : null;
|
|
2010
|
+
const filteredSegs = baseBones !== null && baseBones.size > 0 ? fullSegs.filter((s) => baseBones.has(s.bone)) : fullSegs;
|
|
2011
|
+
const segs = filteredSegs.length > 0 ? filteredSegs : fullSegs;
|
|
2012
|
+
const allArt = alphaAbove(img, ART_ALPHA);
|
|
2013
|
+
const strayVal = 'auto' in mesh ? mesh.auto.source.stray : ('contour' in mesh ? mesh.contour.stray : mesh.stray);
|
|
2014
|
+
const stray = strayVal === undefined ? null : latticeStray(allArt, strayVal);
|
|
2015
|
+
const art = stray === null ? allArt : stray.art;
|
|
2016
|
+
const grid = 'grid' in mesh ? mesh.grid : 28;
|
|
2017
|
+
const r = 'r' in mesh ? mesh.r : 80;
|
|
2018
|
+
const lm = latticeMesh(attName, art, grid, maxLoopPasses);
|
|
2019
|
+
if ('code' in lm) {
|
|
2020
|
+
skin[p.name][attName] = region(file, segs[0].bone, p.x + p.w / 2, p.y + p.h / 2);
|
|
2021
|
+
continue;
|
|
2022
|
+
}
|
|
2023
|
+
const ox = p.x - PAD;
|
|
2024
|
+
const oy = p.y - PAD;
|
|
2025
|
+
const w = img.width;
|
|
2026
|
+
const h = img.height;
|
|
2027
|
+
const limits: InfluenceLimits = meshLimits('influences' in mesh ? mesh.influences : undefined);
|
|
2028
|
+
const segsAt = dressSegsAt(p, segs);
|
|
2029
|
+
const exponent = mesh.exponent ?? DISTANCE_EXPONENT;
|
|
2030
|
+
const rawShares: Influence[][] = [];
|
|
2031
|
+
for (const [vx0, vy0] of lm.vertices) {
|
|
2032
|
+
const wx = vx0 + ox;
|
|
2033
|
+
const wy = vy0 + oy;
|
|
2034
|
+
const vSegs = segsAt(wy);
|
|
2035
|
+
let shares = influences([wx, wy], vSegs, r, limits, exponent);
|
|
2036
|
+
if (shares.length === 0) {
|
|
2037
|
+
shares = influences([wx, wy], vSegs, Infinity, { maxInfluences: 1, minWeight: 0 }, exponent);
|
|
2038
|
+
}
|
|
2039
|
+
rawShares.push(limits.floor === 'continuous' ? roundShares(shares) : latticeShares(shares));
|
|
2040
|
+
}
|
|
2041
|
+
const weights: WeightEntry[][] = [];
|
|
2042
|
+
for (const [vi, [vx0, vy0]] of lm.vertices.entries()) {
|
|
2043
|
+
const wx = vx0 + ox;
|
|
2044
|
+
const wy = vy0 + oy;
|
|
2045
|
+
const ent: WeightEntry[] = rawShares[vi].map(({ bone, weight }) => {
|
|
2046
|
+
const [x, y] = toBoneLocal(world.get(bone) as BoneTransform, spineX(wx), spineY(wy));
|
|
2047
|
+
return { bone, x: places(x), y: places(y), weight };
|
|
2048
|
+
});
|
|
2049
|
+
weights.push(ent);
|
|
2050
|
+
}
|
|
2051
|
+
const uvs: number[] = [];
|
|
2052
|
+
for (const [x, y] of lm.vertices) uvs.push(pyRound(x / w, 6), pyRound(y / h, 6));
|
|
2053
|
+
skin[p.name][attName] = { type: 'mesh', image: file, width: w, height: h, uvs, triangles: lm.triangles, hull: lm.hull, weights };
|
|
2054
|
+
}
|
|
2055
|
+
const slotMap: Record<string, string> = {};
|
|
2056
|
+
for (const [slotName, att] of Object.entries(alt.slots)) slotMap[slotName] = att;
|
|
2057
|
+
const hideList: string[] = [];
|
|
2058
|
+
for (const h of alt.hides ?? []) {
|
|
2059
|
+
if (!hideList.includes(h)) hideList.push(h);
|
|
2060
|
+
if (stills[h] !== undefined && !hideList.includes(`${h}${STILL_SUFFIX}`)) hideList.push(`${h}${STILL_SUFFIX}`);
|
|
2061
|
+
}
|
|
2062
|
+
for (const slotName of Object.keys(alt.slots)) {
|
|
2063
|
+
if (stills[slotName] !== undefined && !hideList.includes(`${slotName}${STILL_SUFFIX}`)) hideList.push(`${slotName}${STILL_SUFFIX}`);
|
|
2064
|
+
}
|
|
2065
|
+
setEntries[alt.name] = {
|
|
2066
|
+
slots: slotMap,
|
|
2067
|
+
...(hideList.length > 0 ? { hides: hideList } : {}),
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
sets = { spec: SETS_SPEC, sets: setEntries };
|
|
2071
|
+
}
|
|
1969
2072
|
// ---- leg stops (issue #404) --------------------------------------------
|
|
1970
2073
|
// Only when the config has `stops`: each link after the first (and any bone hanging from a stopped link) hangs
|
|
1971
2074
|
// from the drawn link `<link>_d` above it, and slots and weights on a stopped link bind `<link>_d`.
|
|
@@ -2007,7 +2110,20 @@ export function buildRig(
|
|
|
2007
2110
|
overrides: cfg.turn?.deform,
|
|
2008
2111
|
});
|
|
2009
2112
|
});
|
|
2010
|
-
return {
|
|
2113
|
+
return {
|
|
2114
|
+
rig,
|
|
2115
|
+
motion,
|
|
2116
|
+
meshReport,
|
|
2117
|
+
images: outImages,
|
|
2118
|
+
controls,
|
|
2119
|
+
meshKeyed,
|
|
2120
|
+
idleKeys,
|
|
2121
|
+
loopPasses,
|
|
2122
|
+
autoMotion,
|
|
2123
|
+
faceAxis,
|
|
2124
|
+
turn: turned?.rows ?? null,
|
|
2125
|
+
...(sets === undefined ? {} : { sets }),
|
|
2126
|
+
};
|
|
2011
2127
|
}
|
|
2012
2128
|
|
|
2013
2129
|
/**
|