refactor: 💡 remove orphaned files
This commit is contained in:
parent
8e413f89ec
commit
c148443b7b
13 changed files with 54 additions and 1038 deletions
|
|
@ -1,36 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# Assert all 3 repos are on the given branch.
|
|
||||||
# Exit 0 if all match. Exit 1 with details if any mismatch (including detached HEAD).
|
|
||||||
# Usage: ./assert-on-branch.sh <branch-name>
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
# shellcheck source=./repos.sh
|
|
||||||
source "$SCRIPT_DIR/repos.sh"
|
|
||||||
|
|
||||||
if [ $# -ne 1 ]; then
|
|
||||||
echo "Usage: $0 <branch-name>" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
WANT="$1"
|
|
||||||
mismatched=()
|
|
||||||
for repo in "${REPOS[@]}"; do
|
|
||||||
p=$(repo_path "$repo")
|
|
||||||
cur=$(git -C "$p" branch --show-current)
|
|
||||||
if [ "$cur" != "$WANT" ]; then
|
|
||||||
mismatched+=("$repo: on '${cur:-DETACHED-HEAD}' (want '$WANT')")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ ${#mismatched[@]} -eq 0 ]; then
|
|
||||||
echo "All 3 repos on branch: $WANT"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Branch mismatch:" >&2
|
|
||||||
for line in "${mismatched[@]}"; do
|
|
||||||
echo " $line" >&2
|
|
||||||
done
|
|
||||||
exit 1
|
|
||||||
|
|
@ -237,8 +237,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"
|
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"
|
emar rcs "${STUBS_BUILD}/libcurl_stub.a" "${STUBS_BUILD}/curl_stub.o"
|
||||||
|
|
||||||
# Note: GLU tesselator is now implemented in wasm/stubs/glu_wasm_impl.cpp
|
# Note: the GLU tesselator is provided by kicad/libs/kimath/glu_tess/glu_tess_impl.cpp,
|
||||||
# It's compiled as part of the GAL library (requires KiCad headers)
|
# compiled as part of the GAL library (requires KiCad headers).
|
||||||
|
|
||||||
# Compile NNG stub (IPC API requires NNG but sockets don't work in WASM)
|
# Compile NNG stub (IPC API requires NNG but sockets don't work in WASM)
|
||||||
emcc -c -I"${STUBS_DIR}" "${STUBS_DIR}/nng_stub.c" -o "${STUBS_BUILD}/nng_stub.o"
|
emcc -c -I"${STUBS_DIR}" "${STUBS_DIR}/nng_stub.c" -o "${STUBS_BUILD}/nng_stub.o"
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
# WASM Compatibility Layer for KiCad
|
|
||||||
# This directory contains WASM-specific implementations that replace
|
|
||||||
# platform-specific code in KiCad without modifying KiCad's source.
|
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.22)
|
|
||||||
project(kicad_wasm_compat)
|
|
||||||
|
|
||||||
# Only build for Emscripten
|
|
||||||
if(NOT EMSCRIPTEN)
|
|
||||||
message(FATAL_ERROR "This project is only for Emscripten builds")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
|
|
||||||
# Include directories
|
|
||||||
include_directories(
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/config
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/shims
|
|
||||||
)
|
|
||||||
|
|
||||||
# kiplatform WASM implementation
|
|
||||||
add_subdirectory(kiplatform)
|
|
||||||
|
|
||||||
# libcontext Asyncify fiber implementation
|
|
||||||
add_subdirectory(libcontext)
|
|
||||||
|
|
@ -1,74 +1,83 @@
|
||||||
# WASM Compatibility Layer
|
# WASM Compatibility Layer
|
||||||
|
|
||||||
This directory contains WASM-specific implementations that allow KiCad to run in a web browser **without modifying KiCad's source code**.
|
This directory contains WASM-specific implementations that allow KiCad to run in a
|
||||||
|
web browser while keeping our KiCad fork **as close to upstream as possible**.
|
||||||
|
|
||||||
## Principle
|
## Principle
|
||||||
|
|
||||||
Instead of patching KiCad source files, we:
|
Instead of patching KiCad source files, we:
|
||||||
1. Override include paths to use our headers first
|
1. Provide alternative implementations for platform-specific code (kiplatform)
|
||||||
2. Provide alternative implementations for platform-specific code
|
2. Stub out libraries/features that can't work in the browser (libgit2, curl, nng, scripting, 3D viewer, ...)
|
||||||
3. Link our libraries instead of system libraries
|
3. Expose KiCad to JavaScript via Embind bindings
|
||||||
|
4. Override host package detection during cross-compilation (cmake find-modules)
|
||||||
|
|
||||||
|
The KiCad-side hooks for this are small `if(EMSCRIPTEN)` branches in KiCad's own
|
||||||
|
CMakeLists that pull sources from this directory — see "How it's wired" below.
|
||||||
|
|
||||||
## Directory Structure
|
## Directory Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
wasm/
|
wasm/
|
||||||
├── CMakeLists.txt # Master CMake for compatibility layer
|
|
||||||
├── README.md # This file
|
├── README.md # This file
|
||||||
├── kiplatform/ # Platform abstraction implementations
|
├── kiplatform/ # Platform abstraction implementations (compiled into KiCad)
|
||||||
│ ├── CMakeLists.txt
|
|
||||||
│ ├── app.cpp # App lifecycle (paths, startup)
|
│ ├── app.cpp # App lifecycle (paths, startup)
|
||||||
│ ├── drivers.cpp # GPU detection (returns "WebGL")
|
│ ├── drivers.cpp # GPU detection (returns "WebGL")
|
||||||
│ ├── environment.cpp # Environment variables (localStorage)
|
│ ├── environment.cpp # Environment variables
|
||||||
│ ├── io.cpp # File I/O (WASM virtual filesystem)
|
│ ├── io.cpp # File I/O (WASM virtual filesystem)
|
||||||
│ ├── policy.cpp # Security policy (always permissive)
|
│ ├── policy.cpp # Security policy (always permissive)
|
||||||
│ ├── secrets.cpp # Credential storage (localStorage)
|
│ ├── secrets.cpp # Credential storage
|
||||||
│ ├── sysinfo.cpp # System information
|
│ ├── sysinfo.cpp # System information
|
||||||
│ └── printing.cpp # Print support (browser print())
|
│ ├── printing.cpp # Print support (browser print())
|
||||||
├── libcontext/ # Coroutine/fiber implementation
|
│ └── ui.cpp # UI helpers
|
||||||
│ ├── CMakeLists.txt
|
├── bindings/ # Embind bindings exposing each app to JavaScript
|
||||||
│ └── fcontext_wasm.cpp # Emscripten Asyncify fibers
|
│ ├── pcbnew_embind.cpp
|
||||||
├── shims/ # Header overrides
|
│ ├── eeschema_embind.cpp
|
||||||
│ └── *.h # Headers that redirect to our impls
|
│ ├── pl_editor_embind.cpp
|
||||||
└── config/ # Build configuration
|
│ └── calculator_embind.cpp
|
||||||
├── kicad_wasm_config.h # Version and feature config
|
├── stubs/ # Stub implementations + header shims for unavailable deps
|
||||||
└── setup.h # Platform setup
|
│ ├── *.c / *.cpp # libgit2, curl, nng, scripting, 3D viewer, frame stubs, ...
|
||||||
|
│ ├── char_traits_uint16_workaround.h
|
||||||
|
│ └── GL/ nng/ ngspice/ # Header stubs found via include paths
|
||||||
|
└── cmake/ # CMake find-module overrides for cross-compilation
|
||||||
|
└── Find*.cmake / Use*.cmake
|
||||||
```
|
```
|
||||||
|
|
||||||
## How It Works
|
## How it's wired
|
||||||
|
|
||||||
### Include Path Override
|
### kiplatform — compiled into KiCad
|
||||||
|
|
||||||
When building KiCad for WASM, we add our directories first in the include path:
|
The `kiplatform/*.cpp` files are added directly to KiCad's kiplatform library by an
|
||||||
|
`if(EMSCRIPTEN)` branch in `kicad/libs/kiplatform/CMakeLists.txt`, which references
|
||||||
|
them as `${PROJECT_SOURCE_DIR}/../wasm/kiplatform/*.cpp`. There is no separate
|
||||||
|
`libkiplatform_wasm.a`.
|
||||||
|
|
||||||
```bash
|
### stubs — compiled by the build script and KiCad CMakeLists
|
||||||
-I$PROJECT_ROOT/wasm/shims
|
|
||||||
-I$PROJECT_ROOT/wasm/kiplatform
|
|
||||||
-I$PROJECT_ROOT/stubs/include
|
|
||||||
```
|
|
||||||
|
|
||||||
This means when KiCad includes `<kiplatform/app.h>`, it finds our version first.
|
`scripts/kicad/build-kicad-target.sh` compiles the C stubs (`libgit2_stub.c`,
|
||||||
|
`curl_stub.c`, `nng_stub.c`) and force-includes `char_traits_uint16_workaround.h`.
|
||||||
|
App-specific `*_frame_stub.cpp` / `*_scripting_stub.cpp` are picked up per app, and
|
||||||
|
the remaining `*_stub.cpp` files are pulled in by `if(EMSCRIPTEN)` branches in the
|
||||||
|
KiCad fork's own CMakeLists. Header stubs under `GL/`, `nng/`, `ngspice/` are resolved
|
||||||
|
via include paths.
|
||||||
|
|
||||||
### Library Override
|
### bindings — per app
|
||||||
|
|
||||||
We build `libkiplatform_wasm.a` and link it instead of the native kiplatform:
|
`build-kicad-target.sh` compiles `wasm/bindings/<app>_embind.cpp` for the app being
|
||||||
|
built (apps without an embind file get an empty placeholder object).
|
||||||
|
|
||||||
```bash
|
### cmake — module path
|
||||||
-L$BUILD_ROOT/wasm -lkiplatform_wasm
|
|
||||||
```
|
|
||||||
|
|
||||||
### CMake Integration
|
`build-kicad-target.sh` passes `-DCMAKE_MODULE_PATH="${PROJECT_ROOT}/wasm/cmake"` so
|
||||||
|
the WASM find-module stubs override host package detection.
|
||||||
|
|
||||||
The main KiCad build is configured to find our implementations:
|
> Coroutine/fiber support is **not** in this directory — it comes from the KiCad fork's
|
||||||
|
> `kicad/thirdparty/libcontext/libcontext.cpp` (`LIBCONTEXT_PLATFORM_wasm32`). The GLU
|
||||||
```cmake
|
> tesselator comes from `kicad/libs/kimath/glu_tess/glu_tess_impl.cpp`.
|
||||||
-DCMAKE_MODULE_PATH="$PROJECT_ROOT/cmake"
|
|
||||||
-DKIPLATFORM_LIBRARY="$BUILD_ROOT/wasm/libkiplatform_wasm.a"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Adding New Implementations
|
## Adding New Implementations
|
||||||
|
|
||||||
1. Create the implementation file in the appropriate directory
|
1. Create the implementation file in the appropriate directory (`kiplatform/`, `stubs/`, `bindings/`).
|
||||||
2. Add it to the CMakeLists.txt
|
2. Wire it in: a stub C file goes in `build-kicad-target.sh`; a kiplatform/app source
|
||||||
3. Ensure the header interface matches KiCad's expected interface
|
goes in the relevant `if(EMSCRIPTEN)` branch of the KiCad-side CMakeLists.
|
||||||
4. Test with a minimal build before full integration
|
3. Ensure the header interface matches KiCad's expected interface.
|
||||||
|
4. Test with a minimal build before full integration.
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
# FindKiplatformWASM.cmake
|
|
||||||
# Find module for WASM implementation of kiplatform
|
|
||||||
#
|
|
||||||
# This module is used instead of the platform-specific kiplatform
|
|
||||||
# when building KiCad for WebAssembly.
|
|
||||||
#
|
|
||||||
# This module defines:
|
|
||||||
# KIPLATFORM_FOUND - System has kiplatform for WASM
|
|
||||||
# KIPLATFORM_INCLUDE_DIRS - Include directories
|
|
||||||
# KIPLATFORM_LIBRARIES - Libraries to link
|
|
||||||
# KIPLATFORM_SOURCES - Source files to compile
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
|
|
||||||
if(NOT EMSCRIPTEN)
|
|
||||||
message(FATAL_ERROR "FindKiplatformWASM is only for Emscripten builds")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Get the directory containing this file
|
|
||||||
get_filename_component(_FIND_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
|
||||||
get_filename_component(KIPLATFORM_WASM_DIR "${_FIND_DIR}/../kiplatform" ABSOLUTE)
|
|
||||||
|
|
||||||
# Define source files
|
|
||||||
set(KIPLATFORM_SOURCES
|
|
||||||
${KIPLATFORM_WASM_DIR}/app.cpp
|
|
||||||
${KIPLATFORM_WASM_DIR}/drivers.cpp
|
|
||||||
${KIPLATFORM_WASM_DIR}/environment.cpp
|
|
||||||
${KIPLATFORM_WASM_DIR}/io.cpp
|
|
||||||
${KIPLATFORM_WASM_DIR}/policy.cpp
|
|
||||||
${KIPLATFORM_WASM_DIR}/secrets.cpp
|
|
||||||
${KIPLATFORM_WASM_DIR}/sysinfo.cpp
|
|
||||||
${KIPLATFORM_WASM_DIR}/ui.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
# Include directory for platform headers
|
|
||||||
# We use KiCad's own headers, just provide our implementation
|
|
||||||
set(KIPLATFORM_INCLUDE_DIRS ${KIPLATFORM_WASM_DIR})
|
|
||||||
|
|
||||||
# No prebuilt library - sources are compiled directly into KiCad
|
|
||||||
set(KIPLATFORM_LIBRARIES "")
|
|
||||||
|
|
||||||
# Mark as found
|
|
||||||
set(KIPLATFORM_FOUND TRUE)
|
|
||||||
|
|
||||||
find_package_handle_standard_args(KiplatformWASM
|
|
||||||
REQUIRED_VARS KIPLATFORM_SOURCES KIPLATFORM_INCLUDE_DIRS
|
|
||||||
)
|
|
||||||
|
|
||||||
mark_as_advanced(KIPLATFORM_SOURCES KIPLATFORM_INCLUDE_DIRS KIPLATFORM_LIBRARIES)
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
# FindLibcontextWASM.cmake
|
|
||||||
# Find module for WASM implementation of libcontext using Asyncify
|
|
||||||
#
|
|
||||||
# This module replaces the platform-specific libcontext assembly
|
|
||||||
# when building KiCad for WebAssembly.
|
|
||||||
#
|
|
||||||
# This module defines:
|
|
||||||
# LIBCONTEXT_FOUND - System has libcontext for WASM
|
|
||||||
# LIBCONTEXT_INCLUDE_DIRS - Include directories
|
|
||||||
# LIBCONTEXT_LIBRARIES - Libraries to link (none for WASM)
|
|
||||||
# LIBCONTEXT_SOURCES - Source files to compile
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
|
|
||||||
if(NOT EMSCRIPTEN)
|
|
||||||
message(FATAL_ERROR "FindLibcontextWASM is only for Emscripten builds")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Get the directory containing this file
|
|
||||||
get_filename_component(_FIND_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
|
||||||
get_filename_component(LIBCONTEXT_WASM_DIR "${_FIND_DIR}/../libcontext" ABSOLUTE)
|
|
||||||
|
|
||||||
# Define source files
|
|
||||||
set(LIBCONTEXT_SOURCES
|
|
||||||
${LIBCONTEXT_WASM_DIR}/libcontext_wasm.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
# Include directories
|
|
||||||
set(LIBCONTEXT_INCLUDE_DIRS ${LIBCONTEXT_WASM_DIR})
|
|
||||||
|
|
||||||
# No prebuilt library
|
|
||||||
set(LIBCONTEXT_LIBRARIES "")
|
|
||||||
|
|
||||||
# Mark as found
|
|
||||||
set(LIBCONTEXT_FOUND TRUE)
|
|
||||||
|
|
||||||
find_package_handle_standard_args(LibcontextWASM
|
|
||||||
REQUIRED_VARS LIBCONTEXT_SOURCES LIBCONTEXT_INCLUDE_DIRS
|
|
||||||
)
|
|
||||||
|
|
||||||
mark_as_advanced(LIBCONTEXT_SOURCES LIBCONTEXT_INCLUDE_DIRS LIBCONTEXT_LIBRARIES)
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
# KiCad WASM Configuration
|
|
||||||
# This file configures KiCad to use WASM-specific implementations
|
|
||||||
|
|
||||||
if(NOT EMSCRIPTEN)
|
|
||||||
message(FATAL_ERROR "KiCadWASMConfig.cmake is only for Emscripten builds")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Set the path to WASM compatibility layer
|
|
||||||
get_filename_component(KICAD_WASM_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
|
|
||||||
|
|
||||||
message(STATUS "KiCad WASM compatibility layer: ${KICAD_WASM_DIR}")
|
|
||||||
|
|
||||||
# Add include directories for WASM implementations
|
|
||||||
# These will be searched BEFORE KiCad's own includes, allowing us to
|
|
||||||
# provide our own implementations without modifying KiCad source
|
|
||||||
set(KICAD_WASM_INCLUDE_DIRS
|
|
||||||
${KICAD_WASM_DIR}/kiplatform
|
|
||||||
${KICAD_WASM_DIR}/libcontext
|
|
||||||
${KICAD_WASM_DIR}/config
|
|
||||||
${KICAD_WASM_DIR}/shims
|
|
||||||
)
|
|
||||||
|
|
||||||
# Define macro to indicate we're using WASM platform
|
|
||||||
add_definitions(-DKICAD_PLATFORM_WASM=1)
|
|
||||||
add_definitions(-D__WXUNIVERSAL__=1)
|
|
||||||
|
|
||||||
# Emscripten-specific compile options
|
|
||||||
add_compile_options(
|
|
||||||
-pthread
|
|
||||||
-sUSE_PTHREADS=1
|
|
||||||
)
|
|
||||||
|
|
||||||
# Emscripten-specific link options
|
|
||||||
add_link_options(
|
|
||||||
-pthread
|
|
||||||
-sUSE_PTHREADS=1
|
|
||||||
-sPTHREAD_POOL_SIZE=4
|
|
||||||
-sASYNCIFY=1
|
|
||||||
-sASYNCIFY_STACK_SIZE=65536
|
|
||||||
-sALLOW_MEMORY_GROWTH=1
|
|
||||||
-sINITIAL_MEMORY=256MB
|
|
||||||
-sMAXIMUM_MEMORY=4GB
|
|
||||||
-sMODULARIZE=1
|
|
||||||
-sEXPORT_ES6=1
|
|
||||||
-sENVIRONMENT=web,worker
|
|
||||||
)
|
|
||||||
|
|
||||||
# Export variables for use by parent CMakeLists
|
|
||||||
set(KICAD_WASM_INCLUDE_DIRS ${KICAD_WASM_INCLUDE_DIRS} PARENT_SCOPE)
|
|
||||||
set(KICAD_WASM_DIR ${KICAD_WASM_DIR} PARENT_SCOPE)
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
# WASM implementation of kiplatform
|
|
||||||
# Provides WASM-specific implementations of KiCad's platform abstraction layer
|
|
||||||
|
|
||||||
set(KIPLATFORM_WASM_SRCS
|
|
||||||
app.cpp
|
|
||||||
drivers.cpp
|
|
||||||
environment.cpp
|
|
||||||
io.cpp
|
|
||||||
policy.cpp
|
|
||||||
secrets.cpp
|
|
||||||
sysinfo.cpp
|
|
||||||
ui.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library(kiplatform_wasm STATIC ${KIPLATFORM_WASM_SRCS})
|
|
||||||
|
|
||||||
target_include_directories(kiplatform_wasm PUBLIC
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../config
|
|
||||||
${PROJECT_ROOT}/kicad/libs/kiplatform/include
|
|
||||||
)
|
|
||||||
|
|
||||||
# Link with wxWidgets
|
|
||||||
target_link_libraries(kiplatform_wasm
|
|
||||||
# wxWidgets will be added during main build
|
|
||||||
)
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# WASM implementation of libcontext using Emscripten Asyncify
|
|
||||||
# Provides fiber/coroutine support for KiCad's router
|
|
||||||
|
|
||||||
set(LIBCONTEXT_WASM_SRCS
|
|
||||||
libcontext_wasm.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library(libcontext_wasm STATIC ${LIBCONTEXT_WASM_SRCS})
|
|
||||||
|
|
||||||
target_include_directories(libcontext_wasm PUBLIC
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Asyncify is required for fiber support
|
|
||||||
# These flags must also be set on the final executable
|
|
||||||
target_compile_options(libcontext_wasm PRIVATE
|
|
||||||
-pthread
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_options(libcontext_wasm INTERFACE
|
|
||||||
-sASYNCIFY=1
|
|
||||||
-sASYNCIFY_STACK_SIZE=65536
|
|
||||||
)
|
|
||||||
|
|
@ -1,180 +0,0 @@
|
||||||
/*
|
|
||||||
* WASM implementation of libcontext using Emscripten Asyncify
|
|
||||||
*
|
|
||||||
* Emscripten's Asyncify allows us to implement fiber/coroutine semantics
|
|
||||||
* by saving and restoring the WebAssembly call stack.
|
|
||||||
*
|
|
||||||
* Implementation strategy:
|
|
||||||
* - Each fiber context stores an Asyncify data buffer
|
|
||||||
* - jump_fcontext suspends current execution and resumes target
|
|
||||||
* - make_fcontext creates a new context with a function entry point
|
|
||||||
*
|
|
||||||
* Note: This requires the WASM module to be compiled with:
|
|
||||||
* -sASYNCIFY=1
|
|
||||||
* -sASYNCIFY_STACK_SIZE=65536 (or larger if needed)
|
|
||||||
*
|
|
||||||
* For more information on Asyncify:
|
|
||||||
* https://emscripten.org/docs/porting/asyncify.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
#ifdef __EMSCRIPTEN__
|
|
||||||
#include <emscripten.h>
|
|
||||||
#include <emscripten/fiber.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Match the API from libcontext.h
|
|
||||||
#define LIBCONTEXT_CALL_CONVENTION
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace libcontext
|
|
||||||
{
|
|
||||||
|
|
||||||
// Context structure that stores fiber state
|
|
||||||
struct fiber_context {
|
|
||||||
emscripten_fiber_t fiber;
|
|
||||||
void (*entry_func)(intptr_t);
|
|
||||||
intptr_t entry_arg;
|
|
||||||
bool initialized;
|
|
||||||
bool running;
|
|
||||||
// Stack for asyncify data
|
|
||||||
char asyncify_stack[65536];
|
|
||||||
// C stack
|
|
||||||
char* c_stack;
|
|
||||||
size_t c_stack_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Current running context
|
|
||||||
static fiber_context* g_current_context = nullptr;
|
|
||||||
static fiber_context g_main_context;
|
|
||||||
static bool g_main_initialized = false;
|
|
||||||
|
|
||||||
// Fiber entry wrapper
|
|
||||||
static void fiber_entry_wrapper(void* arg)
|
|
||||||
{
|
|
||||||
fiber_context* ctx = (fiber_context*)arg;
|
|
||||||
if (ctx && ctx->entry_func) {
|
|
||||||
ctx->entry_func(ctx->entry_arg);
|
|
||||||
}
|
|
||||||
// If entry function returns, we need to handle it
|
|
||||||
// In original libcontext, this would call _exit
|
|
||||||
// For WASM, we'll just return to main context
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef void* fcontext_t;
|
|
||||||
|
|
||||||
void LIBCONTEXT_CALL_CONVENTION release_fcontext( fcontext_t ctx )
|
|
||||||
{
|
|
||||||
#ifdef __EMSCRIPTEN__
|
|
||||||
if (ctx) {
|
|
||||||
fiber_context* fctx = (fiber_context*)ctx;
|
|
||||||
if (fctx->c_stack) {
|
|
||||||
free(fctx->c_stack);
|
|
||||||
}
|
|
||||||
free(fctx);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
intptr_t LIBCONTEXT_CALL_CONVENTION jump_fcontext( fcontext_t* ofc, fcontext_t nfc,
|
|
||||||
intptr_t vp, bool preserve_fpu )
|
|
||||||
{
|
|
||||||
#ifdef __EMSCRIPTEN__
|
|
||||||
// Initialize main context if needed
|
|
||||||
if (!g_main_initialized) {
|
|
||||||
memset(&g_main_context, 0, sizeof(g_main_context));
|
|
||||||
emscripten_fiber_init_from_current_context(
|
|
||||||
&g_main_context.fiber,
|
|
||||||
g_main_context.asyncify_stack,
|
|
||||||
sizeof(g_main_context.asyncify_stack)
|
|
||||||
);
|
|
||||||
g_main_context.initialized = true;
|
|
||||||
g_main_context.running = true;
|
|
||||||
g_current_context = &g_main_context;
|
|
||||||
g_main_initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fiber_context* old_ctx = g_current_context;
|
|
||||||
fiber_context* new_ctx = (fiber_context*)nfc;
|
|
||||||
|
|
||||||
if (!new_ctx || !new_ctx->initialized) {
|
|
||||||
fprintf(stderr, "jump_fcontext: invalid target context\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the argument in the new context
|
|
||||||
new_ctx->entry_arg = vp;
|
|
||||||
|
|
||||||
// Save the old context pointer
|
|
||||||
if (ofc) {
|
|
||||||
*ofc = (fcontext_t)old_ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch contexts
|
|
||||||
g_current_context = new_ctx;
|
|
||||||
old_ctx->running = false;
|
|
||||||
new_ctx->running = true;
|
|
||||||
|
|
||||||
// Perform the fiber switch
|
|
||||||
emscripten_fiber_swap(&old_ctx->fiber, &new_ctx->fiber);
|
|
||||||
|
|
||||||
// When we return here, we've been switched back to
|
|
||||||
// Return the value passed to us
|
|
||||||
return g_current_context->entry_arg;
|
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
fcontext_t LIBCONTEXT_CALL_CONVENTION make_fcontext( void* sp, size_t size,
|
|
||||||
void (* fn)( intptr_t ) )
|
|
||||||
{
|
|
||||||
#ifdef __EMSCRIPTEN__
|
|
||||||
// Allocate context structure
|
|
||||||
fiber_context* ctx = (fiber_context*)malloc(sizeof(fiber_context));
|
|
||||||
if (!ctx) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
memset(ctx, 0, sizeof(fiber_context));
|
|
||||||
|
|
||||||
ctx->entry_func = fn;
|
|
||||||
ctx->entry_arg = 0;
|
|
||||||
ctx->c_stack = (char*)sp - size; // sp points to top of stack
|
|
||||||
ctx->c_stack_size = size;
|
|
||||||
|
|
||||||
// Initialize the fiber
|
|
||||||
// Note: sp is the TOP of the stack (highest address)
|
|
||||||
// The stack grows downward, so we need to pass the bottom
|
|
||||||
void* stack_bottom = (char*)sp - size;
|
|
||||||
|
|
||||||
emscripten_fiber_init(
|
|
||||||
&ctx->fiber,
|
|
||||||
fiber_entry_wrapper,
|
|
||||||
ctx, // User data for entry function
|
|
||||||
stack_bottom, // C stack (bottom)
|
|
||||||
size, // C stack size
|
|
||||||
ctx->asyncify_stack, // Asyncify stack
|
|
||||||
sizeof(ctx->asyncify_stack) // Asyncify stack size
|
|
||||||
);
|
|
||||||
|
|
||||||
ctx->initialized = true;
|
|
||||||
ctx->running = false;
|
|
||||||
|
|
||||||
return (fcontext_t)ctx;
|
|
||||||
#else
|
|
||||||
return nullptr;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
}; // namespace libcontext
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
/*
|
|
||||||
* WASM implementation of libcontext using Emscripten Asyncify
|
|
||||||
*
|
|
||||||
* This header provides WASM-specific definitions for the libcontext API.
|
|
||||||
* The implementation uses Emscripten's Asyncify feature to implement
|
|
||||||
* fiber-like context switching in WebAssembly.
|
|
||||||
*
|
|
||||||
* Asyncify works by:
|
|
||||||
* 1. Instrumenting the WASM code to save/restore the call stack
|
|
||||||
* 2. Allowing execution to suspend at any point
|
|
||||||
* 3. Resuming execution from where it was suspended
|
|
||||||
*
|
|
||||||
* This is used by KiCad's router for coroutine-based routing.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef LIBCONTEXT_WASM_H
|
|
||||||
#define LIBCONTEXT_WASM_H
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
// Define WASM platform
|
|
||||||
#define LIBCONTEXT_PLATFORM_wasm
|
|
||||||
#define LIBCONTEXT_CALL_CONVENTION
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
namespace libcontext {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef void* fcontext_t;
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void LIBCONTEXT_CALL_CONVENTION release_fcontext( fcontext_t ctx );
|
|
||||||
|
|
||||||
intptr_t LIBCONTEXT_CALL_CONVENTION jump_fcontext( fcontext_t* ofc, fcontext_t nfc,
|
|
||||||
intptr_t vp, bool preserve_fpu = true );
|
|
||||||
|
|
||||||
fcontext_t LIBCONTEXT_CALL_CONVENTION make_fcontext( void* sp, size_t size,
|
|
||||||
void (* fn)( intptr_t ) );
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} // extern "C"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} // namespace libcontext
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // LIBCONTEXT_WASM_H
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
/**
|
|
||||||
* gl_immediate_shim.js
|
|
||||||
*
|
|
||||||
* Custom OpenGL immediate mode shims for KiCad WASM port.
|
|
||||||
* Fixes Emscripten LEGACY_GL_EMULATION issues:
|
|
||||||
* 1. Color-per-vertex requirement - injects color before each vertex automatically
|
|
||||||
* 2. Missing double-precision functions (glVertex2d, glVertex3d, glColor3d, glColor4d)
|
|
||||||
*
|
|
||||||
* Usage: emcc ... --js-library=lib/gl_immediate_shim.js
|
|
||||||
*/
|
|
||||||
|
|
||||||
addToLibrary({
|
|
||||||
// ==================================================================
|
|
||||||
// GLImmediateShim - State tracking and function wrapping
|
|
||||||
// ==================================================================
|
|
||||||
|
|
||||||
$GLImmediateShim__deps: ['$GLImmediate', 'glBegin', 'glEnd', 'glVertex2f', 'glVertex3f', 'glColor3f', 'glColor4f'],
|
|
||||||
$GLImmediateShim__postset: 'GLImmediateShim.init();',
|
|
||||||
$GLImmediateShim: {
|
|
||||||
// Current color state (persistent across vertices)
|
|
||||||
currentColor: null,
|
|
||||||
|
|
||||||
// Track if we're inside glBegin/glEnd block
|
|
||||||
inBeginEnd: false,
|
|
||||||
|
|
||||||
// Track if color was called since the last vertex
|
|
||||||
// This prevents double-injection when code already calls color per vertex
|
|
||||||
colorCalledSinceLastVertex: false,
|
|
||||||
|
|
||||||
// Original functions we're wrapping
|
|
||||||
origFns: {},
|
|
||||||
|
|
||||||
// Initialization flag
|
|
||||||
initialized: false,
|
|
||||||
|
|
||||||
init: function() {
|
|
||||||
if (GLImmediateShim.initialized) return;
|
|
||||||
if (typeof GLImmediate === 'undefined') {
|
|
||||||
// GLImmediate not ready yet, will be called again
|
|
||||||
console.log('[GLImmediateShim] Waiting for GLImmediate...');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[GLImmediateShim] Initializing OpenGL immediate mode shims');
|
|
||||||
|
|
||||||
// Initialize current color to white (OpenGL default)
|
|
||||||
GLImmediateShim.currentColor = new Float32Array([1.0, 1.0, 1.0, 1.0]);
|
|
||||||
|
|
||||||
// Store original functions
|
|
||||||
GLImmediateShim.origFns = {
|
|
||||||
glBegin: _glBegin,
|
|
||||||
glEnd: _glEnd,
|
|
||||||
glVertex2f: _glVertex2f,
|
|
||||||
glVertex3f: _glVertex3f,
|
|
||||||
glColor3f: _glColor3f,
|
|
||||||
glColor4f: _glColor4f,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Install shims
|
|
||||||
_glBegin = GLImmediateShim.shimBegin;
|
|
||||||
_glEnd = GLImmediateShim.shimEnd;
|
|
||||||
_glVertex2f = GLImmediateShim.shimVertex2f;
|
|
||||||
_glVertex3f = GLImmediateShim.shimVertex3f;
|
|
||||||
_glColor3f = GLImmediateShim.shimColor3f;
|
|
||||||
_glColor4f = GLImmediateShim.shimColor4f;
|
|
||||||
|
|
||||||
GLImmediateShim.initialized = true;
|
|
||||||
console.log('[GLImmediateShim] Initialized successfully');
|
|
||||||
},
|
|
||||||
|
|
||||||
// ==================================================================
|
|
||||||
// Shim implementations
|
|
||||||
// ==================================================================
|
|
||||||
|
|
||||||
shimBegin: function(mode) {
|
|
||||||
GLImmediateShim.inBeginEnd = true;
|
|
||||||
GLImmediateShim.colorCalledSinceLastVertex = false;
|
|
||||||
GLImmediateShim.origFns.glBegin(mode);
|
|
||||||
},
|
|
||||||
|
|
||||||
shimEnd: function() {
|
|
||||||
GLImmediateShim.inBeginEnd = false;
|
|
||||||
GLImmediateShim.origFns.glEnd();
|
|
||||||
},
|
|
||||||
|
|
||||||
shimColor3f: function(r, g, b) {
|
|
||||||
var c = GLImmediateShim.currentColor;
|
|
||||||
c[0] = r;
|
|
||||||
c[1] = g;
|
|
||||||
c[2] = b;
|
|
||||||
c[3] = 1.0;
|
|
||||||
|
|
||||||
if (GLImmediateShim.inBeginEnd) {
|
|
||||||
// Mark that color was explicitly called for this vertex
|
|
||||||
GLImmediateShim.colorCalledSinceLastVertex = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLImmediateShim.origFns.glColor3f(r, g, b);
|
|
||||||
},
|
|
||||||
|
|
||||||
shimColor4f: function(r, g, b, a) {
|
|
||||||
var c = GLImmediateShim.currentColor;
|
|
||||||
c[0] = r;
|
|
||||||
c[1] = g;
|
|
||||||
c[2] = b;
|
|
||||||
c[3] = a;
|
|
||||||
|
|
||||||
if (GLImmediateShim.inBeginEnd) {
|
|
||||||
// Mark that color was explicitly called for this vertex
|
|
||||||
GLImmediateShim.colorCalledSinceLastVertex = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLImmediateShim.origFns.glColor4f(r, g, b, a);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Inject current color before each vertex (only if not already called)
|
|
||||||
injectColor: function() {
|
|
||||||
if (!GLImmediateShim.inBeginEnd) return;
|
|
||||||
|
|
||||||
// Only inject color if it wasn't already called for this vertex
|
|
||||||
if (!GLImmediateShim.colorCalledSinceLastVertex) {
|
|
||||||
var c = GLImmediateShim.currentColor;
|
|
||||||
GLImmediateShim.origFns.glColor4f(c[0], c[1], c[2], c[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the flag for the next vertex
|
|
||||||
GLImmediateShim.colorCalledSinceLastVertex = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
shimVertex2f: function(x, y) {
|
|
||||||
GLImmediateShim.injectColor();
|
|
||||||
GLImmediateShim.origFns.glVertex2f(x, y);
|
|
||||||
},
|
|
||||||
|
|
||||||
shimVertex3f: function(x, y, z) {
|
|
||||||
GLImmediateShim.injectColor();
|
|
||||||
GLImmediateShim.origFns.glVertex3f(x, y, z);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// ==================================================================
|
|
||||||
// Double-precision vertex functions (missing in Emscripten)
|
|
||||||
// ==================================================================
|
|
||||||
|
|
||||||
glVertex2d__deps: ['glVertex2f'],
|
|
||||||
glVertex2d: function(x, y) {
|
|
||||||
_glVertex2f(x, y);
|
|
||||||
},
|
|
||||||
|
|
||||||
glVertex3d__deps: ['glVertex3f'],
|
|
||||||
glVertex3d: function(x, y, z) {
|
|
||||||
_glVertex3f(x, y, z);
|
|
||||||
},
|
|
||||||
|
|
||||||
glVertex4d__deps: ['glVertex4f'],
|
|
||||||
glVertex4d: function(x, y, z, w) {
|
|
||||||
_glVertex4f(x, y, z, w);
|
|
||||||
},
|
|
||||||
|
|
||||||
// ==================================================================
|
|
||||||
// Double-precision color functions
|
|
||||||
// ==================================================================
|
|
||||||
|
|
||||||
glColor3d__deps: ['glColor3f'],
|
|
||||||
glColor3d: function(r, g, b) {
|
|
||||||
_glColor3f(r, g, b);
|
|
||||||
},
|
|
||||||
|
|
||||||
glColor4d__deps: ['glColor4f'],
|
|
||||||
glColor4d: function(r, g, b, a) {
|
|
||||||
_glColor4f(r, g, b, a);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ensure GLImmediateShim is included in the build
|
|
||||||
if (typeof extraLibraryFuncs !== 'undefined') {
|
|
||||||
extraLibraryFuncs.push('$GLImmediateShim');
|
|
||||||
}
|
|
||||||
|
|
@ -1,333 +0,0 @@
|
||||||
/**
|
|
||||||
* 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"
|
|
||||||
Loading…
Reference in a new issue