@melite/formats 0.0.0-stage → 0.1.5-pre-alpha
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/LICENSE +557 -0
- package/README.md +37 -2
- package/lib/b64.d.ts +2 -0
- package/lib/b64.js +7 -0
- package/lib/bodyPresets.d.ts +40 -0
- package/lib/bodyPresets.js +77 -0
- package/lib/captionSplitter.d.ts +49 -0
- package/lib/captionSplitter.js +172 -0
- package/lib/cardMutations.d.ts +52 -0
- package/lib/cardMutations.js +67 -0
- package/lib/downloads.d.ts +18 -0
- package/lib/downloads.js +64 -0
- package/lib/extractVideoFrame.d.ts +23 -0
- package/lib/extractVideoFrame.js +114 -0
- package/lib/glbLoad.d.ts +54 -0
- package/lib/glbLoad.js +96 -0
- package/lib/glbSkin.d.ts +14 -0
- package/lib/glbSkin.js +124 -0
- package/lib/handPose.d.ts +46 -0
- package/lib/handPose.js +122 -0
- package/lib/index.d.ts +25 -0
- package/lib/index.js +25 -0
- package/lib/keyframeExtract.d.ts +46 -0
- package/lib/keyframeExtract.js +89 -0
- package/lib/maskPrep.d.ts +88 -0
- package/lib/maskPrep.js +386 -0
- package/lib/motionConstraints.d.ts +237 -0
- package/lib/motionConstraints.js +369 -0
- package/lib/motionEnvelope.d.ts +56 -0
- package/lib/motionEnvelope.js +59 -0
- package/lib/motionNpz.d.ts +120 -0
- package/lib/motionNpz.js +497 -0
- package/lib/npz.d.ts +24 -0
- package/lib/npz.js +158 -0
- package/lib/openPoseCanvas.d.ts +17 -0
- package/lib/openPoseCanvas.js +90 -0
- package/lib/pngMeta.d.ts +42 -0
- package/lib/pngMeta.js +212 -0
- package/lib/pngMetaInfo.d.ts +14 -0
- package/lib/pngMetaInfo.js +127 -0
- package/lib/poseCodec.d.ts +20 -0
- package/lib/poseCodec.js +61 -0
- package/lib/poseTransforms.d.ts +66 -0
- package/lib/poseTransforms.js +227 -0
- package/lib/poserSkin.d.ts +75 -0
- package/lib/poserSkin.js +141 -0
- package/lib/prompt-tags.json +132 -0
- package/lib/promptTags.d.ts +42 -0
- package/lib/promptTags.js +116 -0
- package/lib/psdExport.d.ts +28 -0
- package/lib/psdExport.js +272 -0
- package/lib/readFile.d.ts +9 -0
- package/lib/readFile.js +16 -0
- package/lib/renderSnapshot.d.ts +34 -0
- package/lib/renderSnapshot.js +55 -0
- package/lib/somaSkin.d.ts +82 -0
- package/lib/somaSkin.js +267 -0
- package/lib/textToPose.d.ts +394 -0
- package/lib/textToPose.js +607 -0
- package/lib/uploadToEstate.d.ts +25 -0
- package/lib/uploadToEstate.js +97 -0
- package/package.json +149 -4
package/lib/psdExport.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// ─── Big-endian byte writer ──────────────────────────────────────────────────
|
|
2
|
+
class ByteWriter {
|
|
3
|
+
chunks = [];
|
|
4
|
+
view = new DataView(new ArrayBuffer(8));
|
|
5
|
+
u8(v) {
|
|
6
|
+
const b = new Uint8Array(1);
|
|
7
|
+
b[0] = v & 0xff;
|
|
8
|
+
this.chunks.push(b);
|
|
9
|
+
}
|
|
10
|
+
u16(v) {
|
|
11
|
+
const dv = new DataView(new ArrayBuffer(2));
|
|
12
|
+
dv.setUint16(0, v, false);
|
|
13
|
+
this.chunks.push(new Uint8Array(dv.buffer));
|
|
14
|
+
}
|
|
15
|
+
/** signed 16 */
|
|
16
|
+
i16(v) {
|
|
17
|
+
const dv = new DataView(new ArrayBuffer(2));
|
|
18
|
+
dv.setInt16(0, v, false);
|
|
19
|
+
this.chunks.push(new Uint8Array(dv.buffer));
|
|
20
|
+
}
|
|
21
|
+
u32(v) {
|
|
22
|
+
const dv = new DataView(new ArrayBuffer(4));
|
|
23
|
+
dv.setUint32(0, v >>> 0, false);
|
|
24
|
+
this.chunks.push(new Uint8Array(dv.buffer));
|
|
25
|
+
}
|
|
26
|
+
/** signed 32 */
|
|
27
|
+
i32(v) {
|
|
28
|
+
const dv = new DataView(new ArrayBuffer(4));
|
|
29
|
+
dv.setInt32(0, v | 0, false);
|
|
30
|
+
this.chunks.push(new Uint8Array(dv.buffer));
|
|
31
|
+
}
|
|
32
|
+
bytes(b) { this.chunks.push(b); }
|
|
33
|
+
ascii(s) {
|
|
34
|
+
const b = new Uint8Array(s.length);
|
|
35
|
+
for (let i = 0; i < s.length; i++)
|
|
36
|
+
b[i] = s.charCodeAt(i) & 0xff;
|
|
37
|
+
this.chunks.push(b);
|
|
38
|
+
}
|
|
39
|
+
size() { return this.chunks.reduce((n, c) => n + c.length, 0); }
|
|
40
|
+
toUint8() {
|
|
41
|
+
const out = new Uint8Array(this.size());
|
|
42
|
+
let off = 0;
|
|
43
|
+
for (const c of this.chunks) {
|
|
44
|
+
out.set(c, off);
|
|
45
|
+
off += c.length;
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function loadImageData(dataUrl, w, h) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const img = new Image();
|
|
53
|
+
img.onload = () => {
|
|
54
|
+
const c = document.createElement('canvas');
|
|
55
|
+
c.width = w;
|
|
56
|
+
c.height = h;
|
|
57
|
+
const ctx = c.getContext('2d');
|
|
58
|
+
ctx.clearRect(0, 0, w, h);
|
|
59
|
+
ctx.drawImage(img, 0, 0, w, h);
|
|
60
|
+
resolve(ctx.getImageData(0, 0, w, h).data);
|
|
61
|
+
};
|
|
62
|
+
img.onerror = () => reject(new Error('Failed to decode layer for PSD'));
|
|
63
|
+
img.src = dataUrl;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** Map our BlendMode to a PSD 4-char blend key. Unsupported → 'norm'. */
|
|
67
|
+
function blendKey(mode) {
|
|
68
|
+
const map = {
|
|
69
|
+
normal: 'norm',
|
|
70
|
+
multiply: 'mul ',
|
|
71
|
+
screen: 'scrn',
|
|
72
|
+
overlay: 'over',
|
|
73
|
+
darken: 'dark',
|
|
74
|
+
lighten: 'lite',
|
|
75
|
+
'color-dodge': 'd dg',
|
|
76
|
+
'color-burn': 'div ',
|
|
77
|
+
'hard-light': 'hLit',
|
|
78
|
+
'soft-light': 'sLit',
|
|
79
|
+
difference: 'diff',
|
|
80
|
+
exclusion: 'smud',
|
|
81
|
+
hue: 'hue ',
|
|
82
|
+
saturation: 'sat ',
|
|
83
|
+
color: 'colr',
|
|
84
|
+
luminosity: 'lum ',
|
|
85
|
+
};
|
|
86
|
+
return map[mode] ?? 'norm';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
function even(n) { return n % 2 === 0 ? n : n + 1; }
|
|
96
|
+
/**
|
|
97
|
+
* Build a layered PSD Blob. `width`/`height` define the canvas; every layer is
|
|
98
|
+
* composited into that extent. The merged composite flattens visible layers.
|
|
99
|
+
*/
|
|
100
|
+
export async function buildPsd(layers, width, height, mergedRgba) {
|
|
101
|
+
const w = Math.max(1, Math.round(width));
|
|
102
|
+
const h = Math.max(1, Math.round(height));
|
|
103
|
+
// Decode all layers into RGBA planes.
|
|
104
|
+
const decoded = [];
|
|
105
|
+
for (const l of layers) {
|
|
106
|
+
if (!l.dataUrl)
|
|
107
|
+
continue;
|
|
108
|
+
try {
|
|
109
|
+
const rgba = await loadImageData(l.dataUrl, w, h);
|
|
110
|
+
decoded.push({ name: l.name, width: w, height: h, rgba, blendMode: l.blendMode, opacity: l.opacity, visible: l.visible });
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
console.warn('[psd] skipping undecodable layer:', l.name, err);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Merged composite (flattened visible layers) for the Image Data section.
|
|
117
|
+
let merged = mergedRgba;
|
|
118
|
+
if (!merged) {
|
|
119
|
+
const c = document.createElement('canvas');
|
|
120
|
+
c.width = w;
|
|
121
|
+
c.height = h;
|
|
122
|
+
const ctx = c.getContext('2d');
|
|
123
|
+
for (const l of decoded) {
|
|
124
|
+
if (!l.visible)
|
|
125
|
+
continue;
|
|
126
|
+
ctx.globalAlpha = l.opacity;
|
|
127
|
+
ctx.globalCompositeOperation = l.blendMode === 'normal' ? 'source-over' : l.blendMode;
|
|
128
|
+
const lc = document.createElement('canvas');
|
|
129
|
+
lc.width = w;
|
|
130
|
+
lc.height = h;
|
|
131
|
+
lc.getContext('2d').putImageData(new ImageData(l.rgba.slice(), w, h), 0, 0);
|
|
132
|
+
ctx.drawImage(lc, 0, 0);
|
|
133
|
+
}
|
|
134
|
+
ctx.globalAlpha = 1;
|
|
135
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
136
|
+
merged = ctx.getImageData(0, 0, w, h).data;
|
|
137
|
+
}
|
|
138
|
+
// ─── Build layer records + channel image data ───
|
|
139
|
+
// Per layer channels: 0=R, 1=G, 2=B, -1=transparency.
|
|
140
|
+
const CHANNEL_IDS = [0, 1, 2, -1];
|
|
141
|
+
const numChannels = CHANNEL_IDS.length;
|
|
142
|
+
const records = new ByteWriter();
|
|
143
|
+
const channelData = new ByteWriter();
|
|
144
|
+
// Channel plane size = w*h bytes (raw, +2 for compression type).
|
|
145
|
+
const planeSize = w * h;
|
|
146
|
+
for (const layer of decoded) {
|
|
147
|
+
// Split RGBA into planes.
|
|
148
|
+
const planes = {
|
|
149
|
+
0: new Uint8Array(planeSize),
|
|
150
|
+
1: new Uint8Array(planeSize),
|
|
151
|
+
2: new Uint8Array(planeSize),
|
|
152
|
+
[-1]: new Uint8Array(planeSize),
|
|
153
|
+
};
|
|
154
|
+
for (let i = 0; i < planeSize; i++) {
|
|
155
|
+
planes[0][i] = layer.rgba[i * 4];
|
|
156
|
+
planes[1][i] = layer.rgba[i * 4 + 1];
|
|
157
|
+
planes[2][i] = layer.rgba[i * 4 + 2];
|
|
158
|
+
planes[-1][i] = layer.rgba[i * 4 + 3];
|
|
159
|
+
}
|
|
160
|
+
// ── Layer record ──
|
|
161
|
+
records.i32(0); // top
|
|
162
|
+
records.i32(0); // left
|
|
163
|
+
records.i32(h); // bottom
|
|
164
|
+
records.i32(w); // right
|
|
165
|
+
records.u16(numChannels); // channel count
|
|
166
|
+
for (const id of CHANNEL_IDS) {
|
|
167
|
+
records.i16(id); // channel id
|
|
168
|
+
records.u32(2 + planeSize); // channel data length (compression + raw)
|
|
169
|
+
}
|
|
170
|
+
records.ascii('8BIM'); // blend mode signature
|
|
171
|
+
records.ascii(blendKey(layer.blendMode)); // blend key (4 chars)
|
|
172
|
+
records.u8(Math.round(layer.opacity * 255)); // opacity
|
|
173
|
+
records.u8(0); // clipping (base)
|
|
174
|
+
const flags = layer.visible ? 0b00000000 : 0b00000010; // bit1 set = hidden
|
|
175
|
+
records.u8(flags); // flags
|
|
176
|
+
records.u8(0); // filler
|
|
177
|
+
// ── Extra data: mask(0) + blending ranges(0) + name ──
|
|
178
|
+
const extra = new ByteWriter();
|
|
179
|
+
extra.u32(0); // mask data length
|
|
180
|
+
extra.u32(0); // blending ranges length
|
|
181
|
+
// Pascal string name, padded to multiple of 4 (including length byte).
|
|
182
|
+
const nameBytes = new TextEncoder().encode(layer.name.slice(0, 255));
|
|
183
|
+
const nameBuf = new ByteWriter();
|
|
184
|
+
nameBuf.u8(nameBytes.length);
|
|
185
|
+
nameBuf.bytes(nameBytes);
|
|
186
|
+
const nameLen = nameBuf.size();
|
|
187
|
+
const pad = (4 - (nameLen % 4)) % 4;
|
|
188
|
+
nameBuf.bytes(new Uint8Array(pad));
|
|
189
|
+
extra.bytes(nameBuf.toUint8());
|
|
190
|
+
const extraBytes = extra.toUint8();
|
|
191
|
+
records.u32(extraBytes.length);
|
|
192
|
+
records.bytes(extraBytes);
|
|
193
|
+
// ── Channel image data for this layer ──
|
|
194
|
+
for (const id of CHANNEL_IDS) {
|
|
195
|
+
channelData.u16(0); // compression: raw
|
|
196
|
+
channelData.bytes(planes[id]);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Layer count (signed; negative ⇒ first alpha = merged transparency).
|
|
200
|
+
// We use positive count.
|
|
201
|
+
const layerInfoBody = new ByteWriter();
|
|
202
|
+
layerInfoBody.i16(decoded.length);
|
|
203
|
+
layerInfoBody.bytes(records.toUint8());
|
|
204
|
+
layerInfoBody.bytes(channelData.toUint8());
|
|
205
|
+
const layerInfoBytes = layerInfoBody.toUint8();
|
|
206
|
+
// Layer info section: 4-byte length (even-padded) + body.
|
|
207
|
+
const layerInfoPadded = even(layerInfoBytes.length);
|
|
208
|
+
const layerSection = new ByteWriter();
|
|
209
|
+
layerSection.u32(layerInfoPadded);
|
|
210
|
+
layerSection.bytes(layerInfoBytes);
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
if (layerInfoPadded > layerInfoBytes.length) {
|
|
214
|
+
layerSection.bytes(new Uint8Array(layerInfoPadded - layerInfoBytes.length));
|
|
215
|
+
}
|
|
216
|
+
// Layer and Mask Information: 4-byte length + layer section.
|
|
217
|
+
const layerMaskBytes = layerSection.toUint8();
|
|
218
|
+
const layerMaskPadded = even(layerMaskBytes.length);
|
|
219
|
+
// ─── Merged image data: 3 RGB planes (raw) ───
|
|
220
|
+
const mergedPlanes = [
|
|
221
|
+
new Uint8Array(planeSize),
|
|
222
|
+
new Uint8Array(planeSize),
|
|
223
|
+
new Uint8Array(planeSize),
|
|
224
|
+
];
|
|
225
|
+
for (let i = 0; i < planeSize; i++) {
|
|
226
|
+
mergedPlanes[0][i] = merged[i * 4];
|
|
227
|
+
mergedPlanes[1][i] = merged[i * 4 + 1];
|
|
228
|
+
mergedPlanes[2][i] = merged[i * 4 + 2];
|
|
229
|
+
}
|
|
230
|
+
const imageData = new ByteWriter();
|
|
231
|
+
imageData.u16(0); // compression: raw
|
|
232
|
+
for (const p of mergedPlanes)
|
|
233
|
+
imageData.bytes(p);
|
|
234
|
+
// ─── Assemble file ───
|
|
235
|
+
const out = new ByteWriter();
|
|
236
|
+
// Header
|
|
237
|
+
out.ascii('8BPS'); // signature
|
|
238
|
+
out.u16(1); // version
|
|
239
|
+
out.bytes(new Uint8Array(6)); // reserved
|
|
240
|
+
out.u16(3); // channels (RGB)
|
|
241
|
+
out.u32(h); // height
|
|
242
|
+
out.u32(w); // width
|
|
243
|
+
out.u16(8); // depth
|
|
244
|
+
out.u16(3); // color mode (RGB)
|
|
245
|
+
// Color mode data
|
|
246
|
+
out.u32(0);
|
|
247
|
+
// Image resources
|
|
248
|
+
out.u32(0);
|
|
249
|
+
// Layer and mask information
|
|
250
|
+
out.u32(layerMaskPadded);
|
|
251
|
+
out.bytes(layerMaskBytes);
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
if (layerMaskPadded > layerMaskBytes.length) {
|
|
255
|
+
out.bytes(new Uint8Array(layerMaskPadded - layerMaskBytes.length));
|
|
256
|
+
}
|
|
257
|
+
// Image data
|
|
258
|
+
out.bytes(imageData.toUint8());
|
|
259
|
+
return new Blob([out.toUint8()], { type: 'image/vnd.adobe.photoshop' });
|
|
260
|
+
}
|
|
261
|
+
/** Trigger a browser download for a Blob. */
|
|
262
|
+
export function downloadBlob(blob, filename) {
|
|
263
|
+
const url = URL.createObjectURL(blob);
|
|
264
|
+
const a = document.createElement('a');
|
|
265
|
+
a.href = url;
|
|
266
|
+
a.download = filename;
|
|
267
|
+
document.body.appendChild(a);
|
|
268
|
+
a.click();
|
|
269
|
+
a.remove();
|
|
270
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=psdExport.js.map
|
package/lib/readFile.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/** Read a File / Blob and resolve its data: URL. */
|
|
8
|
+
export function readAsDataURL(file) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const reader = new FileReader();
|
|
11
|
+
reader.onload = () => resolve(reader.result);
|
|
12
|
+
reader.onerror = () => reject(reader.error ?? new Error("read failed"));
|
|
13
|
+
reader.readAsDataURL(file);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=readFile.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import * as THREE from '@melite/three-client/client';
|
|
19
|
+
export interface SnapshotOptions {
|
|
20
|
+
/** Square output size (256 — plenty for a tile even at 2× DPR). */
|
|
21
|
+
size?: number;
|
|
22
|
+
/** JPEG quality (0.82 — photo-like renders, 4–6× smaller than PNG). */
|
|
23
|
+
jpegQuality?: number;
|
|
24
|
+
/** Device-pixel-ratio cap (1.5 — the tile is too small for more). */
|
|
25
|
+
maxDpr?: number;
|
|
26
|
+
/** Object whose geometry/materials are disposed after the snapshot
|
|
27
|
+
* (the scene graph the caller built for this render). */
|
|
28
|
+
disposeRoot?: THREE.Object3D;
|
|
29
|
+
}
|
|
30
|
+
/** Render `scene` through `camera` ONCE and return a JPEG data URL.
|
|
31
|
+
* The renderer is created, used, and torn down within this call —
|
|
32
|
+
* no persistent WebGL context survives a snapshot. */
|
|
33
|
+
export declare function snapshotSceneToJpeg(scene: THREE.Scene, camera: THREE.Camera, opts?: SnapshotOptions): string;
|
|
34
|
+
//# sourceMappingURL=renderSnapshot.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import * as THREE from '@melite/three-client/client';
|
|
19
|
+
/** Render `scene` through `camera` ONCE and return a JPEG data URL.
|
|
20
|
+
* The renderer is created, used, and torn down within this call —
|
|
21
|
+
* no persistent WebGL context survives a snapshot. */
|
|
22
|
+
export function snapshotSceneToJpeg(scene, camera, opts = {}) {
|
|
23
|
+
const size = opts.size ?? 256;
|
|
24
|
+
const quality = opts.jpegQuality ?? 0.82;
|
|
25
|
+
const maxDpr = opts.maxDpr ?? 1.5;
|
|
26
|
+
const renderer = new THREE.WebGLRenderer({
|
|
27
|
+
antialias: true,
|
|
28
|
+
alpha: false,
|
|
29
|
+
preserveDrawingBuffer: true,
|
|
30
|
+
});
|
|
31
|
+
try {
|
|
32
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, maxDpr));
|
|
33
|
+
renderer.setSize(size, size, false);
|
|
34
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
35
|
+
renderer.toneMappingExposure = 1.0;
|
|
36
|
+
renderer.render(scene, camera);
|
|
37
|
+
return renderer.domElement.toDataURL("image/jpeg", quality);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
// Free GPU memory + the context itself immediately — the snapshot
|
|
41
|
+
// is already captured, so nothing live should remain per tile.
|
|
42
|
+
if (opts.disposeRoot) {
|
|
43
|
+
opts.disposeRoot.traverse((obj) => {
|
|
44
|
+
if (obj instanceof THREE.Mesh) {
|
|
45
|
+
obj.geometry?.dispose();
|
|
46
|
+
const mats = Array.isArray(obj.material) ? obj.material : [obj.material];
|
|
47
|
+
mats.forEach((m) => m?.dispose());
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
renderer.dispose();
|
|
52
|
+
renderer.forceContextLoss();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=renderSnapshot.js.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* somaSkin — neutral SOMA-77 mesh skinning, a direct port of the kimodo demo's
|
|
3
|
+
* vendor/kimodo/kimodo/viz/soma_skin.py SOMASkin.skin()+lbs().
|
|
4
|
+
*
|
|
5
|
+
* The demo (and only the demo) renders Kimodo motions on a NEUTRAL body via
|
|
6
|
+
* LBS weights stored in skin_standard.npz (bind_vertices + faces +
|
|
7
|
+
* bind_rig_transform + lbs_indices + lbs_weights). That is the "base SOMAX
|
|
8
|
+
* mesh" that actually deforms — distinct from the poser backend's `soma`
|
|
9
|
+
* engine (a static mesh with no LBS weights) and from `anny`/`mhr` (parametric
|
|
10
|
+
* bodies with their own proportions). This module reproduces the demo's math
|
|
11
|
+
* client-side so the asset preview shows a Kimodo motion on the same neutral
|
|
12
|
+
* mesh the demo uses, with no parametric body shape imposed.
|
|
13
|
+
*
|
|
14
|
+
* Flow per frame:
|
|
15
|
+
* fk[j] = [[global_rot[j], global_pos[j]], [0,0,0,1]] (4×4, per joint)
|
|
16
|
+
* affine[j] = (fk[j] · inv(bind_rig[j]))[:3, :] (3×4, per joint)
|
|
17
|
+
* v' = Σ_w lbs_weights[v,w] · (affine[lbs_indices[v,w]] · [bind_v, 1])
|
|
18
|
+
*
|
|
19
|
+
* Units: meters throughout (Kimodo's native unit). No cm↔m conversion — that
|
|
20
|
+
* was a poser-route artifact that doesn't apply here.
|
|
21
|
+
*/
|
|
22
|
+
export declare const SKIN_URL = "/v1/models/kimodo/provisions/skin_standard.npz";
|
|
23
|
+
export interface SomaSkin {
|
|
24
|
+
bindVertices: Float32Array;
|
|
25
|
+
faces: Uint32Array;
|
|
26
|
+
lbsIndices: Uint32Array;
|
|
27
|
+
lbsWeights: Float32Array;
|
|
28
|
+
bindRigInv: Float32Array;
|
|
29
|
+
/** (J, 3) METERS — the neutral / rest-pose joint positions, the TS
|
|
30
|
+
* equivalent of upstream Kimodo's `skeleton.neutral_joints`
|
|
31
|
+
* (vendor/kimodo/.../skeleton/base.py:72). Extracted from the
|
|
32
|
+
* translation column of each joint's forward bind transform so the
|
|
33
|
+
* motion card can render + pose + export BEFORE any GPU inference
|
|
34
|
+
* matching the Kimodo demo (app.py:460 "initialize to character
|
|
35
|
+
* default (rest) pose"). Without this the card treated the rest pose
|
|
36
|
+
* as "nothing" and gated every feature on a generated motion NPZ. */
|
|
37
|
+
restJoints: Float32Array;
|
|
38
|
+
/** (J, 9) row-major — the neutral / rest-pose joint global rotations
|
|
39
|
+
* (3×3 block of each forward bind transform). Pairs with restJoints to
|
|
40
|
+
* form a 1-frame "rest motion" that deformFrame / patchFrame consume
|
|
41
|
+
* unchanged, so refine + export + repose all work on the rest pose. */
|
|
42
|
+
restRots: Float32Array;
|
|
43
|
+
vertexCount: number;
|
|
44
|
+
faceCount: number;
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
jointCount: number;
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
influences: number;
|
|
53
|
+
}
|
|
54
|
+
/** Invert a 4×4 matrix (column-major-agnostic: stored row-major as 16 floats). */
|
|
55
|
+
export declare function invert4(m: Float32Array): Float32Array;
|
|
56
|
+
/** Extract the rest pose from row-major FORWARD bind transforms (J×16):
|
|
57
|
+
* translation column (tx, ty, tz) = indices 3, 7, 11 of each 4×4, and the
|
|
58
|
+
* 3×3 rotation block = 0,1,2 / 4,5,6 / 8,9,10. This is the TS equivalent
|
|
59
|
+
* of Kimodo's neutral_joints — the motion card's pre-inference pose (see
|
|
60
|
+
* SomaSkin.restJoints doc). Shared by both loaders: the npz carries the
|
|
61
|
+
* forward binds directly; the GLB adapter inverts glTF boneInverses into
|
|
62
|
+
* the same row-major forward shape first. */
|
|
63
|
+
export declare function restPoseFromBind(bindRig: Float32Array, jointCount: number): {
|
|
64
|
+
restJoints: Float32Array;
|
|
65
|
+
restRots: Float32Array;
|
|
66
|
+
};
|
|
67
|
+
/** Load + cache the neutral SOMA skin (one fetch per session). */
|
|
68
|
+
export declare function loadSomaSkin(): Promise<SomaSkin>;
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
export declare function deformFrame(globalRot: Float32Array, globalPos: Float32Array, skin: SomaSkin, out: Float32Array): void;
|
|
82
|
+
//# sourceMappingURL=somaSkin.d.ts.map
|