bucciafico-lib 1.0.6 → 1.0.8
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/package.json +1 -1
- package/src/core/SkinViewer.js +154 -9
- package/src/managers/EventManager.js +74 -0
- package/src/managers/PostProcessingManager.js +40 -2
- package/src/objects/SceneSetup.js +49 -8
- package/src/objects/SkinModel.js +205 -12
- package/src/plugins/EditorPlugin.js +45 -14
- package/src/plugins/EffectsPlugin.js +45 -20
- package/src/plugins/IOPlugin.js +46 -17
- package/src/plugins/ItemsPlugin.js +121 -2
- package/src/utils/SkinUtils.js +17 -17
- package/src/utils/ThreeUtils.js +27 -0
- package/src/utils/Voxelizer.js +139 -33
package/src/objects/SkinModel.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUti
|
|
|
3
3
|
import { applySkinUVs } from '../utils/SkinUtils.js';
|
|
4
4
|
import { createVoxelLayer } from '../utils/Voxelizer.js';
|
|
5
5
|
import { createGlowMaterial } from '../materials/GlowMaterial.js';
|
|
6
|
+
import {disposeObjectTree} from "../utils/ThreeUtils.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Represents the Minecraft Character Model (Steve/Alex).
|
|
@@ -16,6 +17,8 @@ export class SkinModel {
|
|
|
16
17
|
this.bodyMeshes = [];
|
|
17
18
|
this.defaultPositions = {};
|
|
18
19
|
this.blackMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
|
|
20
|
+
|
|
21
|
+
this.LAYERS_COUNT = 20;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/**
|
|
@@ -59,15 +62,30 @@ export class SkinModel {
|
|
|
59
62
|
this.bodyMeshes.push(voxelMesh);
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
// 3. Glow
|
|
65
|
+
// 3. Glow Meshes (Multi-Layer Shells)
|
|
63
66
|
const glowParts = [innerGeo.clone()];
|
|
64
67
|
if (voxelGeo) glowParts.push(voxelGeo.clone());
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
this.
|
|
70
|
-
|
|
68
|
+
const baseGlowGeo = BufferGeometryUtils.mergeGeometries(glowParts, false);
|
|
69
|
+
|
|
70
|
+
const partLayers = [];
|
|
71
|
+
|
|
72
|
+
for (let i = 0; i < this.LAYERS_COUNT; i++) {
|
|
73
|
+
const glowMat = createGlowMaterial(size.h);
|
|
74
|
+
|
|
75
|
+
glowMat.uniforms.thickness.value = 0;
|
|
76
|
+
glowMat.uniforms.opacity.value = 0;
|
|
77
|
+
|
|
78
|
+
const layerMesh = new THREE.Mesh(baseGlowGeo, glowMat);
|
|
79
|
+
|
|
80
|
+
layerMesh.userData.layerIndex = i;
|
|
81
|
+
layerMesh.userData.isGlow = true;
|
|
82
|
+
layerMesh.userData.glowMat = glowMat;
|
|
83
|
+
|
|
84
|
+
meshGroup.add(layerMesh);
|
|
85
|
+
partLayers.push(layerMesh);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.glowMeshes.push(partLayers);
|
|
71
89
|
|
|
72
90
|
pivotGroup.add(meshGroup);
|
|
73
91
|
return pivotGroup;
|
|
@@ -79,7 +97,24 @@ export class SkinModel {
|
|
|
79
97
|
* @param {boolean} isSlim - True for Alex model (3px arms), False for Steve (4px arms).
|
|
80
98
|
*/
|
|
81
99
|
build(texture, isSlim = false) {
|
|
82
|
-
|
|
100
|
+
let capeBackup = null;
|
|
101
|
+
if (this.parts.cape) {
|
|
102
|
+
const mesh = this.parts.cape.children.find(c => c.isMesh);
|
|
103
|
+
if (mesh && mesh.material.map) {
|
|
104
|
+
capeBackup = {
|
|
105
|
+
texture: mesh.material.map,
|
|
106
|
+
position: this.parts.cape.position.clone(),
|
|
107
|
+
rotation: this.parts.cape.rotation.clone(),
|
|
108
|
+
scale: this.parts.cape.scale.clone()
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (this.playerGroup.children.length > 0) {
|
|
114
|
+
disposeObjectTree(this.playerGroup);
|
|
115
|
+
this.playerGroup.clear();
|
|
116
|
+
}
|
|
117
|
+
|
|
83
118
|
this.parts = {};
|
|
84
119
|
this.glowMeshes = [];
|
|
85
120
|
this.bodyMeshes = [];
|
|
@@ -102,13 +137,132 @@ export class SkinModel {
|
|
|
102
137
|
this.parts[name] = part;
|
|
103
138
|
this.playerGroup.add(part);
|
|
104
139
|
}
|
|
140
|
+
|
|
141
|
+
if (capeBackup) {
|
|
142
|
+
this.setCape(capeBackup.texture);
|
|
143
|
+
|
|
144
|
+
if (this.parts.cape) {
|
|
145
|
+
this.parts.cape.position.copy(capeBackup.position);
|
|
146
|
+
this.parts.cape.rotation.copy(capeBackup.rotation);
|
|
147
|
+
this.parts.cape.scale.copy(capeBackup.scale);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Adds or updates the Cape mesh.
|
|
154
|
+
* @param {THREE.Texture} texture
|
|
155
|
+
*/
|
|
156
|
+
setCape(texture) {
|
|
157
|
+
if (this.parts.cape) {
|
|
158
|
+
if (this.parts.cape.userData.glowLayers) {
|
|
159
|
+
const layersToRemove = this.parts.cape.userData.glowLayers;
|
|
160
|
+
this.glowMeshes = this.glowMeshes.filter(layers => layers !== layersToRemove);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.playerGroup.remove(this.parts.cape);
|
|
164
|
+
disposeObjectTree(this.parts.cape);
|
|
165
|
+
delete this.parts.cape;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!texture) return;
|
|
169
|
+
|
|
170
|
+
const size = { w: 10, h: 16, d: 1 };
|
|
171
|
+
const pivotPos = new THREE.Vector3(0, 0, -3);
|
|
172
|
+
const meshOffset = new THREE.Vector3(0, -8, 0.6);
|
|
173
|
+
|
|
174
|
+
const pivotGroup = new THREE.Group();
|
|
175
|
+
pivotGroup.position.copy(pivotPos);
|
|
176
|
+
pivotGroup.name = 'cape';
|
|
177
|
+
pivotGroup.rotation.x = 0.2;
|
|
178
|
+
|
|
179
|
+
this.defaultPositions['cape'] = pivotPos.clone();
|
|
180
|
+
|
|
181
|
+
const geo = new THREE.BoxGeometry(size.w, size.h, size.d);
|
|
182
|
+
applySkinUVs(geo, 0, 0, 10, 16, 1, 64, 32);
|
|
183
|
+
|
|
184
|
+
const mat = new THREE.MeshStandardMaterial({
|
|
185
|
+
map: texture,
|
|
186
|
+
side: THREE.DoubleSide,
|
|
187
|
+
transparent: true,
|
|
188
|
+
alphaTest: 0.5
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const mainMesh = new THREE.Mesh(geo, mat);
|
|
192
|
+
mainMesh.position.copy(meshOffset);
|
|
193
|
+
mainMesh.rotation.y = Math.PI;
|
|
194
|
+
mainMesh.userData.originalMat = mat;
|
|
195
|
+
|
|
196
|
+
pivotGroup.add(mainMesh);
|
|
197
|
+
this.bodyMeshes.push(mainMesh);
|
|
198
|
+
|
|
199
|
+
const capeLayers = [];
|
|
200
|
+
const shellGeo = geo.clone();
|
|
201
|
+
|
|
202
|
+
for (let i = 0; i < this.LAYERS_COUNT; i++) {
|
|
203
|
+
const glowMat = createGlowMaterial(size.h);
|
|
204
|
+
|
|
205
|
+
glowMat.uniforms.thickness.value = 0;
|
|
206
|
+
glowMat.uniforms.opacity.value = 0;
|
|
207
|
+
glowMat.polygonOffset = true;
|
|
208
|
+
glowMat.polygonOffsetFactor = i * 0.1;
|
|
209
|
+
|
|
210
|
+
const layerMesh = new THREE.Mesh(shellGeo, glowMat);
|
|
211
|
+
|
|
212
|
+
layerMesh.position.copy(meshOffset);
|
|
213
|
+
layerMesh.rotation.y = Math.PI;
|
|
214
|
+
|
|
215
|
+
layerMesh.userData.layerIndex = i;
|
|
216
|
+
layerMesh.userData.isGlow = true;
|
|
217
|
+
layerMesh.userData.glowMat = glowMat;
|
|
218
|
+
|
|
219
|
+
pivotGroup.add(layerMesh);
|
|
220
|
+
capeLayers.push(layerMesh);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.glowMeshes.push(capeLayers);
|
|
224
|
+
pivotGroup.userData.glowLayers = capeLayers;
|
|
225
|
+
|
|
226
|
+
this.playerGroup.add(pivotGroup);
|
|
227
|
+
this.parts['cape'] = pivotGroup;
|
|
105
228
|
}
|
|
106
229
|
|
|
107
230
|
getGroup() { return this.playerGroup; }
|
|
108
231
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
232
|
+
/**
|
|
233
|
+
* Updates thickness creating a solid volume effect.
|
|
234
|
+
* @param {number} v - Base thickness value.
|
|
235
|
+
*/
|
|
236
|
+
updateBorderThickness(v) {
|
|
237
|
+
const maxThickness = v * 0.05;
|
|
238
|
+
|
|
239
|
+
this.glowMeshes.forEach(layers => {
|
|
240
|
+
layers.forEach((mesh, i) => {
|
|
241
|
+
const progress = (i + 1) / this.LAYERS_COUNT;
|
|
242
|
+
|
|
243
|
+
mesh.userData.glowMat.uniforms.thickness.value = maxThickness * progress;
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
updateGlowHeight(p) {
|
|
249
|
+
this.glowMeshes.forEach(layers => {
|
|
250
|
+
layers.forEach(m => m.userData.glowMat.uniforms.gradientLimit.value = p);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
setGlowEffect(en) {
|
|
255
|
+
this.glowMeshes.forEach(layers => {
|
|
256
|
+
layers.forEach((mesh, i) => {
|
|
257
|
+
if (!en) {
|
|
258
|
+
mesh.userData.glowMat.uniforms.opacity.value = 0.0;
|
|
259
|
+
} else {
|
|
260
|
+
mesh.userData.glowMat.uniforms.opacity.value = 1.0 / (this.LAYERS_COUNT * 0.6);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
112
266
|
darkenBody() { this.bodyMeshes.forEach(m => m.material = this.blackMaterial); }
|
|
113
267
|
restoreBody() { this.bodyMeshes.forEach(m => m.material = m.userData.originalMat); }
|
|
114
268
|
|
|
@@ -117,8 +271,22 @@ export class SkinModel {
|
|
|
117
271
|
part.rotation.set(0,0,0);
|
|
118
272
|
if (this.defaultPositions[name]) part.position.copy(this.defaultPositions[name]);
|
|
119
273
|
}
|
|
274
|
+
|
|
275
|
+
this.playerGroup.position.set(0, 0, 0);
|
|
276
|
+
this.playerGroup.rotation.set(0, 0, 0);
|
|
277
|
+
this.playerGroup.scale.set(1, 1, 1);
|
|
278
|
+
|
|
120
279
|
if (!pose) return;
|
|
280
|
+
|
|
281
|
+
if (pose.root) {
|
|
282
|
+
if (pose.root.pos) this.playerGroup.position.fromArray(pose.root.pos);
|
|
283
|
+
if (pose.root.rot) this.playerGroup.rotation.fromArray(pose.root.rot);
|
|
284
|
+
if (pose.root.scl) this.playerGroup.scale.fromArray(pose.root.scl);
|
|
285
|
+
}
|
|
286
|
+
|
|
121
287
|
for (const [name, data] of Object.entries(pose)) {
|
|
288
|
+
if (name === 'root') continue;
|
|
289
|
+
|
|
122
290
|
if (this.parts[name]) {
|
|
123
291
|
if(data.rot) this.parts[name].rotation.set(...data.rot);
|
|
124
292
|
if(data.pos) this.parts[name].position.set(...data.pos);
|
|
@@ -129,9 +297,34 @@ export class SkinModel {
|
|
|
129
297
|
getPose() {
|
|
130
298
|
const pose = {};
|
|
131
299
|
const f = (n) => parseFloat(n.toFixed(3));
|
|
300
|
+
|
|
132
301
|
for (const [name, part] of Object.entries(this.parts)) {
|
|
133
|
-
pose[name] = {
|
|
302
|
+
pose[name] = {
|
|
303
|
+
rot: [f(part.rotation.x), f(part.rotation.y), f(part.rotation.z)],
|
|
304
|
+
pos: [f(part.position.x), f(part.position.y), f(part.position.z)]
|
|
305
|
+
};
|
|
134
306
|
}
|
|
307
|
+
|
|
308
|
+
pose.root = {
|
|
309
|
+
pos: [f(this.playerGroup.position.x), f(this.playerGroup.position.y), f(this.playerGroup.position.z)],
|
|
310
|
+
rot: [f(this.playerGroup.rotation.x), f(this.playerGroup.rotation.y), f(this.playerGroup.rotation.z)],
|
|
311
|
+
scl: [f(this.playerGroup.scale.x), f(this.playerGroup.scale.y), f(this.playerGroup.scale.z)]
|
|
312
|
+
};
|
|
313
|
+
|
|
135
314
|
return pose;
|
|
136
315
|
}
|
|
316
|
+
|
|
317
|
+
dispose() {
|
|
318
|
+
if (this.playerGroup) {
|
|
319
|
+
if (this.playerGroup.parent) {
|
|
320
|
+
this.playerGroup.parent.remove(this.playerGroup);
|
|
321
|
+
}
|
|
322
|
+
disposeObjectTree(this.playerGroup);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
this.parts = {};
|
|
326
|
+
this.glowMeshes = [];
|
|
327
|
+
this.bodyMeshes = [];
|
|
328
|
+
this.playerGroup = null;
|
|
329
|
+
}
|
|
137
330
|
}
|
|
@@ -39,6 +39,13 @@ export class EditorPlugin {
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
this.transformControl.addEventListener('change', () => {
|
|
43
|
+
if (this.transformControl.object) {
|
|
44
|
+
this.viewer.emit('transform:change', this.transformControl.object);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
|
|
42
49
|
this.viewer.overlayScene.add(this.transformControl);
|
|
43
50
|
}
|
|
44
51
|
|
|
@@ -126,7 +133,10 @@ export class EditorPlugin {
|
|
|
126
133
|
|
|
127
134
|
this.raycaster.setFromCamera(this.mouse, this.viewer.cameraManager.camera);
|
|
128
135
|
|
|
129
|
-
let objectsToCheck = [
|
|
136
|
+
let objectsToCheck = [];
|
|
137
|
+
|
|
138
|
+
const playerGroup = this.viewer.skinModel.getGroup();
|
|
139
|
+
if (playerGroup) objectsToCheck.push(playerGroup);
|
|
130
140
|
|
|
131
141
|
const itemsPlugin = this.viewer.getPlugin('ItemsPlugin');
|
|
132
142
|
if (itemsPlugin) {
|
|
@@ -136,20 +146,27 @@ export class EditorPlugin {
|
|
|
136
146
|
const intersects = this.raycaster.intersectObjects(objectsToCheck, true);
|
|
137
147
|
|
|
138
148
|
if (intersects.length > 0) {
|
|
139
|
-
let
|
|
149
|
+
let hitObject = intersects[0].object;
|
|
150
|
+
let logicalTarget = hitObject;
|
|
140
151
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
while (logicalTarget.parent) {
|
|
153
|
+
if (logicalTarget.parent === playerGroup) {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
if (logicalTarget.parent.type === 'Scene') {
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
if (logicalTarget === playerGroup) {
|
|
146
160
|
break;
|
|
147
161
|
}
|
|
148
|
-
|
|
162
|
+
|
|
163
|
+
logicalTarget = logicalTarget.parent;
|
|
149
164
|
}
|
|
150
165
|
|
|
151
|
-
if (
|
|
152
|
-
|
|
166
|
+
if (!logicalTarget) logicalTarget = hitObject;
|
|
167
|
+
|
|
168
|
+
if (this.transformControl.object !== logicalTarget) {
|
|
169
|
+
this.selectObject(logicalTarget);
|
|
153
170
|
}
|
|
154
171
|
} else {
|
|
155
172
|
this.deselect();
|
|
@@ -164,7 +181,7 @@ export class EditorPlugin {
|
|
|
164
181
|
if (fx) fx.setSelected(obj);
|
|
165
182
|
|
|
166
183
|
// Callback support (can be injected)
|
|
167
|
-
|
|
184
|
+
this.viewer.emit('selection:change', obj);
|
|
168
185
|
}
|
|
169
186
|
|
|
170
187
|
deselect() {
|
|
@@ -173,7 +190,7 @@ export class EditorPlugin {
|
|
|
173
190
|
const fx = this.viewer.getPlugin('EffectsPlugin');
|
|
174
191
|
if (fx) fx.setSelected(null);
|
|
175
192
|
|
|
176
|
-
|
|
193
|
+
this.viewer.emit('selection:cleared');
|
|
177
194
|
}
|
|
178
195
|
|
|
179
196
|
/**
|
|
@@ -207,7 +224,21 @@ export class EditorPlugin {
|
|
|
207
224
|
}
|
|
208
225
|
|
|
209
226
|
dispose() {
|
|
210
|
-
this.viewer.renderer.domElement
|
|
211
|
-
|
|
227
|
+
if (this.viewer.renderer.domElement) {
|
|
228
|
+
this.viewer.renderer.domElement.removeEventListener('pointerdown', this.onPointerDown);
|
|
229
|
+
this.viewer.renderer.domElement.removeEventListener('pointermove', this.onPointerMove);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (this.transformControl) {
|
|
233
|
+
this.transformControl.detach();
|
|
234
|
+
this.transformControl.object = undefined;
|
|
235
|
+
|
|
236
|
+
if (this.transformControl.parent) {
|
|
237
|
+
this.transformControl.parent.remove(this.transformControl);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
this.transformControl.dispose();
|
|
241
|
+
this.transformControl = null;
|
|
242
|
+
}
|
|
212
243
|
}
|
|
213
244
|
}
|
|
@@ -8,7 +8,13 @@ import { PostProcessingManager } from '../managers/PostProcessingManager.js';
|
|
|
8
8
|
export class EffectsPlugin {
|
|
9
9
|
constructor() {
|
|
10
10
|
this.name = 'EffectsPlugin';
|
|
11
|
-
this.
|
|
11
|
+
this.state = {
|
|
12
|
+
enabled: false,
|
|
13
|
+
strength: 0,
|
|
14
|
+
radius: 0,
|
|
15
|
+
height: 0.5,
|
|
16
|
+
thickness: 4
|
|
17
|
+
};
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
init(viewer) {
|
|
@@ -26,15 +32,36 @@ export class EffectsPlugin {
|
|
|
26
32
|
* @param {Object} config - { enabled, strength, radius, height, thickness }
|
|
27
33
|
*/
|
|
28
34
|
updateConfig(config) {
|
|
29
|
-
this.
|
|
35
|
+
this.state = { ...this.state, ...config };
|
|
36
|
+
|
|
37
|
+
this._applyToScene();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Re-applies the current internal state to the 3D model.
|
|
42
|
+
* Useful when the model has been rebuilt (skin change).
|
|
43
|
+
*/
|
|
44
|
+
forceUpdate() {
|
|
45
|
+
this._applyToScene();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Internal method to push state to shaders and composer.
|
|
50
|
+
*/
|
|
51
|
+
_applyToScene() {
|
|
52
|
+
const config = this.state;
|
|
30
53
|
const skin = this.viewer.skinModel;
|
|
31
54
|
|
|
32
|
-
// Update Shader Materials
|
|
33
55
|
skin.setGlowEffect(config.enabled);
|
|
34
|
-
if (config.thickness !== undefined) skin.updateBorderThickness(config.thickness);
|
|
35
|
-
if (config.height !== undefined) skin.updateGlowHeight(config.height);
|
|
36
56
|
|
|
37
|
-
|
|
57
|
+
skin.updateBorderThickness(config.thickness);
|
|
58
|
+
skin.updateGlowHeight(config.height);
|
|
59
|
+
|
|
60
|
+
const itemsPlugin = this.viewer.getPlugin('ItemsPlugin');
|
|
61
|
+
if (itemsPlugin) {
|
|
62
|
+
itemsPlugin.updateAllGlow(config);
|
|
63
|
+
}
|
|
64
|
+
|
|
38
65
|
this.composer.setBloom(config.enabled, config.strength, config.radius, 0.85);
|
|
39
66
|
}
|
|
40
67
|
|
|
@@ -58,13 +85,11 @@ export class EffectsPlugin {
|
|
|
58
85
|
const items = itemsPlugin ? itemsPlugin.items : [];
|
|
59
86
|
|
|
60
87
|
this.composer.renderSelective(
|
|
61
|
-
// 1. Prepare Bloom pass (Hide non-glowing elements)
|
|
62
88
|
() => {
|
|
63
89
|
skin.darkenBody();
|
|
64
90
|
this.viewer.sceneSetup.setGridVisible(false);
|
|
65
91
|
items.forEach(i => i.material = skin.blackMaterial);
|
|
66
92
|
},
|
|
67
|
-
// 2. Restore Scene for main pass
|
|
68
93
|
() => {
|
|
69
94
|
skin.restoreBody();
|
|
70
95
|
this.viewer.sceneSetup.setGridVisible(this.viewer.config.showGrid);
|
|
@@ -75,6 +100,13 @@ export class EffectsPlugin {
|
|
|
75
100
|
);
|
|
76
101
|
}
|
|
77
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Returns the current configuration state.
|
|
105
|
+
*/
|
|
106
|
+
getConfig() {
|
|
107
|
+
return { ...this.state };
|
|
108
|
+
}
|
|
109
|
+
|
|
78
110
|
/**
|
|
79
111
|
* Generates a transparent PNG screenshot.
|
|
80
112
|
* Temporarily resizes renderer if width/height are provided.
|
|
@@ -132,18 +164,11 @@ export class EffectsPlugin {
|
|
|
132
164
|
return dataUrl;
|
|
133
165
|
}
|
|
134
166
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
enabled: this.isEnabled,
|
|
141
|
-
strength: this.composer.bloomPass.strength,
|
|
142
|
-
radius: this.composer.bloomPass.radius,
|
|
143
|
-
|
|
144
|
-
height: glowMesh ? glowMesh.userData.glowMat.uniforms.gradientLimit.value : 0.5,
|
|
145
|
-
thickness: glowMesh ? glowMesh.userData.glowMat.uniforms.thickness.value / 0.05 : 4
|
|
146
|
-
};
|
|
167
|
+
dispose() {
|
|
168
|
+
if (this.composer) {
|
|
169
|
+
this.composer.dispose();
|
|
170
|
+
this.composer = null;
|
|
171
|
+
}
|
|
147
172
|
}
|
|
148
173
|
|
|
149
174
|
}
|
package/src/plugins/IOPlugin.js
CHANGED
|
@@ -20,11 +20,11 @@ export class IOPlugin {
|
|
|
20
20
|
* @param {boolean} options.pose - Include character pose.
|
|
21
21
|
* @param {boolean} options.items - Include items.
|
|
22
22
|
*/
|
|
23
|
-
exportState(options = { skin: true, camera: true, effects: true, pose: true, items: true }) {
|
|
23
|
+
exportState(options = { skin: true, camera: true, effects: true, pose: true, items: true, env: true }) {
|
|
24
24
|
const state = {
|
|
25
25
|
meta: {
|
|
26
26
|
generator: "Bucciafico Studio",
|
|
27
|
-
version: "1.0.
|
|
27
|
+
version: "1.0.8",
|
|
28
28
|
timestamp: Date.now()
|
|
29
29
|
},
|
|
30
30
|
core: {}
|
|
@@ -32,6 +32,7 @@ export class IOPlugin {
|
|
|
32
32
|
|
|
33
33
|
if (options.skin) {
|
|
34
34
|
state.core.skin = this.viewer.skinData || null;
|
|
35
|
+
state.core.cape = this.viewer.capeData || null;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
// 2. Camera & Config
|
|
@@ -44,12 +45,17 @@ export class IOPlugin {
|
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
// 3.
|
|
48
|
+
// 3. Environment
|
|
49
|
+
if (options.env) {
|
|
50
|
+
state.environment = this.viewer.sceneSetup.getLightConfig();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 4. Pose
|
|
48
54
|
if (options.pose) {
|
|
49
55
|
state.pose = this.viewer.skinModel.getPose();
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
//
|
|
58
|
+
// 5. Effects
|
|
53
59
|
if (options.effects) {
|
|
54
60
|
const effectsPlugin = this.viewer.getPlugin('EffectsPlugin');
|
|
55
61
|
if (effectsPlugin) {
|
|
@@ -59,7 +65,7 @@ export class IOPlugin {
|
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
|
|
62
|
-
//
|
|
68
|
+
// 6. Items
|
|
63
69
|
if (options.items) {
|
|
64
70
|
const itemsPlugin = this.viewer.getPlugin('ItemsPlugin');
|
|
65
71
|
if (itemsPlugin && itemsPlugin.items.length > 0) {
|
|
@@ -67,6 +73,7 @@ export class IOPlugin {
|
|
|
67
73
|
name: item.name,
|
|
68
74
|
uuid: item.uuid,
|
|
69
75
|
sourceUrl: item.userData.sourceUrl || null,
|
|
76
|
+
parentId: item.userData.parentId || null,
|
|
70
77
|
transform: {
|
|
71
78
|
pos: item.position.toArray(),
|
|
72
79
|
rot: item.rotation.toArray(),
|
|
@@ -103,27 +110,42 @@ export class IOPlugin {
|
|
|
103
110
|
this.viewer.cameraManager.loadSettingsJSON(data.core.camera);
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
// 3.
|
|
113
|
+
// 3. Environment
|
|
114
|
+
if (data.environment) {
|
|
115
|
+
this.viewer.setEnvironment(data.environment);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const loadPromises = [];
|
|
119
|
+
|
|
120
|
+
// 4. Skin/Cape (Async)
|
|
107
121
|
if (data.core?.skin) {
|
|
108
122
|
const skinInfo = data.core.skin;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
if (skinInfo.type === 'username') {
|
|
124
|
+
loadPromises.push(this.viewer.loadSkinByUsername(skinInfo.value));
|
|
125
|
+
} else if (skinInfo.value) {
|
|
126
|
+
loadPromises.push(this.viewer.loadSkin(skinInfo.value));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (data.core?.cape) {
|
|
131
|
+
const capeInfo = data.core.cape;
|
|
132
|
+
if (capeInfo.type === 'username') {
|
|
133
|
+
loadPromises.push(this.viewer.loadCapeByUsername(capeInfo.value));
|
|
134
|
+
} else if (capeInfo.value) {
|
|
135
|
+
loadPromises.push(this.viewer.loadCape(capeInfo.value));
|
|
117
136
|
}
|
|
137
|
+
} else {
|
|
138
|
+
// Jeśli w JSON nie ma peleryny, a w viewerze jest, to ją czyścimy
|
|
139
|
+
this.viewer.resetCape();
|
|
118
140
|
}
|
|
119
141
|
|
|
120
|
-
//
|
|
142
|
+
// 5. Effects
|
|
121
143
|
if (data.effects?.backlight) {
|
|
122
144
|
const fx = this.viewer.getPlugin('EffectsPlugin');
|
|
123
145
|
if (fx) fx.updateConfig(data.effects.backlight);
|
|
124
146
|
}
|
|
125
147
|
|
|
126
|
-
//
|
|
148
|
+
// 6. Items (Async & Complex)
|
|
127
149
|
const itemsPlugin = this.viewer.getPlugin('ItemsPlugin');
|
|
128
150
|
if (itemsPlugin) {
|
|
129
151
|
[...itemsPlugin.items].forEach(item => itemsPlugin.removeItem(item));
|
|
@@ -134,6 +156,11 @@ export class IOPlugin {
|
|
|
134
156
|
|
|
135
157
|
try {
|
|
136
158
|
const mesh = await itemsPlugin.addItem(itemData.sourceUrl, itemData.name);
|
|
159
|
+
|
|
160
|
+
if (itemData.parentId) {
|
|
161
|
+
itemsPlugin.attachItem(mesh, itemData.parentId);
|
|
162
|
+
}
|
|
163
|
+
|
|
137
164
|
// Apply Transform
|
|
138
165
|
if (itemData.transform) {
|
|
139
166
|
mesh.position.fromArray(itemData.transform.pos);
|
|
@@ -148,9 +175,11 @@ export class IOPlugin {
|
|
|
148
175
|
}
|
|
149
176
|
}
|
|
150
177
|
|
|
151
|
-
//
|
|
178
|
+
// 7. Pose
|
|
152
179
|
if (data.pose) {
|
|
153
180
|
this.viewer.setPose(data.pose);
|
|
154
181
|
}
|
|
182
|
+
|
|
183
|
+
await Promise.all(loadPromises);
|
|
155
184
|
}
|
|
156
185
|
}
|