distark-render 1.0.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/README.md +75 -0
- package/dist/modules/eyeSystem.d.ts +104 -0
- package/dist/modules/eyeSystem.d.ts.map +1 -0
- package/dist/modules/eyeSystem.js +326 -0
- package/dist/modules/eyeSystem.js.map +1 -0
- package/dist/modules/imageLoad.d.ts +86 -0
- package/dist/modules/imageLoad.d.ts.map +1 -0
- package/dist/modules/imageLoad.js +259 -0
- package/dist/modules/imageLoad.js.map +1 -0
- package/dist/modules/mouthSystem.d.ts +147 -0
- package/dist/modules/mouthSystem.d.ts.map +1 -0
- package/dist/modules/mouthSystem.js +277 -0
- package/dist/modules/mouthSystem.js.map +1 -0
- package/dist/modules/renderRig.d.ts +53 -0
- package/dist/modules/renderRig.d.ts.map +1 -0
- package/dist/modules/renderRig.js +1248 -0
- package/dist/modules/renderRig.js.map +1 -0
- package/dist/test-skia.d.ts +15 -0
- package/dist/test-skia.d.ts.map +1 -0
- package/dist/test-skia.js +179 -0
- package/dist/test-skia.js.map +1 -0
- package/dist/types.d.ts +204 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,1248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendering-agnostic character rig data generator
|
|
3
|
+
* Computes all transforms and returns a data structure that can be used with any rendering library
|
|
4
|
+
*/
|
|
5
|
+
// Import ImageLoader class
|
|
6
|
+
import { ImageLoader } from './imageLoad.js';
|
|
7
|
+
/**
|
|
8
|
+
* CharacterRigRenderer class - Handles character rig computation and rendering
|
|
9
|
+
*/
|
|
10
|
+
export class CharacterRigRenderer {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new CharacterRigRenderer instance
|
|
13
|
+
* @param imageLoader - Optional ImageLoader instance (creates a new one if not provided)
|
|
14
|
+
*/
|
|
15
|
+
constructor(imageLoader) {
|
|
16
|
+
this.imageLoader = imageLoader || new ImageLoader();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get the ImageLoader instance
|
|
20
|
+
* @returns The ImageLoader instance
|
|
21
|
+
*/
|
|
22
|
+
getImageLoader() {
|
|
23
|
+
return this.imageLoader;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Helper: apply parent transform to get world space position
|
|
27
|
+
*/
|
|
28
|
+
applyTransform(parent, local) {
|
|
29
|
+
const cos = Math.cos(parent.rotation);
|
|
30
|
+
const sin = Math.sin(parent.rotation);
|
|
31
|
+
return {
|
|
32
|
+
x: parent.x + (local.x * cos - local.y * sin) * parent.scaleX,
|
|
33
|
+
y: parent.y + (local.x * sin + local.y * cos) * parent.scaleY,
|
|
34
|
+
rotation: parent.rotation + local.rotation,
|
|
35
|
+
scaleX: parent.scaleX * local.scaleX,
|
|
36
|
+
scaleY: parent.scaleY * local.scaleY
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Computes all character rig objects with their final transforms and properties
|
|
41
|
+
*/
|
|
42
|
+
computeCharacterRigData(rigData, options = {}) {
|
|
43
|
+
const { canvasWidth = 800, canvasHeight = 600, cameraOffset = { x: 0, y: 0 }, loadedImages = {} } = options;
|
|
44
|
+
// Get rendering parameters from rigData
|
|
45
|
+
const centerX = canvasWidth / 2 + cameraOffset.x;
|
|
46
|
+
const centerY = canvasHeight / 2 + 100 + cameraOffset.y;
|
|
47
|
+
const rotations = rigData.rotationValues || {};
|
|
48
|
+
const selfRotations = rigData.selfRotationValues || {};
|
|
49
|
+
const dimensions = rigData.dimensionValues || {};
|
|
50
|
+
const pivotPoints = rigData.pivotPoints || {};
|
|
51
|
+
const jointOffset = rigData.jointOffset || {};
|
|
52
|
+
const zIndexValues = rigData.zIndexValues || {};
|
|
53
|
+
const eyes = rigData.eyes || {};
|
|
54
|
+
const visibility = rigData.visibility || {}; // Default: all visible if not specified
|
|
55
|
+
const flipX = rigData.flipX || false; // No flip by default
|
|
56
|
+
const flipHead = rigData.flipHead || false; // No head flip by default
|
|
57
|
+
const imageScale = rigData.imageScale ?? 1.0; // Use imageScale from rigData, default to 1.0
|
|
58
|
+
// Create array of all limbs/components with their computed transforms
|
|
59
|
+
const allObjects = [];
|
|
60
|
+
// Root transform (character position in world space)
|
|
61
|
+
const rootTransform = {
|
|
62
|
+
x: centerX,
|
|
63
|
+
y: centerY,
|
|
64
|
+
rotation: 0,
|
|
65
|
+
scaleX: flipX ? -1 : 1,
|
|
66
|
+
scaleY: 1
|
|
67
|
+
};
|
|
68
|
+
// Torso
|
|
69
|
+
if (visibility.torso !== false) {
|
|
70
|
+
const torsoWidth = dimensions.torso?.width || 60;
|
|
71
|
+
const torsoHeight = dimensions.torso?.height || 120;
|
|
72
|
+
const selfRot = selfRotations["torso"] || 0;
|
|
73
|
+
allObjects.push({
|
|
74
|
+
name: 'torso',
|
|
75
|
+
type: 'limb',
|
|
76
|
+
zIndex: zIndexValues['torso'] || 1,
|
|
77
|
+
width: torsoWidth * imageScale,
|
|
78
|
+
height: torsoHeight * imageScale,
|
|
79
|
+
x: rootTransform.x,
|
|
80
|
+
y: rootTransform.y,
|
|
81
|
+
rotation: rootTransform.rotation + selfRot,
|
|
82
|
+
scaleX: rootTransform.scaleX,
|
|
83
|
+
scaleY: rootTransform.scaleY,
|
|
84
|
+
// Anchor point (where the image is drawn from - top center for torso)
|
|
85
|
+
anchorX: 0.5,
|
|
86
|
+
anchorY: 1,
|
|
87
|
+
imageKey: 'imagePaths.torso',
|
|
88
|
+
imageData: loadedImages['imagePaths.torso'],
|
|
89
|
+
selfRotation: selfRot
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// Head
|
|
93
|
+
if (visibility.head !== false) {
|
|
94
|
+
const connectionKey = `torso_head`;
|
|
95
|
+
const pivotPoint = pivotPoints[connectionKey] || { x: 0, y: 0 };
|
|
96
|
+
const headJointOffset = jointOffset[connectionKey] || { x: 0, y: 0 };
|
|
97
|
+
const headWidth = dimensions.head?.width || 80;
|
|
98
|
+
const headHeight = dimensions.head?.height || 80;
|
|
99
|
+
const headRot = rotations["head"] || 0;
|
|
100
|
+
const selfRot = selfRotations["head"] || 0;
|
|
101
|
+
// Calculate head transform
|
|
102
|
+
const headParentTransform = this.applyTransform(rootTransform, {
|
|
103
|
+
x: pivotPoint.x || 0,
|
|
104
|
+
y: pivotPoint.y || 0,
|
|
105
|
+
rotation: headRot,
|
|
106
|
+
scaleX: flipHead ? -1 : 1,
|
|
107
|
+
scaleY: 1
|
|
108
|
+
});
|
|
109
|
+
const headTransform = this.applyTransform(headParentTransform, {
|
|
110
|
+
x: headJointOffset.x || 0,
|
|
111
|
+
y: (headJointOffset.y || 0) - headHeight / 2,
|
|
112
|
+
rotation: selfRot,
|
|
113
|
+
scaleX: 1,
|
|
114
|
+
scaleY: 1
|
|
115
|
+
});
|
|
116
|
+
allObjects.push({
|
|
117
|
+
name: 'head',
|
|
118
|
+
type: 'limb',
|
|
119
|
+
zIndex: zIndexValues['head'] || 1,
|
|
120
|
+
width: headWidth * imageScale,
|
|
121
|
+
height: headHeight * imageScale,
|
|
122
|
+
x: headTransform.x,
|
|
123
|
+
y: headTransform.y,
|
|
124
|
+
rotation: headTransform.rotation,
|
|
125
|
+
scaleX: headTransform.scaleX,
|
|
126
|
+
scaleY: headTransform.scaleY,
|
|
127
|
+
anchorX: 0.5,
|
|
128
|
+
anchorY: 0.5,
|
|
129
|
+
imageKey: 'imagePaths.head',
|
|
130
|
+
imageData: loadedImages['imagePaths.head'],
|
|
131
|
+
selfRotation: selfRot,
|
|
132
|
+
parentTransform: headParentTransform
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// Mouth (child of head)
|
|
136
|
+
if (visibility.mouth !== false) {
|
|
137
|
+
const headPivot = pivotPoints['torso_head'] || { x: 0, y: 0 };
|
|
138
|
+
const mouthOffset = pivotPoints['head_mouth'] || { x: 0, y: 0 };
|
|
139
|
+
const mouthWidth = dimensions.mouth?.width || 40;
|
|
140
|
+
const mouthHeight = dimensions.mouth?.height || 30;
|
|
141
|
+
const selfRot = selfRotations["mouth"] || 0;
|
|
142
|
+
const headRot = rotations["head"] || 0;
|
|
143
|
+
const headBaseTransform = this.applyTransform(rootTransform, {
|
|
144
|
+
x: headPivot.x || 0,
|
|
145
|
+
y: headPivot.y || 0,
|
|
146
|
+
rotation: headRot,
|
|
147
|
+
scaleX: flipHead ? -1 : 1,
|
|
148
|
+
scaleY: 1
|
|
149
|
+
});
|
|
150
|
+
const offsetX = Number.isFinite(mouthOffset.x) ? mouthOffset.x : 0;
|
|
151
|
+
const offsetY = Number.isFinite(mouthOffset.y) ? mouthOffset.y : 0;
|
|
152
|
+
const mouthTransform = this.applyTransform(headBaseTransform, {
|
|
153
|
+
x: offsetX,
|
|
154
|
+
y: offsetY - mouthHeight / 2,
|
|
155
|
+
rotation: selfRot,
|
|
156
|
+
scaleX: 1,
|
|
157
|
+
scaleY: 1
|
|
158
|
+
});
|
|
159
|
+
allObjects.push({
|
|
160
|
+
name: 'mouth',
|
|
161
|
+
type: 'limb',
|
|
162
|
+
zIndex: zIndexValues['mouth'] || 1,
|
|
163
|
+
width: mouthWidth * imageScale,
|
|
164
|
+
height: mouthHeight * imageScale,
|
|
165
|
+
x: mouthTransform.x,
|
|
166
|
+
y: mouthTransform.y,
|
|
167
|
+
rotation: mouthTransform.rotation,
|
|
168
|
+
scaleX: mouthTransform.scaleX,
|
|
169
|
+
scaleY: mouthTransform.scaleY,
|
|
170
|
+
anchorX: 0.5,
|
|
171
|
+
anchorY: 0.5,
|
|
172
|
+
imageKey: 'imagePaths.mouth',
|
|
173
|
+
imageData: loadedImages['imagePaths.mouth'],
|
|
174
|
+
selfRotation: selfRot
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
// Left Eye
|
|
178
|
+
if (visibility.leftEye !== false && loadedImages['eyes.leftEyeImage'] && eyes) {
|
|
179
|
+
const headPivot = pivotPoints['torso_head'] || { x: 0, y: 0 };
|
|
180
|
+
const headRot = rotations["head"] || 0;
|
|
181
|
+
const headBaseTransform = this.applyTransform(rootTransform, {
|
|
182
|
+
x: headPivot.x || 0,
|
|
183
|
+
y: headPivot.y || 0,
|
|
184
|
+
rotation: headRot,
|
|
185
|
+
scaleX: flipHead ? -1 : 1,
|
|
186
|
+
scaleY: 1
|
|
187
|
+
});
|
|
188
|
+
const offsetX = Number.isFinite(eyes.leftEyeXCoor) ? eyes.leftEyeXCoor : -10;
|
|
189
|
+
const offsetY = Number.isFinite(eyes.leftEyeYCoor) ? eyes.leftEyeYCoor : -5;
|
|
190
|
+
const baseLeftEyeWidth = eyes.leftEyeImageWidth || 20;
|
|
191
|
+
const baseLeftEyeHeight = eyes.leftEyeImageHeight || 15;
|
|
192
|
+
const leftEyeWidth = baseLeftEyeWidth * (eyes.leftEyeWidthRatio || 1.0);
|
|
193
|
+
const leftEyeHeight = baseLeftEyeHeight * (eyes.leftEyeHeightRatio || 1.0);
|
|
194
|
+
const leftEyeTransform = this.applyTransform(headBaseTransform, {
|
|
195
|
+
x: offsetX,
|
|
196
|
+
y: offsetY,
|
|
197
|
+
rotation: 0,
|
|
198
|
+
scaleX: 1,
|
|
199
|
+
scaleY: 1
|
|
200
|
+
});
|
|
201
|
+
allObjects.push({
|
|
202
|
+
name: 'leftEye',
|
|
203
|
+
type: 'eye',
|
|
204
|
+
zIndex: zIndexValues['leftEye'] || 6,
|
|
205
|
+
width: leftEyeWidth,
|
|
206
|
+
height: leftEyeHeight,
|
|
207
|
+
x: leftEyeTransform.x,
|
|
208
|
+
y: leftEyeTransform.y,
|
|
209
|
+
rotation: leftEyeTransform.rotation,
|
|
210
|
+
scaleX: leftEyeTransform.scaleX,
|
|
211
|
+
scaleY: leftEyeTransform.scaleY,
|
|
212
|
+
anchorX: 0.5,
|
|
213
|
+
anchorY: 0.5,
|
|
214
|
+
imageKey: 'eyes.leftEyeImage',
|
|
215
|
+
imageData: loadedImages['eyes.leftEyeImage']
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
// Right Eye
|
|
219
|
+
if (visibility.rightEye !== false && loadedImages['eyes.rightEyeImage'] && eyes) {
|
|
220
|
+
const headPivot = pivotPoints['torso_head'] || { x: 0, y: 0 };
|
|
221
|
+
const headRot = rotations["head"] || 0;
|
|
222
|
+
const headBaseTransform = this.applyTransform(rootTransform, {
|
|
223
|
+
x: headPivot.x || 0,
|
|
224
|
+
y: headPivot.y || 0,
|
|
225
|
+
rotation: headRot,
|
|
226
|
+
scaleX: flipHead ? -1 : 1,
|
|
227
|
+
scaleY: 1
|
|
228
|
+
});
|
|
229
|
+
const offsetX = Number.isFinite(eyes.rightEyeXCoor) ? eyes.rightEyeXCoor : 10;
|
|
230
|
+
const offsetY = Number.isFinite(eyes.rightEyeYCoor) ? eyes.rightEyeYCoor : -5;
|
|
231
|
+
const baseRightEyeWidth = eyes.rightEyeImageWidth || 20;
|
|
232
|
+
const baseRightEyeHeight = eyes.rightEyeImageHeight || 15;
|
|
233
|
+
const rightEyeWidth = baseRightEyeWidth * (eyes.rightEyeWidthRatio || 1.0);
|
|
234
|
+
const rightEyeHeight = baseRightEyeHeight * (eyes.rightEyeHeightRatio || 1.0);
|
|
235
|
+
const rightEyeTransform = this.applyTransform(headBaseTransform, {
|
|
236
|
+
x: offsetX,
|
|
237
|
+
y: offsetY,
|
|
238
|
+
rotation: 0,
|
|
239
|
+
scaleX: 1,
|
|
240
|
+
scaleY: 1
|
|
241
|
+
});
|
|
242
|
+
allObjects.push({
|
|
243
|
+
name: 'rightEye',
|
|
244
|
+
type: 'eye',
|
|
245
|
+
zIndex: zIndexValues['rightEye'] || 3,
|
|
246
|
+
width: rightEyeWidth,
|
|
247
|
+
height: rightEyeHeight,
|
|
248
|
+
x: rightEyeTransform.x,
|
|
249
|
+
y: rightEyeTransform.y,
|
|
250
|
+
rotation: rightEyeTransform.rotation,
|
|
251
|
+
scaleX: rightEyeTransform.scaleX,
|
|
252
|
+
scaleY: rightEyeTransform.scaleY,
|
|
253
|
+
anchorX: 0.5,
|
|
254
|
+
anchorY: 0.5,
|
|
255
|
+
imageKey: 'eyes.rightEyeImage',
|
|
256
|
+
imageData: loadedImages['eyes.rightEyeImage']
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
// Left Iris
|
|
260
|
+
if (visibility.leftIris !== false && loadedImages['eyes.leftIris'] && eyes) {
|
|
261
|
+
const headPivot = pivotPoints['torso_head'] || { x: 0, y: 0 };
|
|
262
|
+
const headRot = rotations["head"] || 0;
|
|
263
|
+
const headBaseTransform = this.applyTransform(rootTransform, {
|
|
264
|
+
x: headPivot.x || 0,
|
|
265
|
+
y: headPivot.y || 0,
|
|
266
|
+
rotation: headRot,
|
|
267
|
+
scaleX: flipHead ? -1 : 1,
|
|
268
|
+
scaleY: 1
|
|
269
|
+
});
|
|
270
|
+
const offsetX = Number.isFinite(eyes.leftIrisXCoor) ? eyes.leftIrisXCoor : -10;
|
|
271
|
+
const offsetY = Number.isFinite(eyes.leftIrisYCoor) ? eyes.leftIrisYCoor : -5;
|
|
272
|
+
const baseLeftIrisWidth = eyes.leftIrisWidth || 10;
|
|
273
|
+
const baseLeftIrisHeight = eyes.leftIrisHeight || 10;
|
|
274
|
+
const leftIrisWidth = baseLeftIrisWidth * (eyes.leftIrisWidthRatio || 1.0);
|
|
275
|
+
const leftIrisHeight = baseLeftIrisHeight * (eyes.leftIrisHeightRatio || 1.0);
|
|
276
|
+
const leftIrisTransform = this.applyTransform(headBaseTransform, {
|
|
277
|
+
x: offsetX,
|
|
278
|
+
y: offsetY,
|
|
279
|
+
rotation: 0,
|
|
280
|
+
scaleX: 1,
|
|
281
|
+
scaleY: 1
|
|
282
|
+
});
|
|
283
|
+
allObjects.push({
|
|
284
|
+
name: 'leftIris',
|
|
285
|
+
type: 'iris',
|
|
286
|
+
zIndex: zIndexValues['leftIris'] || 7,
|
|
287
|
+
width: leftIrisWidth,
|
|
288
|
+
height: leftIrisHeight,
|
|
289
|
+
x: leftIrisTransform.x,
|
|
290
|
+
y: leftIrisTransform.y,
|
|
291
|
+
rotation: leftIrisTransform.rotation,
|
|
292
|
+
scaleX: leftIrisTransform.scaleX,
|
|
293
|
+
scaleY: leftIrisTransform.scaleY,
|
|
294
|
+
anchorX: 0.5,
|
|
295
|
+
anchorY: 0.5,
|
|
296
|
+
imageKey: 'eyes.leftIris',
|
|
297
|
+
imageData: loadedImages['eyes.leftIris']
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
// Right Iris
|
|
301
|
+
if (visibility.rightIris !== false && loadedImages['eyes.rightIris'] && eyes) {
|
|
302
|
+
const headPivot = pivotPoints['torso_head'] || { x: 0, y: 0 };
|
|
303
|
+
const headRot = rotations["head"] || 0;
|
|
304
|
+
const headBaseTransform = this.applyTransform(rootTransform, {
|
|
305
|
+
x: headPivot.x || 0,
|
|
306
|
+
y: headPivot.y || 0,
|
|
307
|
+
rotation: headRot,
|
|
308
|
+
scaleX: flipHead ? -1 : 1,
|
|
309
|
+
scaleY: 1
|
|
310
|
+
});
|
|
311
|
+
const offsetX = Number.isFinite(eyes.rightIrisXCoor) ? eyes.rightIrisXCoor : 10;
|
|
312
|
+
const offsetY = Number.isFinite(eyes.rightIrisYCoor) ? eyes.rightIrisYCoor : -5;
|
|
313
|
+
const baseRightIrisWidth = eyes.rightIrisWidth || 10;
|
|
314
|
+
const baseRightIrisHeight = eyes.rightIrisHeight || 10;
|
|
315
|
+
const rightIrisWidth = baseRightIrisWidth * (eyes.rightIrisWidthRatio || 1.0);
|
|
316
|
+
const rightIrisHeight = baseRightIrisHeight * (eyes.rightIrisHeightRatio || 1.0);
|
|
317
|
+
const rightIrisTransform = this.applyTransform(headBaseTransform, {
|
|
318
|
+
x: offsetX,
|
|
319
|
+
y: offsetY,
|
|
320
|
+
rotation: 0,
|
|
321
|
+
scaleX: 1,
|
|
322
|
+
scaleY: 1
|
|
323
|
+
});
|
|
324
|
+
allObjects.push({
|
|
325
|
+
name: 'rightIris',
|
|
326
|
+
type: 'iris',
|
|
327
|
+
zIndex: zIndexValues['rightIris'] || 4,
|
|
328
|
+
width: rightIrisWidth,
|
|
329
|
+
height: rightIrisHeight,
|
|
330
|
+
x: rightIrisTransform.x,
|
|
331
|
+
y: rightIrisTransform.y,
|
|
332
|
+
rotation: rightIrisTransform.rotation,
|
|
333
|
+
scaleX: rightIrisTransform.scaleX,
|
|
334
|
+
scaleY: rightIrisTransform.scaleY,
|
|
335
|
+
anchorX: 0.5,
|
|
336
|
+
anchorY: 0.5,
|
|
337
|
+
imageKey: 'eyes.rightIris',
|
|
338
|
+
imageData: loadedImages['eyes.rightIris']
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
// Left Eye Lid - select image based on current eyelid state
|
|
342
|
+
const eyelidState = eyes.eyelidState || 'open'; // Default to 'open'
|
|
343
|
+
// Select the correct image based on eyelid state
|
|
344
|
+
let leftEyeLidImage;
|
|
345
|
+
if (eyelidState === 'closed') {
|
|
346
|
+
leftEyeLidImage = loadedImages['eyes.leftEyeLidClosed'];
|
|
347
|
+
}
|
|
348
|
+
else if (eyelidState === 'half-closed') {
|
|
349
|
+
leftEyeLidImage = loadedImages['eyes.leftEyeLidHalfClosed'];
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
leftEyeLidImage = loadedImages['eyes.leftEyeLidOpen'];
|
|
353
|
+
}
|
|
354
|
+
// Fallback to any available eyelid image if the specific one isn't loaded
|
|
355
|
+
if (!leftEyeLidImage) {
|
|
356
|
+
leftEyeLidImage = loadedImages['eyes.leftEyeLidOpen'] ||
|
|
357
|
+
loadedImages['eyes.leftEyeLidHalfClosed'] ||
|
|
358
|
+
loadedImages['eyes.leftEyeLidClosed'];
|
|
359
|
+
}
|
|
360
|
+
if (visibility.leftEyeLid !== false && leftEyeLidImage && eyes) {
|
|
361
|
+
const headPivot = pivotPoints['torso_head'] || { x: 0, y: 0 };
|
|
362
|
+
const headRot = rotations["head"] || 0;
|
|
363
|
+
const headBaseTransform = this.applyTransform(rootTransform, {
|
|
364
|
+
x: headPivot.x || 0,
|
|
365
|
+
y: headPivot.y || 0,
|
|
366
|
+
rotation: headRot,
|
|
367
|
+
scaleX: flipHead ? -1 : 1,
|
|
368
|
+
scaleY: 1
|
|
369
|
+
});
|
|
370
|
+
const offsetX = Number.isFinite(eyes.leftEyeLidXCoor) ? eyes.leftEyeLidXCoor : -10;
|
|
371
|
+
const offsetY = Number.isFinite(eyes.leftEyeLidYCoor) ? eyes.leftEyeLidYCoor : -5;
|
|
372
|
+
const baseLeftEyeLidWidth = eyes.leftEyeLidOpenWidth || eyes.leftEyeLidHalfClosedWidth || eyes.leftEyeLidClosedWidth || 20;
|
|
373
|
+
const baseLeftEyeLidHeight = eyes.leftEyeLidOpenHeight || eyes.leftEyeLidHalfClosedHeight || eyes.leftEyeLidClosedHeight || 15;
|
|
374
|
+
const leftEyeLidWidth = baseLeftEyeLidWidth * (eyes.leftEyeLidWidthRatio || 1.0);
|
|
375
|
+
const leftEyeLidHeight = baseLeftEyeLidHeight * (eyes.leftEyeLidHeightRatio || 1.0);
|
|
376
|
+
const leftEyeLidTransform = this.applyTransform(headBaseTransform, {
|
|
377
|
+
x: offsetX,
|
|
378
|
+
y: offsetY,
|
|
379
|
+
rotation: 0,
|
|
380
|
+
scaleX: 1,
|
|
381
|
+
scaleY: 1
|
|
382
|
+
});
|
|
383
|
+
allObjects.push({
|
|
384
|
+
name: 'leftEyeLid',
|
|
385
|
+
type: 'eyelid',
|
|
386
|
+
zIndex: zIndexValues['leftEyeLid'] || 8,
|
|
387
|
+
width: leftEyeLidWidth,
|
|
388
|
+
height: leftEyeLidHeight,
|
|
389
|
+
x: leftEyeLidTransform.x,
|
|
390
|
+
y: leftEyeLidTransform.y,
|
|
391
|
+
rotation: leftEyeLidTransform.rotation,
|
|
392
|
+
scaleX: leftEyeLidTransform.scaleX,
|
|
393
|
+
scaleY: leftEyeLidTransform.scaleY,
|
|
394
|
+
anchorX: 0.5,
|
|
395
|
+
anchorY: 0.5,
|
|
396
|
+
imageKey: 'eyes.leftEyeLid',
|
|
397
|
+
imageData: leftEyeLidImage
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
// Right Eye Lid - select image based on current eyelid state (same state for both eyes)
|
|
401
|
+
// Select the correct image based on eyelid state
|
|
402
|
+
let rightEyeLidImage;
|
|
403
|
+
if (eyelidState === 'closed') {
|
|
404
|
+
rightEyeLidImage = loadedImages['eyes.rightEyeLidClosed'];
|
|
405
|
+
}
|
|
406
|
+
else if (eyelidState === 'half-closed') {
|
|
407
|
+
rightEyeLidImage = loadedImages['eyes.rightEyeLidHalfClosed'];
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
rightEyeLidImage = loadedImages['eyes.rightEyeLidOpen'];
|
|
411
|
+
}
|
|
412
|
+
// Fallback to any available eyelid image if the specific one isn't loaded
|
|
413
|
+
if (!rightEyeLidImage) {
|
|
414
|
+
rightEyeLidImage = loadedImages['eyes.rightEyeLidOpen'] ||
|
|
415
|
+
loadedImages['eyes.rightEyeLidHalfClosed'] ||
|
|
416
|
+
loadedImages['eyes.rightEyeLidClosed'];
|
|
417
|
+
}
|
|
418
|
+
if (visibility.rightEyeLid !== false && rightEyeLidImage && eyes) {
|
|
419
|
+
const headPivot = pivotPoints['torso_head'] || { x: 0, y: 0 };
|
|
420
|
+
const headRot = rotations["head"] || 0;
|
|
421
|
+
const headBaseTransform = this.applyTransform(rootTransform, {
|
|
422
|
+
x: headPivot.x || 0,
|
|
423
|
+
y: headPivot.y || 0,
|
|
424
|
+
rotation: headRot,
|
|
425
|
+
scaleX: flipHead ? -1 : 1,
|
|
426
|
+
scaleY: 1
|
|
427
|
+
});
|
|
428
|
+
const offsetX = Number.isFinite(eyes.rightEyeLidXCoor) ? eyes.rightEyeLidXCoor : 10;
|
|
429
|
+
const offsetY = Number.isFinite(eyes.rightEyeLidYCoor) ? eyes.rightEyeLidYCoor : -5;
|
|
430
|
+
const baseRightEyeLidWidth = eyes.rightEyeLidOpenWidth || 20;
|
|
431
|
+
const baseRightEyeLidHeight = eyes.rightEyeLidOpenHeight || 15;
|
|
432
|
+
const rightEyeLidWidth = baseRightEyeLidWidth * (eyes.rightEyeLidWidthRatio || 1.0);
|
|
433
|
+
const rightEyeLidHeight = baseRightEyeLidHeight * (eyes.rightEyeLidHeightRatio || 1.0);
|
|
434
|
+
const rightEyeLidTransform = this.applyTransform(headBaseTransform, {
|
|
435
|
+
x: offsetX,
|
|
436
|
+
y: offsetY,
|
|
437
|
+
rotation: 0,
|
|
438
|
+
scaleX: 1,
|
|
439
|
+
scaleY: 1
|
|
440
|
+
});
|
|
441
|
+
allObjects.push({
|
|
442
|
+
name: 'rightEyeLid',
|
|
443
|
+
type: 'eyelid',
|
|
444
|
+
zIndex: zIndexValues['rightEyeLid'] || 5,
|
|
445
|
+
width: rightEyeLidWidth,
|
|
446
|
+
height: rightEyeLidHeight,
|
|
447
|
+
x: rightEyeLidTransform.x,
|
|
448
|
+
y: rightEyeLidTransform.y,
|
|
449
|
+
rotation: rightEyeLidTransform.rotation,
|
|
450
|
+
scaleX: rightEyeLidTransform.scaleX,
|
|
451
|
+
scaleY: rightEyeLidTransform.scaleY,
|
|
452
|
+
anchorX: 0.5,
|
|
453
|
+
anchorY: 0.5,
|
|
454
|
+
imageKey: 'eyes.rightEyeLid',
|
|
455
|
+
imageData: rightEyeLidImage
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
// Left Arm chain (leftUpperArm, leftForearm, leftHand)
|
|
459
|
+
// Left Upper Arm
|
|
460
|
+
if (visibility.leftUpperArm !== false) {
|
|
461
|
+
const connectionKey = `torso_leftUpperArm`;
|
|
462
|
+
const pivotPoint = pivotPoints[connectionKey] || { x: 0, y: 0 };
|
|
463
|
+
const jointOff = jointOffset[connectionKey] || { x: 0, y: 0 };
|
|
464
|
+
const width = dimensions.leftUpperArm?.width || 30;
|
|
465
|
+
const height = dimensions.leftUpperArm?.height || 50;
|
|
466
|
+
const limbRot = rotations['leftUpperArm'] || 0;
|
|
467
|
+
const selfRot = selfRotations['leftUpperArm'] || 0;
|
|
468
|
+
const upperArmParentTransform = this.applyTransform(rootTransform, {
|
|
469
|
+
x: pivotPoint.x || 0,
|
|
470
|
+
y: pivotPoint.y || 0,
|
|
471
|
+
rotation: limbRot,
|
|
472
|
+
scaleX: 1,
|
|
473
|
+
scaleY: 1
|
|
474
|
+
});
|
|
475
|
+
const upperArmTransform = this.applyTransform(upperArmParentTransform, {
|
|
476
|
+
x: jointOff.x || 0,
|
|
477
|
+
y: (jointOff.y || 0) - height / 2,
|
|
478
|
+
rotation: selfRot,
|
|
479
|
+
scaleX: 1,
|
|
480
|
+
scaleY: 1
|
|
481
|
+
});
|
|
482
|
+
allObjects.push({
|
|
483
|
+
name: 'leftUpperArm',
|
|
484
|
+
type: 'limb',
|
|
485
|
+
zIndex: zIndexValues['leftUpperArm'] || 1,
|
|
486
|
+
width: width * imageScale,
|
|
487
|
+
height: height * imageScale,
|
|
488
|
+
x: upperArmTransform.x,
|
|
489
|
+
y: upperArmTransform.y,
|
|
490
|
+
rotation: upperArmTransform.rotation,
|
|
491
|
+
scaleX: upperArmTransform.scaleX,
|
|
492
|
+
scaleY: upperArmTransform.scaleY,
|
|
493
|
+
anchorX: 0.5,
|
|
494
|
+
anchorY: 0.5,
|
|
495
|
+
imageKey: 'imagePaths.leftUpperArm',
|
|
496
|
+
imageData: loadedImages['imagePaths.leftUpperArm'],
|
|
497
|
+
selfRotation: selfRot,
|
|
498
|
+
parentTransform: upperArmParentTransform
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
// Left Forearm
|
|
502
|
+
if (visibility.leftForearm !== false) {
|
|
503
|
+
const upperConnectionKey = `torso_leftUpperArm`;
|
|
504
|
+
const upperPivot = pivotPoints[upperConnectionKey] || { x: 0, y: 0 };
|
|
505
|
+
const upperHeight = dimensions.leftUpperArm?.height || 50;
|
|
506
|
+
const upperRot = rotations["leftUpperArm"] || 0;
|
|
507
|
+
const connectionKey = `leftUpperArm_leftForearm`;
|
|
508
|
+
const pivotPoint = pivotPoints[connectionKey] || { x: 0, y: 0 };
|
|
509
|
+
const jointOff = jointOffset[connectionKey] || { x: 0, y: 0 };
|
|
510
|
+
const width = dimensions.leftForearm?.width || 25;
|
|
511
|
+
const height = dimensions.leftForearm?.height || 45;
|
|
512
|
+
const limbRot = rotations["leftForearm"] || 0;
|
|
513
|
+
const selfRot = selfRotations["leftForearm"] || 0;
|
|
514
|
+
const upperArmBase = this.applyTransform(rootTransform, {
|
|
515
|
+
x: upperPivot.x || 0,
|
|
516
|
+
y: upperPivot.y || 0,
|
|
517
|
+
rotation: upperRot,
|
|
518
|
+
scaleX: 1,
|
|
519
|
+
scaleY: 1
|
|
520
|
+
});
|
|
521
|
+
const forearmParent = this.applyTransform(upperArmBase, {
|
|
522
|
+
x: pivotPoint.x || 0,
|
|
523
|
+
y: (pivotPoint.y || 0) - upperHeight,
|
|
524
|
+
rotation: limbRot,
|
|
525
|
+
scaleX: 1,
|
|
526
|
+
scaleY: 1
|
|
527
|
+
});
|
|
528
|
+
const forearmTransform = this.applyTransform(forearmParent, {
|
|
529
|
+
x: jointOff.x || 0,
|
|
530
|
+
y: (jointOff.y || 0) - height / 2,
|
|
531
|
+
rotation: selfRot,
|
|
532
|
+
scaleX: 1,
|
|
533
|
+
scaleY: 1
|
|
534
|
+
});
|
|
535
|
+
allObjects.push({
|
|
536
|
+
name: 'leftForearm',
|
|
537
|
+
type: 'limb',
|
|
538
|
+
zIndex: zIndexValues['leftForearm'] || 1,
|
|
539
|
+
width: width * imageScale,
|
|
540
|
+
height: height * imageScale,
|
|
541
|
+
x: forearmTransform.x,
|
|
542
|
+
y: forearmTransform.y,
|
|
543
|
+
rotation: forearmTransform.rotation,
|
|
544
|
+
scaleX: forearmTransform.scaleX,
|
|
545
|
+
scaleY: forearmTransform.scaleY,
|
|
546
|
+
anchorX: 0.5,
|
|
547
|
+
anchorY: 0.5,
|
|
548
|
+
imageKey: 'imagePaths.leftForearm',
|
|
549
|
+
imageData: loadedImages['imagePaths.leftForearm'],
|
|
550
|
+
selfRotation: selfRot,
|
|
551
|
+
parentTransform: forearmParent
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
// Left Hand
|
|
555
|
+
if (visibility.leftHand !== false) {
|
|
556
|
+
const upperConnectionKey = `torso_leftUpperArm`;
|
|
557
|
+
const upperPivot = pivotPoints[upperConnectionKey] || { x: 0, y: 0 };
|
|
558
|
+
const upperHeight = dimensions.leftUpperArm?.height || 50;
|
|
559
|
+
const upperRot = rotations["leftUpperArm"] || 0;
|
|
560
|
+
const foreConnectionKey = `leftUpperArm_leftForearm`;
|
|
561
|
+
const forePivot = pivotPoints[foreConnectionKey] || { x: 0, y: 0 };
|
|
562
|
+
const foreHeight = dimensions.leftForearm?.height || 45;
|
|
563
|
+
const foreRot = rotations["leftForearm"] || 0;
|
|
564
|
+
const handConnectionKey = `leftForearm_leftHand`;
|
|
565
|
+
const handPivot = pivotPoints[handConnectionKey] || { x: 0, y: 0 };
|
|
566
|
+
const handJointOff = jointOffset[handConnectionKey] || { x: 0, y: 0 };
|
|
567
|
+
const width = dimensions.leftHand?.width || 20;
|
|
568
|
+
const height = dimensions.leftHand?.height || 30;
|
|
569
|
+
const handRot = rotations["leftHand"] || 0;
|
|
570
|
+
const selfRot = selfRotations["leftHand"] || 0;
|
|
571
|
+
const upperArmBase = this.applyTransform(rootTransform, {
|
|
572
|
+
x: upperPivot.x || 0,
|
|
573
|
+
y: upperPivot.y || 0,
|
|
574
|
+
rotation: upperRot,
|
|
575
|
+
scaleX: 1,
|
|
576
|
+
scaleY: 1
|
|
577
|
+
});
|
|
578
|
+
const forearmBase = this.applyTransform(upperArmBase, {
|
|
579
|
+
x: forePivot.x || 0,
|
|
580
|
+
y: (forePivot.y || 0) - upperHeight,
|
|
581
|
+
rotation: foreRot,
|
|
582
|
+
scaleX: 1,
|
|
583
|
+
scaleY: 1
|
|
584
|
+
});
|
|
585
|
+
const handParent = this.applyTransform(forearmBase, {
|
|
586
|
+
x: handPivot.x || 0,
|
|
587
|
+
y: (handPivot.y || 0) - foreHeight,
|
|
588
|
+
rotation: handRot,
|
|
589
|
+
scaleX: 1,
|
|
590
|
+
scaleY: 1
|
|
591
|
+
});
|
|
592
|
+
const handTransform = this.applyTransform(handParent, {
|
|
593
|
+
x: handJointOff.x || 0,
|
|
594
|
+
y: (handJointOff.y || 0) - height / 2,
|
|
595
|
+
rotation: selfRot,
|
|
596
|
+
scaleX: 1,
|
|
597
|
+
scaleY: 1
|
|
598
|
+
});
|
|
599
|
+
allObjects.push({
|
|
600
|
+
name: 'leftHand',
|
|
601
|
+
type: 'limb',
|
|
602
|
+
zIndex: zIndexValues['leftHand'] || 1,
|
|
603
|
+
width: width * imageScale,
|
|
604
|
+
height: height * imageScale,
|
|
605
|
+
x: handTransform.x,
|
|
606
|
+
y: handTransform.y,
|
|
607
|
+
rotation: handTransform.rotation,
|
|
608
|
+
scaleX: handTransform.scaleX,
|
|
609
|
+
scaleY: handTransform.scaleY,
|
|
610
|
+
anchorX: 0.5,
|
|
611
|
+
anchorY: 0.5,
|
|
612
|
+
imageKey: 'imagePaths.leftHand',
|
|
613
|
+
imageData: loadedImages['imagePaths.leftHand'],
|
|
614
|
+
selfRotation: selfRot
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
// Right Arm chain (rightUpperArm, rightForearm, rightHand)
|
|
618
|
+
// Right Upper Arm
|
|
619
|
+
if (visibility.rightUpperArm !== false) {
|
|
620
|
+
const connectionKey = `torso_rightUpperArm`;
|
|
621
|
+
const pivotPoint = pivotPoints[connectionKey] || { x: 0, y: 0 };
|
|
622
|
+
const jointOff = jointOffset[connectionKey] || { x: 0, y: 0 };
|
|
623
|
+
const width = dimensions.rightUpperArm?.width || 30;
|
|
624
|
+
const height = dimensions.rightUpperArm?.height || 50;
|
|
625
|
+
const limbRot = rotations['rightUpperArm'] || 0;
|
|
626
|
+
const selfRot = selfRotations['rightUpperArm'] || 0;
|
|
627
|
+
const upperArmParentTransform = this.applyTransform(rootTransform, {
|
|
628
|
+
x: pivotPoint.x || 0,
|
|
629
|
+
y: pivotPoint.y || 0,
|
|
630
|
+
rotation: limbRot,
|
|
631
|
+
scaleX: 1,
|
|
632
|
+
scaleY: 1
|
|
633
|
+
});
|
|
634
|
+
const upperArmTransform = this.applyTransform(upperArmParentTransform, {
|
|
635
|
+
x: jointOff.x || 0,
|
|
636
|
+
y: (jointOff.y || 0) - height / 2,
|
|
637
|
+
rotation: selfRot,
|
|
638
|
+
scaleX: 1,
|
|
639
|
+
scaleY: 1
|
|
640
|
+
});
|
|
641
|
+
allObjects.push({
|
|
642
|
+
name: 'rightUpperArm',
|
|
643
|
+
type: 'limb',
|
|
644
|
+
zIndex: zIndexValues['rightUpperArm'] || 1,
|
|
645
|
+
width: width * imageScale,
|
|
646
|
+
height: height * imageScale,
|
|
647
|
+
x: upperArmTransform.x,
|
|
648
|
+
y: upperArmTransform.y,
|
|
649
|
+
rotation: upperArmTransform.rotation,
|
|
650
|
+
scaleX: upperArmTransform.scaleX,
|
|
651
|
+
scaleY: upperArmTransform.scaleY,
|
|
652
|
+
anchorX: 0.5,
|
|
653
|
+
anchorY: 0.5,
|
|
654
|
+
imageKey: 'imagePaths.rightUpperArm',
|
|
655
|
+
imageData: loadedImages['imagePaths.rightUpperArm'],
|
|
656
|
+
selfRotation: selfRot,
|
|
657
|
+
parentTransform: upperArmParentTransform
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
// Right Forearm
|
|
661
|
+
if (visibility.rightForearm !== false) {
|
|
662
|
+
const upperConnectionKey = `torso_rightUpperArm`;
|
|
663
|
+
const upperPivot = pivotPoints[upperConnectionKey] || { x: 0, y: 0 };
|
|
664
|
+
const upperHeight = dimensions.rightUpperArm?.height || 50;
|
|
665
|
+
const upperRot = rotations["rightUpperArm"] || 0;
|
|
666
|
+
const connectionKey = `rightUpperArm_rightForearm`;
|
|
667
|
+
const pivotPoint = pivotPoints[connectionKey] || { x: 0, y: 0 };
|
|
668
|
+
const jointOff = jointOffset[connectionKey] || { x: 0, y: 0 };
|
|
669
|
+
const width = dimensions.rightForearm?.width || 25;
|
|
670
|
+
const height = dimensions.rightForearm?.height || 45;
|
|
671
|
+
const limbRot = rotations["rightForearm"] || 0;
|
|
672
|
+
const selfRot = selfRotations["rightForearm"] || 0;
|
|
673
|
+
const upperArmBase = this.applyTransform(rootTransform, {
|
|
674
|
+
x: upperPivot.x || 0,
|
|
675
|
+
y: upperPivot.y || 0,
|
|
676
|
+
rotation: upperRot,
|
|
677
|
+
scaleX: 1,
|
|
678
|
+
scaleY: 1
|
|
679
|
+
});
|
|
680
|
+
const forearmParent = this.applyTransform(upperArmBase, {
|
|
681
|
+
x: pivotPoint.x || 0,
|
|
682
|
+
y: (pivotPoint.y || 0) - upperHeight,
|
|
683
|
+
rotation: limbRot,
|
|
684
|
+
scaleX: 1,
|
|
685
|
+
scaleY: 1
|
|
686
|
+
});
|
|
687
|
+
const forearmTransform = this.applyTransform(forearmParent, {
|
|
688
|
+
x: jointOff.x || 0,
|
|
689
|
+
y: (jointOff.y || 0) - height / 2,
|
|
690
|
+
rotation: selfRot,
|
|
691
|
+
scaleX: 1,
|
|
692
|
+
scaleY: 1
|
|
693
|
+
});
|
|
694
|
+
allObjects.push({
|
|
695
|
+
name: 'rightForearm',
|
|
696
|
+
type: 'limb',
|
|
697
|
+
zIndex: zIndexValues['rightForearm'] || 1,
|
|
698
|
+
width: width * imageScale,
|
|
699
|
+
height: height * imageScale,
|
|
700
|
+
x: forearmTransform.x,
|
|
701
|
+
y: forearmTransform.y,
|
|
702
|
+
rotation: forearmTransform.rotation,
|
|
703
|
+
scaleX: forearmTransform.scaleX,
|
|
704
|
+
scaleY: forearmTransform.scaleY,
|
|
705
|
+
anchorX: 0.5,
|
|
706
|
+
anchorY: 0.5,
|
|
707
|
+
imageKey: 'imagePaths.rightForearm',
|
|
708
|
+
imageData: loadedImages['imagePaths.rightForearm'],
|
|
709
|
+
selfRotation: selfRot,
|
|
710
|
+
parentTransform: forearmParent
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
// Right Hand
|
|
714
|
+
if (visibility.rightHand !== false) {
|
|
715
|
+
const upperConnectionKey = `torso_rightUpperArm`;
|
|
716
|
+
const upperPivot = pivotPoints[upperConnectionKey] || { x: 0, y: 0 };
|
|
717
|
+
const upperHeight = dimensions.rightUpperArm?.height || 50;
|
|
718
|
+
const upperRot = rotations["rightUpperArm"] || 0;
|
|
719
|
+
const foreConnectionKey = `rightUpperArm_rightForearm`;
|
|
720
|
+
const forePivot = pivotPoints[foreConnectionKey] || { x: 0, y: 0 };
|
|
721
|
+
const foreHeight = dimensions.rightForearm?.height || 45;
|
|
722
|
+
const foreRot = rotations["rightForearm"] || 0;
|
|
723
|
+
const handConnectionKey = `rightForearm_rightHand`;
|
|
724
|
+
const handPivot = pivotPoints[handConnectionKey] || { x: 0, y: 0 };
|
|
725
|
+
const handJointOff = jointOffset[handConnectionKey] || { x: 0, y: 0 };
|
|
726
|
+
const width = dimensions.rightHand?.width || 20;
|
|
727
|
+
const height = dimensions.rightHand?.height || 30;
|
|
728
|
+
const handRot = rotations["rightHand"] || 0;
|
|
729
|
+
const selfRot = selfRotations["rightHand"] || 0;
|
|
730
|
+
const upperArmBase = this.applyTransform(rootTransform, {
|
|
731
|
+
x: upperPivot.x || 0,
|
|
732
|
+
y: upperPivot.y || 0,
|
|
733
|
+
rotation: upperRot,
|
|
734
|
+
scaleX: 1,
|
|
735
|
+
scaleY: 1
|
|
736
|
+
});
|
|
737
|
+
const forearmBase = this.applyTransform(upperArmBase, {
|
|
738
|
+
x: forePivot.x || 0,
|
|
739
|
+
y: (forePivot.y || 0) - upperHeight,
|
|
740
|
+
rotation: foreRot,
|
|
741
|
+
scaleX: 1,
|
|
742
|
+
scaleY: 1
|
|
743
|
+
});
|
|
744
|
+
const handParent = this.applyTransform(forearmBase, {
|
|
745
|
+
x: handPivot.x || 0,
|
|
746
|
+
y: (handPivot.y || 0) - foreHeight,
|
|
747
|
+
rotation: handRot,
|
|
748
|
+
scaleX: 1,
|
|
749
|
+
scaleY: 1
|
|
750
|
+
});
|
|
751
|
+
const handTransform = this.applyTransform(handParent, {
|
|
752
|
+
x: handJointOff.x || 0,
|
|
753
|
+
y: (handJointOff.y || 0) - height / 2,
|
|
754
|
+
rotation: selfRot,
|
|
755
|
+
scaleX: 1,
|
|
756
|
+
scaleY: 1
|
|
757
|
+
});
|
|
758
|
+
allObjects.push({
|
|
759
|
+
name: 'rightHand',
|
|
760
|
+
type: 'limb',
|
|
761
|
+
zIndex: zIndexValues['rightHand'] || 1,
|
|
762
|
+
width: width * imageScale,
|
|
763
|
+
height: height * imageScale,
|
|
764
|
+
x: handTransform.x,
|
|
765
|
+
y: handTransform.y,
|
|
766
|
+
rotation: handTransform.rotation,
|
|
767
|
+
scaleX: handTransform.scaleX,
|
|
768
|
+
scaleY: handTransform.scaleY,
|
|
769
|
+
anchorX: 0.5,
|
|
770
|
+
anchorY: 0.5,
|
|
771
|
+
imageKey: 'imagePaths.rightHand',
|
|
772
|
+
imageData: loadedImages['imagePaths.rightHand'],
|
|
773
|
+
selfRotation: selfRot
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
// Left Leg chain (leftThigh, leftLeg)
|
|
777
|
+
// Left Thigh
|
|
778
|
+
if (visibility.leftThigh !== false) {
|
|
779
|
+
const connectionKey = `torso_leftThigh`;
|
|
780
|
+
const pivotPoint = pivotPoints[connectionKey] || { x: 0, y: 0 };
|
|
781
|
+
const jointOff = jointOffset[connectionKey] || { x: 0, y: 0 };
|
|
782
|
+
const width = dimensions.leftThigh?.width || 35;
|
|
783
|
+
const height = dimensions.leftThigh?.height || 60;
|
|
784
|
+
const limbRot = rotations['leftThigh'] || 0;
|
|
785
|
+
const selfRot = selfRotations['leftThigh'] || 0;
|
|
786
|
+
const thighParentTransform = this.applyTransform(rootTransform, {
|
|
787
|
+
x: pivotPoint.x || 0,
|
|
788
|
+
y: pivotPoint.y || 0,
|
|
789
|
+
rotation: limbRot,
|
|
790
|
+
scaleX: 1,
|
|
791
|
+
scaleY: 1
|
|
792
|
+
});
|
|
793
|
+
const thighTransform = this.applyTransform(thighParentTransform, {
|
|
794
|
+
x: jointOff.x || 0,
|
|
795
|
+
y: (jointOff.y || 0) - height / 2,
|
|
796
|
+
rotation: selfRot,
|
|
797
|
+
scaleX: 1,
|
|
798
|
+
scaleY: 1
|
|
799
|
+
});
|
|
800
|
+
allObjects.push({
|
|
801
|
+
name: 'leftThigh',
|
|
802
|
+
type: 'limb',
|
|
803
|
+
zIndex: zIndexValues['leftThigh'] || 1,
|
|
804
|
+
width: width * imageScale,
|
|
805
|
+
height: height * imageScale,
|
|
806
|
+
x: thighTransform.x,
|
|
807
|
+
y: thighTransform.y,
|
|
808
|
+
rotation: thighTransform.rotation,
|
|
809
|
+
scaleX: thighTransform.scaleX,
|
|
810
|
+
scaleY: thighTransform.scaleY,
|
|
811
|
+
anchorX: 0.5,
|
|
812
|
+
anchorY: 0.5,
|
|
813
|
+
imageKey: 'imagePaths.leftThigh',
|
|
814
|
+
imageData: loadedImages['imagePaths.leftThigh'],
|
|
815
|
+
selfRotation: selfRot,
|
|
816
|
+
parentTransform: thighParentTransform
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
// Left Leg
|
|
820
|
+
if (visibility.leftLeg !== false) {
|
|
821
|
+
const upperConnectionKey = `torso_leftThigh`;
|
|
822
|
+
const upperPivot = pivotPoints[upperConnectionKey] || { x: 0, y: 0 };
|
|
823
|
+
const upperHeight = dimensions.leftThigh?.height || 60;
|
|
824
|
+
const upperRot = rotations["leftThigh"] || 0;
|
|
825
|
+
const connectionKey = `leftThigh_leftLeg`;
|
|
826
|
+
const pivotPoint = pivotPoints[connectionKey] || { x: 0, y: 0 };
|
|
827
|
+
const jointOff = jointOffset[connectionKey] || { x: 0, y: 0 };
|
|
828
|
+
const width = dimensions.leftLeg?.width || 30;
|
|
829
|
+
const height = dimensions.leftLeg?.height || 55;
|
|
830
|
+
const limbRot = rotations["leftLeg"] || 0;
|
|
831
|
+
const selfRot = selfRotations["leftLeg"] || 0;
|
|
832
|
+
const thighBase = this.applyTransform(rootTransform, {
|
|
833
|
+
x: upperPivot.x || 0,
|
|
834
|
+
y: upperPivot.y || 0,
|
|
835
|
+
rotation: upperRot,
|
|
836
|
+
scaleX: 1,
|
|
837
|
+
scaleY: 1
|
|
838
|
+
});
|
|
839
|
+
const legParent = this.applyTransform(thighBase, {
|
|
840
|
+
x: pivotPoint.x || 0,
|
|
841
|
+
y: (pivotPoint.y || 0) - upperHeight,
|
|
842
|
+
rotation: limbRot,
|
|
843
|
+
scaleX: 1,
|
|
844
|
+
scaleY: 1
|
|
845
|
+
});
|
|
846
|
+
const legTransform = this.applyTransform(legParent, {
|
|
847
|
+
x: jointOff.x || 0,
|
|
848
|
+
y: (jointOff.y || 0) - height / 2,
|
|
849
|
+
rotation: selfRot,
|
|
850
|
+
scaleX: 1,
|
|
851
|
+
scaleY: 1
|
|
852
|
+
});
|
|
853
|
+
allObjects.push({
|
|
854
|
+
name: 'leftLeg',
|
|
855
|
+
type: 'limb',
|
|
856
|
+
zIndex: zIndexValues['leftLeg'] || 1,
|
|
857
|
+
width: width * imageScale,
|
|
858
|
+
height: height * imageScale,
|
|
859
|
+
x: legTransform.x,
|
|
860
|
+
y: legTransform.y,
|
|
861
|
+
rotation: legTransform.rotation,
|
|
862
|
+
scaleX: legTransform.scaleX,
|
|
863
|
+
scaleY: legTransform.scaleY,
|
|
864
|
+
anchorX: 0.5,
|
|
865
|
+
anchorY: 0.5,
|
|
866
|
+
imageKey: 'imagePaths.leftLeg',
|
|
867
|
+
imageData: loadedImages['imagePaths.leftLeg'],
|
|
868
|
+
selfRotation: selfRot
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
// Right Leg chain (rightThigh, rightLeg)
|
|
872
|
+
// Right Thigh
|
|
873
|
+
if (visibility.rightThigh !== false) {
|
|
874
|
+
const connectionKey = `torso_rightThigh`;
|
|
875
|
+
const pivotPoint = pivotPoints[connectionKey] || { x: 0, y: 0 };
|
|
876
|
+
const jointOff = jointOffset[connectionKey] || { x: 0, y: 0 };
|
|
877
|
+
const width = dimensions.rightThigh?.width || 35;
|
|
878
|
+
const height = dimensions.rightThigh?.height || 60;
|
|
879
|
+
const limbRot = rotations['rightThigh'] || 0;
|
|
880
|
+
const selfRot = selfRotations['rightThigh'] || 0;
|
|
881
|
+
const thighParentTransform = this.applyTransform(rootTransform, {
|
|
882
|
+
x: pivotPoint.x || 0,
|
|
883
|
+
y: pivotPoint.y || 0,
|
|
884
|
+
rotation: limbRot,
|
|
885
|
+
scaleX: 1,
|
|
886
|
+
scaleY: 1
|
|
887
|
+
});
|
|
888
|
+
const thighTransform = this.applyTransform(thighParentTransform, {
|
|
889
|
+
x: jointOff.x || 0,
|
|
890
|
+
y: (jointOff.y || 0) - height / 2,
|
|
891
|
+
rotation: selfRot,
|
|
892
|
+
scaleX: 1,
|
|
893
|
+
scaleY: 1
|
|
894
|
+
});
|
|
895
|
+
allObjects.push({
|
|
896
|
+
name: 'rightThigh',
|
|
897
|
+
type: 'limb',
|
|
898
|
+
zIndex: zIndexValues['rightThigh'] || 1,
|
|
899
|
+
width: width * imageScale,
|
|
900
|
+
height: height * imageScale,
|
|
901
|
+
x: thighTransform.x,
|
|
902
|
+
y: thighTransform.y,
|
|
903
|
+
rotation: thighTransform.rotation,
|
|
904
|
+
scaleX: thighTransform.scaleX,
|
|
905
|
+
scaleY: thighTransform.scaleY,
|
|
906
|
+
anchorX: 0.5,
|
|
907
|
+
anchorY: 0.5,
|
|
908
|
+
imageKey: 'imagePaths.rightThigh',
|
|
909
|
+
imageData: loadedImages['imagePaths.rightThigh'],
|
|
910
|
+
selfRotation: selfRot,
|
|
911
|
+
parentTransform: thighParentTransform
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
// Right Leg
|
|
915
|
+
if (visibility.rightLeg !== false) {
|
|
916
|
+
const upperConnectionKey = `torso_rightThigh`;
|
|
917
|
+
const upperPivot = pivotPoints[upperConnectionKey] || { x: 0, y: 0 };
|
|
918
|
+
const upperHeight = dimensions.rightThigh?.height || 60;
|
|
919
|
+
const upperRot = rotations["rightThigh"] || 0;
|
|
920
|
+
const connectionKey = `rightThigh_rightLeg`;
|
|
921
|
+
const pivotPoint = pivotPoints[connectionKey] || { x: 0, y: 0 };
|
|
922
|
+
const jointOff = jointOffset[connectionKey] || { x: 0, y: 0 };
|
|
923
|
+
const width = dimensions.rightLeg?.width || 30;
|
|
924
|
+
const height = dimensions.rightLeg?.height || 55;
|
|
925
|
+
const limbRot = rotations["rightLeg"] || 0;
|
|
926
|
+
const selfRot = selfRotations["rightLeg"] || 0;
|
|
927
|
+
const thighBase = this.applyTransform(rootTransform, {
|
|
928
|
+
x: upperPivot.x || 0,
|
|
929
|
+
y: upperPivot.y || 0,
|
|
930
|
+
rotation: upperRot,
|
|
931
|
+
scaleX: 1,
|
|
932
|
+
scaleY: 1
|
|
933
|
+
});
|
|
934
|
+
const legParent = this.applyTransform(thighBase, {
|
|
935
|
+
x: pivotPoint.x || 0,
|
|
936
|
+
y: (pivotPoint.y || 0) - upperHeight,
|
|
937
|
+
rotation: limbRot,
|
|
938
|
+
scaleX: 1,
|
|
939
|
+
scaleY: 1
|
|
940
|
+
});
|
|
941
|
+
const legTransform = this.applyTransform(legParent, {
|
|
942
|
+
x: jointOff.x || 0,
|
|
943
|
+
y: (jointOff.y || 0) - height / 2,
|
|
944
|
+
rotation: selfRot,
|
|
945
|
+
scaleX: 1,
|
|
946
|
+
scaleY: 1
|
|
947
|
+
});
|
|
948
|
+
allObjects.push({
|
|
949
|
+
name: 'rightLeg',
|
|
950
|
+
type: 'limb',
|
|
951
|
+
zIndex: zIndexValues['rightLeg'] || 1,
|
|
952
|
+
width: width * imageScale,
|
|
953
|
+
height: height * imageScale,
|
|
954
|
+
x: legTransform.x,
|
|
955
|
+
y: legTransform.y,
|
|
956
|
+
rotation: legTransform.rotation,
|
|
957
|
+
scaleX: legTransform.scaleX,
|
|
958
|
+
scaleY: legTransform.scaleY,
|
|
959
|
+
anchorX: 0.5,
|
|
960
|
+
anchorY: 0.5,
|
|
961
|
+
imageKey: 'imagePaths.rightLeg',
|
|
962
|
+
imageData: loadedImages['imagePaths.rightLeg'],
|
|
963
|
+
selfRotation: selfRot
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
// Sort all objects by zIndex
|
|
967
|
+
allObjects.sort((a, b) => a.zIndex - b.zIndex);
|
|
968
|
+
// Compute pivot points in world space
|
|
969
|
+
const computedPivotPoints = [];
|
|
970
|
+
// Head/torso pivot
|
|
971
|
+
const torsoHead = pivotPoints['torso_head'] || { x: 0, y: 0 };
|
|
972
|
+
const headPivotWorld = this.applyTransform(rootTransform, {
|
|
973
|
+
x: torsoHead.x || 0,
|
|
974
|
+
y: torsoHead.y || 0,
|
|
975
|
+
rotation: 0,
|
|
976
|
+
scaleX: 1,
|
|
977
|
+
scaleY: 1
|
|
978
|
+
});
|
|
979
|
+
computedPivotPoints.push({
|
|
980
|
+
name: 'torso_head',
|
|
981
|
+
x: headPivotWorld.x,
|
|
982
|
+
y: headPivotWorld.y
|
|
983
|
+
});
|
|
984
|
+
// Mouth pivot
|
|
985
|
+
const headAngle = rotations['head'] || 0;
|
|
986
|
+
const headAngleEff = flipHead ? -headAngle : headAngle;
|
|
987
|
+
const headOffX = 0; // head offset defaults to 0
|
|
988
|
+
const headOffY = 0; // head offset defaults to 0
|
|
989
|
+
const headOffEffX = flipHead ? -headOffX : headOffX;
|
|
990
|
+
const cosH = Math.cos(headAngleEff);
|
|
991
|
+
const sinH = Math.sin(headAngleEff);
|
|
992
|
+
const mouthOffset = pivotPoints['head_mouth'] || { x: 0, y: 0 };
|
|
993
|
+
const mouthOffX = flipHead ? -(mouthOffset.x || 0) : (mouthOffset.x || 0);
|
|
994
|
+
const mouthOffY = mouthOffset.y || 0;
|
|
995
|
+
computedPivotPoints.push({
|
|
996
|
+
name: 'head_mouth',
|
|
997
|
+
x: centerX + (torsoHead.x || 0) + headOffEffX + (mouthOffX * cosH - mouthOffY * sinH),
|
|
998
|
+
y: centerY + (torsoHead.y || 0) + headOffY + (mouthOffX * sinH + mouthOffY * cosH)
|
|
999
|
+
});
|
|
1000
|
+
// Left arm chain pivots
|
|
1001
|
+
const lShoulder = pivotPoints['torso_leftUpperArm'] || { x: 0, y: 0 };
|
|
1002
|
+
const lShoulderWorld = this.applyTransform(rootTransform, {
|
|
1003
|
+
x: lShoulder.x || 0,
|
|
1004
|
+
y: lShoulder.y || 0,
|
|
1005
|
+
rotation: 0,
|
|
1006
|
+
scaleX: 1,
|
|
1007
|
+
scaleY: 1
|
|
1008
|
+
});
|
|
1009
|
+
computedPivotPoints.push({ name: 'torso_leftUpperArm', x: lShoulderWorld.x, y: lShoulderWorld.y });
|
|
1010
|
+
const rL1 = rotations['leftUpperArm'] || 0;
|
|
1011
|
+
const leftUpperArmHeight = dimensions.leftUpperArm?.height || 50;
|
|
1012
|
+
const lElbowOff = pivotPoints['leftUpperArm_leftForearm'] || { x: 0, y: 0 };
|
|
1013
|
+
const lElbowBase = this.applyTransform(rootTransform, {
|
|
1014
|
+
x: lShoulder.x || 0,
|
|
1015
|
+
y: lShoulder.y || 0,
|
|
1016
|
+
rotation: rL1,
|
|
1017
|
+
scaleX: 1,
|
|
1018
|
+
scaleY: 1
|
|
1019
|
+
});
|
|
1020
|
+
const lElbowWorld = this.applyTransform(lElbowBase, {
|
|
1021
|
+
x: lElbowOff.x || 0,
|
|
1022
|
+
y: (lElbowOff.y || 0) - leftUpperArmHeight,
|
|
1023
|
+
rotation: 0,
|
|
1024
|
+
scaleX: 1,
|
|
1025
|
+
scaleY: 1
|
|
1026
|
+
});
|
|
1027
|
+
computedPivotPoints.push({ name: 'leftUpperArm_leftForearm', x: lElbowWorld.x, y: lElbowWorld.y });
|
|
1028
|
+
const leftForearmHeight = dimensions.leftForearm?.height || 45;
|
|
1029
|
+
const lWristOff = pivotPoints['leftForearm_leftHand'] || { x: 0, y: 0 };
|
|
1030
|
+
const lForearmBase = this.applyTransform(lElbowBase, {
|
|
1031
|
+
x: lElbowOff.x || 0,
|
|
1032
|
+
y: (lElbowOff.y || 0) - leftUpperArmHeight,
|
|
1033
|
+
rotation: rotations['leftForearm'] || 0,
|
|
1034
|
+
scaleX: 1,
|
|
1035
|
+
scaleY: 1
|
|
1036
|
+
});
|
|
1037
|
+
const lWristWorld = this.applyTransform(lForearmBase, {
|
|
1038
|
+
x: lWristOff.x || 0,
|
|
1039
|
+
y: (lWristOff.y || 0) - leftForearmHeight,
|
|
1040
|
+
rotation: 0,
|
|
1041
|
+
scaleX: 1,
|
|
1042
|
+
scaleY: 1
|
|
1043
|
+
});
|
|
1044
|
+
computedPivotPoints.push({ name: 'leftForearm_leftHand', x: lWristWorld.x, y: lWristWorld.y });
|
|
1045
|
+
// Right arm chain pivots
|
|
1046
|
+
const rShoulder = pivotPoints['torso_rightUpperArm'] || { x: 0, y: 0 };
|
|
1047
|
+
const rShoulderWorld = this.applyTransform(rootTransform, {
|
|
1048
|
+
x: rShoulder.x || 0,
|
|
1049
|
+
y: rShoulder.y || 0,
|
|
1050
|
+
rotation: 0,
|
|
1051
|
+
scaleX: 1,
|
|
1052
|
+
scaleY: 1
|
|
1053
|
+
});
|
|
1054
|
+
computedPivotPoints.push({ name: 'torso_rightUpperArm', x: rShoulderWorld.x, y: rShoulderWorld.y });
|
|
1055
|
+
const rR1 = rotations['rightUpperArm'] || 0;
|
|
1056
|
+
const rightUpperArmHeight = dimensions.rightUpperArm?.height || 50;
|
|
1057
|
+
const rElbowOff = pivotPoints['rightUpperArm_rightForearm'] || { x: 0, y: 0 };
|
|
1058
|
+
const rElbowBase = this.applyTransform(rootTransform, {
|
|
1059
|
+
x: rShoulder.x || 0,
|
|
1060
|
+
y: rShoulder.y || 0,
|
|
1061
|
+
rotation: rR1,
|
|
1062
|
+
scaleX: 1,
|
|
1063
|
+
scaleY: 1
|
|
1064
|
+
});
|
|
1065
|
+
const rElbowWorld = this.applyTransform(rElbowBase, {
|
|
1066
|
+
x: rElbowOff.x || 0,
|
|
1067
|
+
y: (rElbowOff.y || 0) - rightUpperArmHeight,
|
|
1068
|
+
rotation: 0,
|
|
1069
|
+
scaleX: 1,
|
|
1070
|
+
scaleY: 1
|
|
1071
|
+
});
|
|
1072
|
+
computedPivotPoints.push({ name: 'rightUpperArm_rightForearm', x: rElbowWorld.x, y: rElbowWorld.y });
|
|
1073
|
+
const rightForearmHeight = dimensions.rightForearm?.height || 45;
|
|
1074
|
+
const rWristOff = pivotPoints['rightForearm_rightHand'] || { x: 0, y: 0 };
|
|
1075
|
+
const rForearmBase = this.applyTransform(rElbowBase, {
|
|
1076
|
+
x: rElbowOff.x || 0,
|
|
1077
|
+
y: (rElbowOff.y || 0) - rightUpperArmHeight,
|
|
1078
|
+
rotation: rotations['rightForearm'] || 0,
|
|
1079
|
+
scaleX: 1,
|
|
1080
|
+
scaleY: 1
|
|
1081
|
+
});
|
|
1082
|
+
const rWristWorld = this.applyTransform(rForearmBase, {
|
|
1083
|
+
x: rWristOff.x || 0,
|
|
1084
|
+
y: (rWristOff.y || 0) - rightForearmHeight,
|
|
1085
|
+
rotation: 0,
|
|
1086
|
+
scaleX: 1,
|
|
1087
|
+
scaleY: 1
|
|
1088
|
+
});
|
|
1089
|
+
computedPivotPoints.push({ name: 'rightForearm_rightHand', x: rWristWorld.x, y: rWristWorld.y });
|
|
1090
|
+
// Left leg chain pivots
|
|
1091
|
+
const lHip = pivotPoints['torso_leftThigh'] || { x: 0, y: 0 };
|
|
1092
|
+
const lHipWorld = this.applyTransform(rootTransform, {
|
|
1093
|
+
x: lHip.x || 0,
|
|
1094
|
+
y: lHip.y || 0,
|
|
1095
|
+
rotation: 0,
|
|
1096
|
+
scaleX: 1,
|
|
1097
|
+
scaleY: 1
|
|
1098
|
+
});
|
|
1099
|
+
computedPivotPoints.push({ name: 'torso_leftThigh', x: lHipWorld.x, y: lHipWorld.y });
|
|
1100
|
+
const rLT1 = rotations['leftThigh'] || 0;
|
|
1101
|
+
const leftThighHeight = dimensions.leftThigh?.height || 60;
|
|
1102
|
+
const lKneeOff = pivotPoints['leftThigh_leftLeg'] || { x: 0, y: 0 };
|
|
1103
|
+
const lThighBase = this.applyTransform(rootTransform, {
|
|
1104
|
+
x: lHip.x || 0,
|
|
1105
|
+
y: lHip.y || 0,
|
|
1106
|
+
rotation: rLT1,
|
|
1107
|
+
scaleX: 1,
|
|
1108
|
+
scaleY: 1
|
|
1109
|
+
});
|
|
1110
|
+
const lKneeWorld = this.applyTransform(lThighBase, {
|
|
1111
|
+
x: lKneeOff.x || 0,
|
|
1112
|
+
y: (lKneeOff.y || 0) - leftThighHeight,
|
|
1113
|
+
rotation: 0,
|
|
1114
|
+
scaleX: 1,
|
|
1115
|
+
scaleY: 1
|
|
1116
|
+
});
|
|
1117
|
+
computedPivotPoints.push({ name: 'leftThigh_leftLeg', x: lKneeWorld.x, y: lKneeWorld.y });
|
|
1118
|
+
// Right leg chain pivots
|
|
1119
|
+
const rHip = pivotPoints['torso_rightThigh'] || { x: 0, y: 0 };
|
|
1120
|
+
const rHipWorld = this.applyTransform(rootTransform, {
|
|
1121
|
+
x: rHip.x || 0,
|
|
1122
|
+
y: rHip.y || 0,
|
|
1123
|
+
rotation: 0,
|
|
1124
|
+
scaleX: 1,
|
|
1125
|
+
scaleY: 1
|
|
1126
|
+
});
|
|
1127
|
+
computedPivotPoints.push({ name: 'torso_rightThigh', x: rHipWorld.x, y: rHipWorld.y });
|
|
1128
|
+
const rRT1 = rotations['rightThigh'] || 0;
|
|
1129
|
+
const rightThighHeight = dimensions.rightThigh?.height || 60;
|
|
1130
|
+
const rKneeOff = pivotPoints['rightThigh_rightLeg'] || { x: 0, y: 0 };
|
|
1131
|
+
const rThighBase = this.applyTransform(rootTransform, {
|
|
1132
|
+
x: rHip.x || 0,
|
|
1133
|
+
y: rHip.y || 0,
|
|
1134
|
+
rotation: rRT1,
|
|
1135
|
+
scaleX: 1,
|
|
1136
|
+
scaleY: 1
|
|
1137
|
+
});
|
|
1138
|
+
const rKneeWorld = this.applyTransform(rThighBase, {
|
|
1139
|
+
x: rKneeOff.x || 0,
|
|
1140
|
+
y: (rKneeOff.y || 0) - rightThighHeight,
|
|
1141
|
+
rotation: 0,
|
|
1142
|
+
scaleX: 1,
|
|
1143
|
+
scaleY: 1
|
|
1144
|
+
});
|
|
1145
|
+
computedPivotPoints.push({ name: 'rightThigh_rightLeg', x: rKneeWorld.x, y: rKneeWorld.y });
|
|
1146
|
+
// Return the complete data structure
|
|
1147
|
+
return {
|
|
1148
|
+
objects: allObjects,
|
|
1149
|
+
pivotPoints: computedPivotPoints
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Universal Canvas renderer - handles both sync (with cache) and async (loads images)
|
|
1154
|
+
*
|
|
1155
|
+
* @param canvas - Canvas element to render to
|
|
1156
|
+
* @param rigData - Character rig data
|
|
1157
|
+
* @param loadedImages - Optional pre-loaded images. If not provided, will check cache or load from network
|
|
1158
|
+
* @param cameraOffset - Camera offset for positioning
|
|
1159
|
+
* @param showPivotPoints - Whether to show pivot points for debugging
|
|
1160
|
+
* @returns Promise that resolves when rendering is complete
|
|
1161
|
+
*
|
|
1162
|
+
* @example
|
|
1163
|
+
* // With pre-loaded images (fast, synchronous path)
|
|
1164
|
+
* await renderer.render(canvas, rigData, myImages);
|
|
1165
|
+
*
|
|
1166
|
+
* @example
|
|
1167
|
+
* // Without images - auto-loads if needed (async path)
|
|
1168
|
+
* await renderer.render(canvas, rigData);
|
|
1169
|
+
*/
|
|
1170
|
+
async render(canvas, rigData, loadedImages, cameraOffset = { x: 0, y: 0 }, showPivotPoints = true) {
|
|
1171
|
+
// If no images provided, check cache or load
|
|
1172
|
+
if (!loadedImages) {
|
|
1173
|
+
const cache = this.imageLoader.getImageCache();
|
|
1174
|
+
// If cache is empty, load images from network
|
|
1175
|
+
if (cache.size === 0) {
|
|
1176
|
+
console.log('📦 Cache empty - loading images from network...');
|
|
1177
|
+
loadedImages = await this.imageLoader.loadAllRigImages(rigData, true);
|
|
1178
|
+
}
|
|
1179
|
+
else {
|
|
1180
|
+
// Use existing cache
|
|
1181
|
+
loadedImages = {};
|
|
1182
|
+
cache.forEach((img, key) => {
|
|
1183
|
+
loadedImages[key] = img;
|
|
1184
|
+
});
|
|
1185
|
+
console.log('📦 Using cached images for rendering');
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const ctx = canvas.getContext('2d');
|
|
1189
|
+
if (!ctx) {
|
|
1190
|
+
throw new Error('Failed to get 2D context from canvas');
|
|
1191
|
+
}
|
|
1192
|
+
// Clear canvas
|
|
1193
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1194
|
+
ctx.fillStyle = '#f0f0f0';
|
|
1195
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
1196
|
+
// Compute the rig data
|
|
1197
|
+
const rigRenderData = this.computeCharacterRigData(rigData, {
|
|
1198
|
+
canvasWidth: canvas.width,
|
|
1199
|
+
canvasHeight: canvas.height,
|
|
1200
|
+
cameraOffset,
|
|
1201
|
+
loadedImages
|
|
1202
|
+
});
|
|
1203
|
+
// Render all objects
|
|
1204
|
+
rigRenderData.objects.forEach(obj => {
|
|
1205
|
+
// Skip rendering if no image data
|
|
1206
|
+
if (!obj.imageData) {
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
ctx.save();
|
|
1210
|
+
// Move to object position
|
|
1211
|
+
ctx.translate(obj.x, obj.y);
|
|
1212
|
+
// Apply rotation
|
|
1213
|
+
ctx.rotate(obj.rotation);
|
|
1214
|
+
// Apply scale
|
|
1215
|
+
ctx.scale(obj.scaleX, obj.scaleY);
|
|
1216
|
+
// Draw image from anchor point (center by default)
|
|
1217
|
+
const drawX = -obj.width * obj.anchorX;
|
|
1218
|
+
const drawY = -obj.height * obj.anchorY;
|
|
1219
|
+
ctx.drawImage(obj.imageData, drawX, drawY, obj.width, obj.height);
|
|
1220
|
+
ctx.restore();
|
|
1221
|
+
});
|
|
1222
|
+
// Draw pivot points if enabled
|
|
1223
|
+
if (showPivotPoints) {
|
|
1224
|
+
rigRenderData.pivotPoints.forEach(pivot => {
|
|
1225
|
+
ctx.save();
|
|
1226
|
+
ctx.fillStyle = 'rgba(0, 150, 255, 0.9)';
|
|
1227
|
+
ctx.strokeStyle = '#000';
|
|
1228
|
+
ctx.lineWidth = 1;
|
|
1229
|
+
ctx.beginPath();
|
|
1230
|
+
ctx.arc(pivot.x, pivot.y, 4, 0, Math.PI * 2);
|
|
1231
|
+
ctx.fill();
|
|
1232
|
+
ctx.stroke();
|
|
1233
|
+
ctx.restore();
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
// Export a default renderer instance for convenience
|
|
1239
|
+
export const defaultRenderer = new CharacterRigRenderer();
|
|
1240
|
+
// For backwards compatibility - export standalone function
|
|
1241
|
+
export async function renderCharacterRig(canvas, rigData, loadedImages, cameraOffset = { x: 0, y: 0 }, showPivotPoints = true) {
|
|
1242
|
+
return defaultRenderer.render(canvas, rigData, loadedImages, cameraOffset, showPivotPoints);
|
|
1243
|
+
}
|
|
1244
|
+
// Export standalone function for computing rig data
|
|
1245
|
+
export function computeCharacterRigData(rigData, options = {}) {
|
|
1246
|
+
return defaultRenderer.computeCharacterRigData(rigData, options);
|
|
1247
|
+
}
|
|
1248
|
+
//# sourceMappingURL=renderRig.js.map
|