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.
@@ -1,4 +1,7 @@
1
+ import * as THREE from 'three';
1
2
  import { ItemFactory } from '../objects/ItemFactory.js';
3
+ import {disposeObjectTree} from "../utils/ThreeUtils.js";
4
+ import {createGlowMaterial} from "../materials/GlowMaterial.js";
2
5
 
3
6
  /**
4
7
  * Plugin responsible for managing 3D Items (Swords, Blocks).
@@ -8,12 +11,68 @@ export class ItemsPlugin {
8
11
  this.name = 'ItemsPlugin';
9
12
  /** @type {Array<THREE.Mesh>} List of current items on scene */
10
13
  this.items = [];
14
+
15
+ this.LAYERS_COUNT = 20;
11
16
  }
12
17
 
13
18
  init(viewer) {
14
19
  this.viewer = viewer;
15
20
  }
16
21
 
22
+ attachItem(itemMesh, partName) {
23
+ const editor = this.viewer.getPlugin('EditorPlugin');
24
+ if (editor) editor.saveHistory();
25
+
26
+ const skinModel = this.viewer.skinModel;
27
+
28
+ if (!partName) {
29
+ this.viewer.scene.attach(itemMesh);
30
+ itemMesh.userData.parentId = null;
31
+ }
32
+
33
+ else if (skinModel.parts[partName]) {
34
+ const targetGroup = skinModel.parts[partName];
35
+ targetGroup.attach(itemMesh);
36
+ itemMesh.userData.parentId = partName;
37
+ }
38
+
39
+ if (this.viewer.emit) this.viewer.emit('transform:change', itemMesh);
40
+ }
41
+
42
+ _addGlowShells(mesh) {
43
+ mesh.geometry.computeBoundingBox();
44
+ const size = new THREE.Vector3();
45
+ mesh.geometry.boundingBox.getSize(size);
46
+ const itemHeight = size.y || 1;
47
+
48
+ const glowLayers = [];
49
+
50
+ const glowGroup = new THREE.Group();
51
+ glowGroup.name = "GlowShells";
52
+
53
+ const shellGeo = mesh.geometry.clone();
54
+
55
+ for (let i = 0; i < this.LAYERS_COUNT; i++) {
56
+ const glowMat = createGlowMaterial(itemHeight);
57
+
58
+ glowMat.uniforms.thickness.value = 0;
59
+ glowMat.uniforms.opacity.value = 0;
60
+
61
+ glowMat.polygonOffset = true;
62
+ glowMat.polygonOffsetFactor = i * 0.1;
63
+
64
+ const layerMesh = new THREE.Mesh(shellGeo, glowMat);
65
+ layerMesh.userData.isGlowLayer = true;
66
+ layerMesh.userData.glowMat = glowMat;
67
+
68
+ glowLayers.push(layerMesh);
69
+ glowGroup.add(layerMesh);
70
+ }
71
+
72
+ mesh.add(glowGroup);
73
+ mesh.userData.glowLayers = glowLayers;
74
+ }
75
+
17
76
  /**
18
77
  * Creates and adds an item to the scene.
19
78
  * @param {string} url - Texture URL.
@@ -26,11 +85,22 @@ export class ItemsPlugin {
26
85
  return ItemFactory.createFromURL(url, name).then(mesh => {
27
86
  mesh.position.set(8, 8, 8);
28
87
  mesh.userData.sourceUrl = url;
88
+
89
+ this._addGlowShells(mesh);
90
+
91
+ const fx = this.viewer.getPlugin('EffectsPlugin');
92
+ if (fx) {
93
+ const config = fx.getConfig();
94
+ this.updateItemGlow(mesh, config);
95
+ }
96
+
29
97
  this.viewer.scene.add(mesh);
30
98
  this.items.push(mesh);
31
99
 
32
- // Auto-select if editor is present
33
100
  if (editor) editor.selectObject(mesh);
101
+
102
+ if (this.viewer.emit) this.viewer.emit('items:added', mesh);
103
+
34
104
  return mesh;
35
105
  });
36
106
  }
@@ -42,7 +112,44 @@ export class ItemsPlugin {
42
112
  this.viewer.scene.remove(mesh);
43
113
  this.items = this.items.filter(i => i !== mesh);
44
114
 
115
+ disposeObjectTree(mesh);
116
+
45
117
  if (editor) editor.deselect();
118
+
119
+ if (this.viewer.emit) this.viewer.emit('items:removed', mesh);
120
+ }
121
+
122
+ // --- EFFECTS ---
123
+
124
+ updateAllGlow(config) {
125
+ this.items.forEach(item => {
126
+ this.updateItemGlow(item, config);
127
+ });
128
+ }
129
+
130
+ updateItemGlow(item, config) {
131
+ if (!item.userData.glowLayers) return;
132
+
133
+ const maxThickness = (config.thickness || 4) * 0.05;
134
+ const heightLimit = config.height !== undefined ? config.height : 0.5;
135
+ const enabled = config.enabled;
136
+
137
+ item.userData.glowLayers.forEach((layer, i) => {
138
+ const mat = layer.userData.glowMat;
139
+ if (!mat) return;
140
+
141
+ const progress = (i + 1) / this.LAYERS_COUNT;
142
+ mat.uniforms.thickness.value = maxThickness * progress;
143
+
144
+ mat.uniforms.gradientLimit.value = heightLimit;
145
+
146
+ if (!enabled) {
147
+ mat.uniforms.opacity.value = 0.0;
148
+ } else {
149
+ const baseOpacity = 1.2 / this.LAYERS_COUNT;
150
+ mat.uniforms.opacity.value = baseOpacity;
151
+ }
152
+ });
46
153
  }
47
154
 
48
155
  // --- SNAPSHOT HELPERS ---
@@ -51,6 +158,7 @@ export class ItemsPlugin {
51
158
  return this.items.map(item => ({
52
159
  name: item.name,
53
160
  uuid: item.uuid,
161
+ parentId: item.userData.parentId || null,
54
162
  pos: item.position.toArray(),
55
163
  rot: item.rotation.toArray(),
56
164
  scale: item.scale.toArray()
@@ -59,13 +167,24 @@ export class ItemsPlugin {
59
167
 
60
168
  restoreSnapshot(itemsState) {
61
169
  itemsState.forEach(state => {
62
- // Try to find the item by UUID first, then Name
63
170
  const item = this.items.find(i => i.uuid === state.uuid || i.name === state.name);
64
171
  if (item) {
172
+ if (state.parentId !== item.userData.parentId) {
173
+ this.attachItem(item, state.parentId);
174
+ }
175
+
65
176
  item.position.fromArray(state.pos);
66
177
  item.rotation.fromArray(state.rot);
67
178
  item.scale.fromArray(state.scale);
68
179
  }
69
180
  });
70
181
  }
182
+
183
+ dispose() {
184
+ this.items.forEach(mesh => {
185
+ this.viewer.scene.remove(mesh);
186
+ disposeObjectTree(mesh);
187
+ });
188
+ this.items = [];
189
+ }
71
190
  }
@@ -1,33 +1,33 @@
1
1
  /**
2
2
  * Calculates UV coordinates for standard Minecraft skin layout.
3
3
  */
