From cb58b43bd1508379d1654f70cab7ce4a9b2fc983 Mon Sep 17 00:00:00 2001 From: Viktor Vaczi Date: Sat, 3 Jan 2026 08:19:40 +0100 Subject: [PATCH] Enable OpenGL GAL and 3D viewer stubs for WASM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Add glu_wasm_impl.cpp: GLU tesselator using KiCad's earcut triangulation - Add GL/glu.h: GLU header for WASM builds - Add 3D viewer stubs for KICAD_BUILD_3D_VIEWER_WASM=ON - Remove obsolete gl_canvas_stub.cpp (OpenGL now uses real impl) - Remove obsolete glu_stub.c (replaced by glu_wasm_impl.cpp) - Update build-pcbnew.sh comment about GLU implementation The OpenGL GAL now works in WASM using WebGL with LEGACY_GL_EMULATION. 3D viewer is stubbed out but builds successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- kicad | 2 +- scripts/kicad/build-pcbnew.sh | 7 +- wasm/stubs/3d_canvas_stub.cpp | 136 ++++++++ wasm/stubs/3d_scenegraph_stub.cpp | 127 ++++++++ wasm/stubs/3d_viewer_stub.cpp | 257 +++++++++++++++ wasm/stubs/GL/glu.h | 88 ++++++ wasm/stubs/gl_canvas_stub.cpp | 509 ------------------------------ wasm/stubs/glu_stub.c | 44 --- wasm/stubs/glu_wasm_impl.cpp | 333 +++++++++++++++++++ 9 files changed, 945 insertions(+), 558 deletions(-) create mode 100644 wasm/stubs/3d_canvas_stub.cpp create mode 100644 wasm/stubs/3d_scenegraph_stub.cpp create mode 100644 wasm/stubs/3d_viewer_stub.cpp create mode 100644 wasm/stubs/GL/glu.h delete mode 100644 wasm/stubs/gl_canvas_stub.cpp delete mode 100644 wasm/stubs/glu_stub.c create mode 100644 wasm/stubs/glu_wasm_impl.cpp diff --git a/kicad b/kicad index 279f2f1..a61d2ea 160000 --- a/kicad +++ b/kicad @@ -1 +1 @@ -Subproject commit 279f2f1a84dc07a831496334a12373d5454f1d38 +Subproject commit a61d2ea4063937eb9536a05b89b2d3d5ead6dd28 diff --git a/scripts/kicad/build-pcbnew.sh b/scripts/kicad/build-pcbnew.sh index aa69fee..12c6bd0 100755 --- a/scripts/kicad/build-pcbnew.sh +++ b/scripts/kicad/build-pcbnew.sh @@ -164,9 +164,8 @@ emar rcs "${STUBS_BUILD}/libgit2_stub.a" "${STUBS_BUILD}/libgit2_stub.o" emcc -c "${STUBS_DIR}/curl_stub.c" -o "${STUBS_BUILD}/curl_stub.o" emar rcs "${STUBS_BUILD}/libcurl_stub.a" "${STUBS_BUILD}/curl_stub.o" -# Compile GLU stub (for 3D viewer) -emcc -c "${STUBS_DIR}/glu_stub.c" -o "${STUBS_BUILD}/glu_stub.o" -emar rcs "${STUBS_BUILD}/libglu_stub.a" "${STUBS_BUILD}/glu_stub.o" +# Note: GLU tesselator is now implemented in wasm/stubs/glu_wasm_impl.cpp +# It's compiled as part of the GAL library (requires KiCad headers) # Compile PCBnew scripting stub (requires wxWidgets headers) WX_CXXFLAGS=$("${WX_BUILD}/wx-config" --cxxflags 2>/dev/null || echo "-I${WX_BUILD}/lib/wx/include/emscripten-unicode-static-3.2 -I${PROJECT_ROOT}/wxwidgets/include") @@ -240,7 +239,7 @@ emcmake cmake "${KICAD_DIR}" \ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_CXX_FLAGS="${EXTRA_FLAGS} -pthread -sUSE_ZLIB=1 -DKICAD_USE_PLATFORM_WASM=1 -I${SYSROOT}/include -I${STUBS_DIR}" \ -DCMAKE_C_FLAGS="${EXTRA_FLAGS} -pthread -sUSE_ZLIB=1 -I${SYSROOT}/include -I${STUBS_DIR}" \ - -DCMAKE_EXE_LINKER_FLAGS="${LINKER_DEBUG_FLAGS} -pthread -sUSE_ZLIB=1 -sASYNCIFY=1 -sDYNCALLS=1 -sASYNCIFY_STACK_SIZE=65536 -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE='navigator.hardwareConcurrency' -sPTHREAD_POOL_SIZE_STRICT=0 -sALLOW_MEMORY_GROWTH=1 -sINITIAL_MEMORY=256MB -sMAXIMUM_MEMORY=4GB -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString','stringToUTF8','lengthBytesUTF8','dynCall'] -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=['\$dynCall'] --bind -L${SYSROOT}/lib ${STUBS_BUILD}/libgit2_stub.a ${STUBS_BUILD}/libcurl_stub.a ${STUBS_BUILD}/libglu_stub.a ${STUBS_BUILD}/libpcbnew_scripting_stub.a ${STUBS_BUILD}/libnng_stub.a ${STUBS_BUILD}/pcbnew_embind.o" \ + -DCMAKE_EXE_LINKER_FLAGS="${LINKER_DEBUG_FLAGS} -pthread -sUSE_ZLIB=1 -sASYNCIFY=1 -sDYNCALLS=1 -sASYNCIFY_STACK_SIZE=65536 -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE='navigator.hardwareConcurrency' -sPTHREAD_POOL_SIZE_STRICT=0 -sALLOW_MEMORY_GROWTH=1 -sINITIAL_MEMORY=256MB -sMAXIMUM_MEMORY=4GB -sLEGACY_GL_EMULATION -sMAX_WEBGL_VERSION=2 -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString','stringToUTF8','lengthBytesUTF8','dynCall'] -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=['\$dynCall'] --bind -L${SYSROOT}/lib ${STUBS_BUILD}/libgit2_stub.a ${STUBS_BUILD}/libcurl_stub.a ${STUBS_BUILD}/libpcbnew_scripting_stub.a ${STUBS_BUILD}/libnng_stub.a ${STUBS_BUILD}/pcbnew_embind.o" \ -DCMAKE_PREFIX_PATH="${SYSROOT};${WX_BUILD}" \ -DwxWidgets_CONFIG_EXECUTABLE="${WX_BUILD}/wx-config" \ \ diff --git a/wasm/stubs/3d_canvas_stub.cpp b/wasm/stubs/3d_canvas_stub.cpp new file mode 100644 index 0000000..0b040bd --- /dev/null +++ b/wasm/stubs/3d_canvas_stub.cpp @@ -0,0 +1,136 @@ +/* + * 3D Canvas stubs for KiCad WASM build + * When KICAD_BUILD_3D_VIEWER_WASM=OFF, these provide stub implementations + * for 3D canvas and board adapter classes. + */ + +#include <3d_canvas/eda_3d_canvas.h> +#include <3d_canvas/board_adapter.h> +#include <3d_viewer/eda_3d_viewer_settings.h> +#include +#include + +// EDA_3D_CANVAS stub implementations + +EDA_3D_CANVAS::EDA_3D_CANVAS( wxWindow* aParent, const wxGLAttributes& aGLAttribs, + BOARD_ADAPTER& aSettings, CAMERA& aCamera, + S3D_CACHE* a3DCachePointer ) : + HIDPI_GL_3D_CANVAS( aParent, aGLAttribs, aCamera, wxID_ANY ) +{ + (void)aSettings; + (void)a3DCachePointer; + m_3d_render = nullptr; + m_parentStatusBar = nullptr; + m_parentInfoBar = nullptr; + m_eventDispatcher = nullptr; +} + +EDA_3D_CANVAS::~EDA_3D_CANVAS() +{ +} + +void EDA_3D_CANVAS::SetEventDispatcher( TOOL_DISPATCHER* aEventDispatcher ) +{ + m_eventDispatcher = aEventDispatcher; +} + +void EDA_3D_CANVAS::ReloadRequest( BOARD* aBoard, S3D_CACHE* aCachePointer ) +{ + (void)aBoard; + (void)aCachePointer; + // No-op: 3D canvas not available in WASM +} + +void EDA_3D_CANVAS::OnPaint( wxPaintEvent& aEvent ) +{ + (void)aEvent; + // No-op: 3D canvas not available in WASM +} + +void EDA_3D_CANVAS::DoRePaint() +{ + // No-op: 3D canvas not available in WASM +} + +void EDA_3D_CANVAS::SetCurrentRender( RENDER_ENGINE aRenderEngine ) +{ + (void)aRenderEngine; + // No-op: 3D canvas not available in WASM +} + +void EDA_3D_CANVAS::RenderEngineChanged() +{ + // No-op: 3D canvas not available in WASM +} + +void EDA_3D_CANVAS::Request_refresh( bool aRedrawImmediately ) +{ + (void)aRedrawImmediately; + // No-op: 3D canvas not available in WASM +} + +void EDA_3D_CANVAS::DisplayStatus() +{ + // No-op: 3D canvas not available in WASM +} + +bool EDA_3D_CANVAS::doReRender() +{ + return false; +} + + +// BOARD_ADAPTER stub implementations + +BOARD_ADAPTER::BOARD_ADAPTER() : + m_board( nullptr ), + m_3dModelManager( nullptr ), + m_Cfg( nullptr ) +{ +} + +BOARD_ADAPTER::~BOARD_ADAPTER() +{ +} + + +// EDA_3D_VIEWER_SETTINGS stub implementations + +EDA_3D_VIEWER_SETTINGS::EDA_3D_VIEWER_SETTINGS() : + APP_SETTINGS_BASE( "3d_viewer", 3 ) +{ +} + +EDA_3D_VIEWER_SETTINGS::~EDA_3D_VIEWER_SETTINGS() +{ +} + + +// LAYER_PRESET_3D stub +LAYER_PRESET_3D::LAYER_PRESET_3D( const wxString& aName ) : + name( aName ) +{ +} + + +// PARAM_LAYER_PRESET_3D stub +PARAM_LAYER_PRESET_3D::PARAM_LAYER_PRESET_3D( const std::string& aPath, + std::vector* aPresetList ) : + PARAM_LAMBDA( + aPath, + [this]() { return presetsToJson(); }, + [this]( const nlohmann::json& aJson ) { jsonToPresets( aJson ); }, + nlohmann::json::array() ), + m_presets( aPresetList ) +{ +} + +nlohmann::json PARAM_LAYER_PRESET_3D::presetsToJson() +{ + return nlohmann::json::array(); +} + +void PARAM_LAYER_PRESET_3D::jsonToPresets( const nlohmann::json& aJson ) +{ + (void)aJson; +} diff --git a/wasm/stubs/3d_scenegraph_stub.cpp b/wasm/stubs/3d_scenegraph_stub.cpp new file mode 100644 index 0000000..7b8590b --- /dev/null +++ b/wasm/stubs/3d_scenegraph_stub.cpp @@ -0,0 +1,127 @@ +/* + * 3D Scenegraph stubs for KiCad WASM build + * When KICAD_BUILD_3D_VIEWER_WASM=OFF, these provide stub implementations + * for 3D cache and scenegraph classes. + */ + +#include <3d_cache/3d_cache.h> +#include <3d_cache/3d_plugin_manager.h> +#include + +// S3D_CACHE stub implementations + +S3D_CACHE::S3D_CACHE() : + m_FNResolver( nullptr ), + m_Plugins( nullptr ), + m_project( nullptr ) +{ +} + +S3D_CACHE::~S3D_CACHE() +{ +} + +bool S3D_CACHE::Set3DConfigDir( const wxString& aConfigDir ) +{ + m_ConfigDir = aConfigDir; + return true; +} + +bool S3D_CACHE::SetProject( PROJECT* aProject ) +{ + m_project = aProject; + return true; +} + +void S3D_CACHE::SetProgramBase( PGM_BASE* aBase ) +{ + (void)aBase; + // No-op: 3D cache not available in WASM +} + +SCENEGRAPH* S3D_CACHE::Load( const wxString& aModelFile, const wxString& aBasePath, + std::vector aEmbeddedFilesStack ) +{ + (void)aModelFile; + (void)aBasePath; + (void)aEmbeddedFilesStack; + // 3D models not supported in WASM + return nullptr; +} + +FILENAME_RESOLVER* S3D_CACHE::GetResolver() noexcept +{ + return m_FNResolver; +} + +std::list const* S3D_CACHE::GetFileFilters() const +{ + static std::list emptyList; + return &emptyList; +} + +void S3D_CACHE::FlushCache( bool closePlugins ) +{ + (void)closePlugins; + // No-op: no cache in WASM +} + +void S3D_CACHE::ClosePlugins() +{ + // No-op: no plugins in WASM +} + +S3DMODEL* S3D_CACHE::GetModel( const wxString& aModelFileName, const wxString& aBasePath, + std::vector aEmbeddedFilesStack ) +{ + (void)aModelFileName; + (void)aBasePath; + (void)aEmbeddedFilesStack; + // 3D models not supported in WASM + return nullptr; +} + +void S3D_CACHE::CleanCacheDir( int aNumDaysOld ) +{ + (void)aNumDaysOld; + // No-op: no cache in WASM +} + +SCENEGRAPH* S3D_CACHE::checkCache( const wxString& aFileName, S3D_CACHE_ENTRY** aCachePtr ) +{ + (void)aFileName; + if( aCachePtr ) + *aCachePtr = nullptr; + return nullptr; +} + +bool S3D_CACHE::getHash( const wxString& aFileName, HASH_128& aHash ) +{ + (void)aFileName; + (void)aHash; + return false; +} + +bool S3D_CACHE::loadCacheData( S3D_CACHE_ENTRY* aCacheItem ) +{ + (void)aCacheItem; + return false; +} + +bool S3D_CACHE::saveCacheData( S3D_CACHE_ENTRY* aCacheItem ) +{ + (void)aCacheItem; + return false; +} + +SCENEGRAPH* S3D_CACHE::load( const wxString& aModelFile, const wxString& aBasePath, + S3D_CACHE_ENTRY** aCachePtr, + std::vector aEmbeddedFilesStack ) +{ + (void)aModelFile; + (void)aBasePath; + (void)aEmbeddedFilesStack; + if( aCachePtr ) + *aCachePtr = nullptr; + return nullptr; +} diff --git a/wasm/stubs/3d_viewer_stub.cpp b/wasm/stubs/3d_viewer_stub.cpp new file mode 100644 index 0000000..cef606c --- /dev/null +++ b/wasm/stubs/3d_viewer_stub.cpp @@ -0,0 +1,257 @@ +/* + * 3D Viewer stubs for KiCad WASM build + * When KICAD_BUILD_3D_VIEWER_WASM=OFF, the 3D viewer library is not linked. + * These stubs provide implementations for symbols that pcbnew references. + */ + +#include <3d_viewer/eda_3d_viewer_frame.h> +#include <3d_viewer/eda_3d_viewer_settings.h> +#include <3d_viewer/toolbars_3d.h> + +// EDA_3D_VIEWER_FRAME stub implementations +// The 3D viewer is not available in WASM builds + +EDA_3D_VIEWER_FRAME::EDA_3D_VIEWER_FRAME( KIWAY* aKiway, PCB_BASE_FRAME* aParent, + const wxString& aTitle, long style ) : + KIWAY_PLAYER( aKiway, aParent, FRAME_PCB_DISPLAY3D, aTitle, wxDefaultPosition, + wxDefaultSize, style, wxT( "3D Viewer" ) ), + m_currentCamera( m_trackBallCamera ), + m_trackBallCamera( 2 * RANGE_SCALE_3D ) +{ + m_canvas = nullptr; + m_disable_ray_tracing = true; + m_appearancePanel = nullptr; + + // 3D Viewer is not supported in WASM - close immediately + wxLogWarning( "3D Viewer is not available in this build" ); +} + +EDA_3D_VIEWER_FRAME::~EDA_3D_VIEWER_FRAME() +{ +} + +void EDA_3D_VIEWER_FRAME::ReloadRequest() +{ + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::NewDisplay( bool aForceImmediateRedraw ) +{ + (void)aForceImmediateRedraw; + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::Redraw() +{ + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg ) +{ + (void)aCfg; + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg ) +{ + (void)aCfg; + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::CommonSettingsChanged( int aFlags ) +{ + (void)aFlags; + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::ShowChangedLanguage() +{ + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::ToggleAppearanceManager() +{ + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::OnDarkModeToggle() +{ + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::TakeScreenshot( EDA_3D_VIEWER_EXPORT_FORMAT aFormat ) +{ + (void)aFormat; + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::ExportImage( EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxSize& aSize ) +{ + (void)aFormat; + (void)aSize; + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::setupUIConditions() +{ + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::handleIconizeEvent( wxIconizeEvent& aEvent ) +{ + (void)aEvent; + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::Exit3DFrame( wxCommandEvent& event ) +{ + (void)event; + Close( true ); +} + +void EDA_3D_VIEWER_FRAME::OnCloseWindow( wxCloseEvent& event ) +{ + (void)event; + Destroy(); +} + +bool EDA_3D_VIEWER_FRAME::TryBefore( wxEvent& aEvent ) +{ + return wxFrame::TryBefore( aEvent ); +} + +void EDA_3D_VIEWER_FRAME::Process_Special_Functions( wxCommandEvent& event ) +{ + (void)event; + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::onDisableRayTracing( wxCommandEvent& aEvent ) +{ + (void)aEvent; + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::OnActivate( wxActivateEvent& event ) +{ + (void)event; + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::OnSetFocus( wxFocusEvent& event ) +{ + (void)event; + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::doReCreateMenuBar() +{ + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::RenderEngineChanged() +{ + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::refreshRender() +{ + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::loadCommonSettings() +{ + // No-op: 3D viewer not available +} + +void EDA_3D_VIEWER_FRAME::applySettings( EDA_3D_VIEWER_SETTINGS* aSettings ) +{ + (void)aSettings; + // No-op: 3D viewer not available +} + +bool EDA_3D_VIEWER_FRAME::getExportFileName( EDA_3D_VIEWER_EXPORT_FORMAT& aFormat, wxString& fullFileName ) +{ + (void)aFormat; + (void)fullFileName; + return false; +} + +wxImage EDA_3D_VIEWER_FRAME::captureScreenshot( const wxSize& aSize ) +{ + (void)aSize; + return wxImage(); +} + +void EDA_3D_VIEWER_FRAME::setupRenderingConfig( BOARD_ADAPTER& adapter ) +{ + (void)adapter; +} + +wxImage EDA_3D_VIEWER_FRAME::captureCurrentViewScreenshot() +{ + return wxImage(); +} + +wxImage EDA_3D_VIEWER_FRAME::captureRaytracingScreenshot( BOARD_ADAPTER& adapter, TRACK_BALL& camera, const wxSize& aSize ) +{ + (void)adapter; + (void)camera; + (void)aSize; + return wxImage(); +} + +wxImage EDA_3D_VIEWER_FRAME::convertRGBAToImage( uint8_t* rgbaBuffer, const wxSize& realSize ) +{ + (void)rgbaBuffer; + (void)realSize; + return wxImage(); +} + +wxImage EDA_3D_VIEWER_FRAME::captureOpenGLScreenshot( BOARD_ADAPTER& adapter, TRACK_BALL& camera, const wxSize& aSize ) +{ + (void)adapter; + (void)camera; + (void)aSize; + return wxImage(); +} + +void EDA_3D_VIEWER_FRAME::configureCanvas( std::unique_ptr& canvas, EDA_3D_VIEWER_SETTINGS* cfg ) +{ + (void)canvas; + (void)cfg; +} + +void EDA_3D_VIEWER_FRAME::saveOrCopyImage( const wxImage& screenshotImage, EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxString& fullFileName ) +{ + (void)screenshotImage; + (void)aFormat; + (void)fullFileName; +} + +void EDA_3D_VIEWER_FRAME::copyImageToClipboard( const wxImage& screenshotImage ) +{ + (void)screenshotImage; +} + +void EDA_3D_VIEWER_FRAME::saveImageToFile( const wxImage& screenshotImage, EDA_3D_VIEWER_EXPORT_FORMAT aFormat, const wxString& fullFileName ) +{ + (void)screenshotImage; + (void)aFormat; + (void)fullFileName; +} + +// Static member +const wxChar* EDA_3D_VIEWER_FRAME::m_logTrace = wxT( "KI_TRACE_EDA_3D_VIEWER" ); + +// Event table (empty - no events to handle) +wxBEGIN_EVENT_TABLE( EDA_3D_VIEWER_FRAME, KIWAY_PLAYER ) +wxEND_EVENT_TABLE() + + +// EDA_3D_VIEWER_TOOLBAR_SETTINGS stub +std::optional EDA_3D_VIEWER_TOOLBAR_SETTINGS::DefaultToolbarConfig( TOOLBAR_LOC aToolbar ) +{ + (void)aToolbar; + return std::nullopt; +} diff --git a/wasm/stubs/GL/glu.h b/wasm/stubs/GL/glu.h new file mode 100644 index 0000000..4981010 --- /dev/null +++ b/wasm/stubs/GL/glu.h @@ -0,0 +1,88 @@ +/** + * GLU header for WebAssembly builds + * + * Provides GLU function declarations. Implementation is in glu_wasm_impl.cpp + * which uses KiCad's earcut-based polygon triangulation. + */ + +#ifndef __GLU_H__ +#define __GLU_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// GLU types +typedef double GLdouble; +typedef void (*_GLUfuncptr)(void); +typedef struct GLUtesselator GLUtesselator; +typedef struct GLUquadric GLUquadric; + +// 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 + +// Tesselator functions +GLUtesselator* gluNewTess(void); +void gluDeleteTess(GLUtesselator* tess); +void gluTessCallback(GLUtesselator* tess, GLenum which, _GLUfuncptr fn); +void gluTessProperty(GLUtesselator* tess, GLenum which, GLdouble value); +void gluGetTessProperty(GLUtesselator* tess, GLenum which, GLdouble* value); +void gluTessNormal(GLUtesselator* tess, GLdouble x, GLdouble y, GLdouble z); +void gluTessBeginPolygon(GLUtesselator* tess, void* userData); +void gluTessBeginContour(GLUtesselator* tess); +void gluTessVertex(GLUtesselator* tess, GLdouble coords[3], void* data); +void gluTessEndContour(GLUtesselator* tess); +void gluTessEndPolygon(GLUtesselator* tess); +const unsigned char* gluErrorString(GLenum error); + +// Quadric constants +#define GLU_OUTSIDE 100020 +#define GLU_INSIDE 100021 +#define GLU_POINT 100010 +#define GLU_LINE 100011 +#define GLU_FILL 100012 +#define GLU_SILHOUETTE 100013 +#define GLU_SMOOTH 100000 +#define GLU_FLAT 100001 +#define GLU_NONE 100002 + +// Quadric type aliases +typedef GLUquadric GLUquadricObj; + +// Quadric functions (stubs - not implemented for WASM) +GLUquadric* gluNewQuadric(void); +void gluDeleteQuadric(GLUquadric* q); +void gluQuadricDrawStyle(GLUquadric* q, GLenum style); +void gluQuadricNormals(GLUquadric* q, GLenum normals); +void gluCylinder(GLUquadric* q, double base, double top, double height, int slices, int stacks); +void gluDisk(GLUquadric* q, double inner, double outer, int slices, int loops); +void gluSphere(GLUquadric* q, double radius, int slices, int stacks); + +// Matrix functions (stub) +void gluPerspective(double fovy, double aspect, double zNear, double zFar); + +#ifdef __cplusplus +} +#endif + +#endif /* __GLU_H__ */ diff --git a/wasm/stubs/gl_canvas_stub.cpp b/wasm/stubs/gl_canvas_stub.cpp deleted file mode 100644 index 14e54b5..0000000 --- a/wasm/stubs/gl_canvas_stub.cpp +++ /dev/null @@ -1,509 +0,0 @@ -/* - * OpenGL Canvas stubs for KiCad WASM build - * OpenGL GAL is not available in WASM (uses Cairo only). - * This stub provides typeinfo and methods for dynamic_cast to work. - */ - -#include -#include -#include <3d_rendering/raytracing/render_3d_raytrace_gl.h> - -//============================================================================= -// Fixed-function OpenGL stubs for WebGL compatibility -// WebGL only supports OpenGL ES 2.0/3.0 which doesn't have these functions -//============================================================================= - -extern "C" { - -void glLoadMatrixf( const float* m ) -{ - (void)m; - // Fixed-function pipeline not available in WebGL -} - -void glPushMatrix() -{ - // Fixed-function pipeline not available in WebGL -} - -void glPopMatrix() -{ - // Fixed-function pipeline not available in WebGL -} - -void glColor4f( float r, float g, float b, float a ) -{ - (void)r; (void)g; (void)b; (void)a; - // Fixed-function pipeline not available in WebGL -} - -void glTranslatef( float x, float y, float z ) -{ - (void)x; (void)y; (void)z; - // Fixed-function pipeline not available in WebGL -} - -void glScalef( float x, float y, float z ) -{ - (void)x; (void)y; (void)z; - // Fixed-function pipeline not available in WebGL -} - -void glRotatef( float angle, float x, float y, float z ) -{ - (void)angle; (void)x; (void)y; (void)z; - // Fixed-function pipeline not available in WebGL -} - -void glEnableClientState( unsigned int cap ) -{ - (void)cap; - // Fixed-function pipeline not available in WebGL -} - -void glDisableClientState( unsigned int cap ) -{ - (void)cap; - // Fixed-function pipeline not available in WebGL -} - -void glPointSize( float size ) -{ - (void)size; - // Fixed-function pipeline not available in WebGL -} - -void glVertex3f( float x, float y, float z ) -{ - (void)x; (void)y; (void)z; - // Immediate mode not available in WebGL -} - -void glBegin( unsigned int mode ) -{ - (void)mode; - // Immediate mode not available in WebGL -} - -void glEnd() -{ - // Immediate mode not available in WebGL -} - -void glColorMaterial( unsigned int face, unsigned int mode ) -{ - (void)face; (void)mode; - // Fixed-function pipeline not available in WebGL -} - -void glNormalPointer( unsigned int type, int stride, const void* pointer ) -{ - (void)type; (void)stride; (void)pointer; - // Fixed-function pipeline not available in WebGL -} - -void glColorPointer( int size, unsigned int type, int stride, const void* pointer ) -{ - (void)size; (void)type; (void)stride; (void)pointer; - // Fixed-function pipeline not available in WebGL -} - -void glTexCoordPointer( int size, unsigned int type, int stride, const void* pointer ) -{ - (void)size; (void)type; (void)stride; (void)pointer; - // Fixed-function pipeline not available in WebGL -} - -void glVertexPointer( int size, unsigned int type, int stride, const void* pointer ) -{ - (void)size; (void)type; (void)stride; (void)pointer; - // Fixed-function pipeline not available in WebGL -} - -void glMaterialfv( unsigned int face, unsigned int pname, const float* params ) -{ - (void)face; (void)pname; (void)params; - // Fixed-function pipeline not available in WebGL -} - -void glMaterialf( unsigned int face, unsigned int pname, float param ) -{ - (void)face; (void)pname; (void)param; - // Fixed-function pipeline not available in WebGL -} - -void glLightfv( unsigned int light, unsigned int pname, const float* params ) -{ - (void)light; (void)pname; (void)params; - // Fixed-function pipeline not available in WebGL -} - -void glLightModelfv( unsigned int pname, const float* params ) -{ - (void)pname; (void)params; - // Fixed-function pipeline not available in WebGL -} - -void glNormal3f( float nx, float ny, float nz ) -{ - (void)nx; (void)ny; (void)nz; - // Fixed-function pipeline not available in WebGL -} - -void glTexCoord2f( float s, float t ) -{ - (void)s; (void)t; - // Fixed-function pipeline not available in WebGL -} - -void glMatrixMode( unsigned int mode ) -{ - (void)mode; - // Fixed-function pipeline not available in WebGL -} - -void glLoadIdentity() -{ - // Fixed-function pipeline not available in WebGL -} - -void glOrtho( double left, double right, double bottom, double top, double nearVal, double farVal ) -{ - (void)left; (void)right; (void)bottom; (void)top; (void)nearVal; (void)farVal; - // Fixed-function pipeline not available in WebGL -} - -void glFrustum( double left, double right, double bottom, double top, double nearVal, double farVal ) -{ - (void)left; (void)right; (void)bottom; (void)top; (void)nearVal; (void)farVal; - // Fixed-function pipeline not available in WebGL -} - -void glMultMatrixf( const float* m ) -{ - (void)m; - // Fixed-function pipeline not available in WebGL -} - -void glShadeModel( unsigned int mode ) -{ - (void)mode; - // Fixed-function pipeline not available in WebGL -} - -void glTexEnvfv( unsigned int target, unsigned int pname, const float* params ) -{ - (void)target; (void)pname; (void)params; - // Fixed-function pipeline not available in WebGL -} - -void glTexEnvf( unsigned int target, unsigned int pname, float param ) -{ - (void)target; (void)pname; (void)param; - // Fixed-function pipeline not available in WebGL -} - -void glTexEnvi( unsigned int target, unsigned int pname, int param ) -{ - (void)target; (void)pname; (void)param; - // Fixed-function pipeline not available in WebGL -} - -void glLightModeli( unsigned int pname, int param ) -{ - (void)pname; (void)param; - // Fixed-function pipeline not available in WebGL -} - -// Display lists - not available in WebGL -unsigned char glIsList( unsigned int list ) -{ - (void)list; - return 0; // Always return false - no display lists in WebGL -} - -void glDeleteLists( unsigned int list, int range ) -{ - (void)list; (void)range; - // Display lists not available in WebGL -} - -unsigned int glGenLists( int range ) -{ - (void)range; - return 0; // Return 0 to indicate failure - no display lists in WebGL -} - -void glNewList( unsigned int list, unsigned int mode ) -{ - (void)list; (void)mode; - // Display lists not available in WebGL -} - -void glEndList() -{ - // Display lists not available in WebGL -} - -void glCallList( unsigned int list ) -{ - (void)list; - // Display lists not available in WebGL -} - -void glCallLists( int n, unsigned int type, const void* lists ) -{ - (void)n; (void)type; (void)lists; - // Display lists not available in WebGL -} - -// More fixed-function pipeline functions -void glColor3f( float r, float g, float b ) -{ - (void)r; (void)g; (void)b; - // Fixed-function pipeline not available in WebGL -} - -void glColor3fv( const float* v ) -{ - (void)v; - // Fixed-function pipeline not available in WebGL -} - -void glColor4fv( const float* v ) -{ - (void)v; - // Fixed-function pipeline not available in WebGL -} - -void glVertex2f( float x, float y ) -{ - (void)x; (void)y; - // Immediate mode not available in WebGL -} - -void glVertex3fv( const float* v ) -{ - (void)v; - // Immediate mode not available in WebGL -} - -void glNormal3fv( const float* v ) -{ - (void)v; - // Fixed-function pipeline not available in WebGL -} - -void glRectf( float x1, float y1, float x2, float y2 ) -{ - (void)x1; (void)y1; (void)x2; (void)y2; - // Immediate mode not available in WebGL -} - -void glLineWidth( float width ) -{ - (void)width; - // Note: WebGL has limited line width support -} - -void glAlphaFunc( unsigned int func, float ref ) -{ - (void)func; (void)ref; - // Fixed-function pipeline not available in WebGL -} - -void glClientActiveTexture( unsigned int texture ) -{ - (void)texture; - // Fixed-function pipeline not available in WebGL -} - -void glScaled( double x, double y, double z ) -{ - (void)x; (void)y; (void)z; - // Fixed-function pipeline not available in WebGL -} - -void glPolygonMode( unsigned int face, unsigned int mode ) -{ - (void)face; (void)mode; - // Not available in WebGL ES -} - -void glClipPlane( unsigned int plane, const double* equation ) -{ - (void)plane; (void)equation; - // Fixed-function pipeline not available in WebGL -} - -void glFogf( unsigned int pname, float param ) -{ - (void)pname; (void)param; - // Fixed-function pipeline not available in WebGL -} - -void glFogfv( unsigned int pname, const float* params ) -{ - (void)pname; (void)params; - // Fixed-function pipeline not available in WebGL -} - -void glFogi( unsigned int pname, int param ) -{ - (void)pname; (void)param; - // Fixed-function pipeline not available in WebGL -} - -} // extern "C" - -//============================================================================= -// HIDPI_GL_CANVAS stub implementation -//============================================================================= - -HIDPI_GL_CANVAS::HIDPI_GL_CANVAS( const KIGFX::VC_SETTINGS& aSettings, wxWindow* aParent, - const wxGLAttributes& aGLAttribs, wxWindowID aId, - const wxPoint& aPos, const wxSize& aSize, long aStyle, - const wxString& aName, const wxPalette& aPalette ) : - wxGLCanvas( aParent, aGLAttribs, aId, aPos, aSize, aStyle, aName, aPalette ), - m_settings( aSettings ) -{ - // Not used in WASM - OpenGL is disabled -} - -wxSize HIDPI_GL_CANVAS::GetNativePixelSize() const -{ - return wxSize( 0, 0 ); -} - -wxPoint HIDPI_GL_CANVAS::GetNativePosition( const wxPoint& aPoint ) const -{ - return aPoint; -} - -double HIDPI_GL_CANVAS::GetScaleFactor() const -{ - return 1.0; // No HiDPI scaling in WASM stub -} - -//============================================================================= -// HIDPI_GL_3D_CANVAS stub implementation -//============================================================================= - -const float HIDPI_GL_3D_CANVAS::m_delta_move_step_factor = 0.7f; - -HIDPI_GL_3D_CANVAS::HIDPI_GL_3D_CANVAS( const KIGFX::VC_SETTINGS& aVcSettings, CAMERA& aCamera, - wxWindow* aParent, const wxGLAttributes& aGLAttribs, - wxWindowID aId, const wxPoint& aPos, const wxSize& aSize, - long aStyle, const wxString& aName, - const wxPalette& aPalette ) : - HIDPI_GL_CANVAS( aVcSettings, aParent, aGLAttribs, aId, aPos, aSize, aStyle, aName, aPalette ), - m_camera( aCamera ) -{ - m_mouse_is_moving = false; - m_mouse_was_moved = false; - m_camera_is_moving = false; - // Not used in WASM - 3D viewer uses RAM raytracer -} - -void HIDPI_GL_3D_CANVAS::OnMouseMoveCamera( wxMouseEvent& event ) -{ - (void)event; - // Not used in WASM -} - -void HIDPI_GL_3D_CANVAS::OnMouseWheelCamera( wxMouseEvent& event, bool aPan ) -{ - (void)event; - (void)aPan; - // Not used in WASM -} - -//============================================================================= -// RENDER_3D_RAYTRACE_GL stub implementation -//============================================================================= - -RENDER_3D_RAYTRACE_GL::RENDER_3D_RAYTRACE_GL( EDA_3D_CANVAS* aCanvas, BOARD_ADAPTER& aAdapter, - CAMERA& aCamera ) : - RENDER_3D_RAYTRACE_BASE( aAdapter, aCamera ) -{ - (void)aCanvas; - m_openglSupportsVertexBufferObjects = false; - m_pboId = 0; - m_pboDataSize = 0; - // Not used in WASM - uses RAM raytracer instead -} - -RENDER_3D_RAYTRACE_GL::~RENDER_3D_RAYTRACE_GL() -{ -} - -void RENDER_3D_RAYTRACE_GL::SetCurWindowSize( const wxSize& aSize ) -{ - (void)aSize; - // Not used in WASM -} - -bool RENDER_3D_RAYTRACE_GL::Redraw( bool aIsMoving, REPORTER* aStatusReporter, - REPORTER* aWarningReporter ) -{ - (void)aIsMoving; - (void)aStatusReporter; - (void)aWarningReporter; - return false; // Not used in WASM -} - -void RENDER_3D_RAYTRACE_GL::initPbo() -{ - // Not used in WASM -} - -void RENDER_3D_RAYTRACE_GL::deletePbo() -{ - // Not used in WASM -} - -//============================================================================= -// NavLib/3DConnexion SpaceMouse plugin stubs -// These are for 3D mouse support which is not available in WASM -//============================================================================= - -// Define empty implementation classes to satisfy unique_ptr -class NL_3D_VIEWER_PLUGIN_IMPL {}; -class NL_FOOTPRINT_PROPERTIES_PLUGIN_IMPL {}; - -#include <3d_navlib/nl_3d_viewer_plugin.h> -#include <3d_navlib/nl_footprint_properties_plugin.h> - -NL_3D_VIEWER_PLUGIN::NL_3D_VIEWER_PLUGIN( EDA_3D_CANVAS* aViewport ) : - m_impl( nullptr ) -{ - (void)aViewport; - // SpaceMouse not available in WASM -} - -NL_3D_VIEWER_PLUGIN::~NL_3D_VIEWER_PLUGIN() -{ -} - -void NL_3D_VIEWER_PLUGIN::SetFocus( bool aFocus ) -{ - (void)aFocus; - // SpaceMouse not available in WASM -} - -NL_FOOTPRINT_PROPERTIES_PLUGIN::NL_FOOTPRINT_PROPERTIES_PLUGIN( EDA_3D_CANVAS* aViewport ) : - m_impl( nullptr ) -{ - (void)aViewport; - // SpaceMouse not available in WASM -} - -NL_FOOTPRINT_PROPERTIES_PLUGIN::~NL_FOOTPRINT_PROPERTIES_PLUGIN() -{ -} - -void NL_FOOTPRINT_PROPERTIES_PLUGIN::SetFocus( bool aFocus ) -{ - (void)aFocus; - // SpaceMouse not available in WASM -} diff --git a/wasm/stubs/glu_stub.c b/wasm/stubs/glu_stub.c deleted file mode 100644 index 82e0e60..0000000 --- a/wasm/stubs/glu_stub.c +++ /dev/null @@ -1,44 +0,0 @@ -/* GLU stubs for WebAssembly builds - * GLU is not available in WebGL, these stubs prevent link errors. - * The gizmo won't render but the 3D view should work with LEGACY_GL_EMULATION. - */ - -typedef struct GLUquadric GLUquadric; -typedef struct GLUtesselator GLUtesselator; -typedef double GLdouble; -typedef void (*_GLUfuncptr)(void); - -/* Quadric stubs */ -GLUquadric* gluNewQuadric(void) { return (GLUquadric*)0; } -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; -} - -/* Tessellation stubs */ -GLUtesselator* gluNewTess(void) { return (GLUtesselator*)0; } -void gluDeleteTess(GLUtesselator* t) { (void)t; } -void gluTessBeginPolygon(GLUtesselator* t, void* d) { (void)t; (void)d; } -void gluTessEndPolygon(GLUtesselator* t) { (void)t; } -void gluTessBeginContour(GLUtesselator* t) { (void)t; } -void gluTessEndContour(GLUtesselator* t) { (void)t; } -void gluTessVertex(GLUtesselator* t, GLdouble* l, void* d) { (void)t; (void)l; (void)d; } -void gluTessNormal(GLUtesselator* t, GLdouble x, GLdouble y, GLdouble z) { (void)t; (void)x; (void)y; (void)z; } -void gluTessCallback(GLUtesselator* t, unsigned int w, _GLUfuncptr f) { (void)t; (void)w; (void)f; } -void gluTessProperty(GLUtesselator* t, unsigned int w, GLdouble v) { (void)t; (void)w; (void)v; } -void gluGetTessProperty(GLUtesselator* t, unsigned int w, GLdouble* d) { (void)t; (void)w; (void)d; } - -/* Error string stub */ -static const unsigned char glu_error_str[] = "GLU stub error"; -const unsigned char* gluErrorString(unsigned int e) { (void)e; return glu_error_str; } diff --git a/wasm/stubs/glu_wasm_impl.cpp b/wasm/stubs/glu_wasm_impl.cpp new file mode 100644 index 0000000..df0737d --- /dev/null +++ b/wasm/stubs/glu_wasm_impl.cpp @@ -0,0 +1,333 @@ +/** + * 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 +#include +#include + +#include +#include +#include + +// 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 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> contours; + std::vector* 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(value); +} + +void gluGetTessProperty(GLUtesselator* tess, GLenum which, GLdouble* value) +{ + if (!tess || !value) return; + + if (which == GLU_TESS_WINDING_RULE) + *value = static_cast(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(v.coords[0]), + static_cast(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(mainContour[j].coords[0]) == pt.x && + static_cast(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"