@erik9994857/cag 2.0.4 → 2.0.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erik9994857/cag",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "CaG — A code library and custom language for building 3D worlds with .cagc files, .bbmodel support, and auto UV mapping",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -70,6 +70,7 @@ class ModelAPI {
70
70
  geometry: null,
71
71
  texture: null,
72
72
  uvMappings: null,
73
+ allTextures: [],
73
74
  valid: false
74
75
  };
75
76
 
@@ -83,20 +84,24 @@ class ModelAPI {
83
84
  }
84
85
 
85
86
  if (resource.texture) {
86
- result.texture = this.loadTexture(resource.texture);
87
+ const tex = this.loadTexture(resource.texture);
88
+ result.texture = tex;
89
+ result.allTextures = [tex.dataURL];
87
90
  } else if (embeddedTextures.length > 0) {
88
- result.texture = {
89
- path: null,
90
- width: result.geometry ? result.geometry.resolution.width : 16,
91
- height: result.geometry ? result.geometry.resolution.height : 16,
92
- format: "png",
93
- size: 0,
94
- dataURL: embeddedTextures[0].source
95
- };
91
+ const resW = result.geometry ? result.geometry.resolution.width : 16;
92
+ const resH = result.geometry ? result.geometry.resolution.height : 16;
93
+ result.allTextures = embeddedTextures.map(function (t) { return t.source; });
94
+ if (embeddedTextures.length === 1) {
95
+ result.texture = {
96
+ path: null, width: resW, height: resH,
97
+ format: "png", size: 0, dataURL: embeddedTextures[0].source
98
+ };
99
+ } else {
100
+ this.remapToAtlas(result, embeddedTextures, resW, resH);
101
+ }
96
102
  }
97
103
 