4
- export function getUV(x, y, w, h) {
5
- const imgW = 64;
6
- const imgH = 64;
7
- return { u0: x/imgW, u1: (x+w)/imgW, v0: (imgH-y-h)/imgH, v1: (imgH-y)/imgH };
4
+ export function getUV(x, y, w, h, imgW = 64, imgH = 64) {
5
+ return {
6
+ u0: x / imgW,
7
+ u1: (x + w) / imgW,
8
+ v0: (imgH - y - h) / imgH,
9
+ v1: (imgH - y) / imgH
10
+ };
8
11
  }
9
12
 
10
13
  /**
11
14
  * Maps UV coordinates to a box geometry (Cube mapping).
15
+ * @param {THREE.BufferGeometry} geometry
16
+ * @param {number} x - Texture X
17
+ * @param {number} y - Texture Y
18
+ * @param {number} w - Width
19
+ * @param {number} h - Height
20
+ * @param {number} d - Depth
21
+ * @param {number} [imgW=64] - Texture Width
22
+ * @param {number} [imgH=64] - Texture Height
12
23
  */
13
- export function applySkinUVs(geometry, x, y, w, h, d) {
24
+ export function applySkinUVs(geometry, x, y, w, h, d, imgW = 64, imgH = 64) {
14
25
  const uvAttr = geometry.attributes.uv;
15
26
 
16
- /**
17
- * Helper to map a single face.
18
- * @param {number} idx - Face index (0-5).
19
- * @param {number} uX - Texture X.
20
- * @param {number} uY - Texture Y.
21
- * @param {number} uW - Texture Width.
22
- * @param {number} uH - Texture Height.
23
- * @param {boolean} flipX - Mirror horizontally.
24
- * @param {boolean} flipY - Mirror vertically.
25
- */
26
27
  const map = (idx, uX, uY, uW, uH, flipX = false, flipY = false) => {
27
- const uv = getUV(uX, uY, uW, uH);
28
+ const uv = getUV(uX, uY, uW, uH, imgW, imgH);
28
29
  const i = idx * 4;
29
30
 
30
- // Handle flipping
31
31
  const u0 = flipX ? uv.u1 : uv.u0;
32
32
  const u1 = flipX ? uv.u0 : uv.u1;
33
33
  const v0 = flipY ? uv.v1 : uv.v0;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Recursively disposes of a Three.js object and its children.
3
+ * Frees memory for Geometries, Materials, and Textures.
4
+ * @param {THREE.Object3D} object - The object to clean up.
5
+ */
6
+ export function disposeObjectTree(object) {
7
+ if (!object) return;
8
+
9
+ object.traverse((child) => {
10
+ if (child.geometry) {
11
+ child.geometry.dispose();
12
+ }
13
+
14
+ if (child.material) {
15
+ const materials = Array.isArray(child.material) ? child.material : [child.material];
16
+
17
+ materials.forEach((mat) => {
18
+ for (const key in mat) {
19
+ if (mat[key] && mat[key].isTexture) {
20
+ mat[key].dispose();
21
+ }
22
+ }
23
+ mat.dispose();
24
+ });
25
+ }
26
+ });
27
+ }
@@ -1,58 +1,164 @@
1
1
  import * as THREE from 'three';
2
- import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
2
+
3
+ let cachedCanvas = null;
4
+ let cachedCtx = null;
5
+
6
+ function getImageData(image) {
7
+ if (!cachedCanvas) {
8
+ cachedCanvas = document.createElement('canvas');
9
+ cachedCanvas.width = 64;
10
+ cachedCanvas.height = 64;
11
+ cachedCtx = cachedCanvas.getContext('2d', { willReadFrequently: true });
12
+ }
13
+
14
+ cachedCtx.clearRect(0, 0, 64, 64);
15
+ cachedCtx.drawImage(image, 0, 0);
16
+ return cachedCtx.getImageData(0, 0, 64, 64).data;
17
+ }
18
+
19
+ const CUBE = {
20
+ vertices: [
21
+ // Right
22
+ 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5,
23
+ // Left
24
+ -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5,
25
+ // Top
26
+ -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
27
+ // Bottom
28
+ -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5,
29
+ // Front
30
+ -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5,
31
+ // Back
32
+ -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5
33
+ ],
34
+ normals: [
35
+ // Right
36
+ 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
37
+ // Left
38
+ -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0,
39
+ // Top
40
+ 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
41
+ // Bottom
42
+ 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0,
43
+ // Front
44
+ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
45
+ // Back
46
+ 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1
47
+ ],
48
+ indices: [
49
+ 0, 1, 2, 0, 2, 3, // Right
50
+ 4, 5, 6, 4, 6, 7, // Left
51
+ 8, 9, 10, 8, 10, 11, // Top
52
+ 12, 13, 14, 12, 14, 15, // Bottom
53
+ 16, 17, 18, 16, 18, 19, // Front
54
+ 20, 21, 22, 20, 22, 23 // Back
55
+ ]
56
+ };
3
57
 
4
58
  /**
5
59
  * Converts the 2nd layer of a skin (Hat, Jacket) into 3D Voxels.
6
- * This gives the skin actual depth instead of flat planes.
60
+ * Optimized version: Direct buffer manipulation instead of object merging.
61
+ *
7
62
  * @param {THREE.Texture} texture
8
63
  * @param {Object} layerDef - Definition of UVs and dimensions.
9
64
  * @returns {THREE.BufferGeometry|null} Merged geometry of all voxels.
10
65
  */
11
66
  export function createVoxelLayer(texture, layerDef) {
12
- const canvas = document.createElement('canvas');
13
- canvas.width = 64; canvas.height = 64;
14
- const ctx = canvas.getContext('2d');
15
- ctx.drawImage(texture.image, 0, 0);
16
- const imgData = ctx.getImageData(0, 0, 64, 64).data;
17
-
18
- const geometries = [];
67
+ const imgData = getImageData(texture.image);
19
68
  const { outer } = layerDef.uv;
20
69
  const { w, h, d } = layerDef.size;
21
70
 
22
- // Mapping faces of the body part to UV coordinates
71
+ const positions = [];
72
+ const normals = [];
73
+ const uvs = [];
74
+ const indices = [];
75
+
76
+ let vertexOffset = 0;
77
+
78
+ const addVoxel = (cx, cy, cz, sx, sy, sz, u, v) => {
79
+ const uCoord = (u + 0.5) / 64;
80
+ const vCoord = 1.0 - (v + 0.5) / 64;
81
+
82
+ for (let i = 0; i < 24; i++) {
83
+ const vx = CUBE.vertices[i * 3] * sx + cx;
84
+ const vy = CUBE.vertices[i * 3 + 1] * sy + cy;
85
+ const vz = CUBE.vertices[i * 3 + 2] * sz + cz;
86
+
87
+ positions.push(vx, vy, vz);
88
+ normals.push(CUBE.normals[i * 3], CUBE.normals[i * 3 + 1], CUBE.normals[i * 3 + 2]);
89
+ uvs.push(uCoord, vCoord);
90
+ }
91
+
92
+ for (let i = 0; i < CUBE.indices.length; i++) {
93
+ indices.push(CUBE.indices[i] + vertexOffset);
94
+ }
95
+ vertexOffset += 24;
96
+ };
97
+
23
98
  const faces = [
24
- { u: outer.x+d, v: outer.y+d, w: w, h: h, pos: (i,j) => new THREE.Vector3(i-w/2+0.5, j-h/2+0.5, d/2+0.25) },
25
- { u: outer.x+d+w+d, v: outer.y+d, w: w, h: h, pos: (i,j) => new THREE.Vector3(-(i-w/2+0.5), j-h/2+0.5, -d/2-0.25) },
26
- { u: outer.x, v: outer.y+d, w: d, h: h, pos: (i,j) => new THREE.Vector3(-w/2-0.25, j-h/2+0.5, i-d/2+0.5), sx: 0.5 },
27
- { u: outer.x+d+w, v: outer.y+d, w: d, h: h, pos: (i,j) => new THREE.Vector3(w/2+0.25, j-h/2+0.5, -(i-d/2+0.5)), sx: 0.5 },
28
- { u: outer.x+d, v: outer.y, w: w, h: d, pos: (i,j) => new THREE.Vector3(i-w/2+0.5, h/2+0.25, -(j-d/2+0.5)), sy: 0.5 },
29
- { u: outer.x+d+w, v: outer.y, w: w, h: d, pos: (i,j) => new THREE.Vector3(i-w/2+0.5, -h/2-0.25, (d - 1 - j) - d/2 + 0.5), sy: 0.5 }
99
+ // Face 0: Front (Z+)
100
+ {
101
+ u: outer.x + d, v: outer.y + d, width: w, height: h,
102
+ pos: (i, j) => ({ x: i - w/2 + 0.5, y: j - h/2 + 0.5, z: d/2 + 0.25 }),
103
+ scale: { z: 0.5 }
104
+ },
105
+ // Face 1: Back (Z-)
106
+ {
107
+ u: outer.x + d + w + d, v: outer.y + d, width: w, height: h,
108
+ pos: (i, j) => ({ x: -(i - w/2 + 0.5), y: j - h/2 + 0.5, z: -d/2 - 0.25 }),
109
+ scale: { z: 0.5 }
110
+ },
111
+ // Face 2: Right UV / Left 3D (X-)
112
+ {
113
+ u: outer.x, v: outer.y + d, width: d, height: h,
114
+ pos: (i, j) => ({ x: -w/2 - 0.25, y: j - h/2 + 0.5, z: i - d/2 + 0.5 }),
115
+ scale: { x: 0.5 }
116
+ },
117
+ // Face 3: Left UV / Right 3D (X+)
118
+ {
119
+ u: outer.x + d + w, v: outer.y + d, width: d, height: h,
120
+ pos: (i, j) => ({ x: w/2 + 0.25, y: j - h/2 + 0.5, z: -(i - d/2 + 0.5) }),
121
+ scale: { x: 0.5 }
122
+ },
123
+ // Face 4: Top (Y+)
124
+ {
125
+ u: outer.x + d, v: outer.y, width: w, height: d,
126
+ pos: (i, j) => ({ x: i - w/2 + 0.5, y: h/2 + 0.25, z: -(j - d/2 + 0.5) }),
127
+ scale: { y: 0.5 }
128
+ },
129
+ // Face 5: Bottom (Y-)
130
+ {
131
+ u: outer.x + d + w, v: outer.y, width: w, height: d,
132
+ pos: (i, j) => ({ x: i - w/2 + 0.5, y: -h/2 - 0.25, z: (d - 1 - j) - d/2 + 0.5 }),
133
+ scale: { y: 0.5 }
134
+ }
30
135
  ];
31
136
 
32
- const baseGeo = new THREE.BoxGeometry(1, 1, 1);
33
-
34
137
  faces.forEach(f => {
35
- for(let i=0; i<f.w; i++) {
36
- for(let j=0; j<f.h; j++) {
138
+ for (let i = 0; i < f.width; i++) {
139
+ for (let j = 0; j < f.height; j++) {
37
140
  const u = f.u + i;
38
141
  const v = f.v + j;
39
142
 
40
- if(imgData[(v*64+u)*4+3] > 0) {
41
- const geo = baseGeo.clone();
42
-
43
- const uvAttr = geo.attributes.uv;
44
- for(let k=0; k<uvAttr.count; k++) uvAttr.setXY(k, (u+0.5)/64, 1.0-(v+0.5)/64);
45
-
46
- const m = new THREE.Matrix4();
47
- const scale = new THREE.Vector3(f.sx||1, f.sy||1, f.sz||1);
48
- if(f === faces[0] || f === faces[1]) scale.z = 0.5;
49
- m.compose(f.pos(i, (f.h-1)-j), new THREE.Quaternion(), scale);
50
- geo.applyMatrix4(m);
51
- geometries.push(geo);
143
+ const alphaIndex = (v * 64 + u) * 4 + 3;
144
+ if (imgData[alphaIndex] > 0) {
145
+ const pos = f.pos(i, (f.height - 1) - j);
146
+ const sx = f.scale?.x ?? 1;
147
+ const sy = f.scale?.y ?? 1;
148
+ const sz = f.scale?.z ?? 1;
149
+ addVoxel(pos.x, pos.y, pos.z, sx, sy, sz, u, v);
52
150
  }
53
151
  }
54
152
  }
55
153
  });
56
154
 
57
- return geometries.length > 0 ? BufferGeometryUtils.mergeGeometries(geometries) : null;
155
+ if (positions.length === 0) return null;
156
+
157
+ const geometry = new THREE.BufferGeometry();
158
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
159
+ geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
160
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
161
+ geometry.setIndex(indices);
162
+
163
+ return geometry;
58
164
  }