@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 +1 -1
- package/src/api/model-api.js +69 -57
- package/src/models/uv-mapper.js +12 -10
- package/src/runtime/electron-runner.js +40 -13
package/package.json
CHANGED
package/src/api/model-api.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
147
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
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);
|
package/src/models/uv-mapper.js
CHANGED
|
@@ -252,18 +252,20 @@ class UVMapper {
|
|
|
252
252
|
return result;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
autoMap(elementFrom, elementTo) {
|
|
256
|
-
var
|
|
257
|
-
var
|
|
258
|
-
var
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
up:
|
|
266
|
-
down:
|
|
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
|
|
453
|
-
js.push(" var
|
|
454
|
-
js.push("
|
|
455
|
-
js.push("
|
|
456
|
-
js.push("
|
|
457
|
-
js.push("
|
|
458
|
-
js.push("
|
|
459
|
-
js.push("
|
|
460
|
-
js.push("
|
|
461
|
-
js.push("
|
|
462
|
-
js.push("
|
|
463
|
-
js.push("
|
|
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
|
}
|