98
104
  if (result.geometry && result.texture && result.uvMappings) {
99
- result.uvMappings = this.validateAndFixUVMappings(result.uvMappings, result.texture);
100
105
  result.valid = true;
101
106
  } else if (result.geometry) {
102
107
  result.valid = true;
@@ -105,6 +110,28 @@ class ModelAPI {
105
110
  return result;
106
111
  }
107
112
 
113
+ remapToAtlas(result, textures, texW, texH) {
114
+ const numTex = textures.length;
115
+ const atlasW = numTex * texW;
116
+ const atlasH = texH;
117
+
118
+ for (let i = 0; i < result.uvMappings.length; i++) {
119
+ const m = result.uvMappings[i];
120
+ const idx = m.textureIndex || 0;
121
+ const texIdx = Math.min(idx, numTex - 1);
122
+ const offsetU = texIdx * texW;
123
+ m.uv = [m.uv[0] + offsetU, m.uv[1], m.uv[2] + offsetU, m.uv[3]];
124
+ }
125
+
126
+ result.texture = {
127
+ path: null, width: atlasW, height: atlasH,
128
+ format: "png", size: 0, dataURL: textures[0].source
129
+ };
130
+ if (result.geometry) {
131
+ result.geometry.resolution = { width: atlasW, height: atlasH };
132
+ }
133
+ }
134
+
108
135
  parseBBModel(filePath) {
109
136
  const raw = fs.readFileSync(filePath, "utf-8");
110
137
  let data;
@@ -122,10 +149,11 @@ class ModelAPI {
122
149
  };
123
150
 
124
151
  const uvMappings = [];
125
-
126
- const useBoxUV = !!data.box_uv;
127
152
  const uvMapper = new UVMapper(geometry.resolution);
128
153
 
154
+ // Detect box_uv: can be at root level or in meta
155
+ const projectBoxUV = !!(data.box_uv || (data.meta && data.meta.box_uv));
156
+
129
157
  if (data.elements && Array.isArray(data.elements)) {
130
158
  for (const element of data.elements) {
131
159
  const geomElement = {
@@ -137,60 +165,44 @@ class ModelAPI {
137
165
  faces: {}
138
166
  };
139
167
 
140
- const needsAutoUV = useBoxUV || (element.box_uv !== undefined && element.box_uv);
141
- let autoUVs = null;
142
- if (needsAutoUV) {
143
- autoUVs = uvMapper.autoMap(geomElement.from, geomElement.to);
168
+ // Element-level box_uv overrides project-level
169
+ const isBoxUV = element.box_uv !== undefined ? !!element.box_uv : projectBoxUV;
170
+
171
+ // For box UV: compute face UVs from geometry + uv_offset
172
+ let boxUVs = null;
173
+ if (isBoxUV) {
174
+ const uvOff = element.uv_offset || [0, 0];
175
+ boxUVs = uvMapper.autoMap(geomElement.from, geomElement.to, uvOff);
144
176
  }
145
177
 
146
- if (element.faces) {
147
- const faceNames = ["north", "south", "east", "west", "up", "down"];
178
+ const faceNames = ["north", "south", "east", "west", "up", "down"];
179
+
180
+ if (isBoxUV) {
181
+ // Box UV mode: compute all face UVs from geometry
182
+ for (const faceName of faceNames) {
183
+ const texIdx = (element.faces && element.faces[faceName] && element.faces[faceName].texture !== undefined)
184
+ ? element.faces[faceName].texture : 0;
185
+ geomElement.faces[faceName] = { uv: boxUVs[faceName], texture: texIdx };
186
+ uvMappings.push({
187
+ element: geomElement.name, face: faceName,
188
+ uv: boxUVs[faceName], textureIndex: texIdx
189
+ });
190
+ }
191
+ } else if (element.faces) {
192
+ // Per-face UV mode: read UVs directly from bbmodel
148
193
  for (const faceName of faceNames) {
149
194
  if (element.faces[faceName]) {
150
195
  const face = element.faces[faceName];
151
- let faceUV = face.uv;
152
-
153
- if (!faceUV || !Array.isArray(faceUV) || faceUV.length < 4) {
154
- faceUV = autoUVs ? autoUVs[faceName] : [0, 0, 16, 16];
155
- }
156
-
157
- geomElement.faces[faceName] = {
158
- uv: faceUV,
159
- texture: face.texture !== undefined ? face.texture : 0
160
- };
196
+ const faceUV = (face.uv && Array.isArray(face.uv) && face.uv.length >= 4)
197
+ ? face.uv : [0, 0, 16, 16];
198
+ const texIdx = face.texture !== undefined ? face.texture : 0;
199
+ geomElement.faces[faceName] = { uv: faceUV, texture: texIdx };
161
200
  uvMappings.push({
162
- element: geomElement.name,
163
- face: faceName,
164
- uv: faceUV,
165
- textureIndex: face.texture !== undefined ? face.texture : 0
166
- });
167
- } else if (needsAutoUV && autoUVs) {
168
- geomElement.faces[faceName] = {
169
- uv: autoUVs[faceName],
170
- texture: 0
171
- };
172
- uvMappings.push({
173
- element: geomElement.name,
174
- face: faceName,
175
- uv: autoUVs[faceName],
176
- textureIndex: 0
201
+ element: geomElement.name, face: faceName,
202
+ uv: faceUV, textureIndex: texIdx
177
203
  });
178
204
  }
179
205
  }
180
- } else if (needsAutoUV && autoUVs) {
181
- const faceNames = ["north", "south", "east", "west", "up", "down"];
182
- for (const faceName of faceNames) {
183
- geomElement.faces[faceName] = {
184
- uv: autoUVs[faceName],
185
- texture: 0
186
- };
187
- uvMappings.push({
188
- element: geomElement.name,
189
- face: faceName,
190
- uv: autoUVs[faceName],
191
- textureIndex: 0
192
- });
193
- }
194
206
  }
195
207
 
196
208
  geometry.elements.push(geomElement);
@@ -252,18 +252,20 @@ class UVMapper {
252
252
  return result;
253
253
  }
254
254
 
255
- autoMap(elementFrom, elementTo) {
256
- var sizeX = Math.abs(elementTo[0] - elementFrom[0]);
257
- var sizeY = Math.abs(elementTo[1] - elementFrom[1]);
258
- var sizeZ = Math.abs(elementTo[2] - elementFrom[2]);
255
+ autoMap(elementFrom, elementTo, uvOffset) {
256
+ var W = Math.abs(elementTo[0] - elementFrom[0]);
257
+ var H = Math.abs(elementTo[1] - elementFrom[1]);
258
+ var D = Math.abs(elementTo[2] - elementFrom[2]);
259
+ var ox = uvOffset ? uvOffset[0] : 0;
260
+ var oy = uvOffset ? uvOffset[1] : 0;
259
261
 
260
262
  return {
261
- north: [sizeZ, sizeZ, sizeZ + sizeX, sizeZ + sizeY],
262
- south: [sizeZ + sizeX + sizeZ, sizeZ, sizeZ + sizeX + sizeZ + sizeX, sizeZ + sizeY],
263
- east: [0, sizeZ, sizeZ, sizeZ + sizeY],
264
- west: [sizeZ + sizeX, sizeZ, sizeZ + sizeX + sizeZ, sizeZ + sizeY],
265
- up: [sizeZ, 0, sizeZ + sizeX, sizeZ],
266
- down: [sizeZ + sizeX, 0, sizeZ + sizeX + sizeX, sizeZ]
263
+ east: [ox, D + oy, D + ox, D + H + oy],
264
+ north: [D + ox, D + oy, D + W + ox, D + H + oy],
265
+ west: [D + W + ox, D + oy, D * 2 + W + ox, D + H + oy],
266
+ south: [D * 2 + W + ox, D + oy, D * 2 + W * 2 + ox, D + H + oy],
267
+ up: [D + W + ox, D + oy, D + ox, oy],
268
+ down: [D + W * 2 + ox, oy, D + W + ox, D + oy]
267
269
  };
268
270
  }
269
271
 
@@ -215,6 +215,7 @@ ElectronRunner.prototype.getRendererSource = function () {
215
215
  }
216
216
  modelDataForRenderer[smName] = {
217
217
  textureDataURL: smData.textureDataURL || null,
218
+ allTextures: smData.allTextures || [],
218
219
  resW: smData.resolution ? smData.resolution.width : 16,
219
220
  resH: smData.resolution ? smData.resolution.height : 16,
220
221
  faceUV: faceUVMap
@@ -448,19 +449,44 @@ ElectronRunner.prototype.getRendererSource = function () {
448
449
  js.push(" this.blockTexture = gl.createTexture();");
449
450
  js.push(" gl.bindTexture(gl.TEXTURE_2D, this.blockTexture);");
450
451
  js.push(" gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([128,128,128,255]));");
452
+ js.push(" function uploadTex(source) {");
453
+ js.push(" var g = game.gl;");
454
+ js.push(" g.bindTexture(g.TEXTURE_2D, game.blockTexture);");
455
+ js.push(" g.texImage2D(g.TEXTURE_2D, 0, g.RGBA, g.RGBA, g.UNSIGNED_BYTE, source);");
456
+ js.push(" g.texParameteri(g.TEXTURE_2D, g.TEXTURE_MIN_FILTER, g.NEAREST);");
457
+ js.push(" g.texParameteri(g.TEXTURE_2D, g.TEXTURE_MAG_FILTER, g.NEAREST);");
458
+ js.push(" g.texParameteri(g.TEXTURE_2D, g.TEXTURE_WRAP_S, g.CLAMP_TO_EDGE);");
459
+ js.push(" g.texParameteri(g.TEXTURE_2D, g.TEXTURE_WRAP_T, g.CLAMP_TO_EDGE);");
460
+ js.push(" }");
451
461
  js.push(" var texKeys = Object.keys(MODELS);");
452
- js.push(" if (texKeys.length > 0 && MODELS[texKeys[0]].textureDataURL) {");
453
- js.push(" var texImg = new Image();");
454
- js.push(" texImg.onload = function() {");
455
- js.push(" var g = game.gl;");
456
- js.push(" g.bindTexture(g.TEXTURE_2D, game.blockTexture);");
457
- js.push(" g.texImage2D(g.TEXTURE_2D, 0, g.RGBA, g.RGBA, g.UNSIGNED_BYTE, texImg);");
458
- js.push(" g.texParameteri(g.TEXTURE_2D, g.TEXTURE_MIN_FILTER, g.NEAREST);");
459
- js.push(" g.texParameteri(g.TEXTURE_2D, g.TEXTURE_MAG_FILTER, g.NEAREST);");
460
- js.push(" g.texParameteri(g.TEXTURE_2D, g.TEXTURE_WRAP_S, g.CLAMP_TO_EDGE);");
461
- js.push(" g.texParameteri(g.TEXTURE_2D, g.TEXTURE_WRAP_T, g.CLAMP_TO_EDGE);");
462
- js.push(" };");
463
- js.push(" texImg.src = MODELS[texKeys[0]].textureDataURL;");
462
+ js.push(" if (texKeys.length > 0) {");
463
+ js.push(" var m = MODELS[texKeys[0]];");
464
+ js.push(" var srcs = m.allTextures && m.allTextures.length > 0 ? m.allTextures : (m.textureDataURL ? [m.textureDataURL] : []);");
465
+ js.push(" if (srcs.length > 1) {");
466
+ js.push(" var loaded = 0, imgs = [];");
467
+ js.push(" for (var ti = 0; ti < srcs.length; ti++) {");
468
+ js.push(" (function(idx) {");
469
+ js.push(" var img = new Image();");
470
+ js.push(" img.onload = function() {");
471
+ js.push(" imgs[idx] = img;");
472
+ js.push(" loaded++;");
473
+ js.push(" if (loaded === srcs.length) {");
474
+ js.push(" var tw = imgs[0].width, th = imgs[0].height;");
475
+ js.push(" var c = document.createElement('canvas');");
476
+ js.push(" c.width = tw * srcs.length; c.height = th;");
477
+ js.push(" var ctx = c.getContext('2d');");
478
+ js.push(" for (var j = 0; j < imgs.length; j++) ctx.drawImage(imgs[j], j * tw, 0);");
479
+ js.push(" uploadTex(c);");
480
+ js.push(" }");
481
+ js.push(" };");
482
+ js.push(" img.src = srcs[idx];");
483
+ js.push(" })(ti);");
484
+ js.push(" }");
485
+ js.push(" } else if (srcs.length === 1) {");
486
+ js.push(" var texImg = new Image();");
487
+ js.push(" texImg.onload = function() { uploadTex(texImg); };");
488
+ js.push(" texImg.src = srcs[0];");
489
+ js.push(" }");
464
490
  js.push(" }");
465
491
  js.push("");
466
492
  js.push(" gl.enable(gl.DEPTH_TEST);");
@@ -696,7 +722,8 @@ ElectronRunner.extractSettings = function (executor) {
696
722
  settings.models[mName] = {
697
723
  uvMappings: model.uvMappings || [],
698
724
  resolution: { width: texW, height: texH },
699
- textureDataURL: model.texture && model.texture.dataURL ? model.texture.dataURL : null
725
+ textureDataURL: model.texture && model.texture.dataURL ? model.texture.dataURL : null,
726
+ allTextures: model.allTextures || []
700
727
  };
701
728
  }
702
729
  }