pcbjam/wasm/stubs/glu_wasm_impl.cpp

333 lines
10 KiB
C++
Raw Normal View History

/**
* GLU Tesselator implementation for WebAssembly builds
*
* Uses KiCad's earcut-based POLYGON_TRIANGULATION instead of GLU library.
* This enables OpenGL GAL to work in WASM with proper polygon triangulation.
*/
#include <vector>
#include <array>
#include <cstring>
#include <geometry/shape_line_chain.h>
#include <geometry/shape_poly_set.h>
#include <geometry/polygon_triangulation.h>
// GL types
typedef double GLdouble;
typedef float GLfloat;
typedef unsigned int GLenum;
typedef unsigned char GLboolean;
typedef void GLvoid;
typedef void (*_GLUfuncptr)(void);
// GL constants
#ifndef GL_TRUE
#define GL_TRUE 1
#endif
#ifndef GL_FALSE
#define GL_FALSE 0
#endif
// GLU constants
#define GLU_TESS_BEGIN 100100
#define GLU_TESS_VERTEX 100101
#define GLU_TESS_END 100102
#define GLU_TESS_ERROR 100103
#define GLU_TESS_EDGE_FLAG 100104
#define GLU_TESS_COMBINE 100105
#define GLU_TESS_BEGIN_DATA 100106
#define GLU_TESS_VERTEX_DATA 100107
#define GLU_TESS_END_DATA 100108
#define GLU_TESS_ERROR_DATA 100109
#define GLU_TESS_EDGE_FLAG_DATA 100110
#define GLU_TESS_COMBINE_DATA 100111
#define GLU_TESS_WINDING_RULE 100140
#define GLU_TESS_WINDING_ODD 100130
#define GLU_TESS_WINDING_NONZERO 100131
#define GLU_TESS_WINDING_POSITIVE 100132
#define GLU_TESS_WINDING_NEGATIVE 100133
#define GLU_TESS_WINDING_ABS_GEQ_TWO 100134
// Vertex data stored during tesselation
struct TessVertex
{
std::array<GLdouble, 3> coords;
void* userData; // The data pointer passed to gluTessVertex
};
struct GLUtesselator
{
// Callbacks
void (*vertexCallback)(void* vertex) = nullptr;
void (*vertexDataCallback)(void* vertex, void* userData) = nullptr;
void (*combineCallback)(GLdouble coords[3], void* vertex_data[4],
GLfloat weight[4], void** dataOut) = nullptr;
void (*combineDataCallback)(GLdouble coords[3], void* vertex_data[4],
GLfloat weight[4], void** dataOut, void* userData) = nullptr;
void (*edgeFlagCallback)(GLboolean flag) = nullptr;
void (*edgeFlagDataCallback)(GLboolean flag, void* userData) = nullptr;
void (*errorCallback)(GLenum error) = nullptr;
void (*errorDataCallback)(GLenum error, void* userData) = nullptr;
void (*beginCallback)(GLenum type) = nullptr;
void (*beginDataCallback)(GLenum type, void* userData) = nullptr;
void (*endCallback)() = nullptr;
void (*endDataCallback)(void* userData) = nullptr;
// Contour data - each contour is a list of vertices
std::vector<std::vector<TessVertex>> contours;
std::vector<TessVertex>* currentContour = nullptr;
// User data passed to gluTessBeginPolygon
void* polygonUserData = nullptr;
// Properties
GLenum windingRule = GLU_TESS_WINDING_POSITIVE;
};
extern "C" {
GLUtesselator* gluNewTess()
{
return new GLUtesselator();
}
void gluDeleteTess(GLUtesselator* tess)
{
delete tess;
}
void gluTessCallback(GLUtesselator* tess, GLenum which, _GLUfuncptr fn)
{
if (!tess) return;
switch (which) {
case GLU_TESS_VERTEX:
tess->vertexCallback = (void(*)(void*))fn;
break;
case GLU_TESS_VERTEX_DATA:
tess->vertexDataCallback = (void(*)(void*, void*))fn;
break;
case GLU_TESS_COMBINE:
tess->combineCallback = (void(*)(GLdouble[3], void*[4], GLfloat[4], void**))fn;
break;
case GLU_TESS_COMBINE_DATA:
tess->combineDataCallback = (void(*)(GLdouble[3], void*[4], GLfloat[4], void**, void*))fn;
break;
case GLU_TESS_EDGE_FLAG:
tess->edgeFlagCallback = (void(*)(GLboolean))fn;
break;
case GLU_TESS_EDGE_FLAG_DATA:
tess->edgeFlagDataCallback = (void(*)(GLboolean, void*))fn;
break;
case GLU_TESS_ERROR:
tess->errorCallback = (void(*)(GLenum))fn;
break;
case GLU_TESS_ERROR_DATA:
tess->errorDataCallback = (void(*)(GLenum, void*))fn;
break;
case GLU_TESS_BEGIN:
tess->beginCallback = (void(*)(GLenum))fn;
break;
case GLU_TESS_BEGIN_DATA:
tess->beginDataCallback = (void(*)(GLenum, void*))fn;
break;
case GLU_TESS_END:
tess->endCallback = (void(*)())fn;
break;
case GLU_TESS_END_DATA:
tess->endDataCallback = (void(*)(void*))fn;
break;
}
}
void gluTessProperty(GLUtesselator* tess, GLenum which, GLdouble value)
{
if (!tess) return;
if (which == GLU_TESS_WINDING_RULE)
tess->windingRule = static_cast<GLenum>(value);
}
void gluGetTessProperty(GLUtesselator* tess, GLenum which, GLdouble* value)
{
if (!tess || !value) return;
if (which == GLU_TESS_WINDING_RULE)
*value = static_cast<GLdouble>(tess->windingRule);
}
void gluTessNormal(GLUtesselator* tess, GLdouble x, GLdouble y, GLdouble z)
{
// Ignored - earcut works in 2D (XY plane)
(void)tess; (void)x; (void)y; (void)z;
}
void gluTessBeginPolygon(GLUtesselator* tess, void* userData)
{
if (!tess) return;
tess->contours.clear();
tess->currentContour = nullptr;
tess->polygonUserData = userData;
}
void gluTessBeginContour(GLUtesselator* tess)
{
if (!tess) return;
tess->contours.emplace_back();
tess->currentContour = &tess->contours.back();
}
void gluTessVertex(GLUtesselator* tess, GLdouble coords[3], void* data)
{
if (!tess || !tess->currentContour) return;
TessVertex v;
v.coords = {coords[0], coords[1], coords[2]};
v.userData = data;
tess->currentContour->push_back(v);
}
void gluTessEndContour(GLUtesselator* tess)
{
if (!tess) return;
tess->currentContour = nullptr;
}
void gluTessEndPolygon(GLUtesselator* tess)
{
if (!tess) return;
// Need at least one contour with 3+ vertices
if (tess->contours.empty())
return;
const auto& mainContour = tess->contours[0];
if (mainContour.size() < 3)
return;
// Convert to SHAPE_LINE_CHAIN for earcut
// Note: earcut works with integer coordinates, so we scale appropriately
// KiCad uses nanometers internally, so coordinates are already integers
SHAPE_LINE_CHAIN chain;
for (const auto& v : mainContour) {
chain.Append(VECTOR2I(static_cast<int>(v.coords[0]),
static_cast<int>(v.coords[1])));
}
chain.SetClosed(true);
// Triangulate using KiCad's earcut implementation
SHAPE_POLY_SET::TRIANGULATED_POLYGON result(-1);
POLYGON_TRIANGULATION triangulator(result);
if (!triangulator.TesselatePolygon(chain, nullptr)) {
// Tesselation failed - call error callback if registered
if (tess->errorDataCallback)
tess->errorDataCallback(100151, tess->polygonUserData); // GLU_TESS_ERROR1
else if (tess->errorCallback)
tess->errorCallback(100151);
return;
}
// Call edge flag callback to indicate we're producing triangles
// (edge flag callback forces GLU to output only triangles, which earcut always does)
if (tess->edgeFlagDataCallback)
tess->edgeFlagDataCallback(GL_TRUE, tess->polygonUserData);
else if (tess->edgeFlagCallback)
tess->edgeFlagCallback(GL_TRUE);
// Output triangles via vertex callback
// The result contains triangle indices that map back to input vertices
size_t triCount = result.GetTriangleCount();
for (size_t i = 0; i < triCount; i++) {
VECTOR2I a, b, c;
result.GetTriangle(i, a, b, c);
// Find the original vertex indices by matching coordinates
// (POLYGON_TRIANGULATION preserves vertex order for the first N vertices)
auto findVertex = [&](const VECTOR2I& pt) -> size_t {
for (size_t j = 0; j < mainContour.size(); j++) {
if (static_cast<int>(mainContour[j].coords[0]) == pt.x &&
static_cast<int>(mainContour[j].coords[1]) == pt.y) {
return j;
}
}
return 0; // fallback
};
size_t idxA = findVertex(a);
size_t idxB = findVertex(b);
size_t idxC = findVertex(c);
// Call vertex callback for each triangle vertex
// The callback receives the user data pointer that was passed to gluTessVertex
if (tess->vertexDataCallback) {
tess->vertexDataCallback(mainContour[idxA].userData, tess->polygonUserData);
tess->vertexDataCallback(mainContour[idxB].userData, tess->polygonUserData);
tess->vertexDataCallback(mainContour[idxC].userData, tess->polygonUserData);
} else if (tess->vertexCallback) {
tess->vertexCallback(mainContour[idxA].userData);
tess->vertexCallback(mainContour[idxB].userData);
tess->vertexCallback(mainContour[idxC].userData);
}
}
}
const unsigned char* gluErrorString(GLenum error)
{
static const unsigned char errStr[] = "GLU tesselator error";
(void)error;
return errStr;
}
//=============================================================================
// Quadric stubs - not implemented (used for 3D primitives, not critical)
//=============================================================================
typedef struct GLUquadric GLUquadric;
GLUquadric* gluNewQuadric()
{
return nullptr;
}
void gluDeleteQuadric(GLUquadric* q)
{
(void)q;
}
void gluQuadricDrawStyle(GLUquadric* q, int s)
{
(void)q; (void)s;
}
void gluQuadricNormals(GLUquadric* q, int n)
{
(void)q; (void)n;
}
void gluCylinder(GLUquadric* q, double b, double t, double h, int sl, int st)
{
(void)q; (void)b; (void)t; (void)h; (void)sl; (void)st;
}
void gluDisk(GLUquadric* q, double i, double o, int sl, int lp)
{
(void)q; (void)i; (void)o; (void)sl; (void)lp;
}
void gluSphere(GLUquadric* q, double r, int sl, int st)
{
(void)q; (void)r; (void)sl; (void)st;
}
void gluPerspective(double fovy, double aspect, double zNear, double zFar)
{
(void)fovy; (void)aspect; (void)zNear; (void)zFar;
}
} // extern "C"