@erik9994857/cag 2.0.3 → 2.0.5

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.3",
3
+ "version": "2.0.5",
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": {
@@ -1,5 +1,6 @@
1
1
  const path = require("path");
2
2
  const fs = require("fs");
3
+ const UVMapper = require("../models/uv-mapper");
3
4
 
4
5
  class ModelAPI {
5
6
  constructor(resourceMap, defaultModelsPath) {
@@ -69,6 +70,7 @@ class ModelAPI {
69
70
  geometry: null,
70
71
  texture: null,
71
72
  uvMappings: null,
73
+ allTextures: [],
72
74
  valid: false
73
75
  };
74
76
 
@@ -82,20 +84,24 @@ class ModelAPI {
82
84
  }
83
85
 
84
86
  if (resource.texture) {
85
- result.texture = this.loadTexture(resource.texture);
87
+ const tex = this.loadTexture(resource.texture);
88
+ result.texture = tex;
89
+ result.allTextures = [tex.dataURL];
86
90
  } else if (embeddedTextures.length > 0) {
87
- result.texture = {
88
- path: null,
89
- width: result.geometry ? result.geometry.resolution.width : 16,
90
- height: result.geometry ? result.geometry.resolution.height : 16,
91
- format: "png",
92
- size: 0,
93
- dataURL: embeddedTextures[0].source
94
- };
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
+ }
95
102
  }
96
103
 
97
104
  if (result.geometry && result.texture && result.uvMappings) {
98
- result.uvMappings = this.validateAndFixUVMappings(result.uvMappings, result.texture);
99
105
  result.valid = true;
100
106
  } else if (result.geometry) {
101
107
  result.valid = true;
@@ -104,6 +110,28 @@ class ModelAPI {
104
110
  return result;
105
111
  }
106
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
+
107
135
  parseBBModel(filePath) {
108
136
  const raw = fs.readFileSync(filePath, "utf-8");
109
137
  let data;
@@ -121,6 +149,10 @@ class ModelAPI {
121
149
  };
122
150
 
123
151
  const uvMappings = [];
152
+ const uvMapper = new UVMapper(geometry.resolution);
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));
124
156
 
125
157
  if (data.elements && Array.isArray(data.elements)) {
126
158
  for (const element of data.elements) {
@@ -133,20 +165,41 @@ class ModelAPI {
133
165
  faces: {}
134
166
  };
135
167
 
136
- if (element.faces) {
137
- const faceNames = ["north", "south", "east", "west", "up", "down"];
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);
176
+ }
177
+
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
138
193
  for (const faceName of faceNames) {
139
194
  if (element.faces[faceName]) {
140
195
  const face = element.faces[faceName];
141
- geomElement.faces[faceName] = {
142
- uv: face.uv || [0, 0, 16, 16],
143
- texture: face.texture !== undefined ? face.texture : 0
144
- };
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 };
145
200
  uvMappings.push({
146
- element: geomElement.name,
147
- face: faceName,
148
- uv: face.uv || [0, 0, 16, 16],
149
- textureIndex: face.texture !== undefined ? face.texture : 0
201
+ element: geomElement.name, face: faceName,
202
+ uv: faceUV, textureIndex: texIdx
150
203
  });
151
204
  }
152
205
  }
@@ -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, sizeY + sizeZ, sizeZ + sizeX, sizeY + sizeZ + sizeY],
262
- south: [sizeZ + sizeX + sizeZ, sizeY + sizeZ, sizeZ + sizeX + sizeZ + sizeX, sizeY + sizeZ + sizeY],
263
- east: [0, sizeY + sizeZ, sizeZ, sizeY + sizeZ + sizeY],
264
- west: [sizeZ + sizeX, sizeY + sizeZ, sizeZ + sizeX + sizeZ, sizeY + 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
  }