bucciafico-lib 1.0.7 → 1.0.9
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 +39 -39
- package/src/core/SkinViewer.js +203 -12
- package/src/managers/EventManager.js +74 -0
- package/src/managers/PostProcessingManager.js +24 -0
- package/src/objects/SceneSetup.js +49 -8
- package/src/objects/SkinModel.js +296 -32
- package/src/plugins/EditorPlugin.js +45 -14
- package/src/plugins/EffectsPlugin.js +45 -20
- package/src/plugins/IOPlugin.js +83 -33
- package/src/plugins/ItemsPlugin.js +121 -2
- package/src/utils/SkinUtils.js +17 -17
- package/src/utils/TextureUtils.js +23 -0
- package/src/utils/ThreeUtils.js +27 -0
- package/src/utils/Voxelizer.js +139 -33
package/package.json
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "bucciafico-lib",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Modular 3D rendering engine for Minecraft skins based on Three.js",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./index.js",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./index.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"src",
|
|
12
|
-
"index.js",
|
|
13
|
-
"README.md"
|
|
14
|
-
],
|
|
15
|
-
"scripts": {
|
|
16
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
17
|
-
},
|
|
18
|
-
"keywords": [
|
|
19
|
-
"minecraft",
|
|
20
|
-
"3d",
|
|
21
|
-
"threejs",
|
|
22
|
-
"skin",
|
|
23
|
-
"viewer",
|
|
24
|
-
"renderer"
|
|
25
|
-
],
|
|
26
|
-
"author": "Dawid Maj",
|
|
27
|
-
"license": "MIT",
|
|
28
|
-
"peerDependencies": {
|
|
29
|
-
"three": "^0.160.0"
|
|
30
|
-
},
|
|
31
|
-
"repository": {
|
|
32
|
-
"type": "git",
|
|
33
|
-
"url": "git+https://github.com/HappyGFX/bucciafico-lib.git"
|
|
34
|
-
},
|
|
35
|
-
"bugs": {
|
|
36
|
-
"url": "https://github.com/HappyGFX/bucciafico-lib/issues"
|
|
37
|
-
},
|
|
38
|
-
"homepage": "https://github.com/HappyGFX/bucciafico-lib#readme"
|
|
39
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "bucciafico-lib",
|
|
3
|
+
"version": "1.0.9",
|
|
4
|
+
"description": "Modular 3D rendering engine for Minecraft skins based on Three.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"index.js",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"minecraft",
|
|
20
|
+
"3d",
|
|
21
|
+
"threejs",
|
|
22
|
+
"skin",
|
|
23
|
+
"viewer",
|
|
24
|
+
"renderer"
|
|
25
|
+
],
|
|
26
|
+
"author": "Dawid Maj",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"three": "^0.160.0"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/HappyGFX/bucciafico-lib.git"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/HappyGFX/bucciafico-lib/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/HappyGFX/bucciafico-lib#readme"
|
|
39
|
+
}
|
package/src/core/SkinViewer.js
CHANGED
|
@@ -3,6 +3,9 @@ import { CameraManager } from '../managers/CameraManager.js';
|
|
|
3
3
|
import { SceneSetup } from '../objects/SceneSetup.js';
|
|
4
4
|
import { SkinModel } from '../objects/SkinModel.js';
|
|
5
5
|
import { detectSlimSkin } from '../utils/SkinUtils.js';
|
|
6
|
+
import {disposeObjectTree} from "../utils/ThreeUtils.js";
|
|
7
|
+
import {EventManager} from "../managers/EventManager.js";
|
|
8
|
+
import {createPlaceholderTexture} from "../utils/TextureUtils.js";
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Core 3D Viewer class.
|
|
@@ -30,6 +33,9 @@ export class SkinViewer {
|
|
|
30
33
|
...config
|
|
31
34
|
};
|
|
32
35
|
|
|
36
|
+
this.skinData = null;
|
|
37
|
+
this.capeData = null;
|
|
38
|
+
|
|
33
39
|
/** @type {Map<string, Object>} Registered plugins. */
|
|
34
40
|
this.plugins = new Map();
|
|
35
41
|
|
|
@@ -39,6 +45,12 @@ export class SkinViewer {
|
|
|
39
45
|
this.isVisible = true;
|
|
40
46
|
this.needsRender = true;
|
|
41
47
|
|
|
48
|
+
this.events = new EventManager();
|
|
49
|
+
|
|
50
|
+
this.on = this.events.on.bind(this.events);
|
|
51
|
+
this.off = this.events.off.bind(this.events);
|
|
52
|
+
this.emit = this.events.emit.bind(this.events);
|
|
53
|
+
|
|
42
54
|
// --- 1. RENDERER SETUP ---
|
|
43
55
|
this.renderer = new THREE.WebGLRenderer({
|
|
44
56
|
antialias: true,
|
|
@@ -62,6 +74,9 @@ export class SkinViewer {
|
|
|
62
74
|
this.scene.background = new THREE.Color(this.config.bgColor);
|
|
63
75
|
}
|
|
64
76
|
|
|
77
|
+
this.skinModel = new SkinModel();
|
|
78
|
+
this.scene.add(this.skinModel.getGroup());
|
|
79
|
+
|
|
65
80
|
this.overlayScene = new THREE.Scene();
|
|
66
81
|
|
|
67
82
|
this.sceneSetup = new SceneSetup(this.scene);
|
|
@@ -72,8 +87,7 @@ export class SkinViewer {
|
|
|
72
87
|
});
|
|
73
88
|
this.cameraManager.setEnabled(this.config.cameraEnabled);
|
|
74
89
|
|
|
75
|
-
this.
|
|
76
|
-
this.scene.add(this.skinModel.getGroup());
|
|
90
|
+
this.loadPlaceholderSkin();
|
|
77
91
|
|
|
78
92
|
this.observer = new IntersectionObserver((entries) => {
|
|
79
93
|
if (entries[0].isIntersecting) {
|
|
@@ -88,6 +102,7 @@ export class SkinViewer {
|
|
|
88
102
|
|
|
89
103
|
// --- 3. START LOOP ---
|
|
90
104
|
this.animate = this.animate.bind(this);
|
|
105
|
+
this.emit('viewer:ready', this);
|
|
91
106
|
this.animate();
|
|
92
107
|
}
|
|
93
108
|
|
|
@@ -124,14 +139,32 @@ export class SkinViewer {
|
|
|
124
139
|
return this.plugins.get(name);
|
|
125
140
|
}
|
|
126
141
|
|
|
142
|
+
loadPlaceholderSkin() {
|
|
143
|
+
const placeholderTex = createPlaceholderTexture();
|
|
144
|
+
this.skinData = null;
|
|
145
|
+
this.resetCape();
|
|
146
|
+
this.skinModel.build(placeholderTex, false, false);
|
|
147
|
+
this.requestRender();
|
|
148
|
+
}
|
|
149
|
+
|
|
127
150
|
/**
|
|
128
151
|
* Loads a skin from URL.
|
|
129
152
|
* @param {string} imageUrl
|
|
130
153
|
* @returns {Promise<boolean>} isSlim
|
|
131
154
|
*/
|
|
132
155
|
loadSkin(imageUrl) {
|
|
156
|
+
this.emit('skin:loading', imageUrl);
|
|
157
|
+
|
|
133
158
|
return new Promise((resolve, reject) => {
|
|
134
|
-
new THREE.TextureLoader()
|
|
159
|
+
const loader = new THREE.TextureLoader();
|
|
160
|
+
loader.setCrossOrigin('anonymous');
|
|
161
|
+
|
|
162
|
+
loader.load(imageUrl, (texture) => {
|
|
163
|
+
if (this.isDisposed) {
|
|
164
|
+
texture.dispose();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
135
168
|
texture.magFilter = THREE.NearestFilter;
|
|
136
169
|
texture.colorSpace = THREE.SRGBColorSpace;
|
|
137
170
|
|
|
@@ -141,13 +174,24 @@ export class SkinViewer {
|
|
|
141
174
|
const editor = this.getPlugin('EditorPlugin');
|
|
142
175
|
if (editor) editor.deselect();
|
|
143
176
|
|
|
144
|
-
this.skinModel.build(texture, isSlim);
|
|
177
|
+
this.skinModel.build(texture, isSlim, true);
|
|
145
178
|
this.skinModel.setPose(currentPose);
|
|
146
179
|
this.skinData = { type: 'url', value: imageUrl };
|
|
147
180
|
|
|
181
|
+
const fxPlugin = this.getPlugin('EffectsPlugin');
|
|
182
|
+
if (fxPlugin) {
|
|
183
|
+
fxPlugin.forceUpdate();
|
|
184
|
+
}
|
|
185
|
+
|
|
148
186
|
this.requestRender();
|
|
187
|
+
|
|
188
|
+
this.emit('skin:loaded', { isSlim, texture });
|
|
189
|
+
|
|
149
190
|
resolve(isSlim);
|
|
150
|
-
}, undefined,
|
|
191
|
+
}, undefined, (err) => {
|
|
192
|
+
this.emit('skin:error', err);
|
|
193
|
+
reject(err);
|
|
194
|
+
});
|
|
151
195
|
});
|
|
152
196
|
}
|
|
153
197
|
|
|
@@ -161,6 +205,101 @@ export class SkinViewer {
|
|
|
161
205
|
});
|
|
162
206
|
}
|
|
163
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Loads a cape from URL.
|
|
210
|
+
* @param {string} imageUrl
|
|
211
|
+
*/
|
|
212
|
+
loadCape(imageUrl) {
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
const loader = new THREE.TextureLoader();
|
|
215
|
+
loader.setCrossOrigin('anonymous');
|
|
216
|
+
|
|
217
|
+
loader.load(
|
|
218
|
+
imageUrl,
|
|
219
|
+
(texture) => {
|
|
220
|
+
if (this.isDisposed) {
|
|
221
|
+
texture.dispose();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
texture.magFilter = THREE.NearestFilter;
|
|
226
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
227
|
+
|
|
228
|
+
texture.needsUpdate = true;
|
|
229
|
+
|
|
230
|
+
this.skinModel.setCape(texture);
|
|
231
|
+
|
|
232
|
+
const fxPlugin = this.getPlugin('EffectsPlugin');
|
|
233
|
+
if (fxPlugin) {
|
|
234
|
+
fxPlugin.forceUpdate();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
this.capeData = { type: 'url', value: imageUrl };
|
|
238
|
+
|
|
239
|
+
this.requestRender();
|
|
240
|
+
this.emit('cape:loaded', imageUrl);
|
|
241
|
+
resolve();
|
|
242
|
+
},
|
|
243
|
+
undefined,
|
|
244
|
+
(err) => {
|
|
245
|
+
console.error("Error loading cape texture:", err);
|
|
246
|
+
reject(err);
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Loads a cape by username using capes.dev API.
|
|
255
|
+
* Supports Official, Optifine, LabyMod, etc.
|
|
256
|
+
* @param {string} username
|
|
257
|
+
*/
|
|
258
|
+
async loadCapeByUsername(username) {
|
|
259
|
+
this.emit('cape:loading', username);
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const response = await fetch(`https://api.capes.dev/load/${username}`);
|
|
263
|
+
|
|
264
|
+
if (!response.ok) {
|
|
265
|
+
throw new Error(`User not found in capes.dev (Status: ${response.status})`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const data = await response.json();
|
|
269
|
+
let capeUrl = null;
|
|
270
|
+
|
|
271
|
+
if (data.minecraft?.exists && data.minecraft?.imageUrl) {
|
|
272
|
+
capeUrl = data.minecraft.imageUrl;
|
|
273
|
+
} else if (data.optifine?.exists && data.optifine?.imageUrl) {
|
|
274
|
+
capeUrl = data.optifine.imageUrl;
|
|
275
|
+
} else if (data.labymod?.exists && data.labymod?.imageUrl) {
|
|
276
|
+
capeUrl = data.labymod.imageUrl;
|
|
277
|
+
} else if (data.tlauncher?.exists && data.tlauncher?.imageUrl) {
|
|
278
|
+
capeUrl = data.tlauncher.imageUrl;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (capeUrl) {
|
|
282
|
+
await this.loadCape(capeUrl);
|
|
283
|
+
this.capeData = { type: 'username', value: username };
|
|
284
|
+
return true;
|
|
285
|
+
} else {
|
|
286
|
+
this.resetCape();
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
} catch (e) {
|
|
291
|
+
this.emit('cape:error', e);
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
resetCape() {
|
|
297
|
+
this.skinModel.setCape(null);
|
|
298
|
+
this.capeData = null;
|
|
299
|
+
this.requestRender();
|
|
300
|
+
this.emit('cape:removed');
|
|
301
|
+
}
|
|
302
|
+
|
|
164
303
|
setPose(poseData) {
|
|
165
304
|
// Record history if Editor is present
|
|
166
305
|
const editor = this.getPlugin('EditorPlugin');
|
|
@@ -170,6 +309,40 @@ export class SkinViewer {
|
|
|
170
309
|
this.requestRender();
|
|
171
310
|
}
|
|
172
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Updates lighting intensity.
|
|
314
|
+
* @param {Object} config - { global, main, fill }
|
|
315
|
+
*/
|
|
316
|
+
setEnvironment(config) {
|
|
317
|
+
this.sceneSetup.setLightConfig(config);
|
|
318
|
+
this.requestRender();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Updates scene configuration at runtime.
|
|
323
|
+
* @param {Object} config
|
|
324
|
+
* @param {boolean} [config.showGrid]
|
|
325
|
+
* @param {boolean} [config.transparent]
|
|
326
|
+
* @param {string|number} [config.bgColor] - Hex color
|
|
327
|
+
*/
|
|
328
|
+
updateConfig(config) {
|
|
329
|
+
this.config = { ...this.config, ...config };
|
|
330
|
+
|
|
331
|
+
if (config.showGrid !== undefined) {
|
|
332
|
+
this.sceneSetup.setGridVisible(config.showGrid);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (this.config.transparent) {
|
|
336
|
+
this.scene.background = null;
|
|
337
|
+
this.renderer.setClearAlpha(0);
|
|
338
|
+
} else {
|
|
339
|
+
this.renderer.setClearAlpha(1);
|
|
340
|
+
this.scene.background = new THREE.Color(this.config.bgColor);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
this.requestRender();
|
|
344
|
+
}
|
|
345
|
+
|
|
173
346
|
/**
|
|
174
347
|
* Handles window resize. Should be called by the implementation layer.
|
|
175
348
|
*/
|
|
@@ -215,19 +388,37 @@ export class SkinViewer {
|
|
|
215
388
|
}
|
|
216
389
|
|
|
217
390
|
dispose() {
|
|
391
|
+
this.emit('viewer:dispose');
|
|
218
392
|
this.isDisposed = true;
|
|
219
393
|
this.observer.disconnect();
|
|
220
394
|
|
|
221
|
-
if (this.container && this.renderer.domElement) {
|
|
222
|
-
this.container.removeChild(this.renderer.domElement);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
this.renderer.dispose();
|
|
226
|
-
|
|
227
|
-
// Dispose all plugins
|
|
228
395
|
this.plugins.forEach(p => {
|
|
229
396
|
if (p.dispose) p.dispose();
|
|
230
397
|
});
|
|
231
398
|
this.plugins.clear();
|
|
399
|
+
|
|
400
|
+
if (this.skinModel) {
|
|
401
|
+
this.skinModel.dispose();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
disposeObjectTree(this.scene);
|
|
405
|
+
disposeObjectTree(this.overlayScene);
|
|
406
|
+
|
|
407
|
+
if (this.cameraManager) {
|
|
408
|
+
this.cameraManager.controls.dispose();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (this.renderer) {
|
|
412
|
+
this.renderer.dispose();
|
|
413
|
+
this.renderer.forceContextLoss();
|
|
414
|
+
|
|
415
|
+
if (this.container && this.renderer.domElement) {
|
|
416
|
+
this.container.removeChild(this.renderer.domElement);
|
|
417
|
+
}
|
|
418
|
+
this.renderer.domElement = null;
|
|
419
|
+
this.renderer = null;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
this.events.dispose();
|
|
232
423
|
}
|
|
233
424
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Event Emitter implementation.
|
|
3
|
+
* Handles Pub/Sub architecture to decouple Core from Plugins and UI.
|
|
4
|
+
*/
|
|
5
|
+
export class EventManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
/** @type {Map<string, Set<Function>>} */
|
|
8
|
+
this.listeners = new Map();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Subscribe to an event.
|
|
13
|
+
* @param {string} event - Event name (e.g. 'skin:loaded').
|
|
14
|
+
* @param {Function} callback - Function to execute.
|
|
15
|
+
* @returns {Function} Unsubscribe function for convenience.
|
|
16
|
+
*/
|
|
17
|
+
on(event, callback) {
|
|
18
|
+
if (!this.listeners.has(event)) {
|
|
19
|
+
this.listeners.set(event, new Set());
|
|
20
|
+
}
|
|
21
|
+
this.listeners.get(event).add(callback);
|
|
22
|
+
|
|
23
|
+
// Return unsubscribe function pattern
|
|
24
|
+
return () => this.off(event, callback);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Unsubscribe from an event.
|
|
29
|
+
* @param {string} event
|
|
30
|
+
* @param {Function} callback
|
|
31
|
+
*/
|
|
32
|
+
off(event, callback) {
|
|
33
|
+
if (this.listeners.has(event)) {
|
|
34
|
+
this.listeners.get(event).delete(callback);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Subscribe to an event only once.
|
|
40
|
+
* @param {string} event
|
|
41
|
+
* @param {Function} callback
|
|
42
|
+
*/
|
|
43
|
+
once(event, callback) {
|
|
44
|
+
const wrapper = (...args) => {
|
|
45
|
+
callback(...args);
|
|
46
|
+
this.off(event, wrapper);
|
|
47
|
+
};
|
|
48
|
+
this.on(event, wrapper);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Emit an event with optional data.
|
|
53
|
+
* @param {string} event
|
|
54
|
+
* @param {*} [data]
|
|
55
|
+
*/
|
|
56
|
+
emit(event, data) {
|
|
57
|
+
if (this.listeners.has(event)) {
|
|
58
|
+
this.listeners.get(event).forEach(cb => {
|
|
59
|
+
try {
|
|
60
|
+
cb(data);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error(`Error in listener for event "${event}":`, e);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Clears all listeners. Used for disposal.
|
|
70
|
+
*/
|
|
71
|
+
dispose() {
|
|
72
|
+
this.listeners.clear();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -131,4 +131,28 @@ export class PostProcessingManager {
|
|
|
131
131
|
this.bloomPass.radius = Number(rad);
|
|
132
132
|
this.bloomPass.threshold = Number(thr);
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
dispose() {
|
|
136
|
+
if (this.bloomComposer) {
|
|
137
|
+
this.bloomComposer.renderTarget1.dispose();
|
|
138
|
+
this.bloomComposer.renderTarget2.dispose();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (this.finalComposer) {
|
|
142
|
+
this.finalComposer.renderTarget1.dispose();
|
|
143
|
+
this.finalComposer.renderTarget2.dispose();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (this.bloomPass) {
|
|
147
|
+
this.bloomPass.dispose();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (this.outlinePass) {
|
|
151
|
+
this.outlinePass.dispose();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (this.mixPass && this.mixPass.material) {
|
|
155
|
+
this.mixPass.material.dispose();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
134
158
|
}
|
|
@@ -7,19 +7,32 @@ export class SceneSetup {
|
|
|
7
7
|
constructor(scene) {
|
|
8
8
|
this.scene = scene;
|
|
9
9
|
this.gridHelper = null;
|
|
10
|
+
|
|
11
|
+
this.ambientLight = null;
|
|
12
|
+
this.hemiLight = null;
|
|
13
|
+
this.dirLightMain = null;
|
|
14
|
+
this.dirLightFill = null;
|
|
15
|
+
|
|
10
16
|
this.initLights();
|
|
11
17
|
this.initHelpers();
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
initLights() {
|
|
15
|
-
|
|
16
|
-
this.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
this.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
// 1. Global Illumination
|
|
22
|
+
this.ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
|
|
23
|
+
this.scene.add(this.ambientLight);
|
|
24
|
+
|
|
25
|
+
this.hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.5);
|
|
26
|
+
this.scene.add(this.hemiLight);
|
|
27
|
+
|
|
28
|
+
// 2. Directional Lights (Shadows & Definition)
|
|
29
|
+
this.dirLightMain = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
30
|
+
this.dirLightMain.position.set(10, 20, 10);
|
|
31
|
+
this.scene.add(this.dirLightMain);
|
|
32
|
+
|
|
33
|
+
this.dirLightFill = new THREE.DirectionalLight(0xffffff, 0.4);
|
|
34
|
+
this.dirLightFill.position.set(0, 0, 20);
|
|
35
|
+
this.scene.add(this.dirLightFill);
|
|
23
36
|
}
|
|
24
37
|
|
|
25
38
|
initHelpers() {
|
|
@@ -29,4 +42,32 @@ export class SceneSetup {
|
|
|
29
42
|
}
|
|
30
43
|
|
|
31
44
|
setGridVisible(vis) { if(this.gridHelper) this.gridHelper.visible = vis; }
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Updates the intensity of scene lights.
|
|
48
|
+
* @param {Object} config
|
|
49
|
+
* @param {number} [config.global] - Intensity of Ambient/Hemi lights (0.0 - 2.0)
|
|
50
|
+
* @param {number} [config.main] - Intensity of Main Directional Light (0.0 - 2.0)
|
|
51
|
+
* @param {number} [config.fill] - Intensity of Fill Light (0.0 - 2.0)
|
|
52
|
+
*/
|
|
53
|
+
setLightConfig(config) {
|
|
54
|
+
if (config.global !== undefined) {
|
|
55
|
+
this.ambientLight.intensity = config.global;
|
|
56
|
+
this.hemiLight.intensity = config.global * 0.6;
|
|
57
|
+
}
|
|
58
|
+
if (config.main !== undefined) {
|
|
59
|
+
this.dirLightMain.intensity = config.main;
|
|
60
|
+
}
|
|
61
|
+
if (config.fill !== undefined) {
|
|
62
|
+
this.dirLightFill.intensity = config.fill;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getLightConfig() {
|
|
67
|
+
return {
|
|
68
|
+
global: this.ambientLight.intensity,
|
|
69
|
+
main: this.dirLightMain.intensity,
|
|
70
|
+
fill: this.dirLightFill.intensity
|
|
71
|
+
};
|
|
72
|
+
}
|
|
32
73
|
}
|