@voltstack/headless-rasterizer 1.0.0
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/binding.gyp +15 -0
- package/index.js +1 -0
- package/package.json +20 -0
- package/src/rasterizer/rasterizer.h +766 -0
- package/src/rasterizer/stb_image_write.h +1724 -0
- package/src/rasterizer.cpp +75 -0
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright(c) 2025, Rodolfo Herrera Hernandez. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files(the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
#pragma once
|
|
24
|
+
|
|
25
|
+
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
26
|
+
#include "stb_image_write.h"
|
|
27
|
+
|
|
28
|
+
#include <cstdlib>
|
|
29
|
+
#include <cstdint>
|
|
30
|
+
#include <cstring>
|
|
31
|
+
#include <cmath>
|
|
32
|
+
#include <vector>
|
|
33
|
+
#include <thread>
|
|
34
|
+
#include <algorithm>
|
|
35
|
+
#include <string>
|
|
36
|
+
|
|
37
|
+
#include <sys/mman.h>
|
|
38
|
+
#include <sys/stat.h>
|
|
39
|
+
#include <fcntl.h>
|
|
40
|
+
#include <unistd.h>
|
|
41
|
+
|
|
42
|
+
class Rasterizer{
|
|
43
|
+
public:
|
|
44
|
+
/**
|
|
45
|
+
* @brief Rendering options.
|
|
46
|
+
*/
|
|
47
|
+
struct Options{
|
|
48
|
+
// Vertical field-of-view in degress.
|
|
49
|
+
float fovDeg = 60.f;
|
|
50
|
+
// Multiplicative scale factor applied to camera distance.
|
|
51
|
+
float distScale = 1.f;
|
|
52
|
+
// If true: Z is up; otherwise Y is up.
|
|
53
|
+
bool zUp = true;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @brief Rasterize a GLB file to a PNG file
|
|
58
|
+
*/
|
|
59
|
+
bool rasterize(
|
|
60
|
+
const char* glbPath,
|
|
61
|
+
const char* pngPath,
|
|
62
|
+
int width,
|
|
63
|
+
int height,
|
|
64
|
+
float azDeg,
|
|
65
|
+
float elDeg,
|
|
66
|
+
const Options &opts
|
|
67
|
+
){
|
|
68
|
+
// Map the GLB file and extract lightweight views into its buffers.
|
|
69
|
+
MMapFile mm;
|
|
70
|
+
GLBView glb{};
|
|
71
|
+
if(!parseGLB_MMap(glbPath, glb, mm)){
|
|
72
|
+
mm.close();
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if(glb.vertexCount == 0 || !glb.pos){
|
|
77
|
+
mm.close();
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Compute objects bounds to derive camera framing.
|
|
82
|
+
Bounds b = computeBoundsParallel(glb.pos, glb.vertexCount);
|
|
83
|
+
float cx = (b.minX + b.maxX) * 0.5f;
|
|
84
|
+
float cy = (b.minY + b.maxY) * 0.5f;
|
|
85
|
+
float cz = (b.minZ + b.maxZ) * 0.5f;
|
|
86
|
+
|
|
87
|
+
float dx = b.maxX - b.minX, dy = b.maxY - b.minY, dz = b.maxZ - b.minZ;
|
|
88
|
+
float radius = 0.5f * std::sqrt(dx * dx + dy * dy + dz * dz);
|
|
89
|
+
|
|
90
|
+
// Build camera matrices (lookAt + perspective)
|
|
91
|
+
float fovRad = opts.fovDeg * 3.1415926535f / 180.f;
|
|
92
|
+
float aspect = (float)width / height;
|
|
93
|
+
|
|
94
|
+
float distance = 1.2f * radius / std::tan(fovRad * 0.5f) * opts.distScale;
|
|
95
|
+
if(distance < 1e-3f) distance = 1e-3f;
|
|
96
|
+
|
|
97
|
+
float znear = std::max(1e-3f, distance - radius * 2.f);
|
|
98
|
+
float zfar = distance + radius * 2.f;
|
|
99
|
+
|
|
100
|
+
float az = azDeg * 3.1415926535f / 180.f;
|
|
101
|
+
float el = elDeg * 3.1415926535f / 180.f;
|
|
102
|
+
|
|
103
|
+
float dirX, dirY, dirZ;
|
|
104
|
+
if(opts.zUp){
|
|
105
|
+
dirX = std::cos(el) * std::cos(az);
|
|
106
|
+
dirY = std::cos(el) * std::sin(az);
|
|
107
|
+
dirZ = std::sin(el);
|
|
108
|
+
}else{
|
|
109
|
+
dirX = std::cos(el) * std::cos(az);
|
|
110
|
+
dirY = std::sin(el);
|
|
111
|
+
dirZ = std::cos(el) * std::sin(az);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
float eyeX = cx + dirX * distance;
|
|
115
|
+
float eyeY = cy + dirY * distance;
|
|
116
|
+
float eyeZ = cz + dirZ * distance;
|
|
117
|
+
|
|
118
|
+
float upX = 0.f;
|
|
119
|
+
float upY = opts.zUp ? 0.f : 1.f;
|
|
120
|
+
float upZ = opts.zUp ? 1.f : 0.f;
|
|
121
|
+
|
|
122
|
+
Mat4 view = Mat4::lookAt(eyeX, eyeY, eyeZ, cx, cy, cz, upX, upY, upZ);
|
|
123
|
+
Mat4 proj = Mat4::perspective(fovRad, aspect, znear, zfar);
|
|
124
|
+
Mat4 mvp = proj * view;
|
|
125
|
+
|
|
126
|
+
// Allocate and initialize z-buffer + RGBA color buffer
|
|
127
|
+
size_t pixels = (size_t)width * (size_t)height;
|
|
128
|
+
|
|
129
|
+
uint32_t* zBuffer = (uint32_t*) aligned_malloc(pixels * 4);
|
|
130
|
+
uint8_t* colorBuffer = (uint8_t*) aligned_malloc(pixels * 4);
|
|
131
|
+
if(!zBuffer || !colorBuffer){
|
|
132
|
+
free(zBuffer);
|
|
133
|
+
free(colorBuffer);
|
|
134
|
+
mm.close();
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for(size_t i = 0; i < pixels; i++){
|
|
139
|
+
zBuffer[i] = 0xFFFFFFFF;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
std::memset(colorBuffer, 0, pixels * 4);
|
|
143
|
+
|
|
144
|
+
// Rasterize
|
|
145
|
+
if(glb.type == GLBType::TRIANGLES && glb.indexCount > 0){
|
|
146
|
+
rasterizeTriangles(glb, mvp, width, height, zBuffer, colorBuffer);
|
|
147
|
+
}else{
|
|
148
|
+
rasterizePoints(glb.pos, glb.col, glb.vertexCount, mvp, width, height, zBuffer, colorBuffer);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Write PNG output
|
|
152
|
+
int ok = stbi_write_png(pngPath, width, height, 4, colorBuffer, width * 4);
|
|
153
|
+
|
|
154
|
+
// Cleanup
|
|
155
|
+
free(zBuffer);
|
|
156
|
+
free(colorBuffer);
|
|
157
|
+
mm.close();
|
|
158
|
+
return ok != 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private:
|
|
162
|
+
static void* aligned_malloc(size_t size, size_t align = 32){
|
|
163
|
+
void* p = nullptr;
|
|
164
|
+
size = (size + (align - 1)) & ~(align - 1);
|
|
165
|
+
if(posix_memalign(&p, align, size) != 0) return nullptr;
|
|
166
|
+
return p;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static inline uint32_t read_u32(const void* p){
|
|
170
|
+
uint32_t v;
|
|
171
|
+
std::memcpy(&v, p, 4);
|
|
172
|
+
return v;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
struct MMapFile{
|
|
176
|
+
int fd = -1;
|
|
177
|
+
size_t size = 0;
|
|
178
|
+
uint8_t* data = nullptr;
|
|
179
|
+
|
|
180
|
+
bool openRead(const char* path){
|
|
181
|
+
fd = ::open(path, O_RDONLY);
|
|
182
|
+
if(fd < 0) return false;
|
|
183
|
+
|
|
184
|
+
struct stat st;
|
|
185
|
+
if(fstat(fd, &st) != 0) return false;
|
|
186
|
+
|
|
187
|
+
size = (size_t) st.st_size;
|
|
188
|
+
data = (uint8_t*) mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
189
|
+
|
|
190
|
+
if(!data || data == MAP_FAILED){
|
|
191
|
+
data = nullptr;
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
void close(){
|
|
199
|
+
if(data) munmap(data, size);
|
|
200
|
+
if(fd >= 0) ::close(fd);
|
|
201
|
+
|
|
202
|
+
fd = -1;
|
|
203
|
+
size = 0;
|
|
204
|
+
data = nullptr;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
enum class GLBType{
|
|
209
|
+
POINTS,
|
|
210
|
+
TRIANGLES
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
struct GLBView{
|
|
214
|
+
const float* pos = nullptr;
|
|
215
|
+
const float* col = nullptr;
|
|
216
|
+
const float* normals = nullptr;
|
|
217
|
+
const uint16_t* idx16 = nullptr;
|
|
218
|
+
const uint32_t* idx32 = nullptr;
|
|
219
|
+
size_t vertexCount = 0;
|
|
220
|
+
size_t indexCount = 0;
|
|
221
|
+
size_t colorStride = 3; // 3 for VEC3 (RGB), 4 for VEC4 (RGBA)
|
|
222
|
+
GLBType type = GLBType::POINTS;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
static int findInt(const std::string& json, const char* key){
|
|
226
|
+
std::string k = std::string("\"") + key + "\":";
|
|
227
|
+
size_t p = json.find(k);
|
|
228
|
+
if(p == std::string::npos) return -1;
|
|
229
|
+
return std::atoi(json.c_str() + p + k.size());
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Find integer value for a key starting from a given position
|
|
233
|
+
static int findIntFrom(const std::string& json, const char* key, size_t startPos){
|
|
234
|
+
std::string k = std::string("\"") + key + "\":";
|
|
235
|
+
size_t p = json.find(k, startPos);
|
|
236
|
+
if(p == std::string::npos) return -1;
|
|
237
|
+
return std::atoi(json.c_str() + p + k.size());
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Parse bufferView info: byteOffset and byteLength
|
|
241
|
+
struct BufferViewInfo {
|
|
242
|
+
size_t byteOffset = 0;
|
|
243
|
+
size_t byteLength = 0;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
static BufferViewInfo parseBufferView(const std::string& json, int index) {
|
|
247
|
+
BufferViewInfo info;
|
|
248
|
+
// Find "bufferViews" array
|
|
249
|
+
size_t bvPos = json.find("\"bufferViews\"");
|
|
250
|
+
if (bvPos == std::string::npos) return info;
|
|
251
|
+
|
|
252
|
+
// Find the opening bracket
|
|
253
|
+
size_t arrStart = json.find('[', bvPos);
|
|
254
|
+
if (arrStart == std::string::npos) return info;
|
|
255
|
+
|
|
256
|
+
// Navigate to the nth bufferView object
|
|
257
|
+
size_t pos = arrStart + 1;
|
|
258
|
+
int currentIndex = 0;
|
|
259
|
+
int braceDepth = 0;
|
|
260
|
+
size_t objStart = 0;
|
|
261
|
+
|
|
262
|
+
while (pos < json.size() && currentIndex <= index) {
|
|
263
|
+
char c = json[pos];
|
|
264
|
+
if (c == '{') {
|
|
265
|
+
if (braceDepth == 0) objStart = pos;
|
|
266
|
+
braceDepth++;
|
|
267
|
+
} else if (c == '}') {
|
|
268
|
+
braceDepth--;
|
|
269
|
+
if (braceDepth == 0) {
|
|
270
|
+
if (currentIndex == index) {
|
|
271
|
+
// Found our bufferView, parse it
|
|
272
|
+
std::string bvJson = json.substr(objStart, pos - objStart + 1);
|
|
273
|
+
info.byteOffset = findIntFrom(bvJson, "byteOffset", 0);
|
|
274
|
+
if (info.byteOffset == (size_t)-1) info.byteOffset = 0;
|
|
275
|
+
info.byteLength = findIntFrom(bvJson, "byteLength", 0);
|
|
276
|
+
if (info.byteLength == (size_t)-1) info.byteLength = 0;
|
|
277
|
+
return info;
|
|
278
|
+
}
|
|
279
|
+
currentIndex++;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
pos++;
|
|
283
|
+
}
|
|
284
|
+
return info;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
static bool parseGLB_MMap(const char* path, GLBView& out, MMapFile& mm){
|
|
288
|
+
if(!mm.openRead(path)) return false;
|
|
289
|
+
if(mm.size < 12) return false;
|
|
290
|
+
|
|
291
|
+
const uint8_t* p = mm.data;
|
|
292
|
+
if(read_u32(p) != 0x46546C67u || read_u32(p + 4) != 2u) return false;
|
|
293
|
+
|
|
294
|
+
size_t off = 12;
|
|
295
|
+
const uint8_t* jsonData = nullptr;
|
|
296
|
+
uint32_t jsonLen = 0;
|
|
297
|
+
const uint8_t* bin = nullptr;
|
|
298
|
+
uint32_t binLen = 0;
|
|
299
|
+
|
|
300
|
+
while(off + 8 <= mm.size){
|
|
301
|
+
uint32_t clen = read_u32(p + off);
|
|
302
|
+
uint32_t ctyp = read_u32(p + off + 4);
|
|
303
|
+
|
|
304
|
+
off += 8;
|
|
305
|
+
if(off + clen > mm.size) break;
|
|
306
|
+
|
|
307
|
+
if(ctyp == 0x4E4F534Au){
|
|
308
|
+
jsonData = p + off;
|
|
309
|
+
jsonLen = clen;
|
|
310
|
+
}else if(ctyp == 0x004E4942u){
|
|
311
|
+
bin = p + off;
|
|
312
|
+
binLen = clen;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
off += clen;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if(!bin || binLen == 0) return false;
|
|
319
|
+
|
|
320
|
+
std::string json((char*) jsonData, jsonLen);
|
|
321
|
+
|
|
322
|
+
int mode = findInt(json, "mode");
|
|
323
|
+
bool isMesh = (mode == 4);
|
|
324
|
+
out.type = isMesh ? GLBType::TRIANGLES : GLBType::POINTS;
|
|
325
|
+
|
|
326
|
+
if(isMesh){
|
|
327
|
+
// Check if COLOR_0 exists and determine if it's VEC3 or VEC4
|
|
328
|
+
bool hasColors = (json.find("COLOR_0") != std::string::npos);
|
|
329
|
+
bool colorIsVec4 = false;
|
|
330
|
+
if(hasColors){
|
|
331
|
+
size_t colorAccPos = json.find("\"COLOR_0\"");
|
|
332
|
+
if(colorAccPos != std::string::npos){
|
|
333
|
+
size_t vec4Pos = json.find("\"VEC4\"", colorAccPos);
|
|
334
|
+
size_t vec3Pos = json.find("\"VEC3\"", colorAccPos);
|
|
335
|
+
if(vec4Pos != std::string::npos && (vec3Pos == std::string::npos || vec4Pos < vec3Pos)){
|
|
336
|
+
colorIsVec4 = true;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
out.colorStride = colorIsVec4 ? 4 : 3;
|
|
341
|
+
|
|
342
|
+
// Determine index type (5123 = uint16, 5125 = uint32)
|
|
343
|
+
size_t pos5123 = json.find("5123");
|
|
344
|
+
size_t pos5125 = json.find("5125");
|
|
345
|
+
bool useU16 = (pos5123 != std::string::npos && (pos5125 == std::string::npos || pos5123 < pos5125));
|
|
346
|
+
|
|
347
|
+
// Parse bufferViews to get correct offsets
|
|
348
|
+
// Layout for mesh with colors: BV0=positions, BV1=normals, BV2=colors, BV3=indices
|
|
349
|
+
// Layout for mesh without colors: BV0=positions, BV1=normals, BV2=indices
|
|
350
|
+
BufferViewInfo bvPos = parseBufferView(json, 0);
|
|
351
|
+
BufferViewInfo bvNorm = parseBufferView(json, 1);
|
|
352
|
+
BufferViewInfo bvCol, bvIdx;
|
|
353
|
+
|
|
354
|
+
if(hasColors){
|
|
355
|
+
bvCol = parseBufferView(json, 2);
|
|
356
|
+
bvIdx = parseBufferView(json, 3);
|
|
357
|
+
}else{
|
|
358
|
+
bvIdx = parseBufferView(json, 2);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Validate bufferView data - reject if no valid geometry
|
|
362
|
+
if(bvPos.byteLength == 0 || bvIdx.byteLength == 0){
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Validate offsets are within binary buffer bounds
|
|
367
|
+
if(bvPos.byteOffset + bvPos.byteLength > binLen ||
|
|
368
|
+
bvNorm.byteOffset + bvNorm.byteLength > binLen ||
|
|
369
|
+
bvIdx.byteOffset + bvIdx.byteLength > binLen){
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
if(hasColors && bvCol.byteOffset + bvCol.byteLength > binLen){
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Calculate vertex count from position buffer (VEC3 floats)
|
|
377
|
+
out.vertexCount = bvPos.byteLength / (3 * sizeof(float));
|
|
378
|
+
|
|
379
|
+
// Calculate index count
|
|
380
|
+
if(useU16){
|
|
381
|
+
out.indexCount = bvIdx.byteLength / sizeof(uint16_t);
|
|
382
|
+
}else{
|
|
383
|
+
out.indexCount = bvIdx.byteLength / sizeof(uint32_t);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Reject empty meshes
|
|
387
|
+
if(out.vertexCount == 0 || out.indexCount == 0){
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Set pointers using bufferView offsets
|
|
392
|
+
out.pos = (const float*)(bin + bvPos.byteOffset);
|
|
393
|
+
out.normals = (const float*)(bin + bvNorm.byteOffset);
|
|
394
|
+
|
|
395
|
+
if(hasColors && bvCol.byteLength > 0){
|
|
396
|
+
out.col = (const float*)(bin + bvCol.byteOffset);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if(out.indexCount > 0){
|
|
400
|
+
if(useU16){
|
|
401
|
+
out.idx16 = (const uint16_t*)(bin + bvIdx.byteOffset);
|
|
402
|
+
}else{
|
|
403
|
+
out.idx32 = (const uint32_t*)(bin + bvIdx.byteOffset);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}else{
|
|
407
|
+
// Point cloud: simple interleaved layout
|
|
408
|
+
size_t totalFloats = binLen / 4;
|
|
409
|
+
const float* floatData = (const float*) bin;
|
|
410
|
+
|
|
411
|
+
if((totalFloats % 6) == 0){
|
|
412
|
+
out.vertexCount = totalFloats / 6;
|
|
413
|
+
out.pos = floatData;
|
|
414
|
+
out.col = floatData + (out.vertexCount * 3);
|
|
415
|
+
}else if((totalFloats % 3) == 0){
|
|
416
|
+
out.vertexCount = totalFloats / 3;
|
|
417
|
+
out.pos = floatData;
|
|
418
|
+
out.col = nullptr;
|
|
419
|
+
}else{
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return out.pos != nullptr && out.vertexCount > 0;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
struct Mat4{
|
|
428
|
+
float m[16];
|
|
429
|
+
|
|
430
|
+
static Mat4 identity(){
|
|
431
|
+
Mat4 r{};
|
|
432
|
+
r.m[0] = r.m[5] = r.m[10] = r.m[15] = 1.f;
|
|
433
|
+
return r;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
static Mat4 lookAt(
|
|
437
|
+
float eyeX,
|
|
438
|
+
float eyeY,
|
|
439
|
+
float eyeZ,
|
|
440
|
+
float cx,
|
|
441
|
+
float cy,
|
|
442
|
+
float cz,
|
|
443
|
+
float upX,
|
|
444
|
+
float upY,
|
|
445
|
+
float upZ
|
|
446
|
+
){
|
|
447
|
+
float fx = cx - eyeX;
|
|
448
|
+
float fy = cy - eyeY;
|
|
449
|
+
float fz = cz - eyeZ;
|
|
450
|
+
|
|
451
|
+
float fl = std::sqrt(fx * fx + fy * fy + fz * fz);
|
|
452
|
+
if(fl < 1e-20f) fl = 1.f;
|
|
453
|
+
|
|
454
|
+
fx /= fl;
|
|
455
|
+
fy /= fl;
|
|
456
|
+
fz /= fl;
|
|
457
|
+
|
|
458
|
+
float sx = fy * upZ - fz * upY;
|
|
459
|
+
float sy = fz * upX - fx * upZ;
|
|
460
|
+
float sz = fx * upY - fy * upX;
|
|
461
|
+
|
|
462
|
+
float sl = std::sqrt(sx * sx + sy * sy + sz * sz);
|
|
463
|
+
if(sl < 1e-20f) sl = 1.f;
|
|
464
|
+
|
|
465
|
+
sx /= sl;
|
|
466
|
+
sy /= sl;
|
|
467
|
+
sz /= sl;
|
|
468
|
+
|
|
469
|
+
float ux = sy * fz - sz * fy;
|
|
470
|
+
float uy = sz * fx - sx * fz;
|
|
471
|
+
float uz = sx * fy - sy * fx;
|
|
472
|
+
|
|
473
|
+
Mat4 r{};
|
|
474
|
+
r.m[0] = sx;
|
|
475
|
+
r.m[4] = sy;
|
|
476
|
+
r.m[8] = sz;
|
|
477
|
+
r.m[12] = -(sx * eyeX + sy * eyeY + sz * eyeZ);
|
|
478
|
+
|
|
479
|
+
r.m[1] = ux;
|
|
480
|
+
r.m[5] = uy;
|
|
481
|
+
r.m[9] = uz;
|
|
482
|
+
r.m[13] = -(ux * eyeX + uy * eyeY + uz * eyeZ);
|
|
483
|
+
|
|
484
|
+
r.m[2] = -fx;
|
|
485
|
+
r.m[6] = -fy;
|
|
486
|
+
r.m[10] = -fz;
|
|
487
|
+
r.m[14] = (fx * eyeX + fy * eyeY + fz * eyeZ);
|
|
488
|
+
|
|
489
|
+
r.m[3] = 0.f;
|
|
490
|
+
r.m[7] = 0.f;
|
|
491
|
+
r.m[11] = 0.f;
|
|
492
|
+
r.m[15] = 1.f;
|
|
493
|
+
|
|
494
|
+
return r;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
static Mat4 perspective(float fovRad, float aspect, float znear, float zfar){
|
|
498
|
+
float f = 1.0f / std::tan(fovRad * 0.5f);
|
|
499
|
+
Mat4 r{};
|
|
500
|
+
r.m[0] = f / aspect;
|
|
501
|
+
r.m[5] = f;
|
|
502
|
+
r.m[10] = (zfar + znear) / (znear - zfar);
|
|
503
|
+
r.m[11] = -1.f;
|
|
504
|
+
r.m[14] = (2.f * zfar * znear) / (znear - zfar);
|
|
505
|
+
return r;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
Mat4 operator*(const Mat4& b) const{
|
|
509
|
+
Mat4 r{};
|
|
510
|
+
for(int i = 0; i < 4; i++){
|
|
511
|
+
const float a0 = m[i];
|
|
512
|
+
const float a1 = m[4 + i];
|
|
513
|
+
const float a2 = m[8 + i];
|
|
514
|
+
const float a3 = m[12 + i];
|
|
515
|
+
|
|
516
|
+
r.m[0 * 4 + i] = a0 * b.m[0] + a1 * b.m[1] + a2 * b.m[2] + a3 * b.m[3];
|
|
517
|
+
r.m[1 * 4 + i] = a0 * b.m[4] + a1 * b.m[5] + a2 * b.m[6] + a3 * b.m[7];
|
|
518
|
+
r.m[2 * 4 + i] = a0 * b.m[8] + a1 * b.m[9] + a2 * b.m[10] + a3 * b.m[11];
|
|
519
|
+
r.m[3 * 4 + i] = a0 * b.m[12] + a1 * b.m[13] + a2 * b.m[14] + a3 * b.m[15];
|
|
520
|
+
}
|
|
521
|
+
return r;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
struct Vec4{
|
|
526
|
+
float x, y, z, w;
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
static inline Vec4 project4(const Mat4& mvp, float x, float y, float z){
|
|
530
|
+
Vec4 r;
|
|
531
|
+
r.x = mvp.m[0] * x + mvp.m[4] * y + mvp.m[8] * z + mvp.m[12];
|
|
532
|
+
r.y = mvp.m[1] * x + mvp.m[5] * y + mvp.m[9] * z + mvp.m[13];
|
|
533
|
+
r.z = mvp.m[2] * x + mvp.m[6] * y + mvp.m[10] * z + mvp.m[14];
|
|
534
|
+
r.w = mvp.m[3] * x + mvp.m[7] * y + mvp.m[11] * z + mvp.m[15];
|
|
535
|
+
return r;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
static inline uint8_t toU8(float x){
|
|
539
|
+
if(x < 0.f) x = 0.f;
|
|
540
|
+
if(x > 1.f) x = 1.f;
|
|
541
|
+
return (uint8_t)(x * 255.f + 0.5f);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
struct Bounds{
|
|
545
|
+
float minX, minY, minZ;
|
|
546
|
+
float maxX, maxY, maxZ;
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
static Bounds computeBoundsParallel(const float* pos, size_t n){
|
|
550
|
+
unsigned T = std::min(16u, std::max(1u, std::thread::hardware_concurrency()));
|
|
551
|
+
std::vector<Bounds> b(T);
|
|
552
|
+
size_t block = (n + T - 1) / T;
|
|
553
|
+
|
|
554
|
+
std::vector<std::thread> threads;
|
|
555
|
+
for(unsigned t = 0; t < T; t++){
|
|
556
|
+
size_t start = t * block;
|
|
557
|
+
size_t end = std::min(start + block, n);
|
|
558
|
+
threads.emplace_back([&, t, start, end](){
|
|
559
|
+
Bounds bb = {1e30f, 1e30f, 1e30f, -1e30f, -1e30f, -1e30f};
|
|
560
|
+
for(size_t i = start; i < end; i++){
|
|
561
|
+
float x = pos[i * 3], y = pos[i * 3 + 1], z = pos[i * 3 + 2];
|
|
562
|
+
if(x < bb.minX) bb.minX = x;
|
|
563
|
+
if(x > bb.maxX) bb.maxX = x;
|
|
564
|
+
|
|
565
|
+
if(y < bb.minY) bb.minY = y;
|
|
566
|
+
if(y > bb.maxY) bb.maxY = y;
|
|
567
|
+
|
|
568
|
+
if(z < bb.minZ) bb.minZ = z;
|
|
569
|
+
if(z > bb.maxZ) bb.maxZ = z;
|
|
570
|
+
}
|
|
571
|
+
b[t] = bb;
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
for(auto &a : threads) a.join();
|
|
576
|
+
|
|
577
|
+
Bounds out = b[0];
|
|
578
|
+
for(unsigned t = 1; t < T; t++){
|
|
579
|
+
out.minX = std::min(out.minX, b[t].minX);
|
|
580
|
+
out.maxX = std::max(out.maxX, b[t].maxX);
|
|
581
|
+
|
|
582
|
+
out.minY = std::min(out.minY, b[t].minY);
|
|
583
|
+
out.maxY = std::max(out.maxY, b[t].maxY);
|
|
584
|
+
|
|
585
|
+
out.minZ = std::min(out.minZ, b[t].minZ);
|
|
586
|
+
out.maxZ = std::max(out.maxZ, b[t].maxZ);
|
|
587
|
+
}
|
|
588
|
+
return out;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
static void rasterizePoints(
|
|
592
|
+
const float* pos,
|
|
593
|
+
const float* col,
|
|
594
|
+
size_t n,
|
|
595
|
+
const Mat4& mvp,
|
|
596
|
+
int width,
|
|
597
|
+
int height,
|
|
598
|
+
uint32_t* zBuffer,
|
|
599
|
+
uint8_t* colorBuffer
|
|
600
|
+
){
|
|
601
|
+
// Adaptive point radius based on density
|
|
602
|
+
float density = (float) n / ((float) width * height);
|
|
603
|
+
int pointRadius = density <= 0.1f ? 5 : (density <= 0.5f ? 4 : (density <= 1.5f ? 3 : 2));
|
|
604
|
+
int r2 = pointRadius * pointRadius;
|
|
605
|
+
|
|
606
|
+
// Keep z/color writes ordered to avoid non-deterministic buffer races.
|
|
607
|
+
for(size_t i = 0; i < n; i++){
|
|
608
|
+
Vec4 vec = project4(mvp, pos[i * 3], pos[i * 3 + 1], pos[i * 3 + 2]);
|
|
609
|
+
if(vec.w <= 1e-6f) continue;
|
|
610
|
+
|
|
611
|
+
float invW = 1.f / vec.w;
|
|
612
|
+
float ndcX = vec.x * invW, ndcY = vec.y * invW, ndcZ = vec.z * invW;
|
|
613
|
+
if(ndcZ < -1.f || ndcZ > 1.f) continue;
|
|
614
|
+
|
|
615
|
+
int ix = (int)((ndcX * 0.5f + 0.5f) * width);
|
|
616
|
+
int iy = (int)((1.f - (ndcY * 0.5f + 0.5f)) * height);
|
|
617
|
+
if(ix < -pointRadius || ix >= width + pointRadius ||
|
|
618
|
+
iy < -pointRadius || iy >= height + pointRadius) continue;
|
|
619
|
+
|
|
620
|
+
uint32_t z24 = (uint32_t)((ndcZ + 1.f) * 0.5f * 16777215.f);
|
|
621
|
+
uint8_t R = col ? toU8(col[i * 3]) : 180;
|
|
622
|
+
uint8_t G = col ? toU8(col[i * 3 + 1]) : 180;
|
|
623
|
+
uint8_t B = col ? toU8(col[i * 3 + 2]) : 180;
|
|
624
|
+
|
|
625
|
+
// Draw circular splat
|
|
626
|
+
for(int dy = -pointRadius; dy <= pointRadius; dy++){
|
|
627
|
+
int yy = iy + dy;
|
|
628
|
+
if(yy < 0 || yy >= height) continue;
|
|
629
|
+
|
|
630
|
+
for(int dx = -pointRadius; dx <= pointRadius; dx++){
|
|
631
|
+
int xx = ix + dx;
|
|
632
|
+
if(xx < 0 || xx >= width) continue;
|
|
633
|
+
|
|
634
|
+
// Circle test
|
|
635
|
+
if(dx * dx + dy * dy > r2) continue;
|
|
636
|
+
|
|
637
|
+
size_t idx = yy * width + xx;
|
|
638
|
+
if(z24 < zBuffer[idx]){
|
|
639
|
+
zBuffer[idx] = z24;
|
|
640
|
+
colorBuffer[idx * 4] = R;
|
|
641
|
+
colorBuffer[idx * 4 + 1] = G;
|
|
642
|
+
colorBuffer[idx * 4 + 2] = B;
|
|
643
|
+
colorBuffer[idx * 4 + 3] = 255;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
static inline float edgeFunc(float ax, float ay, float bx, float by, float cx, float cy){
|
|
651
|
+
return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
static void rasterizeTriangle(
|
|
655
|
+
float x0, float y0, float z0, uint8_t r0, uint8_t g0, uint8_t b0,
|
|
656
|
+
float x1, float y1, float z1, uint8_t r1, uint8_t g1, uint8_t b1,
|
|
657
|
+
float x2, float y2, float z2, uint8_t r2, uint8_t g2, uint8_t b2,
|
|
658
|
+
int width, int height, uint32_t* zBuffer, uint8_t* colorBuffer
|
|
659
|
+
){
|
|
660
|
+
float area = edgeFunc(x0, y0, x1, y1, x2, y2);
|
|
661
|
+
if(area >= 0) return;
|
|
662
|
+
float invArea = 1.f / area;
|
|
663
|
+
|
|
664
|
+
int minX = std::max(0, (int)std::floor(std::min({x0, x1, x2})));
|
|
665
|
+
int maxX = std::min(width - 1, (int)std::ceil(std::max({x0, x1, x2})));
|
|
666
|
+
int minY = std::max(0, (int)std::floor(std::min({y0, y1, y2})));
|
|
667
|
+
int maxY = std::min(height - 1, (int)std::ceil(std::max({y0, y1, y2})));
|
|
668
|
+
|
|
669
|
+
for(int py = minY; py <= maxY; py++){
|
|
670
|
+
float pyf = py + 0.5f;
|
|
671
|
+
for(int px = minX; px <= maxX; px++){
|
|
672
|
+
float pxf = px + 0.5f;
|
|
673
|
+
|
|
674
|
+
float w0 = edgeFunc(x1, y1, x2, y2, pxf, pyf) * invArea;
|
|
675
|
+
float w1 = edgeFunc(x2, y2, x0, y0, pxf, pyf) * invArea;
|
|
676
|
+
float w2 = edgeFunc(x0, y0, x1, y1, pxf, pyf) * invArea;
|
|
677
|
+
|
|
678
|
+
if(w0 < 0 || w1 < 0 || w2 < 0) continue;
|
|
679
|
+
|
|
680
|
+
float z = z0 * w0 + z1 * w1 + z2 * w2;
|
|
681
|
+
uint32_t z24 = (uint32_t)(z * 16777215.f);
|
|
682
|
+
|
|
683
|
+
size_t idx = py * width + px;
|
|
684
|
+
if(z24 < zBuffer[idx]){
|
|
685
|
+
zBuffer[idx] = z24;
|
|
686
|
+
colorBuffer[idx * 4] = (uint8_t)(r0 * w0 + r1 * w1 + r2 * w2);
|
|
687
|
+
colorBuffer[idx * 4 + 1] = (uint8_t)(g0 * w0 + g1 * w1 + g2 * w2);
|
|
688
|
+
colorBuffer[idx * 4 + 2] = (uint8_t)(b0 * w0 + b1 * w1 + b2 * w2);
|
|
689
|
+
colorBuffer[idx * 4 + 3] = 255;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
static void rasterizeTriangles(
|
|
696
|
+
const GLBView& glb,
|
|
697
|
+
const Mat4& mvp,
|
|
698
|
+
int width,
|
|
699
|
+
int height,
|
|
700
|
+
uint32_t* zBuffer,
|
|
701
|
+
uint8_t* colorBuffer
|
|
702
|
+
){
|
|
703
|
+
size_t triCount = glb.indexCount / 3;
|
|
704
|
+
if(triCount == 0) return;
|
|
705
|
+
|
|
706
|
+
// Keep z/color writes ordered to avoid non-deterministic buffer races.
|
|
707
|
+
for(size_t tri = 0; tri < triCount; tri++){
|
|
708
|
+
uint32_t i0, i1, i2;
|
|
709
|
+
if(glb.idx16){
|
|
710
|
+
i0 = glb.idx16[tri * 3];
|
|
711
|
+
i1 = glb.idx16[tri * 3 + 1];
|
|
712
|
+
i2 = glb.idx16[tri * 3 + 2];
|
|
713
|
+
}else if(glb.idx32){
|
|
714
|
+
i0 = glb.idx32[tri * 3];
|
|
715
|
+
i1 = glb.idx32[tri * 3 + 1];
|
|
716
|
+
i2 = glb.idx32[tri * 3 + 2];
|
|
717
|
+
}else{
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if(i0 >= glb.vertexCount || i1 >= glb.vertexCount || i2 >= glb.vertexCount) continue;
|
|
722
|
+
|
|
723
|
+
Vec4 v0 = project4(mvp, glb.pos[i0 * 3], glb.pos[i0 * 3 + 1], glb.pos[i0 * 3 + 2]);
|
|
724
|
+
Vec4 v1 = project4(mvp, glb.pos[i1 * 3], glb.pos[i1 * 3 + 1], glb.pos[i1 * 3 + 2]);
|
|
725
|
+
Vec4 v2 = project4(mvp, glb.pos[i2 * 3], glb.pos[i2 * 3 + 1], glb.pos[i2 * 3 + 2]);
|
|
726
|
+
|
|
727
|
+
if(v0.w <= 1e-6f || v1.w <= 1e-6f || v2.w <= 1e-6f) continue;
|
|
728
|
+
|
|
729
|
+
float x0 = (v0.x / v0.w * 0.5f + 0.5f) * width;
|
|
730
|
+
float y0 = (1.f - (v0.y / v0.w * 0.5f + 0.5f)) * height;
|
|
731
|
+
float z0 = (v0.z / v0.w + 1.f) * 0.5f;
|
|
732
|
+
|
|
733
|
+
float x1 = (v1.x / v1.w * 0.5f + 0.5f) * width;
|
|
734
|
+
float y1 = (1.f - (v1.y / v1.w * 0.5f + 0.5f)) * height;
|
|
735
|
+
float z1 = (v1.z / v1.w + 1.f) * 0.5f;
|
|
736
|
+
|
|
737
|
+
float x2 = (v2.x / v2.w * 0.5f + 0.5f) * width;
|
|
738
|
+
float y2 = (1.f - (v2.y / v2.w * 0.5f + 0.5f)) * height;
|
|
739
|
+
float z2 = (v2.z / v2.w + 1.f) * 0.5f;
|
|
740
|
+
|
|
741
|
+
uint8_t r0 = 180, g0 = 180, b0 = 180;
|
|
742
|
+
uint8_t r1 = 180, g1 = 180, b1 = 180;
|
|
743
|
+
uint8_t r2 = 180, g2 = 180, b2 = 180;
|
|
744
|
+
|
|
745
|
+
if(glb.col){
|
|
746
|
+
size_t stride = glb.colorStride;
|
|
747
|
+
r0 = toU8(glb.col[i0 * stride]);
|
|
748
|
+
g0 = toU8(glb.col[i0 * stride + 1]);
|
|
749
|
+
b0 = toU8(glb.col[i0 * stride + 2]);
|
|
750
|
+
|
|
751
|
+
r1 = toU8(glb.col[i1 * stride]);
|
|
752
|
+
g1 = toU8(glb.col[i1 * stride + 1]);
|
|
753
|
+
b1 = toU8(glb.col[i1 * stride + 2]);
|
|
754
|
+
|
|
755
|
+
r2 = toU8(glb.col[i2 * stride]);
|
|
756
|
+
g2 = toU8(glb.col[i2 * stride + 1]);
|
|
757
|
+
b2 = toU8(glb.col[i2 * stride + 2]);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
rasterizeTriangle(x0, y0, z0, r0, g0, b0,
|
|
761
|
+
x1, y1, z1, r1, g1, b1,
|
|
762
|
+
x2, y2, z2, r2, g2, b2,
|
|
763
|
+
width, height, zBuffer, colorBuffer);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
};
|