commit 5c7d1905a97cb87ac91eb662f31752d6a75331f8 Author: saarsena@gmail.com Date: Thu Apr 2 03:41:50 2026 -0400 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7a1b00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Build directories +build/ +cmake-build-*/ +out/ +.cache/ +# IDE files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# Compiled files +*.o +*.obj +*.exe +*.out +*.app + +# CMake generated +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +Makefile +compile_commands.json + +# Dependencies fetched by CMake +_deps/ +external/*/ + +# OS files +.DS_Store +Thumbs.db diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cb5cba5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,120 @@ +cmake_minimum_required(VERSION 3.20) +project(sdl3_flecs_template VERSION 1.0.0 LANGUAGES CXX) + +# C++ Standard +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Build type defaults to Debug +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif() + +# Compiler warnings +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + add_compile_options(-Wall -Wextra -Wpedantic) + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_compile_options(-g -O0) + else() + add_compile_options(-O2) + endif() +elseif(MSVC) + add_compile_options(/W4) +endif() + +# ============================================================================= +# Dependencies +# ============================================================================= + +# SDL3 - Using FetchContent for automatic download +include(FetchContent) + +# SDL3 +FetchContent_Declare( + SDL3 + GIT_REPOSITORY https://github.com/libsdl-org/SDL.git + GIT_TAG main + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE +) + +set(SDL_SHARED OFF CACHE BOOL "" FORCE) +set(SDL_STATIC ON CACHE BOOL "" FORCE) +set(SDL_TEST OFF CACHE BOOL "" FORCE) + +# Platform-specific SDL backends +if(EMSCRIPTEN) + set(SDL_X11 OFF CACHE BOOL "" FORCE) + set(SDL_WAYLAND OFF CACHE BOOL "" FORCE) +else() + # Disable X11, use Wayland only on Linux + set(SDL_X11 OFF CACHE BOOL "" FORCE) + set(SDL_WAYLAND ON CACHE BOOL "" FORCE) +endif() + +FetchContent_MakeAvailable(SDL3) + +# FLECS +FetchContent_Declare( + flecs + GIT_REPOSITORY https://github.com/SanderMertens/flecs.git + GIT_TAG master + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE +) + +FetchContent_MakeAvailable(flecs) + +# ============================================================================= +# Main Executable +# ============================================================================= + +add_executable(${PROJECT_NAME} + src/main.cpp + src/game.cpp + src/systems.cpp + src/pipeline.cpp +) + +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_link_libraries(${PROJECT_NAME} PRIVATE + SDL3::SDL3-static + flecs::flecs_static +) + +# Platform-specific settings +if(EMSCRIPTEN) + set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX ".html") + target_link_options(${PROJECT_NAME} PRIVATE + -sUSE_SDL=0 + -sALLOW_MEMORY_GROWTH=1 + -sTOTAL_MEMORY=67108864 + -sMAX_WEBGL_VERSION=2 + --shell-file ${CMAKE_CURRENT_SOURCE_DIR}/web/shell.html + ) +elseif(WIN32) + target_link_libraries(${PROJECT_NAME} PRIVATE winmm imm32 version setupapi) +elseif(UNIX AND NOT APPLE) + find_package(PkgConfig REQUIRED) + # Linux dependencies are handled by SDL3 +endif() + +# ============================================================================= +# Install +# ============================================================================= + +install(TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION bin +) + +# ============================================================================= +# CPack (optional packaging) +# ============================================================================= + +set(CPACK_PROJECT_NAME ${PROJECT_NAME}) +set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) +include(CPack) diff --git a/README.md b/README.md new file mode 100644 index 0000000..8088406 --- /dev/null +++ b/README.md @@ -0,0 +1,165 @@ +# SDL3 + FLECS Template + +A scaffold/template for building games using SDL3 and the FLECS Entity Component System. + +## Features + +- **SDL3** - Modern cross-platform multimedia library +- **FLECS** - Fast and lightweight Entity Component System +- **CMake** - Cross-platform build system with FetchContent for dependencies +- **Clean Architecture** - Separated components, systems, and game logic + +## Project Structure + +``` +. +├── CMakeLists.txt # Build configuration +├── include/ +│ ├── components/ # ECS component definitions +│ │ ├── transform.h # Position, Velocity, Rotation, Scale +│ │ ├── render.h # Color, Sprite, RectShape, CircleShape +│ │ ├── input.h # PlayerControlled, InputState +│ │ └── common.h # Name, ToDestroy, Lifetime, Health +│ ├── components.h # Master component include +│ ├── systems.h # System declarations +│ └── game.h # Game context and main loop +├── src/ +│ ├── main.c # Entry point +│ ├── game.c # Game initialization and loop +│ └── systems.c # ECS system implementations +└── external/ # (unused - deps fetched via CMake) +``` + +## Building + +### Prerequisites + +- CMake 3.20+ +- C11 compatible compiler (GCC, Clang, MSVC) +- Git (for fetching dependencies) + +### Linux/macOS + +```bash +# Create build directory +mkdir build && cd build + +# Configure +cmake .. + +# Build +cmake --build . + +# Run +./sdl3_flecs_template +``` + +### Windows (Visual Studio) + +```bash +mkdir build && cd build +cmake .. -G "Visual Studio 17 2022" +cmake --build . --config Release +.\Release\sdl3_flecs_template.exe +``` + +### Windows (MinGW) + +```bash +mkdir build && cd build +cmake .. -G "MinGW Makefiles" +cmake --build . +.\sdl3_flecs_template.exe +``` + +## Usage Guide + +### Adding Components + +1. Create a new header in `include/components/` (e.g., `physics.h`) +2. Define your component struct: + ```c + typedef struct RigidBody { + float mass; + float friction; + } RigidBody; + ``` +3. Include it in `include/components.h` +4. Register it in `src/systems.c`: + ```c + ECS_COMPONENT_DECLARE(RigidBody); + // In register_components(): + ECS_COMPONENT_DEFINE(world, RigidBody); + ``` + +### Adding Systems + +1. Declare the system in `include/systems.h`: + ```c + void PhysicsSystem(ecs_iter_t* it); + ``` +2. Implement it in `src/systems.c`: + ```c + void PhysicsSystem(ecs_iter_t* it) { + Position* pos = ecs_field(it, Position, 0); + RigidBody* rb = ecs_field(it, RigidBody, 1); + + for (int i = 0; i < it->count; i++) { + // Physics logic here + } + } + ``` +3. Register it in `register_systems()`: + ```c + ecs_system(world, { + .entity = ecs_entity(world, { + .name = "PhysicsSystem", + .add = ecs_ids(ecs_dependson(EcsOnUpdate)) + }), + .query.terms = { + { .id = ecs_id(Position), .inout = EcsInOut }, + { .id = ecs_id(RigidBody), .inout = EcsIn } + }, + .callback = PhysicsSystem + }); + ``` + +### Creating Entities + +```c +ecs_entity_t player = ecs_new(world); + +ecs_set(world, player, Position, { .x = 100, .y = 100 }); +ecs_set(world, player, Velocity, { .x = 0, .y = 0 }); +ecs_set(world, player, RectShape, { .width = 32, .height = 32 }); +ecs_set(world, player, Color, { .r = 0, .g = 255, .b = 0, .a = 255 }); +ecs_set(world, player, PlayerControlled, { .player_id = 0 }); +``` + +### System Phases + +FLECS provides built-in phases for ordering systems: + +- `EcsOnLoad` - Load external data +- `EcsPostLoad` - Process loaded data +- `EcsPreUpdate` - Prepare for update +- `EcsOnUpdate` - Main update logic +- `EcsOnValidate` - Validate state +- `EcsPostUpdate` - Post-update cleanup +- `EcsPreStore` - Prepare for rendering +- `EcsOnStore` - Render/store output + +## Controls + +- **ESC** - Quit +- **P** - Pause/unpause + +## License + +This template is released into the public domain. Use it however you like! + +## Resources + +- [SDL3 Documentation](https://wiki.libsdl.org/SDL3) +- [FLECS Documentation](https://www.flecs.dev/flecs/md_docs_2Docs.html) +- [FLECS Examples](https://github.com/SanderMertens/flecs/tree/master/examples) diff --git a/build-web/CPackConfig.cmake b/build-web/CPackConfig.cmake new file mode 100644 index 0000000..75f72b2 --- /dev/null +++ b/build-web/CPackConfig.cmake @@ -0,0 +1,82 @@ +# This file will be configured to contain variables for CPack. These variables +# should be set in the CMake list file of the project before CPack module is +# included. The list of available CPACK_xxx variables and their associated +# documentation may be obtained using +# cpack --help-variable-list +# +# Some variables are common to all generators (e.g. CPACK_PACKAGE_NAME) +# and some are specific to a generator +# (e.g. CPACK_NSIS_EXTRA_INSTALL_COMMANDS). The generator specific variables +# usually begin with CPACK__xxxx. + + +set(CPACK_BINARY_DEB "OFF") +set(CPACK_BINARY_FREEBSD "OFF") +set(CPACK_BINARY_IFW "OFF") +set(CPACK_BINARY_NSIS "OFF") +set(CPACK_BINARY_RPM "OFF") +set(CPACK_BINARY_STGZ "ON") +set(CPACK_BINARY_TBZ2 "OFF") +set(CPACK_BINARY_TGZ "ON") +set(CPACK_BINARY_TXZ "OFF") +set(CPACK_BINARY_TZ "ON") +set(CPACK_BUILD_SOURCE_DIRS "/home/saarsena/Dev/renderpip;/home/saarsena/Dev/renderpip/build-web") +set(CPACK_CMAKE_GENERATOR "Unix Makefiles") +set(CPACK_COMPONENT_UNSPECIFIED_HIDDEN "TRUE") +set(CPACK_COMPONENT_UNSPECIFIED_REQUIRED "TRUE") +set(CPACK_DEFAULT_PACKAGE_DESCRIPTION_FILE "/usr/share/cmake/Templates/CPack.GenericDescription.txt") +set(CPACK_DEFAULT_PACKAGE_DESCRIPTION_SUMMARY "sdl3_flecs_template built using CMake") +set(CPACK_GENERATOR "STGZ;TGZ;TZ") +set(CPACK_INNOSETUP_ARCHITECTURE "x86") +set(CPACK_INSTALL_CMAKE_PROJECTS "/home/saarsena/Dev/renderpip/build-web;sdl3_flecs_template;ALL;/") +set(CPACK_INSTALL_PREFIX "/home/saarsena/emsdk/upstream/emscripten/cache/sysroot") +set(CPACK_MODULE_PATH "/home/saarsena/emsdk/upstream/emscripten/cmake/Modules;/home/saarsena/emsdk/upstream/emscripten/cmake/Modules;/home/saarsena/emsdk/upstream/emscripten/cmake/Modules;/home/saarsena/emsdk/upstream/emscripten/cmake/Modules") +set(CPACK_NSIS_DISPLAY_NAME "sdl3_flecs_template 1.0.0") +set(CPACK_NSIS_INSTALLER_ICON_CODE "") +set(CPACK_NSIS_INSTALLER_MUI_ICON_CODE "") +set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES") +set(CPACK_NSIS_PACKAGE_NAME "sdl3_flecs_template 1.0.0") +set(CPACK_NSIS_UNINSTALL_NAME "Uninstall") +set(CPACK_OBJCOPY_EXECUTABLE "/usr/bin/llvm-objcopy") +set(CPACK_OBJDUMP_EXECUTABLE "/usr/bin/llvm-objdump") +set(CPACK_OUTPUT_CONFIG_FILE "/home/saarsena/Dev/renderpip/build-web/CPackConfig.cmake") +set(CPACK_PACKAGE_DEFAULT_LOCATION "/") +set(CPACK_PACKAGE_DESCRIPTION_FILE "/usr/share/cmake/Templates/CPack.GenericDescription.txt") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "sdl3_flecs_template built using CMake") +set(CPACK_PACKAGE_FILE_NAME "sdl3_flecs_template-1.0.0-Emscripten") +set(CPACK_PACKAGE_INSTALL_DIRECTORY "sdl3_flecs_template 1.0.0") +set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "sdl3_flecs_template 1.0.0") +set(CPACK_PACKAGE_NAME "sdl3_flecs_template") +set(CPACK_PACKAGE_RELOCATABLE "true") +set(CPACK_PACKAGE_VENDOR "Humanity") +set(CPACK_PACKAGE_VERSION "1.0.0") +set(CPACK_PACKAGE_VERSION_MAJOR "1") +set(CPACK_PACKAGE_VERSION_MINOR "0") +set(CPACK_PACKAGE_VERSION_PATCH "0") +set(CPACK_PROJECT_NAME "sdl3_flecs_template") +set(CPACK_PROJECT_VERSION "1.0.0") +set(CPACK_READELF_EXECUTABLE "/usr/bin/llvm-readelf") +set(CPACK_RESOURCE_FILE_LICENSE "/usr/share/cmake/Templates/CPack.GenericLicense.txt") +set(CPACK_RESOURCE_FILE_README "/usr/share/cmake/Templates/CPack.GenericDescription.txt") +set(CPACK_RESOURCE_FILE_WELCOME "/usr/share/cmake/Templates/CPack.GenericWelcome.txt") +set(CPACK_SET_DESTDIR "OFF") +set(CPACK_SOURCE_GENERATOR "TBZ2;TGZ;TXZ;TZ") +set(CPACK_SOURCE_OUTPUT_CONFIG_FILE "/home/saarsena/Dev/renderpip/build-web/CPackSourceConfig.cmake") +set(CPACK_SOURCE_RPM "OFF") +set(CPACK_SOURCE_TBZ2 "ON") +set(CPACK_SOURCE_TGZ "ON") +set(CPACK_SOURCE_TXZ "ON") +set(CPACK_SOURCE_TZ "ON") +set(CPACK_SOURCE_ZIP "OFF") +set(CPACK_SYSTEM_NAME "Emscripten") +set(CPACK_THREADS "1") +set(CPACK_TOPLEVEL_TAG "Emscripten") +set(CPACK_WIX_SIZEOF_VOID_P "4") + +if(NOT CPACK_PROPERTIES_FILE) + set(CPACK_PROPERTIES_FILE "/home/saarsena/Dev/renderpip/build-web/CPackProperties.cmake") +endif() + +if(EXISTS ${CPACK_PROPERTIES_FILE}) + include(${CPACK_PROPERTIES_FILE}) +endif() diff --git a/build-web/CPackSourceConfig.cmake b/build-web/CPackSourceConfig.cmake new file mode 100644 index 0000000..8d9ee6e --- /dev/null +++ b/build-web/CPackSourceConfig.cmake @@ -0,0 +1,90 @@ +# This file will be configured to contain variables for CPack. These variables +# should be set in the CMake list file of the project before CPack module is +# included. The list of available CPACK_xxx variables and their associated +# documentation may be obtained using +# cpack --help-variable-list +# +# Some variables are common to all generators (e.g. CPACK_PACKAGE_NAME) +# and some are specific to a generator +# (e.g. CPACK_NSIS_EXTRA_INSTALL_COMMANDS). The generator specific variables +# usually begin with CPACK__xxxx. + + +set(CPACK_BINARY_DEB "OFF") +set(CPACK_BINARY_FREEBSD "OFF") +set(CPACK_BINARY_IFW "OFF") +set(CPACK_BINARY_NSIS "OFF") +set(CPACK_BINARY_RPM "OFF") +set(CPACK_BINARY_STGZ "ON") +set(CPACK_BINARY_TBZ2 "OFF") +set(CPACK_BINARY_TGZ "ON") +set(CPACK_BINARY_TXZ "OFF") +set(CPACK_BINARY_TZ "ON") +set(CPACK_BUILD_SOURCE_DIRS "/home/saarsena/Dev/renderpip;/home/saarsena/Dev/renderpip/build-web") +set(CPACK_CMAKE_GENERATOR "Unix Makefiles") +set(CPACK_COMPONENT_UNSPECIFIED_HIDDEN "TRUE") +set(CPACK_COMPONENT_UNSPECIFIED_REQUIRED "TRUE") +set(CPACK_DEFAULT_PACKAGE_DESCRIPTION_FILE "/usr/share/cmake/Templates/CPack.GenericDescription.txt") +set(CPACK_DEFAULT_PACKAGE_DESCRIPTION_SUMMARY "sdl3_flecs_template built using CMake") +set(CPACK_GENERATOR "TBZ2;TGZ;TXZ;TZ") +set(CPACK_IGNORE_FILES "/CVS/;/\\.svn/;/\\.bzr/;/\\.hg/;/\\.git/;\\.swp\$;\\.#;/#") +set(CPACK_INNOSETUP_ARCHITECTURE "x86") +set(CPACK_INSTALLED_DIRECTORIES "/home/saarsena/Dev/renderpip;/") +set(CPACK_INSTALL_CMAKE_PROJECTS "") +set(CPACK_INSTALL_PREFIX "/home/saarsena/emsdk/upstream/emscripten/cache/sysroot") +set(CPACK_MODULE_PATH "/home/saarsena/emsdk/upstream/emscripten/cmake/Modules;/home/saarsena/emsdk/upstream/emscripten/cmake/Modules;/home/saarsena/emsdk/upstream/emscripten/cmake/Modules;/home/saarsena/emsdk/upstream/emscripten/cmake/Modules") +set(CPACK_NSIS_DISPLAY_NAME "sdl3_flecs_template 1.0.0") +set(CPACK_NSIS_INSTALLER_ICON_CODE "") +set(CPACK_NSIS_INSTALLER_MUI_ICON_CODE "") +set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES") +set(CPACK_NSIS_PACKAGE_NAME "sdl3_flecs_template 1.0.0") +set(CPACK_NSIS_UNINSTALL_NAME "Uninstall") +set(CPACK_OBJCOPY_EXECUTABLE "/usr/bin/llvm-objcopy") +set(CPACK_OBJDUMP_EXECUTABLE "/usr/bin/llvm-objdump") +set(CPACK_OUTPUT_CONFIG_FILE "/home/saarsena/Dev/renderpip/build-web/CPackConfig.cmake") +set(CPACK_PACKAGE_DEFAULT_LOCATION "/") +set(CPACK_PACKAGE_DESCRIPTION_FILE "/usr/share/cmake/Templates/CPack.GenericDescription.txt") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "sdl3_flecs_template built using CMake") +set(CPACK_PACKAGE_FILE_NAME "sdl3_flecs_template-1.0.0-Source") +set(CPACK_PACKAGE_INSTALL_DIRECTORY "sdl3_flecs_template 1.0.0") +set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "sdl3_flecs_template 1.0.0") +set(CPACK_PACKAGE_NAME "sdl3_flecs_template") +set(CPACK_PACKAGE_RELOCATABLE "true") +set(CPACK_PACKAGE_VENDOR "Humanity") +set(CPACK_PACKAGE_VERSION "1.0.0") +set(CPACK_PACKAGE_VERSION_MAJOR "1") +set(CPACK_PACKAGE_VERSION_MINOR "0") +set(CPACK_PACKAGE_VERSION_PATCH "0") +set(CPACK_PROJECT_NAME "sdl3_flecs_template") +set(CPACK_PROJECT_VERSION "1.0.0") +set(CPACK_READELF_EXECUTABLE "/usr/bin/llvm-readelf") +set(CPACK_RESOURCE_FILE_LICENSE "/usr/share/cmake/Templates/CPack.GenericLicense.txt") +set(CPACK_RESOURCE_FILE_README "/usr/share/cmake/Templates/CPack.GenericDescription.txt") +set(CPACK_RESOURCE_FILE_WELCOME "/usr/share/cmake/Templates/CPack.GenericWelcome.txt") +set(CPACK_RPM_PACKAGE_SOURCES "ON") +set(CPACK_SET_DESTDIR "OFF") +set(CPACK_SOURCE_GENERATOR "TBZ2;TGZ;TXZ;TZ") +set(CPACK_SOURCE_IGNORE_FILES "/CVS/;/\\.svn/;/\\.bzr/;/\\.hg/;/\\.git/;\\.swp\$;\\.#;/#") +set(CPACK_SOURCE_INSTALLED_DIRECTORIES "/home/saarsena/Dev/renderpip;/") +set(CPACK_SOURCE_OUTPUT_CONFIG_FILE "/home/saarsena/Dev/renderpip/build-web/CPackSourceConfig.cmake") +set(CPACK_SOURCE_PACKAGE_FILE_NAME "sdl3_flecs_template-1.0.0-Source") +set(CPACK_SOURCE_RPM "OFF") +set(CPACK_SOURCE_TBZ2 "ON") +set(CPACK_SOURCE_TGZ "ON") +set(CPACK_SOURCE_TOPLEVEL_TAG "Emscripten-Source") +set(CPACK_SOURCE_TXZ "ON") +set(CPACK_SOURCE_TZ "ON") +set(CPACK_SOURCE_ZIP "OFF") +set(CPACK_STRIP_FILES "") +set(CPACK_SYSTEM_NAME "Emscripten") +set(CPACK_THREADS "1") +set(CPACK_TOPLEVEL_TAG "Emscripten-Source") +set(CPACK_WIX_SIZEOF_VOID_P "4") + +if(NOT CPACK_PROPERTIES_FILE) + set(CPACK_PROPERTIES_FILE "/home/saarsena/Dev/renderpip/build-web/CPackProperties.cmake") +endif() + +if(EXISTS ${CPACK_PROPERTIES_FILE}) + include(${CPACK_PROPERTIES_FILE}) +endif() diff --git a/build-web/sdl3_flecs_template.html b/build-web/sdl3_flecs_template.html new file mode 100644 index 0000000..1455015 --- /dev/null +++ b/build-web/sdl3_flecs_template.html @@ -0,0 +1 @@ +Render Pipeline Visualizer
Loading...
\ No newline at end of file diff --git a/build-web/sdl3_flecs_template.js b/build-web/sdl3_flecs_template.js new file mode 100644 index 0000000..627f706 --- /dev/null +++ b/build-web/sdl3_flecs_template.js @@ -0,0 +1 @@ +var Module=typeof Module!="undefined"?Module:{};var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=globalThis.document?.currentScript?.src;if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);if(typeof module!="undefined"){module["exports"]=Module}quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;SOCKFS.root=FS.mount(SOCKFS,{},null);if(!Module["noFSInit"]&&!FS.initialized)FS.init();TTY.init();wasmExports["uf"]();FS.ignorePermissions=false}function preMain(){}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("sdl3_flecs_template.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP16;var HEAP32;var HEAP64;var HEAP8;var HEAPF32;var HEAPF64;var HEAPU16;var HEAPU32;var HEAPU64;var HEAPU8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var runDependencies=0;var dependenciesFulfilled=null;var removeRunDependency=id=>{runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}};var addRunDependency=id=>{runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)};var noExitRuntime=true;function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":HEAP8[ptr]=value;break;case"i8":HEAP8[ptr]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":HEAP64[ptr>>3]=BigInt(value);break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;case"*":HEAPU32[ptr>>2]=value;break;default:abort(`invalid type for setValue: ${type}`)}}var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();var initRandomFill=()=>{if(ENVIRONMENT_IS_NODE){var nodeCrypto=require("node:crypto");return view=>nodeCrypto.randomFillSync(view)}return view=>crypto.getRandomValues(view)};var randomFill=view=>{(randomFill=initRandomFill())(view)};var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.slice(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.slice(0,-1)}return root+dir},basename:path=>path&&path.match(/([^\/]+|\/)\/*$/)[1],join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).slice(1);to=PATH_FS.resolve(to).slice(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var FS_stdin_getChar_buffer=[];var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var intArrayFromString=(stringy,dontAddNull,length)=>{var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array};var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc(BUFSIZE);var bytesRead=0;var fd=process.stdin.fd;try{bytesRead=fs.readSync(fd,buf,0,BUFSIZE)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}}else if(globalThis.window?.prompt){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else{}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output?.length>0){err(UTF8ArrayToString(tty.output));tty.output=[]}}}};var mmapAlloc=size=>{abort()};var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16895,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=MEMFS.emptyFileContents??=new Uint8Array(0)}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.atime=node.mtime=node.ctime=Date.now();if(parent){parent.contents[name]=node;parent.atime=parent.mtime=parent.ctime=node.atime}return node},getFileDataAsTypedArray(node){return node.contents.subarray(0,node.usedBytes)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents.length;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity)newCapacity=Math.max(newCapacity,256);var oldContents=MEMFS.getFileDataAsTypedArray(node);node.contents=new Uint8Array(newCapacity);node.contents.set(oldContents)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;var oldContents=node.contents;node.contents=new Uint8Array(newSize);node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)));node.usedBytes=newSize},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.atime);attr.mtime=new Date(node.mtime);attr.ctime=new Date(node.ctime);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){for(const key of["mode","atime","mtime","ctime"]){if(attr[key]!=null){node[key]=attr[key]}}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){if(!MEMFS.doesNotExistError){MEMFS.doesNotExistError=new FS.ErrnoError(44);MEMFS.doesNotExistError.stack=""}throw MEMFS.doesNotExistError},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){if(FS.isDir(old_node.mode)){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}FS.hashRemoveNode(new_node)}delete old_node.parent.contents[old_node.name];new_dir.contents[new_name]=old_node;old_node.name=new_name;new_dir.ctime=new_dir.mtime=old_node.parent.ctime=old_node.parent.mtime=Date.now()},unlink(parent,name){delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},readdir(node){return[".","..",...Object.keys(node.contents)]},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);buffer.set(contents.subarray(position,position+size),offset);return size},write(stream,buffer,offset,length,position,canOwn){if(buffer.buffer===HEAP8.buffer){canOwn=false}if(!length)return 0;var node=stream.node;node.mtime=node.ctime=Date.now();if(canOwn){node.contents=buffer.subarray(offset,offset+length);node.usedBytes=length}else if(node.usedBytes===0&&position===0){node.contents=buffer.slice(offset,offset+length);node.usedBytes=length}else{MEMFS.expandFileStorage(node,position+length);node.contents.set(buffer.subarray(offset,offset+length),position);node.usedBytes=Math.max(node.usedBytes,position+length)}return length},llseek(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){position+=stream.node.usedBytes}}if(position<0){throw new FS.ErrnoError(28)}return position},mmap(stream,length,position,prot,flags){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}var ptr;var allocated;var contents=stream.node.contents;if(!(flags&2)&&contents.buffer===HEAP8.buffer){allocated=false;ptr=contents.byteOffset}else{allocated=true;ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}if(contents){if(position>0||position+length{if(typeof str!="string")return str;var flagModes={r:0,"r+":2,w:512|64|1,"w+":512|64|2,a:1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};var FS_fileDataToTypedArray=data=>{if(typeof data=="string"){data=intArrayFromString(data,true)}if(!data.subarray){data=new Uint8Array(data)}return data};var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};var asyncLoad=async url=>{var arrayBuffer=await readAsync(url);return new Uint8Array(arrayBuffer)};var FS_createDataFile=(...args)=>FS.createDataFile(...args);var getUniqueRunDependency=id=>id;var preloadPlugins=[];var FS_handledByPreloadPlugin=async(byteArray,fullname)=>{if(typeof Browser!="undefined")Browser.init();for(var plugin of preloadPlugins){if(plugin["canHandle"](fullname)){return plugin["handle"](byteArray,fullname)}}return byteArray};var FS_preloadFile=async(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);addRunDependency(dep);try{var byteArray=url;if(typeof url=="string"){byteArray=await asyncLoad(url)}byteArray=await FS_handledByPreloadPlugin(byteArray,fullname);preFinish?.();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}}finally{removeRunDependency(dep)}};var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{FS_preloadFile(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish).then(onload).catch(onerror)};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,filesystems:null,syncFSRequests:0,ErrnoError:class{name="ErrnoError";constructor(errno){this.errno=errno}},FSStream:class{shared={};get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{node_ops={};stream_ops={};readMode=292|73;writeMode=146;mounted=null;constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.rdev=rdev;this.atime=this.mtime=this.ctime=Date.now()}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){if(!path){throw new FS.ErrnoError(44)}opts.follow_mount??=true;if(!PATH.isAbs(path)){path=FS.cwd()+"/"+path}linkloop:for(var nlinks=0;nlinks<40;nlinks++){var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}if(perms.includes("w")&&!(node.mode&146)){return 2}if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){if(!FS.isDir(dir.mode)){return 54}try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else if(FS.isDir(node.mode)){return 31}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}var mode=FS.flagsToPermissionString(flags);if(FS.isDir(node.mode)){if(mode!=="r"||flags&(512|64)){return 31}}return FS.nodePermissions(node,mode)},checkOpExists(op,err){if(!op){throw new FS.ErrnoError(err)}return op},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},doSetAttr(stream,node,attr){var setattr=stream?.stream_ops.setattr;var arg=setattr?stream:node;setattr??=node.node_ops.setattr;FS.checkOpExists(setattr,63);setattr(arg,attr)},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push(...m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}for(var mount of mounts){if(mount.type.syncfs){mount.type.syncfs(mount,populate,done)}else{done(null)}}},mount(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type,opts,mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);for(var[hash,current]of Object.entries(FS.nameTable)){while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}}node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name){throw new FS.ErrnoError(28)}if(name==="."||name===".."){throw new FS.ErrnoError(20)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},statfs(path){return FS.statfsNode(FS.lookupPath(path,{follow:true}).node)},statfsStream(stream){return FS.statfsNode(stream.node)},statfsNode(node){var rtn={bsize:4096,frsize:4096,blocks:1e6,bfree:5e5,bavail:5e5,files:FS.nextInode,ffree:FS.nextInode-1,fsid:42,flags:2,namelen:255};if(node.node_ops.statfs){Object.assign(rtn,node.node_ops.statfs(node.mount.opts.root))}return rtn},create(path,mode=438){mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode=511){mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var dir of dirs){if(!dir)continue;if(d||PATH.isAbs(path))d+="/";d+=dir;try{FS.mkdir(d,mode)}catch(e){if(e.errno!=20)throw e}}},mkdev(path,mode,dev){if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink(oldpath,newpath){if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename(old_path,new_path){var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name);old_node.parent=new_dir}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir(path){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var readdir=FS.checkOpExists(node.node_ops.readdir,54);return readdir(node)},unlink(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink(path){var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return link.node_ops.readlink(link)},stat(path,dontFollow){var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;var getattr=FS.checkOpExists(node.node_ops.getattr,63);return getattr(node)},fstat(fd){var stream=FS.getStreamChecked(fd);var node=stream.node;var getattr=stream.stream_ops.getattr;var arg=getattr?stream:node;getattr??=node.node_ops.getattr;FS.checkOpExists(getattr,63);return getattr(arg)},lstat(path){return FS.stat(path,true)},doChmod(stream,node,mode,dontFollow){FS.doSetAttr(stream,node,{mode:mode&4095|node.mode&~4095,ctime:Date.now(),dontFollow})},chmod(path,mode,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChmod(null,node,mode,dontFollow)},lchmod(path,mode){FS.chmod(path,mode,true)},fchmod(fd,mode){var stream=FS.getStreamChecked(fd);FS.doChmod(stream,stream.node,mode,false)},doChown(stream,node,dontFollow){FS.doSetAttr(stream,node,{timestamp:Date.now(),dontFollow})},chown(path,uid,gid,dontFollow){var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}FS.doChown(null,node,dontFollow)},lchown(path,uid,gid){FS.chown(path,uid,gid,true)},fchown(fd,uid,gid){var stream=FS.getStreamChecked(fd);FS.doChown(stream,stream.node,false)},doTruncate(stream,node,len){if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}FS.doSetAttr(stream,node,{size:len,timestamp:Date.now()})},truncate(path,len){if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}FS.doTruncate(null,node,len)},ftruncate(fd,len){var stream=FS.getStreamChecked(fd);if(len<0||(stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.doTruncate(stream,stream.node,len)},utime(path,atime,mtime){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var setattr=FS.checkOpExists(node.node_ops.setattr,63);setattr(node,{atime,mtime})},open(path,flags,mode=438){if(path===""){throw new FS.ErrnoError(44)}flags=FS_modeStringToFlags(flags);if(flags&64){mode=mode&4095|32768}else{mode=0}var node;var isDirPath;if(typeof path=="object"){node=path}else{isDirPath=path.endsWith("/");var lookup=FS.lookupPath(path,{follow:!(flags&131072),noent_okay:true});node=lookup.node;path=lookup.path}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else if(isDirPath){throw new FS.ErrnoError(31)}else{node=FS.mknod(path,mode|511,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node,path:FS.getPath(node),flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(created){FS.chmod(node,mode&511)}return stream},close(stream){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed(stream){return stream.fd===null},llseek(stream,offset,whence){if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read(stream,buffer,offset,length,position){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write(stream,buffer,offset,length,position,canOwn){if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},mmap(stream,length,position,prot,flags){if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}if(!length){throw new FS.ErrnoError(28)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync(stream,buffer,offset,length,mmapFlags){if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},ioctl(stream,cmd,arg){if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile(path,opts={}){opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){abort(`Invalid encoding type "${opts.encoding}"`)}var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){buf=UTF8ArrayToString(buf)}FS.close(stream);return buf},writeFile(path,data,opts={}){opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);data=FS_fileDataToTypedArray(data);FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn);FS.close(stream)},cwd:()=>FS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length,llseek:()=>0});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomFill(randomBuffer);randomLeft=randomBuffer.byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16895,73);node.stream_ops={llseek:MEMFS.stream_ops.llseek};node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path},id:fd+1};ret.parent=ret;return ret},readdir(){return Array.from(FS.streams.entries()).filter(([k,v])=>v).map(([k,v])=>k.toString())}};return node}},{},"/proc/self/fd")},createStandardStreams(input,output,error){if(input){FS.createDevice("/dev","stdin",input)}else{FS.symlink("/dev/tty","/dev/stdin")}if(output){FS.createDevice("/dev","stdout",null,output)}else{FS.symlink("/dev/tty","/dev/stdout")}if(error){FS.createDevice("/dev","stderr",null,error)}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},staticInit(){FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={MEMFS}},init(input,output,error){FS.initialized=true;input??=Module["stdin"];output??=Module["stdout"];error??=Module["stderr"];FS.createStandardStreams(input,output,error)},quit(){FS.initialized=false;for(var stream of FS.streams){if(stream){FS.close(stream)}}},findObject(path,dontResolveLastLink){var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath(path,dontResolveLastLink){try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath(parent,path,canRead,canWrite){parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){if(e.errno!=20)throw e}parent=current}return current},createFile(parent,name,properties,canRead,canWrite){var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile(parent,name,data,canRead,canWrite,canOwn){var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS_getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){data=FS_fileDataToTypedArray(data);FS.chmod(node,mode|146);var stream=FS.open(node,577);FS.write(stream,data,0,data.length,0,canOwn);FS.close(stream);FS.chmod(node,mode)}},createDevice(parent,name,input,output){var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(!!input,!!output);FS.createDevice.major??=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open(stream){stream.seekable=false},close(stream){if(output?.buffer?.length){output(10)}},read(stream,buffer,offset,length,pos){var bytesRead=0;for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)abort("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)abort("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")abort("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true}get length(){if(!this.lengthKnown){this.cacheLength()}return this._length}get chunkSize(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}if(globalThis.XMLHttpRequest){if(!ENVIRONMENT_IS_WORKER)abort("Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc");var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};for(const[key,fn]of Object.entries(node.stream_ops)){stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)}}function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var SOCKFS={websocketArgs:{},callbacks:{},on(event,callback){SOCKFS.callbacks[event]=callback},emit(event,param){SOCKFS.callbacks[event]?.(param)},mount(mount){SOCKFS.websocketArgs=Module["websocket"]||{};(Module["websocket"]??={})["on"]=SOCKFS.on;return FS.createNode(null,"/",16895,0)},createSocket(family,type,protocol){if(family!=2){throw new FS.ErrnoError(5)}type&=~526336;if(type!=1&&type!=2){throw new FS.ErrnoError(28)}var streaming=type==1;if(streaming&&protocol&&protocol!=6){throw new FS.ErrnoError(66)}var sock={family,type,protocol,server:null,error:null,peers:{},pending:[],recv_queue:[],sock_ops:SOCKFS.websocket_sock_ops};var name=SOCKFS.nextname();var node=FS.createNode(SOCKFS.root,name,49152,0);node.sock=sock;var stream=FS.createStream({path:name,node,flags:2,seekable:false,stream_ops:SOCKFS.stream_ops});sock.stream=stream;return sock},getSocket(fd){var stream=FS.getStream(fd);if(!stream||!FS.isSocket(stream.node.mode)){return null}return stream.node.sock},stream_ops:{poll(stream){var sock=stream.node.sock;return sock.sock_ops.poll(sock)},ioctl(stream,request,varargs){var sock=stream.node.sock;return sock.sock_ops.ioctl(sock,request,varargs)},read(stream,buffer,offset,length,position){var sock=stream.node.sock;var msg=sock.sock_ops.recvmsg(sock,length);if(!msg){return 0}buffer.set(msg.buffer,offset);return msg.buffer.length},write(stream,buffer,offset,length,position){var sock=stream.node.sock;return sock.sock_ops.sendmsg(sock,buffer,offset,length)},close(stream){var sock=stream.node.sock;sock.sock_ops.close(sock)}},nextname(){if(!SOCKFS.nextname.current){SOCKFS.nextname.current=0}return`socket[${SOCKFS.nextname.current++}]`},websocket_sock_ops:{createPeer(sock,addr,port){var ws;if(typeof addr=="object"){ws=addr;addr=null;port=null}if(ws){if(ws._socket){addr=ws._socket.remoteAddress;port=ws._socket.remotePort}else{var result=/ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url);if(!result){throw new Error("WebSocket URL must be in the format ws(s)://address:port")}addr=result[1];port=parseInt(result[2],10)}}else{try{var url="ws://".replace("#","//");var subProtocols="binary";var opts=undefined;if(SOCKFS.websocketArgs["url"]){url=SOCKFS.websocketArgs["url"]}if(SOCKFS.websocketArgs["subprotocol"]){subProtocols=SOCKFS.websocketArgs["subprotocol"]}else if(SOCKFS.websocketArgs["subprotocol"]===null){subProtocols="null"}if(url==="ws://"||url==="wss://"){var parts=addr.split("/");url=url+parts[0]+":"+port+"/"+parts.slice(1).join("/")}if(subProtocols!=="null"){subProtocols=subProtocols.replace(/^ +| +$/g,"").split(/ *, */);opts=subProtocols}var WebSocketConstructor;if(ENVIRONMENT_IS_NODE){WebSocketConstructor=require("ws")}else{WebSocketConstructor=WebSocket}ws=new WebSocketConstructor(url,opts);ws.binaryType="arraybuffer"}catch(e){throw new FS.ErrnoError(23)}}var peer={addr,port,socket:ws,msg_send_queue:[]};SOCKFS.websocket_sock_ops.addPeer(sock,peer);SOCKFS.websocket_sock_ops.handlePeerEvents(sock,peer);if(sock.type===2&&typeof sock.sport!="undefined"){peer.msg_send_queue.push(new Uint8Array([255,255,255,255,"p".charCodeAt(0),"o".charCodeAt(0),"r".charCodeAt(0),"t".charCodeAt(0),(sock.sport&65280)>>8,sock.sport&255]))}return peer},getPeer(sock,addr,port){return sock.peers[addr+":"+port]},addPeer(sock,peer){sock.peers[peer.addr+":"+peer.port]=peer},removePeer(sock,peer){delete sock.peers[peer.addr+":"+peer.port]},handlePeerEvents(sock,peer){var first=true;var handleOpen=function(){sock.connecting=false;SOCKFS.emit("open",sock.stream.fd);try{var queued=peer.msg_send_queue.shift();while(queued){peer.socket.send(queued);queued=peer.msg_send_queue.shift()}}catch(e){peer.socket.close()}};function handleMessage(data){if(typeof data=="string"){var encoder=new TextEncoder;data=encoder.encode(data)}else{if(data.byteLength==0){return}data=new Uint8Array(data)}var wasfirst=first;first=false;if(wasfirst&&data.length===10&&data[0]===255&&data[1]===255&&data[2]===255&&data[3]===255&&data[4]==="p".charCodeAt(0)&&data[5]==="o".charCodeAt(0)&&data[6]==="r".charCodeAt(0)&&data[7]==="t".charCodeAt(0)){var newport=data[8]<<8|data[9];SOCKFS.websocket_sock_ops.removePeer(sock,peer);peer.port=newport;SOCKFS.websocket_sock_ops.addPeer(sock,peer);return}sock.recv_queue.push({addr:peer.addr,port:peer.port,data});SOCKFS.emit("message",sock.stream.fd)}if(ENVIRONMENT_IS_NODE){peer.socket.on("open",handleOpen);peer.socket.on("message",function(data,isBinary){if(!isBinary){return}handleMessage(new Uint8Array(data).buffer)});peer.socket.on("close",function(){SOCKFS.emit("close",sock.stream.fd)});peer.socket.on("error",function(error){sock.error=14;SOCKFS.emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])})}else{peer.socket.onopen=handleOpen;peer.socket.onclose=function(){SOCKFS.emit("close",sock.stream.fd)};peer.socket.onmessage=function peer_socket_onmessage(event){handleMessage(event.data)};peer.socket.onerror=function(error){sock.error=14;SOCKFS.emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])}}},poll(sock){if(sock.type===1&&sock.server){return sock.pending.length?64|1:0}var mask=0;var dest=sock.type===1?SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport):null;if(sock.recv_queue.length||!dest||dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=64|1}if(!dest||dest&&dest.socket.readyState===dest.socket.OPEN){mask|=4}if(dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){if(sock.connecting){mask|=4}else{mask|=16}}return mask},ioctl(sock,request,arg){switch(request){case 21531:var bytes=0;if(sock.recv_queue.length){bytes=sock.recv_queue[0].data.length}HEAP32[arg>>2]=bytes;return 0;case 21537:var on=HEAP32[arg>>2];if(on){sock.stream.flags|=2048}else{sock.stream.flags&=~2048}return 0;default:return 28}},close(sock){if(sock.server){try{sock.server.close()}catch(e){}sock.server=null}for(var peer of Object.values(sock.peers)){try{peer.socket.close()}catch(e){}SOCKFS.websocket_sock_ops.removePeer(sock,peer)}return 0},bind(sock,addr,port){if(typeof sock.saddr!="undefined"||typeof sock.sport!="undefined"){throw new FS.ErrnoError(28)}sock.saddr=addr;sock.sport=port;if(sock.type===2){if(sock.server){sock.server.close();sock.server=null}try{sock.sock_ops.listen(sock,0)}catch(e){if(!(e.name==="ErrnoError"))throw e;if(e.errno!==138)throw e}}},connect(sock,addr,port){if(sock.server){throw new FS.ErrnoError(138)}if(typeof sock.daddr!="undefined"&&typeof sock.dport!="undefined"){var dest=SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport);if(dest){if(dest.socket.readyState===dest.socket.CONNECTING){throw new FS.ErrnoError(7)}else{throw new FS.ErrnoError(30)}}}var peer=SOCKFS.websocket_sock_ops.createPeer(sock,addr,port);sock.daddr=peer.addr;sock.dport=peer.port;sock.connecting=true},listen(sock,backlog){if(!ENVIRONMENT_IS_NODE){throw new FS.ErrnoError(138)}if(sock.server){throw new FS.ErrnoError(28)}var WebSocketServer=require("ws").Server;var host=sock.saddr;sock.server=new WebSocketServer({host,port:sock.sport});SOCKFS.emit("listen",sock.stream.fd);sock.server.on("connection",function(ws){if(sock.type===1){var newsock=SOCKFS.createSocket(sock.family,sock.type,sock.protocol);var peer=SOCKFS.websocket_sock_ops.createPeer(newsock,ws);newsock.daddr=peer.addr;newsock.dport=peer.port;sock.pending.push(newsock);SOCKFS.emit("connection",newsock.stream.fd)}else{SOCKFS.websocket_sock_ops.createPeer(sock,ws);SOCKFS.emit("connection",sock.stream.fd)}});sock.server.on("close",function(){SOCKFS.emit("close",sock.stream.fd);sock.server=null});sock.server.on("error",function(error){sock.error=23;SOCKFS.emit("error",[sock.stream.fd,sock.error,"EHOSTUNREACH: Host is unreachable"])})},accept(listensock){if(!listensock.server||!listensock.pending.length){throw new FS.ErrnoError(28)}var newsock=listensock.pending.shift();newsock.stream.flags=listensock.stream.flags;return newsock},getname(sock,peer){var addr,port;if(peer){if(sock.daddr===undefined||sock.dport===undefined){throw new FS.ErrnoError(53)}addr=sock.daddr;port=sock.dport}else{addr=sock.saddr||0;port=sock.sport||0}return{addr,port}},sendmsg(sock,buffer,offset,length,addr,port){if(sock.type===2){if(addr===undefined||port===undefined){addr=sock.daddr;port=sock.dport}if(addr===undefined||port===undefined){throw new FS.ErrnoError(17)}}else{addr=sock.daddr;port=sock.dport}var dest=SOCKFS.websocket_sock_ops.getPeer(sock,addr,port);if(sock.type===1){if(!dest||dest.socket.readyState===dest.socket.CLOSING||dest.socket.readyState===dest.socket.CLOSED){throw new FS.ErrnoError(53)}}if(ArrayBuffer.isView(buffer)){offset+=buffer.byteOffset;buffer=buffer.buffer}var data=buffer.slice(offset,offset+length);if(!dest||dest.socket.readyState!==dest.socket.OPEN){if(sock.type===2){if(!dest||dest.socket.readyState===dest.socket.CLOSING||dest.socket.readyState===dest.socket.CLOSED){dest=SOCKFS.websocket_sock_ops.createPeer(sock,addr,port)}}dest.msg_send_queue.push(data);return length}try{dest.socket.send(data);return length}catch(e){throw new FS.ErrnoError(28)}},recvmsg(sock,length){if(sock.type===1&&sock.server){throw new FS.ErrnoError(53)}var queued=sock.recv_queue.shift();if(!queued){if(sock.type===1){var dest=SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport);if(!dest){throw new FS.ErrnoError(53)}if(dest.socket.readyState===dest.socket.CLOSING||dest.socket.readyState===dest.socket.CLOSED){return null}throw new FS.ErrnoError(6)}throw new FS.ErrnoError(6)}var queuedLength=queued.data.byteLength||queued.data.length;var queuedOffset=queued.data.byteOffset||0;var queuedBuffer=queued.data.buffer||queued.data;var bytesRead=Math.min(length,queuedLength);var res={buffer:new Uint8Array(queuedBuffer,queuedOffset,bytesRead),addr:queued.addr,port:queued.port};if(sock.type===1&&bytesRead{var socket=SOCKFS.getSocket(fd);if(!socket)throw new FS.ErrnoError(8);return socket};var inetPton4=str=>{var b=str.split(".");for(var i=0;i<4;i++){var tmp=Number(b[i]);if(isNaN(tmp))return null;b[i]=tmp}return(b[0]|b[1]<<8|b[2]<<16|b[3]<<24)>>>0};var inetPton6=str=>{var words;var w,offset,z;var valid6regx=/^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i;var parts=[];if(!valid6regx.test(str)){return null}if(str==="::"){return[0,0,0,0,0,0,0,0]}if(str.startsWith("::")){str=str.replace("::","Z:")}else{str=str.replace("::",":Z:")}if(str.indexOf(".")>0){str=str.replace(new RegExp("[.]","g"),":");words=str.split(":");words[words.length-4]=Number(words[words.length-4])+Number(words[words.length-3])*256;words[words.length-3]=Number(words[words.length-2])+Number(words[words.length-1])*256;words=words.slice(0,words.length-2)}else{words=str.split(":")}offset=0;z=0;for(w=0;wHEAPU8.fill(0,ptr,ptr+size);var writeSockaddr=(sa,family,addr,port,addrlen)=>{switch(family){case 2:addr=inetPton4(addr);zeroMemory(sa,16);if(addrlen){HEAP32[addrlen>>2]=16}HEAP16[sa>>1]=family;HEAP32[sa+4>>2]=addr;HEAP16[sa+2>>1]=_htons(port);break;case 10:addr=inetPton6(addr);zeroMemory(sa,28);if(addrlen){HEAP32[addrlen>>2]=28}HEAP32[sa>>2]=family;HEAP32[sa+8>>2]=addr[0];HEAP32[sa+12>>2]=addr[1];HEAP32[sa+16>>2]=addr[2];HEAP32[sa+20>>2]=addr[3];HEAP16[sa+2>>1]=_htons(port);break;default:return 5}return 0};var DNS={address_map:{id:1,addrs:{},names:{}},lookup_name(name){var res=inetPton4(name);if(res!==null){return name}res=inetPton6(name);if(res!==null){return name}var addr;if(DNS.address_map.addrs[name]){addr=DNS.address_map.addrs[name]}else{var id=DNS.address_map.id++;addr="172.29."+(id&255)+"."+(id&65280);DNS.address_map.names[addr]=name;DNS.address_map.addrs[name]=addr}return addr},lookup_addr(addr){if(DNS.address_map.names[addr]){return DNS.address_map.names[addr]}return null}};function ___syscall_accept4(fd,addr,addrlen,flags,d1,d2){try{var sock=getSocketFromFD(fd);var newsock=sock.sock_ops.accept(sock);if(addr){var errno=writeSockaddr(addr,newsock.family,DNS.lookup_name(newsock.daddr),newsock.dport,addrlen)}return newsock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var inetNtop4=addr=>(addr&255)+"."+(addr>>8&255)+"."+(addr>>16&255)+"."+(addr>>24&255);var inetNtop6=ints=>{var str="";var word=0;var longest=0;var lastzero=0;var zstart=0;var len=0;var i=0;var parts=[ints[0]&65535,ints[0]>>16,ints[1]&65535,ints[1]>>16,ints[2]&65535,ints[2]>>16,ints[3]&65535,ints[3]>>16];var hasipv4=true;var v4part="";for(i=0;i<5;i++){if(parts[i]!==0){hasipv4=false;break}}if(hasipv4){v4part=inetNtop4(parts[6]|parts[7]<<16);if(parts[5]===-1){str="::ffff:";str+=v4part;return str}if(parts[5]===0){str="::";if(v4part==="0.0.0.0")v4part="";if(v4part==="0.0.0.1")v4part="1";str+=v4part;return str}}for(word=0;word<8;word++){if(parts[word]===0){if(word-lastzero>1){len=0}lastzero=word;len++}if(len>longest){longest=len;zstart=word-longest+1}}for(word=0;word<8;word++){if(longest>1){if(parts[word]===0&&word>=zstart&&word{var family=HEAP16[sa>>1];var port=_ntohs(HEAPU16[sa+2>>1]);var addr;switch(family){case 2:if(salen!==16){return{errno:28}}addr=HEAP32[sa+4>>2];addr=inetNtop4(addr);break;case 10:if(salen!==28){return{errno:28}}addr=[HEAP32[sa+8>>2],HEAP32[sa+12>>2],HEAP32[sa+16>>2],HEAP32[sa+20>>2]];addr=inetNtop6(addr);break;default:return{errno:5}}return{family,addr,port}};var getSocketAddress=(addrp,addrlen)=>{var info=readSockaddr(addrp,addrlen);if(info.errno)throw new FS.ErrnoError(info.errno);info.addr=DNS.lookup_addr(info.addr)||info.addr;return info};function ___syscall_bind(fd,addr,addrlen,d1,d2,d3){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.bind(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var syscallGetVarargI=()=>{var ret=HEAP32[+SYSCALLS.varargs>>2];SYSCALLS.varargs+=4;return ret};var syscallGetVarargP=syscallGetVarargI;var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var SYSCALLS={calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return dir+"/"+path},writeStat(buf,stat){HEAPU32[buf>>2]=stat.dev;HEAPU32[buf+4>>2]=stat.mode;HEAPU32[buf+8>>2]=stat.nlink;HEAPU32[buf+12>>2]=stat.uid;HEAPU32[buf+16>>2]=stat.gid;HEAPU32[buf+20>>2]=stat.rdev;HEAP64[buf+24>>3]=BigInt(stat.size);HEAP32[buf+32>>2]=4096;HEAP32[buf+36>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();HEAP64[buf+40>>3]=BigInt(Math.floor(atime/1e3));HEAPU32[buf+48>>2]=atime%1e3*1e3*1e3;HEAP64[buf+56>>3]=BigInt(Math.floor(mtime/1e3));HEAPU32[buf+64>>2]=mtime%1e3*1e3*1e3;HEAP64[buf+72>>3]=BigInt(Math.floor(ctime/1e3));HEAPU32[buf+80>>2]=ctime%1e3*1e3*1e3;HEAP64[buf+88>>3]=BigInt(stat.ino);return 0},writeStatFs(buf,stats){HEAPU32[buf+4>>2]=stats.bsize;HEAPU32[buf+60>>2]=stats.bsize;HEAP64[buf+8>>3]=BigInt(stats.blocks);HEAP64[buf+16>>3]=BigInt(stats.bfree);HEAP64[buf+24>>3]=BigInt(stats.bavail);HEAP64[buf+32>>3]=BigInt(stats.files);HEAP64[buf+40>>3]=BigInt(stats.ffree);HEAPU32[buf+48>>2]=stats.fsid;HEAPU32[buf+64>>2]=stats.flags;HEAPU32[buf+56>>2]=stats.namelen},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=syscallGetVarargI();if(arg<0){return-28}while(FS.streams[arg]){arg++}var newStream;newStream=FS.dupStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=syscallGetVarargI();stream.flags|=arg;return 0}case 12:{var arg=syscallGetVarargP();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0}return-28}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_fdatasync(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:{if(!stream.tty)return-59;return 0}case 21505:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream);var argp=syscallGetVarargP();HEAP32[argp>>2]=termios.c_iflag||0;HEAP32[argp+4>>2]=termios.c_oflag||0;HEAP32[argp+8>>2]=termios.c_cflag||0;HEAP32[argp+12>>2]=termios.c_lflag||0;for(var i=0;i<32;i++){HEAP8[argp+i+17]=termios.c_cc[i]||0}return 0}return 0}case 21510:case 21511:case 21512:{if(!stream.tty)return-59;return 0}case 21506:case 21507:case 21508:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){var argp=syscallGetVarargP();var c_iflag=HEAP32[argp>>2];var c_oflag=HEAP32[argp+4>>2];var c_cflag=HEAP32[argp+8>>2];var c_lflag=HEAP32[argp+12>>2];var c_cc=[];for(var i=0;i<32;i++){c_cc.push(HEAP8[argp+i+17])}return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag,c_oflag,c_cflag,c_lflag,c_cc})}return 0}case 21519:{if(!stream.tty)return-59;var argp=syscallGetVarargP();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21537:case 21531:{var argp=syscallGetVarargP();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tiocgwinsz){var winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty);var argp=syscallGetVarargP();HEAP16[argp>>1]=winsize[0];HEAP16[argp+2>>1]=winsize[1]}return 0}case 21524:{if(!stream.tty)return-59;return 0}case 21515:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_listen(fd,backlog){try{var sock=getSocketFromFD(fd);sock.sock_ops.listen(sock,backlog);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_recvfrom(fd,buf,len,flags,addr,addrlen){try{var sock=getSocketFromFD(fd);var msg=sock.sock_ops.recvmsg(sock,len);if(!msg)return 0;if(addr){var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(msg.addr),msg.port,addrlen)}HEAPU8.set(msg.buffer,buf);return msg.buffer.byteLength}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_sendto(fd,message,length,flags,addr,addr_len){try{var sock=getSocketFromFD(fd);if(!addr){return FS.write(sock.stream,HEAP8,message,length)}var dest=getSocketAddress(addr,addr_len);return sock.sock_ops.sendmsg(sock,HEAP8,message,length,dest.addr,dest.port)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_socket(domain,type,protocol){try{var sock=SOCKFS.createSocket(domain,type,protocol);return sock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.writeStat(buf,FS.stat(path))}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __abort_js=()=>abort("");var _emscripten_get_now=()=>performance.now();var _emscripten_date_now=()=>Date.now();var nowIsMonotonic=1;var checkWasiClock=clock_id=>clock_id>=0&&clock_id<=3;var INT53_MAX=9007199254740992;var INT53_MIN=-9007199254740992;var bigintToI53Checked=num=>numINT53_MAX?NaN:Number(num);function _clock_time_get(clk_id,ignored_precision,ptime){ignored_precision=bigintToI53Checked(ignored_precision);if(!checkWasiClock(clk_id)){return 28}var now;if(clk_id===0){now=_emscripten_date_now()}else if(nowIsMonotonic){now=_emscripten_get_now()}else{return 52}var nsec=Math.round(now*1e3*1e3);HEAP64[ptime>>3]=BigInt(nsec);return 0}var readEmAsmArgsArray=[];var readEmAsmArgs=(sigPtr,buf)=>{readEmAsmArgsArray.length=0;var ch;while(ch=HEAPU8[sigPtr++]){var wide=ch!=105;wide&=ch!=112;buf+=wide&&buf%8?4:0;readEmAsmArgsArray.push(ch==112?HEAPU32[buf>>2]:ch==106?HEAP64[buf>>3]:ch==105?HEAP32[buf>>2]:HEAPF64[buf>>3]);buf+=wide?8:4}return readEmAsmArgsArray};var runMainThreadEmAsm=(emAsmAddr,sigPtr,argbuf,sync)=>{var args=readEmAsmArgs(sigPtr,argbuf);return ASM_CONSTS[emAsmAddr](...args)};var _emscripten_asm_const_int_sync_on_main_thread=(emAsmAddr,sigPtr,argbuf)=>runMainThreadEmAsm(emAsmAddr,sigPtr,argbuf,1);var _emscripten_asm_const_double_sync_on_main_thread=_emscripten_asm_const_int_sync_on_main_thread;var runEmAsmFunction=(code,sigPtr,argbuf)=>{var args=readEmAsmArgs(sigPtr,argbuf);return ASM_CONSTS[code](...args)};var _emscripten_asm_const_int=(code,sigPtr,argbuf)=>runEmAsmFunction(code,sigPtr,argbuf);var _emscripten_asm_const_ptr_sync_on_main_thread=(emAsmAddr,sigPtr,argbuf)=>runMainThreadEmAsm(emAsmAddr,sigPtr,argbuf,1);var _emscripten_set_main_loop_timing=(mode,value)=>{MainLoop.timingMode=mode;MainLoop.timingValue=value;if(!MainLoop.func){return 1}if(!MainLoop.running){MainLoop.running=true}if(mode==0){MainLoop.scheduler=function MainLoop_scheduler_setTimeout(){var timeUntilNextTick=Math.max(0,MainLoop.tickStartTime+value-_emscripten_get_now())|0;setTimeout(MainLoop.runner,timeUntilNextTick)}}else if(mode==1){MainLoop.scheduler=function MainLoop_scheduler_rAF(){MainLoop.requestAnimationFrame(MainLoop.runner)}}else{if(!MainLoop.setImmediate){if(globalThis.setImmediate){MainLoop.setImmediate=setImmediate}else{var setImmediates=[];var emscriptenMainLoopMessageId="setimmediate";var MainLoop_setImmediate_messageHandler=event=>{if(event.data===emscriptenMainLoopMessageId||event.data.target===emscriptenMainLoopMessageId){event.stopPropagation();setImmediates.shift()()}};addEventListener("message",MainLoop_setImmediate_messageHandler,true);MainLoop.setImmediate=func=>{setImmediates.push(func);if(ENVIRONMENT_IS_WORKER){Module["setImmediates"]??=[];Module["setImmediates"].push(func);postMessage({target:emscriptenMainLoopMessageId})}else postMessage(emscriptenMainLoopMessageId,"*")}}}MainLoop.scheduler=function MainLoop_scheduler_setImmediate(){MainLoop.setImmediate(MainLoop.runner)}}return 0};var runtimeKeepaliveCounter=0;var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var setMainLoop=(iterFunc,fps,simulateInfiniteLoop,arg,noSetTiming)=>{MainLoop.func=iterFunc;MainLoop.arg=arg;var thisMainLoopId=MainLoop.currentlyRunningMainloop;function checkIsRunning(){if(thisMainLoopId0){var start=Date.now();var blocker=MainLoop.queue.shift();blocker.func(blocker.arg);if(MainLoop.remainingBlockers){var remaining=MainLoop.remainingBlockers;var next=remaining%1==0?remaining-1:Math.floor(remaining);if(blocker.counted){MainLoop.remainingBlockers=next}else{next=next+.5;MainLoop.remainingBlockers=(8*remaining+next)/9}}MainLoop.updateStatus();if(!checkIsRunning())return;setTimeout(MainLoop.runner,0);return}if(!checkIsRunning())return;MainLoop.currentFrameNumber=MainLoop.currentFrameNumber+1|0;if(MainLoop.timingMode==1&&MainLoop.timingValue>1&&MainLoop.currentFrameNumber%MainLoop.timingValue!=0){MainLoop.scheduler();return}else if(MainLoop.timingMode==0){MainLoop.tickStartTime=_emscripten_get_now()}MainLoop.runIter(iterFunc);if(!checkIsRunning())return;MainLoop.scheduler()};if(!noSetTiming){if(fps>0){_emscripten_set_main_loop_timing(0,1e3/fps)}else{_emscripten_set_main_loop_timing(1,1)}MainLoop.scheduler()}if(simulateInfiniteLoop){throw"unwind"}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var MainLoop={running:false,scheduler:null,currentlyRunningMainloop:0,func:null,arg:0,timingMode:0,timingValue:0,currentFrameNumber:0,queue:[],preMainLoop:[],postMainLoop:[],pause(){MainLoop.scheduler=null;MainLoop.currentlyRunningMainloop++},resume(){MainLoop.currentlyRunningMainloop++;var timingMode=MainLoop.timingMode;var timingValue=MainLoop.timingValue;var func=MainLoop.func;MainLoop.func=null;setMainLoop(func,0,false,MainLoop.arg,true);_emscripten_set_main_loop_timing(timingMode,timingValue);MainLoop.scheduler()},updateStatus(){if(Module["setStatus"]){var message=Module["statusMessage"]||"Please wait...";var remaining=MainLoop.remainingBlockers??0;var expected=MainLoop.expectedBlockers??0;if(remaining){if(remaining=MainLoop.nextRAF){MainLoop.nextRAF+=1e3/60}}var delay=Math.max(MainLoop.nextRAF-now,0);setTimeout(func,delay)},requestAnimationFrame(func){if(globalThis.requestAnimationFrame){requestAnimationFrame(func)}else{MainLoop.fakeRequestAnimationFrame(func)}}};var _emscripten_cancel_main_loop=()=>{MainLoop.pause();MainLoop.func=null};var JSEvents={removeAllEventListeners(){while(JSEvents.eventHandlers.length){JSEvents._removeHandler(JSEvents.eventHandlers.length-1)}JSEvents.deferredCalls=[]},inEventHandler:0,deferredCalls:[],deferCall(targetFunction,precedence,argsList){function arraysHaveEqualContent(arrA,arrB){if(arrA.length!=arrB.length)return false;for(var i in arrA){if(arrA[i]!=arrB[i])return false}return true}for(var call of JSEvents.deferredCalls){if(call.targetFunction==targetFunction&&arraysHaveEqualContent(call.argsList,argsList)){return}}JSEvents.deferredCalls.push({targetFunction,precedence,argsList});JSEvents.deferredCalls.sort((x,y)=>x.precedence-y.precedence)},removeDeferredCalls(targetFunction){JSEvents.deferredCalls=JSEvents.deferredCalls.filter(call=>call.targetFunction!=targetFunction)},canPerformEventHandlerRequests(){if(navigator.userActivation){return navigator.userActivation.isActive}return JSEvents.inEventHandler&&JSEvents.currentEventHandler.allowsDeferredCalls},runDeferredCalls(){if(!JSEvents.canPerformEventHandlerRequests()){return}var deferredCalls=JSEvents.deferredCalls;JSEvents.deferredCalls=[];for(var call of deferredCalls){call.targetFunction(...call.argsList)}},eventHandlers:[],removeAllHandlersOnTarget:(target,eventTypeString)=>{for(var i=0;icString>2?UTF8ToString(cString):cString;var findEventTarget=target=>{target=maybeCStringToJsString(target);var domElement=specialHTMLTargets[target]||globalThis.document?.querySelector(target);return domElement};var findCanvasEventTarget=findEventTarget;var _emscripten_get_canvas_element_size=(target,width,height)=>{var canvas=findCanvasEventTarget(target);if(!canvas)return-4;HEAP32[width>>2]=canvas.width;HEAP32[height>>2]=canvas.height};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var stackAlloc=sz=>__emscripten_stack_alloc(sz);var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};var getCanvasElementSize=target=>{var sp=stackSave();var w=stackAlloc(8);var h=w+4;var targetInt=stringToUTF8OnStack(target.id);var ret=_emscripten_get_canvas_element_size(targetInt,w,h);var size=[HEAP32[w>>2],HEAP32[h>>2]];stackRestore(sp);return size};var _emscripten_set_canvas_element_size=(target,width,height)=>{var canvas=findCanvasEventTarget(target);if(!canvas)return-4;canvas.width=width;canvas.height=height;return 0};var setCanvasElementSize=(target,width,height)=>{if(!target.controlTransferredOffscreen){target.width=width;target.height=height}else{var sp=stackSave();var targetInt=stringToUTF8OnStack(target.id);_emscripten_set_canvas_element_size(targetInt,width,height);stackRestore(sp)}};var currentFullscreenStrategy={};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var registerRestoreOldStyle=canvas=>{var canvasSize=getCanvasElementSize(canvas);var oldWidth=canvasSize[0];var oldHeight=canvasSize[1];var oldCssWidth=canvas.style.width;var oldCssHeight=canvas.style.height;var oldBackgroundColor=canvas.style.backgroundColor;var oldDocumentBackgroundColor=document.body.style.backgroundColor;var oldPaddingLeft=canvas.style.paddingLeft;var oldPaddingRight=canvas.style.paddingRight;var oldPaddingTop=canvas.style.paddingTop;var oldPaddingBottom=canvas.style.paddingBottom;var oldMarginLeft=canvas.style.marginLeft;var oldMarginRight=canvas.style.marginRight;var oldMarginTop=canvas.style.marginTop;var oldMarginBottom=canvas.style.marginBottom;var oldDocumentBodyMargin=document.body.style.margin;var oldDocumentOverflow=document.documentElement.style.overflow;var oldDocumentScroll=document.body.scroll;var oldImageRendering=canvas.style.imageRendering;function restoreOldStyle(){if(!getFullscreenElement()){document.removeEventListener("fullscreenchange",restoreOldStyle);document.removeEventListener("webkitfullscreenchange",restoreOldStyle);setCanvasElementSize(canvas,oldWidth,oldHeight);canvas.style.width=oldCssWidth;canvas.style.height=oldCssHeight;canvas.style.backgroundColor=oldBackgroundColor;if(!oldDocumentBackgroundColor)document.body.style.backgroundColor="white";document.body.style.backgroundColor=oldDocumentBackgroundColor;canvas.style.paddingLeft=oldPaddingLeft;canvas.style.paddingRight=oldPaddingRight;canvas.style.paddingTop=oldPaddingTop;canvas.style.paddingBottom=oldPaddingBottom;canvas.style.marginLeft=oldMarginLeft;canvas.style.marginRight=oldMarginRight;canvas.style.marginTop=oldMarginTop;canvas.style.marginBottom=oldMarginBottom;document.body.style.margin=oldDocumentBodyMargin;document.documentElement.style.overflow=oldDocumentOverflow;document.body.scroll=oldDocumentScroll;canvas.style.imageRendering=oldImageRendering;if(canvas.GLctxObject)canvas.GLctxObject.GLctx.viewport(0,0,oldWidth,oldHeight);if(currentFullscreenStrategy.canvasResizedCallback){getWasmTableEntry(currentFullscreenStrategy.canvasResizedCallback)(37,0,currentFullscreenStrategy.canvasResizedCallbackUserData)}}}document.addEventListener("fullscreenchange",restoreOldStyle);document.addEventListener("webkitfullscreenchange",restoreOldStyle);return restoreOldStyle};var setLetterbox=(element,topBottom,leftRight)=>{element.style.paddingLeft=element.style.paddingRight=leftRight+"px";element.style.paddingTop=element.style.paddingBottom=topBottom+"px"};var getBoundingClientRect=e=>specialHTMLTargets.indexOf(e)<0?e.getBoundingClientRect():{left:0,top:0};var JSEvents_resizeCanvasForFullscreen=(target,strategy)=>{var restoreOldStyle=registerRestoreOldStyle(target);var cssWidth=strategy.softFullscreen?innerWidth:screen.width;var cssHeight=strategy.softFullscreen?innerHeight:screen.height;var rect=getBoundingClientRect(target);var windowedCssWidth=rect.width;var windowedCssHeight=rect.height;var canvasSize=getCanvasElementSize(target);var windowedRttWidth=canvasSize[0];var windowedRttHeight=canvasSize[1];if(strategy.scaleMode==3){setLetterbox(target,(cssHeight-windowedCssHeight)/2,(cssWidth-windowedCssWidth)/2);cssWidth=windowedCssWidth;cssHeight=windowedCssHeight}else if(strategy.scaleMode==2){if(cssWidth*windowedRttHeight{if(strategy.scaleMode!=0||strategy.canvasResolutionScaleMode!=0){JSEvents_resizeCanvasForFullscreen(target,strategy)}if(target.requestFullscreen){target.requestFullscreen()}else if(target.webkitRequestFullscreen){target.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)}else{return JSEvents.fullscreenEnabled()?-3:-1}currentFullscreenStrategy=strategy;if(strategy.canvasResizedCallback){getWasmTableEntry(strategy.canvasResizedCallback)(37,0,strategy.canvasResizedCallbackUserData)}return 0};var _emscripten_exit_fullscreen=()=>{if(!JSEvents.fullscreenEnabled())return-1;JSEvents.removeDeferredCalls(JSEvents_requestFullscreen);var d=specialHTMLTargets[1];if(d.exitFullscreen){d.fullscreenElement&&d.exitFullscreen()}else if(d.webkitExitFullscreen){d.webkitFullscreenElement&&d.webkitExitFullscreen()}else{return-1}return 0};var requestPointerLock=target=>{if(target.requestPointerLock){target.requestPointerLock()}else{if(document.body.requestPointerLock){return-3}return-1}return 0};var _emscripten_exit_pointerlock=()=>{JSEvents.removeDeferredCalls(requestPointerLock);if(!document.exitPointerLock)return-1;document.exitPointerLock();return 0};var _emscripten_get_device_pixel_ratio=()=>globalThis.devicePixelRatio??1;var _emscripten_get_element_css_size=(target,width,height)=>{target=findEventTarget(target);if(!target)return-4;var rect=getBoundingClientRect(target);HEAPF64[width>>3]=rect.width;HEAPF64[height>>3]=rect.height;return 0};function getFullscreenElement(){return document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.webkitCurrentFullScreenElement||document.msFullscreenElement}var fillFullscreenChangeEventData=eventStruct=>{var fullscreenElement=getFullscreenElement();var isFullscreen=!!fullscreenElement;HEAP8[eventStruct]=isFullscreen;HEAP8[eventStruct+1]=JSEvents.fullscreenEnabled();var reportedElement=isFullscreen?fullscreenElement:JSEvents.previousFullscreenElement;var nodeName=JSEvents.getNodeNameForTarget(reportedElement);var id=reportedElement?.id||"";stringToUTF8(nodeName,eventStruct+2,128);stringToUTF8(id,eventStruct+130,128);HEAP32[eventStruct+260>>2]=reportedElement?reportedElement.clientWidth:0;HEAP32[eventStruct+264>>2]=reportedElement?reportedElement.clientHeight:0;HEAP32[eventStruct+268>>2]=screen.width;HEAP32[eventStruct+272>>2]=screen.height;if(isFullscreen){JSEvents.previousFullscreenElement=fullscreenElement}};var _emscripten_get_fullscreen_status=fullscreenStatus=>{if(!JSEvents.fullscreenEnabled())return-1;fillFullscreenChangeEventData(fullscreenStatus);return 0};var fillGamepadEventData=(eventStruct,e)=>{HEAPF64[eventStruct>>3]=e.timestamp;for(var i=0;i>3]=e.axes[i]}for(var i=0;i>3]=e.buttons[i].value}else{HEAPF64[eventStruct+i*8+528>>3]=e.buttons[i]}}for(var i=0;i>2]=e.index;HEAP32[eventStruct+8>>2]=e.axes.length;HEAP32[eventStruct+12>>2]=e.buttons.length;stringToUTF8(e.id,eventStruct+1112,64);stringToUTF8(e.mapping,eventStruct+1176,64)};var _emscripten_get_gamepad_status=(index,gamepadState)=>{if(index<0||index>=JSEvents.lastGamepadState.length)return-5;if(!JSEvents.lastGamepadState[index])return-7;fillGamepadEventData(gamepadState,JSEvents.lastGamepadState[index]);return 0};var _emscripten_get_main_loop_timing=(mode,value)=>{if(mode)HEAP32[mode>>2]=MainLoop.timingMode;if(value)HEAP32[value>>2]=MainLoop.timingValue};var _emscripten_get_num_gamepads=()=>JSEvents.lastGamepadState.length;var safeSetTimeout=(func,timeout)=>setTimeout(()=>{callUserCallback(func)},timeout);var warnOnce=text=>{warnOnce.shown||={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;if(ENVIRONMENT_IS_NODE)text="warning: "+text;err(text)}};var Browser={useWebGL:false,isFullscreen:false,pointerLock:false,moduleContextCreatedCallbacks:[],workers:[],preloadedImages:{},preloadedAudios:{},getCanvas:()=>Module["canvas"],init(){if(Browser.initted)return;Browser.initted=true;var imagePlugin={};imagePlugin["canHandle"]=name=>!Module["noImageDecoding"]&&/\.(jpg|jpeg|png|bmp|webp)$/i.test(name);imagePlugin["handle"]=async(byteArray,name)=>{var b=new Blob([byteArray],{type:Browser.getMimetype(name)});if(b.size!==byteArray.length){b=new Blob([new Uint8Array(byteArray).buffer],{type:Browser.getMimetype(name)})}var url=URL.createObjectURL(b);return new Promise((resolve,reject)=>{var img=new Image;img.onload=()=>{var canvas=document.createElement("canvas");canvas.width=img.width;canvas.height=img.height;var ctx=canvas.getContext("2d");ctx.drawImage(img,0,0);Browser.preloadedImages[name]=canvas;URL.revokeObjectURL(url);resolve(byteArray)};img.onerror=event=>{err(`Image ${url} could not be decoded`);reject()};img.src=url})};preloadPlugins.push(imagePlugin);var audioPlugin={};audioPlugin["canHandle"]=name=>!Module["noAudioDecoding"]&&name.slice(-4)in{".ogg":1,".wav":1,".mp3":1};audioPlugin["handle"]=async(byteArray,name)=>new Promise((resolve,reject)=>{var done=false;function finish(audio){if(done)return;done=true;Browser.preloadedAudios[name]=audio;resolve(byteArray)}var b=new Blob([byteArray],{type:Browser.getMimetype(name)});var url=URL.createObjectURL(b);var audio=new Audio;audio.addEventListener("canplaythrough",()=>finish(audio),false);audio.onerror=event=>{if(done)return;err(`warning: browser could not fully decode audio ${name}, trying slower base64 approach`);function encode64(data){var BASE="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var PAD="=";var ret="";var leftchar=0;var leftbits=0;for(var i=0;i=6){var curr=leftchar>>leftbits-6&63;leftbits-=6;ret+=BASE[curr]}}if(leftbits==2){ret+=BASE[(leftchar&3)<<4];ret+=PAD+PAD}else if(leftbits==4){ret+=BASE[(leftchar&15)<<2];ret+=PAD}return ret}audio.src="data:audio/x-"+name.slice(-3)+";base64,"+encode64(byteArray);finish(audio)};audio.src=url;safeSetTimeout(()=>{finish(audio)},1e4)});preloadPlugins.push(audioPlugin);function pointerLockChange(){var canvas=Browser.getCanvas();Browser.pointerLock=document.pointerLockElement===canvas}var canvas=Browser.getCanvas();if(canvas){document.addEventListener("pointerlockchange",pointerLockChange,false);if(Module["elementPointerLock"]){canvas.addEventListener("click",ev=>{if(!Browser.pointerLock&&Browser.getCanvas().requestPointerLock){Browser.getCanvas().requestPointerLock();ev.preventDefault()}},false)}}},createContext(canvas,useWebGL,setInModule,webGLContextAttributes){if(useWebGL&&Module["ctx"]&&canvas==Browser.getCanvas())return Module["ctx"];var ctx;var contextHandle;if(useWebGL){var contextAttributes={antialias:false,alpha:false,majorVersion:typeof WebGL2RenderingContext!="undefined"?2:1};if(webGLContextAttributes){for(var attribute in webGLContextAttributes){contextAttributes[attribute]=webGLContextAttributes[attribute]}}if(typeof GL!="undefined"){contextHandle=GL.createContext(canvas,contextAttributes);if(contextHandle){ctx=GL.getContext(contextHandle).GLctx}}}else{ctx=canvas.getContext("2d")}if(!ctx)return null;if(setInModule){Module["ctx"]=ctx;if(useWebGL)GL.makeContextCurrent(contextHandle);Browser.useWebGL=useWebGL;Browser.moduleContextCreatedCallbacks.forEach(callback=>callback());Browser.init()}return ctx},fullscreenHandlersInstalled:false,lockPointer:undefined,resizeCanvas:undefined,requestFullscreen(lockPointer,resizeCanvas){Browser.lockPointer=lockPointer;Browser.resizeCanvas=resizeCanvas;if(typeof Browser.lockPointer=="undefined")Browser.lockPointer=true;if(typeof Browser.resizeCanvas=="undefined")Browser.resizeCanvas=false;var canvas=Browser.getCanvas();function fullscreenChange(){Browser.isFullscreen=false;var canvasContainer=canvas.parentNode;if(getFullscreenElement()===canvasContainer){canvas.exitFullscreen=Browser.exitFullscreen;if(Browser.lockPointer)canvas.requestPointerLock();Browser.isFullscreen=true;if(Browser.resizeCanvas){Browser.setFullscreenCanvasSize()}else{Browser.updateCanvasDimensions(canvas)}}else{canvasContainer.parentNode.insertBefore(canvas,canvasContainer);canvasContainer.parentNode.removeChild(canvasContainer);if(Browser.resizeCanvas){Browser.setWindowedCanvasSize()}else{Browser.updateCanvasDimensions(canvas)}}Module["onFullScreen"]?.(Browser.isFullscreen);Module["onFullscreen"]?.(Browser.isFullscreen)}if(!Browser.fullscreenHandlersInstalled){Browser.fullscreenHandlersInstalled=true;document.addEventListener("fullscreenchange",fullscreenChange,false);document.addEventListener("mozfullscreenchange",fullscreenChange,false);document.addEventListener("webkitfullscreenchange",fullscreenChange,false);document.addEventListener("MSFullscreenChange",fullscreenChange,false)}var canvasContainer=document.createElement("div");canvas.parentNode.insertBefore(canvasContainer,canvas);canvasContainer.appendChild(canvas);canvasContainer.requestFullscreen=canvasContainer["requestFullscreen"]||canvasContainer["mozRequestFullScreen"]||canvasContainer["msRequestFullscreen"]||(canvasContainer["webkitRequestFullscreen"]?()=>canvasContainer["webkitRequestFullscreen"](Element["ALLOW_KEYBOARD_INPUT"]):null)||(canvasContainer["webkitRequestFullScreen"]?()=>canvasContainer["webkitRequestFullScreen"](Element["ALLOW_KEYBOARD_INPUT"]):null);canvasContainer.requestFullscreen()},exitFullscreen(){if(!Browser.isFullscreen){return false}var CFS=document["exitFullscreen"]||document["cancelFullScreen"]||document["mozCancelFullScreen"]||document["msExitFullscreen"]||document["webkitCancelFullScreen"]||(()=>{});CFS.apply(document,[]);return true},safeSetTimeout(func,timeout){return safeSetTimeout(func,timeout)},getMimetype(name){return{jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",bmp:"image/bmp",ogg:"audio/ogg",wav:"audio/wav",mp3:"audio/mpeg"}[name.slice(name.lastIndexOf(".")+1)]},getUserMedia(func){window.getUserMedia||=navigator["getUserMedia"]||navigator["mozGetUserMedia"];window.getUserMedia(func)},getMovementX(event){return event["movementX"]||event["mozMovementX"]||event["webkitMovementX"]||0},getMovementY(event){return event["movementY"]||event["mozMovementY"]||event["webkitMovementY"]||0},getMouseWheelDelta(event){var delta=0;switch(event.type){case"DOMMouseScroll":delta=event.detail/3;break;case"mousewheel":delta=event.wheelDelta/120;break;case"wheel":delta=event.deltaY;switch(event.deltaMode){case 0:delta/=100;break;case 1:delta/=3;break;case 2:delta*=80;break;default:abort("unrecognized mouse wheel delta mode: "+event.deltaMode)}break;default:abort("unrecognized mouse wheel event: "+event.type)}return delta},mouseX:0,mouseY:0,mouseMovementX:0,mouseMovementY:0,touches:{},lastTouches:{},calculateMouseCoords(pageX,pageY){var canvas=Browser.getCanvas();var rect=canvas.getBoundingClientRect();var scrollX=typeof window.scrollX!="undefined"?window.scrollX:window.pageXOffset;var scrollY=typeof window.scrollY!="undefined"?window.scrollY:window.pageYOffset;var adjustedX=pageX-(scrollX+rect.left);var adjustedY=pageY-(scrollY+rect.top);adjustedX=adjustedX*(canvas.width/rect.width);adjustedY=adjustedY*(canvas.height/rect.height);return{x:adjustedX,y:adjustedY}},setMouseCoords(pageX,pageY){const{x,y}=Browser.calculateMouseCoords(pageX,pageY);Browser.mouseMovementX=x-Browser.mouseX;Browser.mouseMovementY=y-Browser.mouseY;Browser.mouseX=x;Browser.mouseY=y},calculateMouseEvent(event){if(Browser.pointerLock){if(event.type!="mousemove"&&"mozMovementX"in event){Browser.mouseMovementX=Browser.mouseMovementY=0}else{Browser.mouseMovementX=Browser.getMovementX(event);Browser.mouseMovementY=Browser.getMovementY(event)}Browser.mouseX+=Browser.mouseMovementX;Browser.mouseY+=Browser.mouseMovementY}else{if(event.type==="touchstart"||event.type==="touchend"||event.type==="touchmove"){var touch=event.touch;if(touch===undefined){return}var coords=Browser.calculateMouseCoords(touch.pageX,touch.pageY);if(event.type==="touchstart"){Browser.lastTouches[touch.identifier]=coords;Browser.touches[touch.identifier]=coords}else if(event.type==="touchend"||event.type==="touchmove"){var last=Browser.touches[touch.identifier];last||=coords;Browser.lastTouches[touch.identifier]=last;Browser.touches[touch.identifier]=coords}return}Browser.setMouseCoords(event.pageX,event.pageY)}},resizeListeners:[],updateResizeListeners(){var canvas=Browser.getCanvas();Browser.resizeListeners.forEach(listener=>listener(canvas.width,canvas.height))},setCanvasSize(width,height,noUpdates){var canvas=Browser.getCanvas();Browser.updateCanvasDimensions(canvas,width,height);if(!noUpdates)Browser.updateResizeListeners()},windowedWidth:0,windowedHeight:0,setFullscreenCanvasSize(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen>>2];flags=flags|8388608;HEAP32[SDL.screen>>2]=flags}Browser.updateCanvasDimensions(Browser.getCanvas());Browser.updateResizeListeners()},setWindowedCanvasSize(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen>>2];flags=flags&~8388608;HEAP32[SDL.screen>>2]=flags}Browser.updateCanvasDimensions(Browser.getCanvas());Browser.updateResizeListeners()},updateCanvasDimensions(canvas,wNative,hNative){if(wNative&&hNative){canvas.widthNative=wNative;canvas.heightNative=hNative}else{wNative=canvas.widthNative;hNative=canvas.heightNative}var w=wNative;var h=hNative;if(Module["forcedAspectRatio"]>0){if(w/h{HEAP32[width>>2]=screen.width;HEAP32[height>>2]=screen.height};var GLctx;var webgl_enable_ANGLE_instanced_arrays=ctx=>{var ext=ctx.getExtension("ANGLE_instanced_arrays");if(ext){ctx["vertexAttribDivisor"]=(index,divisor)=>ext["vertexAttribDivisorANGLE"](index,divisor);ctx["drawArraysInstanced"]=(mode,first,count,primcount)=>ext["drawArraysInstancedANGLE"](mode,first,count,primcount);ctx["drawElementsInstanced"]=(mode,count,type,indices,primcount)=>ext["drawElementsInstancedANGLE"](mode,count,type,indices,primcount);return 1}};var webgl_enable_OES_vertex_array_object=ctx=>{var ext=ctx.getExtension("OES_vertex_array_object");if(ext){ctx["createVertexArray"]=()=>ext["createVertexArrayOES"]();ctx["deleteVertexArray"]=vao=>ext["deleteVertexArrayOES"](vao);ctx["bindVertexArray"]=vao=>ext["bindVertexArrayOES"](vao);ctx["isVertexArray"]=vao=>ext["isVertexArrayOES"](vao);return 1}};var webgl_enable_WEBGL_draw_buffers=ctx=>{var ext=ctx.getExtension("WEBGL_draw_buffers");if(ext){ctx["drawBuffers"]=(n,bufs)=>ext["drawBuffersWEBGL"](n,bufs);return 1}};var webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance=ctx=>!!(ctx.dibvbi=ctx.getExtension("WEBGL_draw_instanced_base_vertex_base_instance"));var webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance=ctx=>!!(ctx.mdibvbi=ctx.getExtension("WEBGL_multi_draw_instanced_base_vertex_base_instance"));var webgl_enable_EXT_polygon_offset_clamp=ctx=>!!(ctx.extPolygonOffsetClamp=ctx.getExtension("EXT_polygon_offset_clamp"));var webgl_enable_EXT_clip_control=ctx=>!!(ctx.extClipControl=ctx.getExtension("EXT_clip_control"));var webgl_enable_WEBGL_polygon_mode=ctx=>!!(ctx.webglPolygonMode=ctx.getExtension("WEBGL_polygon_mode"));var webgl_enable_WEBGL_multi_draw=ctx=>!!(ctx.multiDrawWebgl=ctx.getExtension("WEBGL_multi_draw"));var getEmscriptenSupportedExtensions=ctx=>{var supportedExtensions=["ANGLE_instanced_arrays","EXT_blend_minmax","EXT_disjoint_timer_query","EXT_frag_depth","EXT_shader_texture_lod","EXT_sRGB","OES_element_index_uint","OES_fbo_render_mipmap","OES_standard_derivatives","OES_texture_float","OES_texture_half_float","OES_texture_half_float_linear","OES_vertex_array_object","WEBGL_color_buffer_float","WEBGL_depth_texture","WEBGL_draw_buffers","EXT_color_buffer_float","EXT_conservative_depth","EXT_disjoint_timer_query_webgl2","EXT_texture_norm16","NV_shader_noperspective_interpolation","WEBGL_clip_cull_distance","EXT_clip_control","EXT_color_buffer_half_float","EXT_depth_clamp","EXT_float_blend","EXT_polygon_offset_clamp","EXT_texture_compression_bptc","EXT_texture_compression_rgtc","EXT_texture_filter_anisotropic","KHR_parallel_shader_compile","OES_texture_float_linear","WEBGL_blend_func_extended","WEBGL_compressed_texture_astc","WEBGL_compressed_texture_etc","WEBGL_compressed_texture_etc1","WEBGL_compressed_texture_s3tc","WEBGL_compressed_texture_s3tc_srgb","WEBGL_debug_renderer_info","WEBGL_debug_shaders","WEBGL_lose_context","WEBGL_multi_draw","WEBGL_polygon_mode"];return(ctx.getSupportedExtensions()||[]).filter(ext=>supportedExtensions.includes(ext))};var GL={counter:1,buffers:[],programs:[],framebuffers:[],renderbuffers:[],textures:[],shaders:[],vaos:[],contexts:[],offscreenCanvases:{},queries:[],samplers:[],transformFeedbacks:[],syncs:[],stringCache:{},stringiCache:{},unpackAlignment:4,unpackRowLength:0,recordError:errorCode=>{if(!GL.lastError){GL.lastError=errorCode}},getNewId:table=>{var ret=GL.counter++;for(var i=table.length;i{for(var i=0;i>2]=id}},getSource:(shader,count,string,length)=>{var source="";for(var i=0;i>2]:undefined;source+=UTF8ToString(HEAPU32[string+i*4>>2],len)}return source},createContext:(canvas,webGLContextAttributes)=>{if(!canvas.getContextSafariWebGL2Fixed){canvas.getContextSafariWebGL2Fixed=canvas.getContext;function fixedGetContext(ver,attrs){var gl=canvas.getContextSafariWebGL2Fixed(ver,attrs);return ver=="webgl"==gl instanceof WebGLRenderingContext?gl:null}canvas.getContext=fixedGetContext}var ctx=webGLContextAttributes.majorVersion>1?canvas.getContext("webgl2",webGLContextAttributes):canvas.getContext("webgl",webGLContextAttributes);if(!ctx)return 0;var handle=GL.registerContext(ctx,webGLContextAttributes);return handle},registerContext:(ctx,webGLContextAttributes)=>{var handle=GL.getNewId(GL.contexts);var context={handle,attributes:webGLContextAttributes,version:webGLContextAttributes.majorVersion,GLctx:ctx};if(ctx.canvas)ctx.canvas.GLctxObject=context;GL.contexts[handle]=context;if(typeof webGLContextAttributes.enableExtensionsByDefault=="undefined"||webGLContextAttributes.enableExtensionsByDefault){GL.initExtensions(context)}return handle},makeContextCurrent:contextHandle=>{GL.currentContext=GL.contexts[contextHandle];Module["ctx"]=GLctx=GL.currentContext?.GLctx;return!(contextHandle&&!GLctx)},getContext:contextHandle=>GL.contexts[contextHandle],deleteContext:contextHandle=>{if(GL.currentContext===GL.contexts[contextHandle]){GL.currentContext=null}if(typeof JSEvents=="object"){JSEvents.removeAllHandlersOnTarget(GL.contexts[contextHandle].GLctx.canvas)}if(GL.contexts[contextHandle]?.GLctx.canvas){GL.contexts[contextHandle].GLctx.canvas.GLctxObject=undefined}GL.contexts[contextHandle]=null},initExtensions:context=>{context||=GL.currentContext;if(context.initExtensionsDone)return;context.initExtensionsDone=true;var GLctx=context.GLctx;webgl_enable_WEBGL_multi_draw(GLctx);webgl_enable_EXT_polygon_offset_clamp(GLctx);webgl_enable_EXT_clip_control(GLctx);webgl_enable_WEBGL_polygon_mode(GLctx);webgl_enable_ANGLE_instanced_arrays(GLctx);webgl_enable_OES_vertex_array_object(GLctx);webgl_enable_WEBGL_draw_buffers(GLctx);webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(GLctx);webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(GLctx);if(context.version>=2){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query_webgl2")}if(context.version<2||!GLctx.disjointTimerQueryExt){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query")}for(var ext of getEmscriptenSupportedExtensions(GLctx)){if(!ext.includes("lose_context")&&!ext.includes("debug")){GLctx.getExtension(ext)}}}};var _emscripten_glActiveTexture=x0=>GLctx.activeTexture(x0);var _emscripten_glAttachShader=(program,shader)=>{GLctx.attachShader(GL.programs[program],GL.shaders[shader])};var _emscripten_glBeginQuery=(target,id)=>{GLctx.beginQuery(target,GL.queries[id])};var _emscripten_glBeginQueryEXT=(target,id)=>{GLctx.disjointTimerQueryExt["beginQueryEXT"](target,GL.queries[id])};var _emscripten_glBeginTransformFeedback=x0=>GLctx.beginTransformFeedback(x0);var _emscripten_glBindAttribLocation=(program,index,name)=>{GLctx.bindAttribLocation(GL.programs[program],index,UTF8ToString(name))};var _emscripten_glBindBuffer=(target,buffer)=>{if(target==35051){GLctx.currentPixelPackBufferBinding=buffer}else if(target==35052){GLctx.currentPixelUnpackBufferBinding=buffer}GLctx.bindBuffer(target,GL.buffers[buffer])};var _emscripten_glBindBufferBase=(target,index,buffer)=>{GLctx.bindBufferBase(target,index,GL.buffers[buffer])};var _emscripten_glBindBufferRange=(target,index,buffer,offset,ptrsize)=>{GLctx.bindBufferRange(target,index,GL.buffers[buffer],offset,ptrsize)};var _emscripten_glBindFramebuffer=(target,framebuffer)=>{GLctx.bindFramebuffer(target,GL.framebuffers[framebuffer])};var _emscripten_glBindRenderbuffer=(target,renderbuffer)=>{GLctx.bindRenderbuffer(target,GL.renderbuffers[renderbuffer])};var _emscripten_glBindSampler=(unit,sampler)=>{GLctx.bindSampler(unit,GL.samplers[sampler])};var _emscripten_glBindTexture=(target,texture)=>{GLctx.bindTexture(target,GL.textures[texture])};var _emscripten_glBindTransformFeedback=(target,id)=>{GLctx.bindTransformFeedback(target,GL.transformFeedbacks[id])};var _emscripten_glBindVertexArray=vao=>{GLctx.bindVertexArray(GL.vaos[vao])};var _glBindVertexArray=_emscripten_glBindVertexArray;var _emscripten_glBindVertexArrayOES=_glBindVertexArray;var _emscripten_glBlendColor=(x0,x1,x2,x3)=>GLctx.blendColor(x0,x1,x2,x3);var _emscripten_glBlendEquation=x0=>GLctx.blendEquation(x0);var _emscripten_glBlendEquationSeparate=(x0,x1)=>GLctx.blendEquationSeparate(x0,x1);var _emscripten_glBlendFunc=(x0,x1)=>GLctx.blendFunc(x0,x1);var _emscripten_glBlendFuncSeparate=(x0,x1,x2,x3)=>GLctx.blendFuncSeparate(x0,x1,x2,x3);var _emscripten_glBlitFramebuffer=(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9)=>GLctx.blitFramebuffer(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9);var _emscripten_glBufferData=(target,size,data,usage)=>{if(GL.currentContext.version>=2){if(data&&size){GLctx.bufferData(target,HEAPU8,usage,data,size)}else{GLctx.bufferData(target,size,usage)}return}GLctx.bufferData(target,data?HEAPU8.subarray(data,data+size):size,usage)};var _emscripten_glBufferSubData=(target,offset,size,data)=>{if(GL.currentContext.version>=2){size&&GLctx.bufferSubData(target,offset,HEAPU8,data,size);return}GLctx.bufferSubData(target,offset,HEAPU8.subarray(data,data+size))};var _emscripten_glCheckFramebufferStatus=x0=>GLctx.checkFramebufferStatus(x0);var _emscripten_glClear=x0=>GLctx.clear(x0);var _emscripten_glClearBufferfi=(x0,x1,x2,x3)=>GLctx.clearBufferfi(x0,x1,x2,x3);var _emscripten_glClearBufferfv=(buffer,drawbuffer,value)=>{GLctx.clearBufferfv(buffer,drawbuffer,HEAPF32,value>>2)};var _emscripten_glClearBufferiv=(buffer,drawbuffer,value)=>{GLctx.clearBufferiv(buffer,drawbuffer,HEAP32,value>>2)};var _emscripten_glClearBufferuiv=(buffer,drawbuffer,value)=>{GLctx.clearBufferuiv(buffer,drawbuffer,HEAPU32,value>>2)};var _emscripten_glClearColor=(x0,x1,x2,x3)=>GLctx.clearColor(x0,x1,x2,x3);var _emscripten_glClearDepthf=x0=>GLctx.clearDepth(x0);var _emscripten_glClearStencil=x0=>GLctx.clearStencil(x0);var _emscripten_glClientWaitSync=(sync,flags,timeout)=>{timeout=Number(timeout);return GLctx.clientWaitSync(GL.syncs[sync],flags,timeout)};var _emscripten_glClipControlEXT=(origin,depth)=>{GLctx.extClipControl["clipControlEXT"](origin,depth)};var _emscripten_glColorMask=(red,green,blue,alpha)=>{GLctx.colorMask(!!red,!!green,!!blue,!!alpha)};var _emscripten_glCompileShader=shader=>{GLctx.compileShader(GL.shaders[shader])};var _emscripten_glCompressedTexImage2D=(target,level,internalFormat,width,height,border,imageSize,data)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding||!imageSize){GLctx.compressedTexImage2D(target,level,internalFormat,width,height,border,imageSize,data);return}GLctx.compressedTexImage2D(target,level,internalFormat,width,height,border,HEAPU8,data,imageSize);return}GLctx.compressedTexImage2D(target,level,internalFormat,width,height,border,HEAPU8.subarray(data,data+imageSize))};var _emscripten_glCompressedTexImage3D=(target,level,internalFormat,width,height,depth,border,imageSize,data)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.compressedTexImage3D(target,level,internalFormat,width,height,depth,border,imageSize,data)}else{GLctx.compressedTexImage3D(target,level,internalFormat,width,height,depth,border,HEAPU8,data,imageSize)}};var _emscripten_glCompressedTexSubImage2D=(target,level,xoffset,yoffset,width,height,format,imageSize,data)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding||!imageSize){GLctx.compressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,imageSize,data);return}GLctx.compressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,HEAPU8,data,imageSize);return}GLctx.compressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,HEAPU8.subarray(data,data+imageSize))};var _emscripten_glCompressedTexSubImage3D=(target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.compressedTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data)}else{GLctx.compressedTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,HEAPU8,data,imageSize)}};var _emscripten_glCopyBufferSubData=(x0,x1,x2,x3,x4)=>GLctx.copyBufferSubData(x0,x1,x2,x3,x4);var _emscripten_glCopyTexImage2D=(x0,x1,x2,x3,x4,x5,x6,x7)=>GLctx.copyTexImage2D(x0,x1,x2,x3,x4,x5,x6,x7);var _emscripten_glCopyTexSubImage2D=(x0,x1,x2,x3,x4,x5,x6,x7)=>GLctx.copyTexSubImage2D(x0,x1,x2,x3,x4,x5,x6,x7);var _emscripten_glCopyTexSubImage3D=(x0,x1,x2,x3,x4,x5,x6,x7,x8)=>GLctx.copyTexSubImage3D(x0,x1,x2,x3,x4,x5,x6,x7,x8);var _emscripten_glCreateProgram=()=>{var id=GL.getNewId(GL.programs);var program=GLctx.createProgram();program.name=id;program.maxUniformLength=program.maxAttributeLength=program.maxUniformBlockNameLength=0;program.uniformIdCounter=1;GL.programs[id]=program;return id};var _emscripten_glCreateShader=shaderType=>{var id=GL.getNewId(GL.shaders);GL.shaders[id]=GLctx.createShader(shaderType);return id};var _emscripten_glCullFace=x0=>GLctx.cullFace(x0);var _emscripten_glDeleteBuffers=(n,buffers)=>{for(var i=0;i>2];var buffer=GL.buffers[id];if(!buffer)continue;GLctx.deleteBuffer(buffer);buffer.name=0;GL.buffers[id]=null;if(id==GLctx.currentPixelPackBufferBinding)GLctx.currentPixelPackBufferBinding=0;if(id==GLctx.currentPixelUnpackBufferBinding)GLctx.currentPixelUnpackBufferBinding=0}};var _emscripten_glDeleteFramebuffers=(n,framebuffers)=>{for(var i=0;i>2];var framebuffer=GL.framebuffers[id];if(!framebuffer)continue;GLctx.deleteFramebuffer(framebuffer);framebuffer.name=0;GL.framebuffers[id]=null}};var _emscripten_glDeleteProgram=id=>{if(!id)return;var program=GL.programs[id];if(!program){GL.recordError(1281);return}GLctx.deleteProgram(program);program.name=0;GL.programs[id]=null};var _emscripten_glDeleteQueries=(n,ids)=>{for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx.deleteQuery(query);GL.queries[id]=null}};var _emscripten_glDeleteQueriesEXT=(n,ids)=>{for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx.disjointTimerQueryExt["deleteQueryEXT"](query);GL.queries[id]=null}};var _emscripten_glDeleteRenderbuffers=(n,renderbuffers)=>{for(var i=0;i>2];var renderbuffer=GL.renderbuffers[id];if(!renderbuffer)continue;GLctx.deleteRenderbuffer(renderbuffer);renderbuffer.name=0;GL.renderbuffers[id]=null}};var _emscripten_glDeleteSamplers=(n,samplers)=>{for(var i=0;i>2];var sampler=GL.samplers[id];if(!sampler)continue;GLctx.deleteSampler(sampler);sampler.name=0;GL.samplers[id]=null}};var _emscripten_glDeleteShader=id=>{if(!id)return;var shader=GL.shaders[id];if(!shader){GL.recordError(1281);return}GLctx.deleteShader(shader);GL.shaders[id]=null};var _emscripten_glDeleteSync=id=>{if(!id)return;var sync=GL.syncs[id];if(!sync){GL.recordError(1281);return}GLctx.deleteSync(sync);sync.name=0;GL.syncs[id]=null};var _emscripten_glDeleteTextures=(n,textures)=>{for(var i=0;i>2];var texture=GL.textures[id];if(!texture)continue;GLctx.deleteTexture(texture);texture.name=0;GL.textures[id]=null}};var _emscripten_glDeleteTransformFeedbacks=(n,ids)=>{for(var i=0;i>2];var transformFeedback=GL.transformFeedbacks[id];if(!transformFeedback)continue;GLctx.deleteTransformFeedback(transformFeedback);transformFeedback.name=0;GL.transformFeedbacks[id]=null}};var _emscripten_glDeleteVertexArrays=(n,vaos)=>{for(var i=0;i>2];GLctx.deleteVertexArray(GL.vaos[id]);GL.vaos[id]=null}};var _glDeleteVertexArrays=_emscripten_glDeleteVertexArrays;var _emscripten_glDeleteVertexArraysOES=_glDeleteVertexArrays;var _emscripten_glDepthFunc=x0=>GLctx.depthFunc(x0);var _emscripten_glDepthMask=flag=>{GLctx.depthMask(!!flag)};var _emscripten_glDepthRangef=(x0,x1)=>GLctx.depthRange(x0,x1);var _emscripten_glDetachShader=(program,shader)=>{GLctx.detachShader(GL.programs[program],GL.shaders[shader])};var _emscripten_glDisable=x0=>GLctx.disable(x0);var _emscripten_glDisableVertexAttribArray=index=>{GLctx.disableVertexAttribArray(index)};var _emscripten_glDrawArrays=(mode,first,count)=>{GLctx.drawArrays(mode,first,count)};var _emscripten_glDrawArraysInstanced=(mode,first,count,primcount)=>{GLctx.drawArraysInstanced(mode,first,count,primcount)};var _glDrawArraysInstanced=_emscripten_glDrawArraysInstanced;var _emscripten_glDrawArraysInstancedANGLE=_glDrawArraysInstanced;var _emscripten_glDrawArraysInstancedARB=_glDrawArraysInstanced;var _emscripten_glDrawArraysInstancedEXT=_glDrawArraysInstanced;var _emscripten_glDrawArraysInstancedNV=_glDrawArraysInstanced;var tempFixedLengthArray=[];var _emscripten_glDrawBuffers=(n,bufs)=>{var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx.drawBuffers(bufArray)};var _glDrawBuffers=_emscripten_glDrawBuffers;var _emscripten_glDrawBuffersEXT=_glDrawBuffers;var _emscripten_glDrawBuffersWEBGL=_glDrawBuffers;var _emscripten_glDrawElements=(mode,count,type,indices)=>{GLctx.drawElements(mode,count,type,indices)};var _emscripten_glDrawElementsInstanced=(mode,count,type,indices,primcount)=>{GLctx.drawElementsInstanced(mode,count,type,indices,primcount)};var _glDrawElementsInstanced=_emscripten_glDrawElementsInstanced;var _emscripten_glDrawElementsInstancedANGLE=_glDrawElementsInstanced;var _emscripten_glDrawElementsInstancedARB=_glDrawElementsInstanced;var _emscripten_glDrawElementsInstancedEXT=_glDrawElementsInstanced;var _emscripten_glDrawElementsInstancedNV=_glDrawElementsInstanced;var _glDrawElements=_emscripten_glDrawElements;var _emscripten_glDrawRangeElements=(mode,start,end,count,type,indices)=>{_glDrawElements(mode,count,type,indices)};var _emscripten_glEnable=x0=>GLctx.enable(x0);var _emscripten_glEnableVertexAttribArray=index=>{GLctx.enableVertexAttribArray(index)};var _emscripten_glEndQuery=x0=>GLctx.endQuery(x0);var _emscripten_glEndQueryEXT=target=>{GLctx.disjointTimerQueryExt["endQueryEXT"](target)};var _emscripten_glEndTransformFeedback=()=>GLctx.endTransformFeedback();var _emscripten_glFenceSync=(condition,flags)=>{var sync=GLctx.fenceSync(condition,flags);if(sync){var id=GL.getNewId(GL.syncs);sync.name=id;GL.syncs[id]=sync;return id}return 0};var _emscripten_glFinish=()=>GLctx.finish();var _emscripten_glFlush=()=>GLctx.flush();var _emscripten_glFramebufferRenderbuffer=(target,attachment,renderbuffertarget,renderbuffer)=>{GLctx.framebufferRenderbuffer(target,attachment,renderbuffertarget,GL.renderbuffers[renderbuffer])};var _emscripten_glFramebufferTexture2D=(target,attachment,textarget,texture,level)=>{GLctx.framebufferTexture2D(target,attachment,textarget,GL.textures[texture],level)};var _emscripten_glFramebufferTextureLayer=(target,attachment,texture,level,layer)=>{GLctx.framebufferTextureLayer(target,attachment,GL.textures[texture],level,layer)};var _emscripten_glFrontFace=x0=>GLctx.frontFace(x0);var _emscripten_glGenBuffers=(n,buffers)=>{GL.genObject(n,buffers,"createBuffer",GL.buffers)};var _emscripten_glGenFramebuffers=(n,ids)=>{GL.genObject(n,ids,"createFramebuffer",GL.framebuffers)};var _emscripten_glGenQueries=(n,ids)=>{GL.genObject(n,ids,"createQuery",GL.queries)};var _emscripten_glGenQueriesEXT=(n,ids)=>{for(var i=0;i>2]=0;return}var id=GL.getNewId(GL.queries);query.name=id;GL.queries[id]=query;HEAP32[ids+i*4>>2]=id}};var _emscripten_glGenRenderbuffers=(n,renderbuffers)=>{GL.genObject(n,renderbuffers,"createRenderbuffer",GL.renderbuffers)};var _emscripten_glGenSamplers=(n,samplers)=>{GL.genObject(n,samplers,"createSampler",GL.samplers)};var _emscripten_glGenTextures=(n,textures)=>{GL.genObject(n,textures,"createTexture",GL.textures)};var _emscripten_glGenTransformFeedbacks=(n,ids)=>{GL.genObject(n,ids,"createTransformFeedback",GL.transformFeedbacks)};var _emscripten_glGenVertexArrays=(n,arrays)=>{GL.genObject(n,arrays,"createVertexArray",GL.vaos)};var _glGenVertexArrays=_emscripten_glGenVertexArrays;var _emscripten_glGenVertexArraysOES=_glGenVertexArrays;var _emscripten_glGenerateMipmap=x0=>GLctx.generateMipmap(x0);var __glGetActiveAttribOrUniform=(funcName,program,index,bufSize,length,size,type,name)=>{program=GL.programs[program];var info=GLctx[funcName](program,index);if(info){var numBytesWrittenExclNull=name&&stringToUTF8(info.name,name,bufSize);if(length)HEAP32[length>>2]=numBytesWrittenExclNull;if(size)HEAP32[size>>2]=info.size;if(type)HEAP32[type>>2]=info.type}};var _emscripten_glGetActiveAttrib=(program,index,bufSize,length,size,type,name)=>__glGetActiveAttribOrUniform("getActiveAttrib",program,index,bufSize,length,size,type,name);var _emscripten_glGetActiveUniform=(program,index,bufSize,length,size,type,name)=>__glGetActiveAttribOrUniform("getActiveUniform",program,index,bufSize,length,size,type,name);var _emscripten_glGetActiveUniformBlockName=(program,uniformBlockIndex,bufSize,length,uniformBlockName)=>{program=GL.programs[program];var result=GLctx.getActiveUniformBlockName(program,uniformBlockIndex);if(!result)return;if(uniformBlockName&&bufSize>0){var numBytesWrittenExclNull=stringToUTF8(result,uniformBlockName,bufSize);if(length)HEAP32[length>>2]=numBytesWrittenExclNull}else{if(length)HEAP32[length>>2]=0}};var _emscripten_glGetActiveUniformBlockiv=(program,uniformBlockIndex,pname,params)=>{if(!params){GL.recordError(1281);return}program=GL.programs[program];if(pname==35393){var name=GLctx.getActiveUniformBlockName(program,uniformBlockIndex);HEAP32[params>>2]=name.length+1;return}var result=GLctx.getActiveUniformBlockParameter(program,uniformBlockIndex,pname);if(result===null)return;if(pname==35395){for(var i=0;i>2]=result[i]}}else{HEAP32[params>>2]=result}};var _emscripten_glGetActiveUniformsiv=(program,uniformCount,uniformIndices,pname,params)=>{if(!params){GL.recordError(1281);return}if(uniformCount>0&&uniformIndices==0){GL.recordError(1281);return}program=GL.programs[program];var ids=[];for(var i=0;i>2])}var result=GLctx.getActiveUniforms(program,ids,pname);if(!result)return;var len=result.length;for(var i=0;i>2]=result[i]}};var _emscripten_glGetAttachedShaders=(program,maxCount,count,shaders)=>{var result=GLctx.getAttachedShaders(GL.programs[program]);var len=result.length;if(len>maxCount){len=maxCount}HEAP32[count>>2]=len;for(var i=0;i>2]=id}};var _emscripten_glGetAttribLocation=(program,name)=>GLctx.getAttribLocation(GL.programs[program],UTF8ToString(name));var writeI53ToI64=(ptr,num)=>{HEAPU32[ptr>>2]=num;var lower=HEAPU32[ptr>>2];HEAPU32[ptr+4>>2]=(num-lower)/4294967296};var webglGetExtensions=()=>{var exts=getEmscriptenSupportedExtensions(GLctx);exts=exts.concat(exts.map(e=>"GL_"+e));return exts};var emscriptenWebGLGet=(name_,p,type)=>{if(!p){GL.recordError(1281);return}var ret=undefined;switch(name_){case 36346:ret=1;break;case 36344:if(type!=0&&type!=1){GL.recordError(1280)}return;case 34814:case 36345:ret=0;break;case 34466:var formats=GLctx.getParameter(34467);ret=formats?formats.length:0;break;case 33309:if(GL.currentContext.version<2){GL.recordError(1282);return}ret=webglGetExtensions().length;break;case 33307:case 33308:if(GL.currentContext.version<2){GL.recordError(1280);return}ret=name_==33307?3:0;break}if(ret===undefined){var result=GLctx.getParameter(name_);switch(typeof result){case"number":ret=result;break;case"boolean":ret=result?1:0;break;case"string":GL.recordError(1280);return;case"object":if(result===null){switch(name_){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 36662:case 36663:case 35053:case 35055:case 36010:case 35097:case 35869:case 32874:case 36389:case 35983:case 35368:case 34068:{ret=0;break}default:{GL.recordError(1280);return}}}else if(result instanceof Float32Array||result instanceof Uint32Array||result instanceof Int32Array||result instanceof Array){for(var i=0;i>2]=result[i];break;case 2:HEAPF32[p+i*4>>2]=result[i];break;case 4:HEAP8[p+i]=result[i]?1:0;break}}return}else{try{ret=result.name|0}catch(e){GL.recordError(1280);err(`GL_INVALID_ENUM in glGet${type}v: Unknown object returned from WebGL getParameter(${name_})! (error: ${e})`);return}}break;default:GL.recordError(1280);err(`GL_INVALID_ENUM in glGet${type}v: Native code calling glGet${type}v(${name_}) and it returns ${result} of type ${typeof result}!`);return}}switch(type){case 1:writeI53ToI64(p,ret);break;case 0:HEAP32[p>>2]=ret;break;case 2:HEAPF32[p>>2]=ret;break;case 4:HEAP8[p]=ret?1:0;break}};var _emscripten_glGetBooleanv=(name_,p)=>emscriptenWebGLGet(name_,p,4);var _emscripten_glGetBufferParameteri64v=(target,value,data)=>{if(!data){GL.recordError(1281);return}writeI53ToI64(data,GLctx.getBufferParameter(target,value))};var _emscripten_glGetBufferParameteriv=(target,value,data)=>{if(!data){GL.recordError(1281);return}HEAP32[data>>2]=GLctx.getBufferParameter(target,value)};var _emscripten_glGetError=()=>{var error=GLctx.getError()||GL.lastError;GL.lastError=0;return error};var _emscripten_glGetFloatv=(name_,p)=>emscriptenWebGLGet(name_,p,2);var _emscripten_glGetFragDataLocation=(program,name)=>GLctx.getFragDataLocation(GL.programs[program],UTF8ToString(name));var _emscripten_glGetFramebufferAttachmentParameteriv=(target,attachment,pname,params)=>{var result=GLctx.getFramebufferAttachmentParameter(target,attachment,pname);if(result instanceof WebGLRenderbuffer||result instanceof WebGLTexture){result=result.name|0}HEAP32[params>>2]=result};var emscriptenWebGLGetIndexed=(target,index,data,type)=>{if(!data){GL.recordError(1281);return}var result=GLctx.getIndexedParameter(target,index);var ret;switch(typeof result){case"boolean":ret=result?1:0;break;case"number":ret=result;break;case"object":if(result===null){switch(target){case 35983:case 35368:ret=0;break;default:{GL.recordError(1280);return}}}else if(result instanceof WebGLBuffer){ret=result.name|0}else{GL.recordError(1280);return}break;default:GL.recordError(1280);return}switch(type){case 1:writeI53ToI64(data,ret);break;case 0:HEAP32[data>>2]=ret;break;case 2:HEAPF32[data>>2]=ret;break;case 4:HEAP8[data]=ret?1:0;break;default:abort("internal emscriptenWebGLGetIndexed() error, bad type: "+type)}};var _emscripten_glGetInteger64i_v=(target,index,data)=>emscriptenWebGLGetIndexed(target,index,data,1);var _emscripten_glGetInteger64v=(name_,p)=>{emscriptenWebGLGet(name_,p,1)};var _emscripten_glGetIntegeri_v=(target,index,data)=>emscriptenWebGLGetIndexed(target,index,data,0);var _emscripten_glGetIntegerv=(name_,p)=>emscriptenWebGLGet(name_,p,0);var _emscripten_glGetInternalformativ=(target,internalformat,pname,bufSize,params)=>{if(bufSize<0){GL.recordError(1281);return}if(!params){GL.recordError(1281);return}var ret=GLctx.getInternalformatParameter(target,internalformat,pname);if(ret===null)return;for(var i=0;i>2]=ret[i]}};var _emscripten_glGetProgramBinary=(program,bufSize,length,binaryFormat,binary)=>{GL.recordError(1282)};var _emscripten_glGetProgramInfoLog=(program,maxLength,length,infoLog)=>{var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull};var _emscripten_glGetProgramiv=(program,pname,p)=>{if(!p){GL.recordError(1281);return}if(program>=GL.counter){GL.recordError(1281);return}program=GL.programs[program];if(pname==35716){var log=GLctx.getProgramInfoLog(program);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35719){if(!program.maxUniformLength){var numActiveUniforms=GLctx.getProgramParameter(program,35718);for(var i=0;i>2]=program.maxUniformLength}else if(pname==35722){if(!program.maxAttributeLength){var numActiveAttributes=GLctx.getProgramParameter(program,35721);for(var i=0;i>2]=program.maxAttributeLength}else if(pname==35381){if(!program.maxUniformBlockNameLength){var numActiveUniformBlocks=GLctx.getProgramParameter(program,35382);for(var i=0;i>2]=program.maxUniformBlockNameLength}else{HEAP32[p>>2]=GLctx.getProgramParameter(program,pname)}};var _emscripten_glGetQueryObjecti64vEXT=(id,pname,params)=>{if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param;if(GL.currentContext.version<2){param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname)}else{param=GLctx.getQueryParameter(query,pname)}var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}writeI53ToI64(params,ret)};var _emscripten_glGetQueryObjectivEXT=(id,pname,params)=>{if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret};var _glGetQueryObjecti64vEXT=_emscripten_glGetQueryObjecti64vEXT;var _emscripten_glGetQueryObjectui64vEXT=_glGetQueryObjecti64vEXT;var _emscripten_glGetQueryObjectuiv=(id,pname,params)=>{if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx.getQueryParameter(query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret};var _glGetQueryObjectivEXT=_emscripten_glGetQueryObjectivEXT;var _emscripten_glGetQueryObjectuivEXT=_glGetQueryObjectivEXT;var _emscripten_glGetQueryiv=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getQuery(target,pname)};var _emscripten_glGetQueryivEXT=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.disjointTimerQueryExt["getQueryEXT"](target,pname)};var _emscripten_glGetRenderbufferParameteriv=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getRenderbufferParameter(target,pname)};var _emscripten_glGetSamplerParameterfv=(sampler,pname,params)=>{if(!params){GL.recordError(1281);return}HEAPF32[params>>2]=GLctx.getSamplerParameter(GL.samplers[sampler],pname)};var _emscripten_glGetSamplerParameteriv=(sampler,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getSamplerParameter(GL.samplers[sampler],pname)};var _emscripten_glGetShaderInfoLog=(shader,maxLength,length,infoLog)=>{var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull};var _emscripten_glGetShaderPrecisionFormat=(shaderType,precisionType,range,precision)=>{var result=GLctx.getShaderPrecisionFormat(shaderType,precisionType);HEAP32[range>>2]=result.rangeMin;HEAP32[range+4>>2]=result.rangeMax;HEAP32[precision>>2]=result.precision};var _emscripten_glGetShaderSource=(shader,bufSize,length,source)=>{var result=GLctx.getShaderSource(GL.shaders[shader]);if(!result)return;var numBytesWrittenExclNull=bufSize>0&&source?stringToUTF8(result,source,bufSize):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull};var _emscripten_glGetShaderiv=(shader,pname,p)=>{if(!p){GL.recordError(1281);return}if(pname==35716){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var logLength=log?log.length+1:0;HEAP32[p>>2]=logLength}else if(pname==35720){var source=GLctx.getShaderSource(GL.shaders[shader]);var sourceLength=source?source.length+1:0;HEAP32[p>>2]=sourceLength}else{HEAP32[p>>2]=GLctx.getShaderParameter(GL.shaders[shader],pname)}};var stringToNewUTF8=str=>{var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8(str,ret,size);return ret};var _emscripten_glGetString=name_=>{var ret=GL.stringCache[name_];if(!ret){switch(name_){case 7939:ret=stringToNewUTF8(webglGetExtensions().join(" "));break;case 7936:case 7937:case 37445:case 37446:var s=GLctx.getParameter(name_);if(!s){GL.recordError(1280)}ret=s?stringToNewUTF8(s):0;break;case 7938:var webGLVersion=GLctx.getParameter(7938);var glVersion=`OpenGL ES 2.0 (${webGLVersion})`;if(GL.currentContext.version>=2)glVersion=`OpenGL ES 3.0 (${webGLVersion})`;ret=stringToNewUTF8(glVersion);break;case 35724:var glslVersion=GLctx.getParameter(35724);var ver_re=/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/;var ver_num=glslVersion.match(ver_re);if(ver_num!==null){if(ver_num[1].length==3)ver_num[1]=ver_num[1]+"0";glslVersion=`OpenGL ES GLSL ES ${ver_num[1]} (${glslVersion})`}ret=stringToNewUTF8(glslVersion);break;default:GL.recordError(1280)}GL.stringCache[name_]=ret}return ret};var _emscripten_glGetStringi=(name,index)=>{if(GL.currentContext.version<2){GL.recordError(1282);return 0}var stringiCache=GL.stringiCache[name];if(stringiCache){if(index<0||index>=stringiCache.length){GL.recordError(1281);return 0}return stringiCache[index]}switch(name){case 7939:var exts=webglGetExtensions().map(stringToNewUTF8);stringiCache=GL.stringiCache[name]=exts;if(index<0||index>=stringiCache.length){GL.recordError(1281);return 0}return stringiCache[index];default:GL.recordError(1280);return 0}};var _emscripten_glGetSynciv=(sync,pname,bufSize,length,values)=>{if(bufSize<0){GL.recordError(1281);return}if(!values){GL.recordError(1281);return}var ret=GLctx.getSyncParameter(GL.syncs[sync],pname);if(ret!==null){HEAP32[values>>2]=ret;if(length)HEAP32[length>>2]=1}};var _emscripten_glGetTexParameterfv=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAPF32[params>>2]=GLctx.getTexParameter(target,pname)};var _emscripten_glGetTexParameteriv=(target,pname,params)=>{if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getTexParameter(target,pname)};var _emscripten_glGetTransformFeedbackVarying=(program,index,bufSize,length,size,type,name)=>{program=GL.programs[program];var info=GLctx.getTransformFeedbackVarying(program,index);if(!info)return;if(name&&bufSize>0){var numBytesWrittenExclNull=stringToUTF8(info.name,name,bufSize);if(length)HEAP32[length>>2]=numBytesWrittenExclNull}else{if(length)HEAP32[length>>2]=0}if(size)HEAP32[size>>2]=info.size;if(type)HEAP32[type>>2]=info.type};var _emscripten_glGetUniformBlockIndex=(program,uniformBlockName)=>GLctx.getUniformBlockIndex(GL.programs[program],UTF8ToString(uniformBlockName));var _emscripten_glGetUniformIndices=(program,uniformCount,uniformNames,uniformIndices)=>{if(!uniformIndices){GL.recordError(1281);return}if(uniformCount>0&&(uniformNames==0||uniformIndices==0)){GL.recordError(1281);return}program=GL.programs[program];var names=[];for(var i=0;i>2]));var result=GLctx.getUniformIndices(program,names);if(!result)return;var len=result.length;for(var i=0;i>2]=result[i]}};var jstoi_q=str=>parseInt(str);var webglGetLeftBracePos=name=>name.slice(-1)=="]"&&name.lastIndexOf("[");var webglPrepareUniformLocationsBeforeFirstUse=program=>{var uniformLocsById=program.uniformLocsById,uniformSizeAndIdsByName=program.uniformSizeAndIdsByName,i,j;if(!uniformLocsById){program.uniformLocsById=uniformLocsById={};program.uniformArrayNamesById={};var numActiveUniforms=GLctx.getProgramParameter(program,35718);for(i=0;i0?nm.slice(0,lb):nm;var id=program.uniformIdCounter;program.uniformIdCounter+=sz;uniformSizeAndIdsByName[arrayName]=[sz,id];for(j=0;j{name=UTF8ToString(name);if(program=GL.programs[program]){webglPrepareUniformLocationsBeforeFirstUse(program);var uniformLocsById=program.uniformLocsById;var arrayIndex=0;var uniformBaseName=name;var leftBrace=webglGetLeftBracePos(name);if(leftBrace>0){arrayIndex=jstoi_q(name.slice(leftBrace+1))>>>0;uniformBaseName=name.slice(0,leftBrace)}var sizeAndId=program.uniformSizeAndIdsByName[uniformBaseName];if(sizeAndId&&arrayIndex{var p=GLctx.currentProgram;if(p){var webglLoc=p.uniformLocsById[location];if(typeof webglLoc=="number"){p.uniformLocsById[location]=webglLoc=GLctx.getUniformLocation(p,p.uniformArrayNamesById[location]+(webglLoc>0?`[${webglLoc}]`:""))}return webglLoc}else{GL.recordError(1282)}};var emscriptenWebGLGetUniform=(program,location,params,type)=>{if(!params){GL.recordError(1281);return}program=GL.programs[program];webglPrepareUniformLocationsBeforeFirstUse(program);var data=GLctx.getUniform(program,webglGetUniformLocation(location));if(typeof data=="number"||typeof data=="boolean"){switch(type){case 0:HEAP32[params>>2]=data;break;case 2:HEAPF32[params>>2]=data;break}}else{for(var i=0;i>2]=data[i];break;case 2:HEAPF32[params+i*4>>2]=data[i];break}}}};var _emscripten_glGetUniformfv=(program,location,params)=>{emscriptenWebGLGetUniform(program,location,params,2)};var _emscripten_glGetUniformiv=(program,location,params)=>{emscriptenWebGLGetUniform(program,location,params,0)};var _emscripten_glGetUniformuiv=(program,location,params)=>emscriptenWebGLGetUniform(program,location,params,0);var emscriptenWebGLGetVertexAttrib=(index,pname,params,type)=>{if(!params){GL.recordError(1281);return}var data=GLctx.getVertexAttrib(index,pname);if(pname==34975){HEAP32[params>>2]=data&&data["name"]}else if(typeof data=="number"||typeof data=="boolean"){switch(type){case 0:HEAP32[params>>2]=data;break;case 2:HEAPF32[params>>2]=data;break;case 5:HEAP32[params>>2]=Math.fround(data);break}}else{for(var i=0;i>2]=data[i];break;case 2:HEAPF32[params+i*4>>2]=data[i];break;case 5:HEAP32[params+i*4>>2]=Math.fround(data[i]);break}}}};var _emscripten_glGetVertexAttribIiv=(index,pname,params)=>{emscriptenWebGLGetVertexAttrib(index,pname,params,0)};var _glGetVertexAttribIiv=_emscripten_glGetVertexAttribIiv;var _emscripten_glGetVertexAttribIuiv=_glGetVertexAttribIiv;var _emscripten_glGetVertexAttribPointerv=(index,pname,pointer)=>{if(!pointer){GL.recordError(1281);return}HEAP32[pointer>>2]=GLctx.getVertexAttribOffset(index,pname)};var _emscripten_glGetVertexAttribfv=(index,pname,params)=>{emscriptenWebGLGetVertexAttrib(index,pname,params,2)};var _emscripten_glGetVertexAttribiv=(index,pname,params)=>{emscriptenWebGLGetVertexAttrib(index,pname,params,5)};var _emscripten_glHint=(x0,x1)=>GLctx.hint(x0,x1);var _emscripten_glInvalidateFramebuffer=(target,numAttachments,attachments)=>{var list=tempFixedLengthArray[numAttachments];for(var i=0;i>2]}GLctx.invalidateFramebuffer(target,list)};var _emscripten_glInvalidateSubFramebuffer=(target,numAttachments,attachments,x,y,width,height)=>{var list=tempFixedLengthArray[numAttachments];for(var i=0;i>2]}GLctx.invalidateSubFramebuffer(target,list,x,y,width,height)};var _emscripten_glIsBuffer=buffer=>{var b=GL.buffers[buffer];if(!b)return 0;return GLctx.isBuffer(b)};var _emscripten_glIsEnabled=x0=>GLctx.isEnabled(x0);var _emscripten_glIsFramebuffer=framebuffer=>{var fb=GL.framebuffers[framebuffer];if(!fb)return 0;return GLctx.isFramebuffer(fb)};var _emscripten_glIsProgram=program=>{program=GL.programs[program];if(!program)return 0;return GLctx.isProgram(program)};var _emscripten_glIsQuery=id=>{var query=GL.queries[id];if(!query)return 0;return GLctx.isQuery(query)};var _emscripten_glIsQueryEXT=id=>{var query=GL.queries[id];if(!query)return 0;return GLctx.disjointTimerQueryExt["isQueryEXT"](query)};var _emscripten_glIsRenderbuffer=renderbuffer=>{var rb=GL.renderbuffers[renderbuffer];if(!rb)return 0;return GLctx.isRenderbuffer(rb)};var _emscripten_glIsSampler=id=>{var sampler=GL.samplers[id];if(!sampler)return 0;return GLctx.isSampler(sampler)};var _emscripten_glIsShader=shader=>{var s=GL.shaders[shader];if(!s)return 0;return GLctx.isShader(s)};var _emscripten_glIsSync=sync=>GLctx.isSync(GL.syncs[sync]);var _emscripten_glIsTexture=id=>{var texture=GL.textures[id];if(!texture)return 0;return GLctx.isTexture(texture)};var _emscripten_glIsTransformFeedback=id=>GLctx.isTransformFeedback(GL.transformFeedbacks[id]);var _emscripten_glIsVertexArray=array=>{var vao=GL.vaos[array];if(!vao)return 0;return GLctx.isVertexArray(vao)};var _glIsVertexArray=_emscripten_glIsVertexArray;var _emscripten_glIsVertexArrayOES=_glIsVertexArray;var _emscripten_glLineWidth=x0=>GLctx.lineWidth(x0);var _emscripten_glLinkProgram=program=>{program=GL.programs[program];GLctx.linkProgram(program);program.uniformLocsById=0;program.uniformSizeAndIdsByName={}};var _emscripten_glPauseTransformFeedback=()=>GLctx.pauseTransformFeedback();var _emscripten_glPixelStorei=(pname,param)=>{if(pname==3317){GL.unpackAlignment=param}else if(pname==3314){GL.unpackRowLength=param}GLctx.pixelStorei(pname,param)};var _emscripten_glPolygonModeWEBGL=(face,mode)=>{GLctx.webglPolygonMode["polygonModeWEBGL"](face,mode)};var _emscripten_glPolygonOffset=(x0,x1)=>GLctx.polygonOffset(x0,x1);var _emscripten_glPolygonOffsetClampEXT=(factor,units,clamp)=>{GLctx.extPolygonOffsetClamp["polygonOffsetClampEXT"](factor,units,clamp)};var _emscripten_glProgramBinary=(program,binaryFormat,binary,length)=>{GL.recordError(1280)};var _emscripten_glProgramParameteri=(program,pname,value)=>{GL.recordError(1280)};var _emscripten_glQueryCounterEXT=(id,target)=>{GLctx.disjointTimerQueryExt["queryCounterEXT"](GL.queries[id],target)};var _emscripten_glReadBuffer=x0=>GLctx.readBuffer(x0);var computeUnpackAlignedImageSize=(width,height,sizePerPixel)=>{function roundedToNextMultipleOf(x,y){return x+y-1&-y}var plainRowSize=(GL.unpackRowLength||width)*sizePerPixel;var alignedRowSize=roundedToNextMultipleOf(plainRowSize,GL.unpackAlignment);return height*alignedRowSize};var colorChannelsInGlTextureFormat=format=>{var colorChannels={5:3,6:4,8:2,29502:3,29504:4,26917:2,26918:2,29846:3,29847:4};return colorChannels[format-6402]||1};var heapObjectForWebGLType=type=>{type-=5120;if(type==0)return HEAP8;if(type==1)return HEAPU8;if(type==2)return HEAP16;if(type==4)return HEAP32;if(type==6)return HEAPF32;if(type==5||type==28922||type==28520||type==30779||type==30782)return HEAPU32;return HEAPU16};var toTypedArrayIndex=(pointer,heap)=>pointer>>>31-Math.clz32(heap.BYTES_PER_ELEMENT);var emscriptenWebGLGetTexPixelData=(type,format,width,height,pixels,internalFormat)=>{var heap=heapObjectForWebGLType(type);var sizePerPixel=colorChannelsInGlTextureFormat(format)*heap.BYTES_PER_ELEMENT;var bytes=computeUnpackAlignedImageSize(width,height,sizePerPixel);return heap.subarray(toTypedArrayIndex(pixels,heap),toTypedArrayIndex(pixels+bytes,heap))};var _emscripten_glReadPixels=(x,y,width,height,format,type,pixels)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelPackBufferBinding){GLctx.readPixels(x,y,width,height,format,type,pixels);return}var heap=heapObjectForWebGLType(type);var target=toTypedArrayIndex(pixels,heap);GLctx.readPixels(x,y,width,height,format,type,heap,target);return}var pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,format);if(!pixelData){GL.recordError(1280);return}GLctx.readPixels(x,y,width,height,format,type,pixelData)};var _emscripten_glReleaseShaderCompiler=()=>{};var _emscripten_glRenderbufferStorage=(x0,x1,x2,x3)=>GLctx.renderbufferStorage(x0,x1,x2,x3);var _emscripten_glRenderbufferStorageMultisample=(x0,x1,x2,x3,x4)=>GLctx.renderbufferStorageMultisample(x0,x1,x2,x3,x4);var _emscripten_glResumeTransformFeedback=()=>GLctx.resumeTransformFeedback();var _emscripten_glSampleCoverage=(value,invert)=>{GLctx.sampleCoverage(value,!!invert)};var _emscripten_glSamplerParameterf=(sampler,pname,param)=>{GLctx.samplerParameterf(GL.samplers[sampler],pname,param)};var _emscripten_glSamplerParameterfv=(sampler,pname,params)=>{var param=HEAPF32[params>>2];GLctx.samplerParameterf(GL.samplers[sampler],pname,param)};var _emscripten_glSamplerParameteri=(sampler,pname,param)=>{GLctx.samplerParameteri(GL.samplers[sampler],pname,param)};var _emscripten_glSamplerParameteriv=(sampler,pname,params)=>{var param=HEAP32[params>>2];GLctx.samplerParameteri(GL.samplers[sampler],pname,param)};var _emscripten_glScissor=(x0,x1,x2,x3)=>GLctx.scissor(x0,x1,x2,x3);var _emscripten_glShaderBinary=(count,shaders,binaryformat,binary,length)=>{GL.recordError(1280)};var _emscripten_glShaderSource=(shader,count,string,length)=>{var source=GL.getSource(shader,count,string,length);GLctx.shaderSource(GL.shaders[shader],source)};var _emscripten_glStencilFunc=(x0,x1,x2)=>GLctx.stencilFunc(x0,x1,x2);var _emscripten_glStencilFuncSeparate=(x0,x1,x2,x3)=>GLctx.stencilFuncSeparate(x0,x1,x2,x3);var _emscripten_glStencilMask=x0=>GLctx.stencilMask(x0);var _emscripten_glStencilMaskSeparate=(x0,x1)=>GLctx.stencilMaskSeparate(x0,x1);var _emscripten_glStencilOp=(x0,x1,x2)=>GLctx.stencilOp(x0,x1,x2);var _emscripten_glStencilOpSeparate=(x0,x1,x2,x3)=>GLctx.stencilOpSeparate(x0,x1,x2,x3);var _emscripten_glTexImage2D=(target,level,internalFormat,width,height,border,format,type,pixels)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels);return}if(pixels){var heap=heapObjectForWebGLType(type);var index=toTypedArrayIndex(pixels,heap);GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,heap,index);return}}var pixelData=pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat):null;GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixelData)};var _emscripten_glTexImage3D=(target,level,internalFormat,width,height,depth,border,format,type,pixels)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.texImage3D(target,level,internalFormat,width,height,depth,border,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texImage3D(target,level,internalFormat,width,height,depth,border,format,type,heap,toTypedArrayIndex(pixels,heap))}else{GLctx.texImage3D(target,level,internalFormat,width,height,depth,border,format,type,null)}};var _emscripten_glTexParameterf=(x0,x1,x2)=>GLctx.texParameterf(x0,x1,x2);var _emscripten_glTexParameterfv=(target,pname,params)=>{var param=HEAPF32[params>>2];GLctx.texParameterf(target,pname,param)};var _emscripten_glTexParameteri=(x0,x1,x2)=>GLctx.texParameteri(x0,x1,x2);var _emscripten_glTexParameteriv=(target,pname,params)=>{var param=HEAP32[params>>2];GLctx.texParameteri(target,pname,param)};var _emscripten_glTexStorage2D=(x0,x1,x2,x3,x4)=>GLctx.texStorage2D(x0,x1,x2,x3,x4);var _emscripten_glTexStorage3D=(x0,x1,x2,x3,x4,x5)=>GLctx.texStorage3D(x0,x1,x2,x3,x4,x5);var _emscripten_glTexSubImage2D=(target,level,xoffset,yoffset,width,height,format,type,pixels)=>{if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels);return}if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,heap,toTypedArrayIndex(pixels,heap));return}}var pixelData=pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,0):null;GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixelData)};var _emscripten_glTexSubImage3D=(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels)=>{if(GLctx.currentPixelUnpackBufferBinding){GLctx.texSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,heap,toTypedArrayIndex(pixels,heap))}else{GLctx.texSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,null)}};var _emscripten_glTransformFeedbackVaryings=(program,count,varyings,bufferMode)=>{program=GL.programs[program];var vars=[];for(var i=0;i>2]));GLctx.transformFeedbackVaryings(program,vars,bufferMode)};var _emscripten_glUniform1f=(location,v0)=>{GLctx.uniform1f(webglGetUniformLocation(location),v0)};var miniTempWebGLFloatBuffers=[];var _emscripten_glUniform1fv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform1fv(webglGetUniformLocation(location),HEAPF32,value>>2,count);return}if(count<=288){var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1fv(webglGetUniformLocation(location),view)};var _emscripten_glUniform1i=(location,v0)=>{GLctx.uniform1i(webglGetUniformLocation(location),v0)};var miniTempWebGLIntBuffers=[];var _emscripten_glUniform1iv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform1iv(webglGetUniformLocation(location),HEAP32,value>>2,count);return}if(count<=288){var view=miniTempWebGLIntBuffers[count];for(var i=0;i>2]}}else{var view=HEAP32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1iv(webglGetUniformLocation(location),view)};var _emscripten_glUniform1ui=(location,v0)=>{GLctx.uniform1ui(webglGetUniformLocation(location),v0)};var _emscripten_glUniform1uiv=(location,count,value)=>{count&&GLctx.uniform1uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count)};var _emscripten_glUniform2f=(location,v0,v1)=>{GLctx.uniform2f(webglGetUniformLocation(location),v0,v1)};var _emscripten_glUniform2fv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform2fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*2);return}if(count<=144){count*=2;var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2];view[i+1]=HEAPF32[value+(4*i+4)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2fv(webglGetUniformLocation(location),view)};var _emscripten_glUniform2i=(location,v0,v1)=>{GLctx.uniform2i(webglGetUniformLocation(location),v0,v1)};var _emscripten_glUniform2iv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform2iv(webglGetUniformLocation(location),HEAP32,value>>2,count*2);return}if(count<=144){count*=2;var view=miniTempWebGLIntBuffers[count];for(var i=0;i>2];view[i+1]=HEAP32[value+(4*i+4)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2iv(webglGetUniformLocation(location),view)};var _emscripten_glUniform2ui=(location,v0,v1)=>{GLctx.uniform2ui(webglGetUniformLocation(location),v0,v1)};var _emscripten_glUniform2uiv=(location,count,value)=>{count&&GLctx.uniform2uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count*2)};var _emscripten_glUniform3f=(location,v0,v1,v2)=>{GLctx.uniform3f(webglGetUniformLocation(location),v0,v1,v2)};var _emscripten_glUniform3fv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform3fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*3);return}if(count<=96){count*=3;var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3fv(webglGetUniformLocation(location),view)};var _emscripten_glUniform3i=(location,v0,v1,v2)=>{GLctx.uniform3i(webglGetUniformLocation(location),v0,v1,v2)};var _emscripten_glUniform3iv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform3iv(webglGetUniformLocation(location),HEAP32,value>>2,count*3);return}if(count<=96){count*=3;var view=miniTempWebGLIntBuffers[count];for(var i=0;i>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3iv(webglGetUniformLocation(location),view)};var _emscripten_glUniform3ui=(location,v0,v1,v2)=>{GLctx.uniform3ui(webglGetUniformLocation(location),v0,v1,v2)};var _emscripten_glUniform3uiv=(location,count,value)=>{count&&GLctx.uniform3uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count*3)};var _emscripten_glUniform4f=(location,v0,v1,v2,v3)=>{GLctx.uniform4f(webglGetUniformLocation(location),v0,v1,v2,v3)};var _emscripten_glUniform4fv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform4fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*4);return}if(count<=72){var view=miniTempWebGLFloatBuffers[4*count];var heap=HEAPF32;value=value>>2;count*=4;for(var i=0;i>2,value+count*16>>2)}GLctx.uniform4fv(webglGetUniformLocation(location),view)};var _emscripten_glUniform4i=(location,v0,v1,v2,v3)=>{GLctx.uniform4i(webglGetUniformLocation(location),v0,v1,v2,v3)};var _emscripten_glUniform4iv=(location,count,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniform4iv(webglGetUniformLocation(location),HEAP32,value>>2,count*4);return}if(count<=72){count*=4;var view=miniTempWebGLIntBuffers[count];for(var i=0;i>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2];view[i+3]=HEAP32[value+(4*i+12)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4iv(webglGetUniformLocation(location),view)};var _emscripten_glUniform4ui=(location,v0,v1,v2,v3)=>{GLctx.uniform4ui(webglGetUniformLocation(location),v0,v1,v2,v3)};var _emscripten_glUniform4uiv=(location,count,value)=>{count&&GLctx.uniform4uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count*4)};var _emscripten_glUniformBlockBinding=(program,uniformBlockIndex,uniformBlockBinding)=>{program=GL.programs[program];GLctx.uniformBlockBinding(program,uniformBlockIndex,uniformBlockBinding)};var _emscripten_glUniformMatrix2fv=(location,count,transpose,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniformMatrix2fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*4);return}if(count<=72){count*=4;var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniformMatrix2fv(webglGetUniformLocation(location),!!transpose,view)};var _emscripten_glUniformMatrix2x3fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix2x3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*6)};var _emscripten_glUniformMatrix2x4fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix2x4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*8)};var _emscripten_glUniformMatrix3fv=(location,count,transpose,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*9);return}if(count<=32){count*=9;var view=miniTempWebGLFloatBuffers[count];for(var i=0;i>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2];view[i+4]=HEAPF32[value+(4*i+16)>>2];view[i+5]=HEAPF32[value+(4*i+20)>>2];view[i+6]=HEAPF32[value+(4*i+24)>>2];view[i+7]=HEAPF32[value+(4*i+28)>>2];view[i+8]=HEAPF32[value+(4*i+32)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*36>>2)}GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,view)};var _emscripten_glUniformMatrix3x2fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix3x2fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*6)};var _emscripten_glUniformMatrix3x4fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix3x4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*12)};var _emscripten_glUniformMatrix4fv=(location,count,transpose,value)=>{if(GL.currentContext.version>=2){count&&GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*16);return}if(count<=18){var view=miniTempWebGLFloatBuffers[16*count];var heap=HEAPF32;value=value>>2;count*=16;for(var i=0;i>2,value+count*64>>2)}GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,view)};var _emscripten_glUniformMatrix4x2fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix4x2fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*8)};var _emscripten_glUniformMatrix4x3fv=(location,count,transpose,value)=>{count&&GLctx.uniformMatrix4x3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*12)};var _emscripten_glUseProgram=program=>{program=GL.programs[program];GLctx.useProgram(program);GLctx.currentProgram=program};var _emscripten_glValidateProgram=program=>{GLctx.validateProgram(GL.programs[program])};var _emscripten_glVertexAttrib1f=(x0,x1)=>GLctx.vertexAttrib1f(x0,x1);var _emscripten_glVertexAttrib1fv=(index,v)=>{GLctx.vertexAttrib1f(index,HEAPF32[v>>2])};var _emscripten_glVertexAttrib2f=(x0,x1,x2)=>GLctx.vertexAttrib2f(x0,x1,x2);var _emscripten_glVertexAttrib2fv=(index,v)=>{GLctx.vertexAttrib2f(index,HEAPF32[v>>2],HEAPF32[v+4>>2])};var _emscripten_glVertexAttrib3f=(x0,x1,x2,x3)=>GLctx.vertexAttrib3f(x0,x1,x2,x3);var _emscripten_glVertexAttrib3fv=(index,v)=>{GLctx.vertexAttrib3f(index,HEAPF32[v>>2],HEAPF32[v+4>>2],HEAPF32[v+8>>2])};var _emscripten_glVertexAttrib4f=(x0,x1,x2,x3,x4)=>GLctx.vertexAttrib4f(x0,x1,x2,x3,x4);var _emscripten_glVertexAttrib4fv=(index,v)=>{GLctx.vertexAttrib4f(index,HEAPF32[v>>2],HEAPF32[v+4>>2],HEAPF32[v+8>>2],HEAPF32[v+12>>2])};var _emscripten_glVertexAttribDivisor=(index,divisor)=>{GLctx.vertexAttribDivisor(index,divisor)};var _glVertexAttribDivisor=_emscripten_glVertexAttribDivisor;var _emscripten_glVertexAttribDivisorANGLE=_glVertexAttribDivisor;var _emscripten_glVertexAttribDivisorARB=_glVertexAttribDivisor;var _emscripten_glVertexAttribDivisorEXT=_glVertexAttribDivisor;var _emscripten_glVertexAttribDivisorNV=_glVertexAttribDivisor;var _emscripten_glVertexAttribI4i=(x0,x1,x2,x3,x4)=>GLctx.vertexAttribI4i(x0,x1,x2,x3,x4);var _emscripten_glVertexAttribI4iv=(index,v)=>{GLctx.vertexAttribI4i(index,HEAP32[v>>2],HEAP32[v+4>>2],HEAP32[v+8>>2],HEAP32[v+12>>2])};var _emscripten_glVertexAttribI4ui=(x0,x1,x2,x3,x4)=>GLctx.vertexAttribI4ui(x0,x1,x2,x3,x4);var _emscripten_glVertexAttribI4uiv=(index,v)=>{GLctx.vertexAttribI4ui(index,HEAPU32[v>>2],HEAPU32[v+4>>2],HEAPU32[v+8>>2],HEAPU32[v+12>>2])};var _emscripten_glVertexAttribIPointer=(index,size,type,stride,ptr)=>{GLctx.vertexAttribIPointer(index,size,type,stride,ptr)};var _emscripten_glVertexAttribPointer=(index,size,type,normalized,stride,ptr)=>{GLctx.vertexAttribPointer(index,size,type,!!normalized,stride,ptr)};var _emscripten_glViewport=(x0,x1,x2,x3)=>GLctx.viewport(x0,x1,x2,x3);var _emscripten_glWaitSync=(sync,flags,timeout)=>{timeout=Number(timeout);GLctx.waitSync(GL.syncs[sync],flags,timeout)};var _emscripten_has_asyncify=()=>0;var doRequestFullscreen=(target,strategy)=>{if(!JSEvents.fullscreenEnabled())return-1;target=findEventTarget(target);if(!target)return-4;if(!target.requestFullscreen&&!target.webkitRequestFullscreen){return-3}if(!JSEvents.canPerformEventHandlerRequests()){if(strategy.deferUntilInEventHandler){JSEvents.deferCall(JSEvents_requestFullscreen,1,[target,strategy]);return 1}return-2}return JSEvents_requestFullscreen(target,strategy)};var _emscripten_request_fullscreen_strategy=(target,deferUntilInEventHandler,fullscreenStrategy)=>{var strategy={scaleMode:HEAP32[fullscreenStrategy>>2],canvasResolutionScaleMode:HEAP32[fullscreenStrategy+4>>2],filteringMode:HEAP32[fullscreenStrategy+8>>2],deferUntilInEventHandler,canvasResizedCallback:HEAP32[fullscreenStrategy+12>>2],canvasResizedCallbackUserData:HEAP32[fullscreenStrategy+16>>2]};return doRequestFullscreen(target,strategy)};var _emscripten_request_pointerlock=(target,deferUntilInEventHandler)=>{target=findEventTarget(target);if(!target)return-4;if(!target.requestPointerLock){return-1}if(!JSEvents.canPerformEventHandlerRequests()){if(deferUntilInEventHandler){JSEvents.deferCall(requestPointerLock,2,[target]);return 1}return-2}return requestPointerLock(target)};var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var _emscripten_sample_gamepad_data=()=>{try{if(navigator.getGamepads)return(JSEvents.lastGamepadState=navigator.getGamepads())?0:-1}catch(e){navigator.getGamepads=null}return-1};var registerBeforeUnloadEventCallback=(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString)=>{var beforeUnloadEventHandlerFunc=e=>{var confirmationMessage=getWasmTableEntry(callbackfunc)(eventTypeId,0,userData);if(confirmationMessage){confirmationMessage=UTF8ToString(confirmationMessage)}if(confirmationMessage){e.preventDefault();e.returnValue=confirmationMessage;return confirmationMessage}};var eventHandler={target:findEventTarget(target),eventTypeString,eventTypeId,userData,callbackfunc,handlerFunc:beforeUnloadEventHandlerFunc,useCapture};return JSEvents.registerOrRemoveHandler(eventHandler)};var _emscripten_set_beforeunload_callback_on_thread=(userData,callbackfunc,targetThread)=>{if(typeof onbeforeunload=="undefined")return-1;if(targetThread!==1)return-5;return registerBeforeUnloadEventCallback(2,userData,true,callbackfunc,28,"beforeunload")};var registerFocusEventCallback=(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread)=>{var eventSize=256;JSEvents.focusEvent||=_malloc(eventSize);var focusEventHandlerFunc=e=>{var nodeName=JSEvents.getNodeNameForTarget(e.target);var id=e.target.id?e.target.id:"";var focusEvent=JSEvents.focusEvent;stringToUTF8(nodeName,focusEvent+0,128);stringToUTF8(id,focusEvent+128,128);if(getWasmTableEntry(callbackfunc)(eventTypeId,focusEvent,userData))e.preventDefault()};var eventHandler={target:findEventTarget(target),eventTypeString,eventTypeId,userData,callbackfunc,handlerFunc:focusEventHandlerFunc,useCapture};return JSEvents.registerOrRemoveHandler(eventHandler)};var _emscripten_set_blur_callback_on_thread=(target,userData,useCapture,callbackfunc,targetThread)=>registerFocusEventCallback(target,userData,useCapture,callbackfunc,12,"blur",targetThread);var fillDeviceMotionEventData=(eventStruct,e,target)=>{var supportedFields=0;var a=e["acceleration"];supportedFields|=a&&1;var ag=e["accelerationIncludingGravity"];supportedFields|=ag&&2;var rr=e["rotationRate"];supportedFields|=rr&&4;a=a||{};ag=ag||{};rr=rr||{};HEAP32[eventStruct+72>>2]=supportedFields;HEAPF64[eventStruct>>3]=a["x"];HEAPF64[eventStruct+8>>3]=a["y"];HEAPF64[eventStruct+16>>3]=a["z"];HEAPF64[eventStruct+24>>3]=ag["x"];HEAPF64[eventStruct+32>>3]=ag["y"];HEAPF64[eventStruct+40>>3]=ag["z"];HEAPF64[eventStruct+48>>3]=rr["alpha"];HEAPF64[eventStruct+56>>3]=rr["beta"];HEAPF64[eventStruct+64>>3]=rr["gamma"]};var registerDeviceMotionEventCallback=(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread)=>{var eventSize=80;JSEvents.deviceMotionEvent||=_malloc(eventSize);var deviceMotionEventHandlerFunc=e=>{fillDeviceMotionEventData(JSEvents.deviceMotionEvent,e,target);if(getWasmTableEntry(callbackfunc)(eventTypeId,JSEvents.deviceMotionEvent,userData))e.preventDefault()};var eventHandler={target:findEventTarget(target),eventTypeString,eventTypeId,userData,callbackfunc,handlerFunc:deviceMotionEventHandlerFunc,useCapture};return JSEvents.registerOrRemoveHandler(eventHandler)};var _emscripten_set_devicemotion_callback_on_thread=(userData,useCapture,callbackfunc,targetThread)=>registerDeviceMotionEventCallback(2,userData,useCapture,callbackfunc,17,"devicemotion",targetThread);var _emscripten_set_element_css_size=(target,width,height)=>{target=findEventTarget(target);if(!target)return-4;target.style.width=width+"px";target.style.height=height+"px";return 0};var _emscripten_set_focus_callback_on_thread=(target,userData,useCapture,callbackfunc,targetThread)=>registerFocusEventCallback(target,userData,useCapture,callbackfunc,13,"focus",targetThread);var registerFullscreenChangeEventCallback=(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread)=>{var eventSize=276;JSEvents.fullscreenChangeEvent||=_malloc(eventSize);var fullscreenChangeEventHandlerFunc=e=>{var fullscreenChangeEvent=JSEvents.fullscreenChangeEvent;fillFullscreenChangeEventData(fullscreenChangeEvent);if(getWasmTableEntry(callbackfunc)(eventTypeId,fullscreenChangeEvent,userData))e.preventDefault()};var eventHandler={target,eventTypeString,eventTypeId,userData,callbackfunc,handlerFunc:fullscreenChangeEventHandlerFunc,useCapture};return JSEvents.registerOrRemoveHandler(eventHandler)};var _emscripten_set_fullscreenchange_callback_on_thread=(target,userData,useCapture,callbackfunc,targetThread)=>{if(!JSEvents.fullscreenEnabled())return-1;target=findEventTarget(target);if(!target)return-4;registerFullscreenChangeEventCallback(target,userData,useCapture,callbackfunc,19,"webkitfullscreenchange",targetThread);return registerFullscreenChangeEventCallback(target,userData,useCapture,callbackfunc,19,"fullscreenchange",targetThread)};var registerGamepadEventCallback=(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread)=>{var eventSize=1240;JSEvents.gamepadEvent||=_malloc(eventSize);var gamepadEventHandlerFunc=e=>{var gamepadEvent=JSEvents.gamepadEvent;fillGamepadEventData(gamepadEvent,e["gamepad"]);if(getWasmTableEntry(callbackfunc)(eventTypeId,gamepadEvent,userData))e.preventDefault()};var eventHandler={target:findEventTarget(target),allowsDeferredCalls:true,eventTypeString,eventTypeId,userData,callbackfunc,handlerFunc:gamepadEventHandlerFunc,useCapture};return JSEvents.registerOrRemoveHandler(eventHandler)};var _emscripten_set_gamepadconnected_callback_on_thread=(userData,useCapture,callbackfunc,targetThread)=>{if(_emscripten_sample_gamepad_data())return-1;return registerGamepadEventCallback(2,userData,useCapture,callbackfunc,26,"gamepadconnected",targetThread)};var _emscripten_set_gamepaddisconnected_callback_on_thread=(userData,useCapture,callbackfunc,targetThread)=>{if(_emscripten_sample_gamepad_data())return-1;return registerGamepadEventCallback(2,userData,useCapture,callbackfunc,27,"gamepaddisconnected",targetThread)};var registerKeyEventCallback=(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread)=>{var eventSize=160;JSEvents.keyEvent||=_malloc(eventSize);var keyEventHandlerFunc=e=>{var keyEventData=JSEvents.keyEvent;HEAPF64[keyEventData>>3]=e.timeStamp;var idx=keyEventData>>2;HEAP32[idx+2]=e.location;HEAP8[keyEventData+12]=e.ctrlKey;HEAP8[keyEventData+13]=e.shiftKey;HEAP8[keyEventData+14]=e.altKey;HEAP8[keyEventData+15]=e.metaKey;HEAP8[keyEventData+16]=e.repeat;HEAP32[idx+5]=e.charCode;HEAP32[idx+6]=e.keyCode;HEAP32[idx+7]=e.which;stringToUTF8(e.key||"",keyEventData+32,32);stringToUTF8(e.code||"",keyEventData+64,32);stringToUTF8(e.char||"",keyEventData+96,32);stringToUTF8(e.locale||"",keyEventData+128,32);if(getWasmTableEntry(callbackfunc)(eventTypeId,keyEventData,userData))e.preventDefault()};var eventHandler={target:findEventTarget(target),eventTypeString,eventTypeId,userData,callbackfunc,handlerFunc:keyEventHandlerFunc,useCapture};return JSEvents.registerOrRemoveHandler(eventHandler)};var _emscripten_set_keydown_callback_on_thread=(target,userData,useCapture,callbackfunc,targetThread)=>registerKeyEventCallback(target,userData,useCapture,callbackfunc,2,"keydown",targetThread);var _emscripten_set_keypress_callback_on_thread=(target,userData,useCapture,callbackfunc,targetThread)=>registerKeyEventCallback(target,userData,useCapture,callbackfunc,1,"keypress",targetThread);var _emscripten_set_keyup_callback_on_thread=(target,userData,useCapture,callbackfunc,targetThread)=>registerKeyEventCallback(target,userData,useCapture,callbackfunc,3,"keyup",targetThread);var _emscripten_set_main_loop=(func,fps,simulateInfiniteLoop)=>{var iterFunc=getWasmTableEntry(func);setMainLoop(iterFunc,fps,simulateInfiniteLoop)};var screenOrientation=()=>{if(!window.screen)return undefined;return screen.orientation||screen["mozOrientation"]||screen["webkitOrientation"]};var fillOrientationChangeEventData=eventStruct=>{var orientationsType1=["portrait-primary","portrait-secondary","landscape-primary","landscape-secondary"];var orientationsType2=["portrait","portrait","landscape","landscape"];var orientationIndex=0;var orientationAngle=0;var screenOrientObj=screenOrientation();if(typeof screenOrientObj==="object"){orientationIndex=orientationsType1.indexOf(screenOrientObj.type);if(orientationIndex<0){orientationIndex=orientationsType2.indexOf(screenOrientObj.type)}if(orientationIndex>=0){orientationIndex=1<>2]=orientationIndex;HEAP32[eventStruct+4>>2]=orientationAngle};var registerOrientationChangeEventCallback=(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread)=>{var eventSize=8;JSEvents.orientationChangeEvent||=_malloc(eventSize);var orientationChangeEventHandlerFunc=e=>{var orientationChangeEvent=JSEvents.orientationChangeEvent;fillOrientationChangeEventData(orientationChangeEvent);if(getWasmTableEntry(callbackfunc)(eventTypeId,orientationChangeEvent,userData))e.preventDefault()};var eventHandler={target,eventTypeString,eventTypeId,userData,callbackfunc,handlerFunc:orientationChangeEventHandlerFunc,useCapture};return JSEvents.registerOrRemoveHandler(eventHandler)};var _emscripten_set_orientationchange_callback_on_thread=(userData,useCapture,callbackfunc,targetThread)=>{if(!window.screen||!screen.orientation)return-1;return registerOrientationChangeEventCallback(screen.orientation,userData,useCapture,callbackfunc,18,"change",targetThread)};var fillPointerlockChangeEventData=eventStruct=>{var pointerLockElement=document.pointerLockElement;var isPointerlocked=!!pointerLockElement;HEAP8[eventStruct]=isPointerlocked;var nodeName=JSEvents.getNodeNameForTarget(pointerLockElement);var id=pointerLockElement?.id||"";stringToUTF8(nodeName,eventStruct+1,128);stringToUTF8(id,eventStruct+129,128)};var registerPointerlockChangeEventCallback=(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread)=>{var eventSize=257;JSEvents.pointerlockChangeEvent||=_malloc(eventSize);var pointerlockChangeEventHandlerFunc=e=>{var pointerlockChangeEvent=JSEvents.pointerlockChangeEvent;fillPointerlockChangeEventData(pointerlockChangeEvent);if(getWasmTableEntry(callbackfunc)(eventTypeId,pointerlockChangeEvent,userData))e.preventDefault()};var eventHandler={target,eventTypeString,eventTypeId,userData,callbackfunc,handlerFunc:pointerlockChangeEventHandlerFunc,useCapture};return JSEvents.registerOrRemoveHandler(eventHandler)};var _emscripten_set_pointerlockchange_callback_on_thread=(target,userData,useCapture,callbackfunc,targetThread)=>{if(!document.body?.requestPointerLock){return-1}target=findEventTarget(target);if(!target)return-4;return registerPointerlockChangeEventCallback(target,userData,useCapture,callbackfunc,20,"pointerlockchange",targetThread)};var registerUiEventCallback=(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread)=>{var eventSize=36;JSEvents.uiEvent||=_malloc(eventSize);target=findEventTarget(target);var uiEventHandlerFunc=e=>{if(e.target!=target){return}var b=document.body;if(!b){return}var uiEvent=JSEvents.uiEvent;HEAP32[uiEvent>>2]=0;HEAP32[uiEvent+4>>2]=b.clientWidth;HEAP32[uiEvent+8>>2]=b.clientHeight;HEAP32[uiEvent+12>>2]=innerWidth;HEAP32[uiEvent+16>>2]=innerHeight;HEAP32[uiEvent+20>>2]=outerWidth;HEAP32[uiEvent+24>>2]=outerHeight;HEAP32[uiEvent+28>>2]=pageXOffset|0;HEAP32[uiEvent+32>>2]=pageYOffset|0;if(getWasmTableEntry(callbackfunc)(eventTypeId,uiEvent,userData))e.preventDefault()};var eventHandler={target,eventTypeString,eventTypeId,userData,callbackfunc,handlerFunc:uiEventHandlerFunc,useCapture};return JSEvents.registerOrRemoveHandler(eventHandler)};var _emscripten_set_resize_callback_on_thread=(target,userData,useCapture,callbackfunc,targetThread)=>registerUiEventCallback(target,userData,useCapture,callbackfunc,10,"resize",targetThread);var fillVisibilityChangeEventData=eventStruct=>{var visibilityStates=["hidden","visible","prerender","unloaded"];var visibilityState=visibilityStates.indexOf(document.visibilityState);HEAP8[eventStruct]=document.hidden;HEAP32[eventStruct+4>>2]=visibilityState};var registerVisibilityChangeEventCallback=(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread)=>{var eventSize=8;JSEvents.visibilityChangeEvent||=_malloc(eventSize);var visibilityChangeEventHandlerFunc=e=>{var visibilityChangeEvent=JSEvents.visibilityChangeEvent;fillVisibilityChangeEventData(visibilityChangeEvent);if(getWasmTableEntry(callbackfunc)(eventTypeId,visibilityChangeEvent,userData))e.preventDefault()};var eventHandler={target,eventTypeString,eventTypeId,userData,callbackfunc,handlerFunc:visibilityChangeEventHandlerFunc,useCapture};return JSEvents.registerOrRemoveHandler(eventHandler)};var _emscripten_set_visibilitychange_callback_on_thread=(userData,useCapture,callbackfunc,targetThread)=>{if(!specialHTMLTargets[1]){return-4}return registerVisibilityChangeEventCallback(specialHTMLTargets[1],userData,useCapture,callbackfunc,21,"visibilitychange",targetThread)};var fillMouseEventData=(eventStruct,e,target)=>{HEAPF64[eventStruct>>3]=e.timeStamp;var idx=eventStruct>>2;HEAP32[idx+2]=e.screenX;HEAP32[idx+3]=e.screenY;HEAP32[idx+4]=e.clientX;HEAP32[idx+5]=e.clientY;HEAP8[eventStruct+24]=e.ctrlKey;HEAP8[eventStruct+25]=e.shiftKey;HEAP8[eventStruct+26]=e.altKey;HEAP8[eventStruct+27]=e.metaKey;HEAP16[idx*2+14]=e.button;HEAP16[idx*2+15]=e.buttons;HEAP32[idx+8]=e["movementX"];HEAP32[idx+9]=e["movementY"];var rect=getBoundingClientRect(target);HEAP32[idx+10]=e.clientX-(rect.left|0);HEAP32[idx+11]=e.clientY-(rect.top|0)};var registerWheelEventCallback=(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread)=>{var eventSize=96;JSEvents.wheelEvent||=_malloc(eventSize);var wheelHandlerFunc=e=>{var wheelEvent=JSEvents.wheelEvent;fillMouseEventData(wheelEvent,e,target);HEAPF64[wheelEvent+64>>3]=e["deltaX"];HEAPF64[wheelEvent+72>>3]=e["deltaY"];HEAPF64[wheelEvent+80>>3]=e["deltaZ"];HEAP32[wheelEvent+88>>2]=e["deltaMode"];if(getWasmTableEntry(callbackfunc)(eventTypeId,wheelEvent,userData))e.preventDefault()};var eventHandler={target,allowsDeferredCalls:true,eventTypeString,eventTypeId,userData,callbackfunc,handlerFunc:wheelHandlerFunc,useCapture};return JSEvents.registerOrRemoveHandler(eventHandler)};var _emscripten_set_wheel_callback_on_thread=(target,userData,useCapture,callbackfunc,targetThread)=>{target=findEventTarget(target);if(!target)return-4;if(typeof target.onwheel!="undefined"){return registerWheelEventCallback(target,userData,useCapture,callbackfunc,9,"wheel",targetThread)}else{return-1}};var _emscripten_set_window_title=title=>document.title=UTF8ToString(title);var _emscripten_sleep=()=>{abort("Please compile your program with async support in order to use asynchronous operations like emscripten_sleep")};var webglPowerPreferences=["default","low-power","high-performance"];var _emscripten_webgl_do_create_context=(target,attributes)=>{var attr32=attributes>>2;var powerPreference=HEAP32[attr32+(8>>2)];var contextAttributes={alpha:!!HEAP8[attributes+0],depth:!!HEAP8[attributes+1],stencil:!!HEAP8[attributes+2],antialias:!!HEAP8[attributes+3],premultipliedAlpha:!!HEAP8[attributes+4],preserveDrawingBuffer:!!HEAP8[attributes+5],powerPreference:webglPowerPreferences[powerPreference],failIfMajorPerformanceCaveat:!!HEAP8[attributes+12],majorVersion:HEAP32[attr32+(16>>2)],minorVersion:HEAP32[attr32+(20>>2)],enableExtensionsByDefault:HEAP8[attributes+24],explicitSwapControl:HEAP8[attributes+25],proxyContextToMainThread:HEAP32[attr32+(28>>2)],renderViaOffscreenBackBuffer:HEAP8[attributes+32]};var canvas=findCanvasEventTarget(target);if(!canvas){return 0}if(contextAttributes.explicitSwapControl){return 0}var contextHandle=GL.createContext(canvas,contextAttributes);return contextHandle};var _emscripten_webgl_create_context=_emscripten_webgl_do_create_context;var _emscripten_webgl_destroy_context=contextHandle=>{if(GL.currentContext==contextHandle)GL.currentContext=0;GL.deleteContext(contextHandle)};var _emscripten_webgl_make_context_current=contextHandle=>{var success=GL.makeContextCurrent(contextHandle);return success?0:-5};var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var _environ_get=(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,Infinity)+1;envp+=4}return 0};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings){bufSize+=lengthBytesUTF8(string)+1}HEAPU32[penviron_buf_size>>2]=bufSize;return 0};function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_seek(fd,offset,whence,newOffset){offset=bigintToI53Checked(offset);try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>3]=BigInt(stream.position);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var _getnameinfo=(sa,salen,node,nodelen,serv,servlen,flags)=>{var info=readSockaddr(sa,salen);if(info.errno){return-6}var port=info.port;var addr=info.addr;var overflowed=false;if(node&&nodelen){var lookup;if(flags&1||!(lookup=DNS.lookup_addr(addr))){if(flags&8){return-2}}else{addr=lookup}var numBytesWrittenExclNull=stringToUTF8(addr,node,nodelen);if(numBytesWrittenExclNull+1>=nodelen){overflowed=true}}if(serv&&servlen){port=""+port;var numBytesWrittenExclNull=stringToUTF8(port,serv,servlen);if(numBytesWrittenExclNull+1>=servlen){overflowed=true}}if(overflowed){return-12}return 0};var autoResumeAudioContext=ctx=>{for(var event of["keydown","mousedown","touchstart"]){for(var element of[document,document.getElementById("canvas")]){element?.addEventListener(event,()=>{if(ctx.state==="suspended")ctx.resume()},{once:true})}}};var dynCall=(sig,ptr,args=[],promising=false)=>{var func=getWasmTableEntry(ptr);var rtn=func(...args);function convert(rtn){return rtn}return convert(rtn)};FS.createPreloadedFile=FS_createPreloadedFile;FS.preloadFile=FS_preloadFile;FS.staticInit();Module["requestAnimationFrame"]=MainLoop.requestAnimationFrame;Module["pauseMainLoop"]=MainLoop.pause;Module["resumeMainLoop"]=MainLoop.resume;MainLoop.init();for(let i=0;i<32;++i)tempFixedLengthArray.push(new Array(i));var miniTempWebGLFloatBuffersStorage=new Float32Array(288);for(var i=0;i<=288;++i){miniTempWebGLFloatBuffers[i]=miniTempWebGLFloatBuffersStorage.subarray(0,i)}var miniTempWebGLIntBuffersStorage=new Int32Array(288);for(var i=0;i<=288;++i){miniTempWebGLIntBuffers[i]=miniTempWebGLIntBuffersStorage.subarray(0,i)}{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["preloadPlugins"])preloadPlugins=Module["preloadPlugins"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var ASM_CONSTS={163340:()=>{if(typeof Module["SDL3"]==="undefined"){Module["SDL3"]={}}var SDL3=Module["SDL3"];if(typeof SDL3.JSVarToCPtr==="undefined"){SDL3.JSVarToCPtr=function(v){return v}}if(typeof SDL3.CPtrToHeap32Index==="undefined"){SDL3.CPtrToHeap32Index=function(ptr){return ptr>>>2}}},163654:$0=>{var str=UTF8ToString($0)+"\n\n"+"Abort/Retry/Ignore/AlwaysIgnore? [ariA] :";var reply=window.prompt(str,"i");if(reply===null){reply="i"}return reply.length===1?reply.charCodeAt(0):-1},163869:()=>{Module["SDL3"].dummy_audio={};Module["SDL3"].dummy_audio.timers=[];Module["SDL3"].dummy_audio.timers[0]=undefined;Module["SDL3"].dummy_audio.timers[1]=undefined},164046:($0,$1,$2,$3,$4)=>{var a=Module["SDL3"].dummy_audio;if(a.timers[$0]!==undefined){clearInterval(a.timers[$0])}a.timers[$0]=setInterval(function(){dynCall("vi",$3,[$4])},$1/$2*1e3)},164238:$0=>{var a=Module["SDL3"].dummy_audio;if(a.timers[$0]!==undefined){clearInterval(a.timers[$0])}a.timers[$0]=undefined},164369:()=>{if(typeof AudioContext!=="undefined"){return true}else if(typeof webkitAudioContext!=="undefined"){return true}return false},164516:()=>{if(typeof navigator.mediaDevices!=="undefined"&&typeof navigator.mediaDevices.getUserMedia!=="undefined"){return true}else if(typeof navigator.webkitGetUserMedia!=="undefined"){return true}return false},164750:()=>{var SDL3=Module["SDL3"];if(typeof SDL3.audio_playback==="undefined"){SDL3.audio_playback={}}if(typeof SDL3.audio_recording==="undefined"){SDL3.audio_recording={}}if(!SDL3.audioContext){if(typeof AudioContext!=="undefined"){SDL3.audioContext=new AudioContext}else if(typeof webkitAudioContext!=="undefined"){SDL3.audioContext=new webkitAudioContext}if(SDL3.audioContext){if(typeof navigator.userActivation==="undefined"){autoResumeAudioContext(SDL3.audioContext)}}}return SDL3.audioContext!==undefined},165329:()=>Module["SDL3"].audioContext.sampleRate,165380:($0,$1,$2,$3)=>{var SDL3=Module["SDL3"];var have_microphone=function(stream){if(SDL3.audio_recording.silenceTimer!==undefined){clearInterval(SDL3.audio_recording.silenceTimer);SDL3.audio_recording.silenceTimer=undefined;SDL3.audio_recording.silenceBuffer=undefined}SDL3.audio_recording.mediaStreamNode=SDL3.audioContext.createMediaStreamSource(stream);SDL3.audio_recording.scriptProcessorNode=SDL3.audioContext.createScriptProcessor($1,$0,1);SDL3.audio_recording.scriptProcessorNode.onaudioprocess=function(audioProcessingEvent){if(SDL3===undefined||SDL3.audio_recording===undefined){return}audioProcessingEvent.outputBuffer.getChannelData(0).fill(0);SDL3.audio_recording.currentRecordingBuffer=audioProcessingEvent.inputBuffer;dynCall("ip",$2,[$3])};SDL3.audio_recording.mediaStreamNode.connect(SDL3.audio_recording.scriptProcessorNode);SDL3.audio_recording.scriptProcessorNode.connect(SDL3.audioContext.destination);SDL3.audio_recording.stream=stream};var no_microphone=function(error){};SDL3.audio_recording.silenceBuffer=SDL3.audioContext.createBuffer($0,$1,SDL3.audioContext.sampleRate);SDL3.audio_recording.silenceBuffer.getChannelData(0).fill(0);var silence_callback=function(){SDL3.audio_recording.currentRecordingBuffer=SDL3.audio_recording.silenceBuffer;dynCall("ip",$2,[$3])};SDL3.audio_recording.silenceTimer=setInterval(silence_callback,$1/SDL3.audioContext.sampleRate*1e3);if(navigator.mediaDevices!==undefined&&navigator.mediaDevices.getUserMedia!==undefined){navigator.mediaDevices.getUserMedia({audio:true,video:false}).then(have_microphone).catch(no_microphone)}else if(navigator.webkitGetUserMedia!==undefined){navigator.webkitGetUserMedia({audio:true,video:false},have_microphone,no_microphone)}},167221:($0,$1,$2,$3)=>{var SDL3=Module["SDL3"];SDL3.audio_playback.scriptProcessorNode=SDL3.audioContext["createScriptProcessor"]($1,0,$0);SDL3.audio_playback.scriptProcessorNode["onaudioprocess"]=function(e){if(SDL3===undefined||SDL3.audio_playback===undefined){return}if(SDL3.audio_playback.silenceTimer!==undefined){clearInterval(SDL3.audio_playback.silenceTimer);SDL3.audio_playback.silenceTimer=undefined;SDL3.audio_playback.silenceBuffer=undefined}SDL3.audio_playback.currentPlaybackBuffer=e["outputBuffer"];dynCall("ip",$2,[$3])};SDL3.audio_playback.scriptProcessorNode["connect"](SDL3.audioContext["destination"]);if(SDL3.audioContext.state==="suspended"){SDL3.audio_playback.silenceBuffer=SDL3.audioContext.createBuffer($0,$1,SDL3.audioContext.sampleRate);SDL3.audio_playback.silenceBuffer.getChannelData(0).fill(0);var silence_callback=function(){if(typeof navigator.userActivation!=="undefined"){if(navigator.userActivation.hasBeenActive){SDL3.audioContext.resume()}}SDL3.audio_playback.currentPlaybackBuffer=SDL3.audio_playback.silenceBuffer;dynCall("ip",$2,[$3]);SDL3.audio_playback.currentPlaybackBuffer=undefined};SDL3.audio_playback.silenceTimer=setInterval(silence_callback,$1/SDL3.audioContext.sampleRate*1e3)}},168537:$0=>{var SDL3=Module["SDL3"];if($0){if(SDL3.audio_recording.silenceTimer!==undefined){clearInterval(SDL3.audio_recording.silenceTimer)}if(SDL3.audio_recording.stream!==undefined){var tracks=SDL3.audio_recording.stream.getAudioTracks();for(var i=0;i{var SDL3=Module["SDL3"];var buf=SDL3.CPtrToHeap32Index($0);var numChannels=SDL3.audio_playback.currentPlaybackBuffer["numberOfChannels"];for(var c=0;c{var SDL3=Module["SDL3"];var numChannels=SDL3.audio_recording.currentRecordingBuffer.numberOfChannels;for(var c=0;c{Module["SDL3"].camera={}},170885:()=>navigator.mediaDevices===undefined?0:1,170944:($0,$1,$2,$3,$4)=>{const device=$0;const w=$1;const h=$2;const framerate_numerator=$3;const framerate_denominator=$4;const outcome=Module._SDLEmscriptenCameraPermissionOutcome;const iterate=Module._SDLEmscriptenThreadIterate;const constraints={};if(w<=0||h<=0){constraints.video=true}else{constraints.video={};constraints.video.width=w;constraints.video.height=h}if(framerate_numerator>0&&framerate_denominator>0){var fps=framerate_numerator/framerate_denominator;constraints.video.frameRate={ideal:fps}}function grabNextCameraFrame(){const SDL3=Module["SDL3"];if(typeof SDL3==="undefined"||typeof SDL3.camera==="undefined"||typeof SDL3.camera.stream==="undefined"){return}const nextframems=SDL3.camera.next_frame_time;const now=performance.now();if(now>=nextframems){iterate(device);while(SDL3.camera.next_frame_time{const settings=stream.getVideoTracks()[0].getSettings();const actualw=settings.width;const actualh=settings.height;const actualfps=settings.frameRate;console.log("Camera is opened! Actual spec: ("+actualw+"x"+actualh+"), fps="+actualfps);if(outcome(device,1,actualw,actualh,actualfps)){const video=document.createElement("video");video.width=actualw;video.height=actualh;video.style.display="none";video.srcObject=stream;const canvas=document.createElement("canvas");canvas.width=actualw;canvas.height=actualh;canvas.style.display="none";const ctx2d=canvas.getContext("2d");const SDL3=Module["SDL3"];SDL3.camera.width=actualw;SDL3.camera.height=actualh;SDL3.camera.fps=actualfps;SDL3.camera.fpsincrms=1e3/actualfps;SDL3.camera.stream=stream;SDL3.camera.video=video;SDL3.camera.canvas=canvas;SDL3.camera.ctx2d=ctx2d;SDL3.camera.next_frame_time=performance.now();video.play();video.addEventListener("loadedmetadata",()=>{grabNextCameraFrame()})}}).catch(err=>{console.error("Tried to open camera but it threw an error! "+err.name+": "+err.message);outcome(device,0,0,0,0)})},173250:()=>{const SDL3=Module["SDL3"];if(typeof SDL3==="undefined"||typeof SDL3.camera==="undefined"||typeof SDL3.camera.stream==="undefined"){return}SDL3.camera.stream.getTracks().forEach(track=>track.stop());SDL3.camera={}},173501:($0,$1,$2)=>{const w=$0;const h=$1;const rgba=$2;const SDL3=Module["SDL3"];if(typeof SDL3==="undefined"||typeof SDL3.camera==="undefined"||typeof SDL3.camera.ctx2d==="undefined"){return 0}SDL3.camera.ctx2d.drawImage(SDL3.camera.video,0,0,w,h);const imgrgba=SDL3.camera.ctx2d.getImageData(0,0,w,h).data;HEAPU8.set(imgrgba,rgba);return 1},173879:()=>{if(typeof Module["SDL3"]!=="undefined"){Module["SDL3"].camera=undefined}},173966:$0=>{let gamepads=navigator["getGamepads"]();if(!gamepads){return 0}let gamepad=gamepads[$0];if(!gamepad||!gamepad["vibrationActuator"]){return 0}return 1},174141:($0,$1,$2)=>{let gamepads=navigator["getGamepads"]();if(!gamepads){return 0}let gamepad=gamepads[$0];if(!gamepad||!gamepad["vibrationActuator"]){return 0}gamepad["vibrationActuator"]["playEffect"]("dual-rumble",{startDelay:0,duration:3e3,weakMagnitude:$2/65535,strongMagnitude:$1/65535});return 1},174477:($0,$1,$2,$3)=>{var w=$0;var h=$1;var pixels=$2;var canvasId=UTF8ToString($3);var canvas=document.querySelector(canvasId);var SDL3=Module["SDL3"];if(SDL3.ctxCanvas!==canvas){SDL3.ctx=Browser.createContext(canvas,false,true);if(!SDL3.ctx){return false}SDL3.ctxCanvas=canvas}if(SDL3.w!==w||SDL3.h!==h||SDL3.imageCtx!==SDL3.ctx){SDL3.image=SDL3.ctx.createImageData(w,h);SDL3.w=w;SDL3.h=h;SDL3.imageCtx=SDL3.ctx}var data=SDL3.image.data;var src=pixels/4;if(SDL3.data32Data!==data){SDL3.data32=new Int32Array(data.buffer);SDL3.data32Data=data}var data32=SDL3.data32;data32.set(HEAP32.subarray(src,src+data32.length));SDL3.ctx.putImageData(SDL3.image,0,0);return true},175226:()=>{var SDL3=Module["SDL3"];SDL3["mouse_x"]=0;SDL3["mouse_y"]=0;SDL3["mouse_buttons"]=[];for(var i=0;i<5;++i){SDL3["mouse_buttons"][i]=false}document.addEventListener("mousemove",function(e){var SDL3=Module["SDL3"];SDL3["mouse_x"]=e.clientX;SDL3["mouse_y"]=e.clientY});document.addEventListener("mousedown",function(e){var SDL3=Module["SDL3"];if(0<=e.button&&e.button{var w=$0;var h=$1;var hot_x=$2;var hot_y=$3;var pixels=$4;var canvas=document.createElement("canvas");canvas.width=w;canvas.height=h;var ctx=canvas.getContext("2d");var image=ctx.createImageData(w,h);var data=image.data;var src=pixels/4;var data32=new Int32Array(data.buffer);data32.set(HEAP32.subarray(src,src+data32.length));ctx.putImageData(image,0,0);var url=hot_x===0&&hot_y===0?"url("+canvas.toDataURL()+"), auto":"url("+canvas.toDataURL()+") "+hot_x+" "+hot_y+", auto";var urlBuf=_SDL_malloc(url.length+1);stringToUTF8(url,urlBuf,url.length+1);return urlBuf},176572:$0=>{if(Module["canvas"]){Module["canvas"].style["cursor"]=UTF8ToString($0)}},176655:()=>{if(Module["canvas"]){Module["canvas"].style["cursor"]="none"}},176724:()=>Module["SDL3"]["mouse_x"],176762:()=>Module["SDL3"]["mouse_y"],176800:$0=>Module["SDL3"]["mouse_buttons"][$0],176848:$0=>{var data=$0;document.sdlEventHandlerLockKeysCheck=function(event){if(event.key!="CapsLock"&&event.key!="NumLock"&&event.key!="ScrollLock"){_Emscripten_HandleLockKeysCheck(Module["SDL3"].JSVarToCPtr(data),event.getModifierState("CapsLock"),event.getModifierState("NumLock"),event.getModifierState("ScrollLock"))}};document.addEventListener("keydown",document.sdlEventHandlerLockKeysCheck)},177275:()=>{document.removeEventListener("keydown",document.sdlEventHandlerLockKeysCheck)},177359:$0=>{var target=document;if(target){target.sdlEventHandlerMouseButtonUpGlobal=function(event){var SDL3=Module["SDL3"];var d=SDL3.makePointerEventCStruct(0,0,event);if(d!=0){_Emscripten_HandleMouseButtonUpGlobal(SDL3.JSVarToCPtr($0),d);_SDL_free(d)}};target.addEventListener("pointerup",target.sdlEventHandlerMouseButtonUpGlobal)}},177720:$0=>{var SDL3=Module["SDL3"];if(SDL3.makePointerEventCStruct===undefined){SDL3.makePointerEventCStruct=function(left,top,event){var ptrtype=0;if(event.pointerType=="mouse"){ptrtype=1}else if(event.pointerType=="touch"){ptrtype=2}else if(event.pointerType=="pen"){ptrtype=3}else{return 0}var ptr=_SDL_malloc($0);if(ptr!=0){var idx=SDL3.CPtrToHeap32Index(ptr);HEAP32[idx++]=ptrtype;HEAP32[idx++]=event.pointerId;HEAP32[idx++]=typeof event.button!=="undefined"?event.button:-1;HEAP32[idx++]=event.buttons;HEAP32[idx++]=event.type=="pointerdown"?1:0;HEAPF32[idx++]=event.movementX;HEAPF32[idx++]=event.movementY;HEAPF32[idx++]=event.clientX-left;HEAPF32[idx++]=event.clientY-top;if(ptrtype==3){HEAPF32[idx++]=event.pressure;HEAPF32[idx++]=event.tangentialPressure;HEAPF32[idx++]=event.tiltX;HEAPF32[idx++]=event.tiltY;HEAPF32[idx++]=event.twist}}return ptr}}},178712:$0=>{var id=UTF8ToString($0);try{var canvas=document.querySelector(id);if(canvas){return canvas===document.activeElement}}catch(e){}return false},178878:()=>document.hasFocus(),178910:()=>{var target=document;if(target){target.removeEventListener("pointerup",target.sdlEventHandlerMouseButtonUpGlobal);target.sdlEventHandlerMouseButtonUpGlobal=undefined}},179092:()=>document.body.clientWidth,179130:()=>document.body.clientHeight,179169:()=>window.innerWidth,179199:()=>window.innerHeight,179230:()=>window.outerWidth,179260:()=>window.outerHeight,179291:()=>window.pageXOffset,179322:()=>window.pageYOffset,179353:($0,$1)=>{var target=document.querySelector(UTF8ToString($1));if(target){var SDL3=Module["SDL3"];var data=$0;target.sdlEventHandlerPointerEnter=function(event){var rect=target.getBoundingClientRect();var d=SDL3.makePointerEventCStruct(rect.left,rect.top,event);if(d!=0){_Emscripten_HandlePointerEnter(SDL3.JSVarToCPtr(data),d);_SDL_free(d)}};target.sdlEventHandlerPointerLeave=function(event){var rect=target.getBoundingClientRect();var d=SDL3.makePointerEventCStruct(rect.left,rect.top,event);if(d!=0){_Emscripten_HandlePointerLeave(SDL3.JSVarToCPtr(data),d);_SDL_free(d)}};target.sdlEventHandlerPointerGeneric=function(event){var rect=target.getBoundingClientRect();var d=SDL3.makePointerEventCStruct(rect.left,rect.top,event);if(d!=0){_Emscripten_HandlePointerGeneric(SDL3.JSVarToCPtr(data),d);_SDL_free(d)}};target.style.touchAction="none";target.addEventListener("pointerenter",target.sdlEventHandlerPointerEnter);target.addEventListener("pointerleave",target.sdlEventHandlerPointerLeave);target.addEventListener("pointercancel",target.sdlEventHandlerPointerLeave);target.addEventListener("pointerdown",target.sdlEventHandlerPointerGeneric);target.addEventListener("pointermove",target.sdlEventHandlerPointerGeneric);target.addEventListener("pointerup",target.sdlEventHandlerPointerGeneric)}},180741:($0,$1,$2)=>{var target=document.querySelector(UTF8ToString($1));if(target){var data=$0;var SDL3=Module["SDL3"];var makeDropEventCStruct=function(event){var ptr=0;ptr=_SDL_malloc($2);if(ptr!=0){var idx=ptr>>2;var rect=target.getBoundingClientRect();HEAP32[idx++]=event.clientX-rect.left;HEAP32[idx++]=event.clientY-rect.top}return ptr};SDL3.eventHandlerDropDragover=function(event){event.preventDefault();var d=makeDropEventCStruct(event);if(d!=0){_Emscripten_SendDragEvent(data,d);_SDL_free(d)}};target.addEventListener("dragover",SDL3.eventHandlerDropDragover);SDL3.drop_count=0;try{FS.mkdir("/tmp/filedrop")}catch(e){}SDL3.eventHandlerDropDrop=function(event){event.preventDefault();if(event.dataTransfer.types.includes("text/plain")){let plain_text=stringToNewUTF8(event.dataTransfer.getData("text/plain"));_Emscripten_SendDragTextEvent(data,plain_text);_Emscripten_force_free(plain_text)}else if(event.dataTransfer.types.includes("Files")){let files_read=0;const files_to_read=event.dataTransfer.files.length;for(let i=0;i{var target=document.querySelector(UTF8ToString($0));if(target){var SDL3=Module["SDL3"];target.removeEventListener("dragleave",SDL3.eventHandlerDropDragend);target.removeEventListener("dragend",SDL3.eventHandlerDropDragend);target.removeEventListener("drop",SDL3.eventHandlerDropDrop);SDL3.drop_count=undefined;FS.rmdir("/tmp/filedrop");target.removeEventListener("dragover",SDL3.eventHandlerDropDragover);SDL3.eventHandlerDropDragover=undefined;SDL3.eventHandlerDropDrop=undefined;SDL3.eventHandlerDropDragend=undefined}},183938:$0=>{var target=document.querySelector(UTF8ToString($0));if(target){target.removeEventListener("pointerenter",target.sdlEventHandlerPointerEnter);target.removeEventListener("pointerleave",target.sdlEventHandlerPointerLeave);target.removeEventListener("pointercancel",target.sdlEventHandlerPointerLeave);target.removeEventListener("pointerdown",target.sdlEventHandlerPointerGeneric);target.removeEventListener("pointermove",target.sdlEventHandlerPointerGeneric);target.removeEventListener("pointerup",target.sdlEventHandlerPointerGeneric);target.style.touchAction="";target.sdlEventHandlerPointerEnter=undefined;target.sdlEventHandlerPointerLeave=undefined;target.sdlEventHandlerPointerGeneric=undefined}},184672:()=>{if(!window.matchMedia){return-1}if(window.matchMedia("(prefers-color-scheme: light)").matches){return 0}if(window.matchMedia("(prefers-color-scheme: dark)").matches){return 1}return-1},184881:()=>{if(typeof Module["SDL3"]!=="undefined"){var SDL3=Module["SDL3"];SDL3.themeChangedMatchMedia.removeEventListener("change",SDL3.eventHandlerThemeChanged);SDL3.themeChangedMatchMedia=undefined;SDL3.eventHandlerThemeChanged=undefined}},185134:()=>window.innerWidth,185164:()=>window.innerHeight,185195:$0=>{Module["requestFullscreen"]=function(lockPointer,resizeCanvas){_requestFullscreenThroughSDL($0)}},185304:($0,$1)=>{var pngData=HEAPU8.buffer instanceof ArrayBuffer?HEAPU8.subarray($0,$0+$1):HEAPU8.slice($0,$0+$1);var blob=new Blob([pngData],{type:"image/png"});var url=URL.createObjectURL(blob);var link=document.querySelector("link[rel~='icon']");if(!link){link=document.createElement("link");link.rel="icon";link.type="image/png";document.head.appendChild(link)}if(link.href&&link.href.startsWith("blob:")){URL.revokeObjectURL(link.href)}link.href=url},185797:()=>{Module["requestFullscreen"]=function(lockPointer,resizeCanvas){}},185871:()=>window.innerWidth,185901:()=>window.innerHeight,185932:$0=>{var canvas=document.querySelector(UTF8ToString($0));canvas.SDL3_original_position=canvas.style.position;canvas.SDL3_original_top=canvas.style.top;canvas.SDL3_original_left=canvas.style.left;var div=document.createElement("div");div.id="SDL3_fill_document_background_elements";div.SDL3_canvas=canvas;div.SDL3_canvas_parent=canvas.parentNode;div.SDL3_canvas_nextsib=canvas.nextSibling;var children=Array.from(document.body.children);for(var child of children){div.appendChild(child)}document.body.appendChild(div);div.style.display="none";document.body.appendChild(canvas);canvas.style.position="fixed";canvas.style.top="0";canvas.style.left="0"},186630:()=>{var div=document.getElementById("SDL3_fill_document_background_elements");if(div){if(div.SDL3_canvas_nextsib){div.SDL3_canvas_parent.insertBefore(div.SDL3_canvas,div.SDL3_canvas_nextsib)}else{div.SDL3_canvas_parent.appendChild(div.SDL3_canvas)}while(div.firstChild){document.body.insertBefore(div.firstChild,div)}div.SDL3_canvas.style.position=div.SDL3_canvas.SDL3_original_position;div.SDL3_canvas.style.top=div.SDL3_canvas.SDL3_original_top;div.SDL3_canvas.style.left=div.SDL3_canvas.SDL3_original_left;div.remove()}},187189:()=>{if(window.matchMedia){var SDL3=Module["SDL3"];SDL3.eventHandlerThemeChanged=function(event){_Emscripten_SendSystemThemeChangedEvent()};SDL3.themeChangedMatchMedia=window.matchMedia("(prefers-color-scheme: dark)");SDL3.themeChangedMatchMedia.addEventListener("change",SDL3.eventHandlerThemeChanged)}},187511:($0,$1,$2,$3,$4)=>{var title=UTF8ToString($0);var message=UTF8ToString($1);var background=UTF8ToString($2);var color=UTF8ToString($3);var id=UTF8ToString($4);var dialog=document.createElement("dialog");dialog.classList.add("SDL3_messagebox");dialog.id=id;dialog.style.color=color;dialog.style.backgroundColor=background;document.body.append(dialog);var h1=document.createElement("h1");h1.innerText=title;dialog.append(h1);var p=document.createElement("p");p.innerText=message;dialog.append(p);dialog.showModal()},188052:($0,$1,$2,$3,$4,$5,$6,$7)=>{var dialog_id=UTF8ToString($0);var text=UTF8ToString($1);var responseId=$2;var clickOnReturn=$3;var clickOnEscape=$4;var border=UTF8ToString($5);var background=UTF8ToString($6);var hovered=UTF8ToString($7);var dialog=document.getElementById(dialog_id);if(!dialog){return false}var button=document.createElement("button");button.innerText=text;button.style.borderColor=border;button.style.backgroundColor=background;dialog.addEventListener("keydown",function(e){if(clickOnReturn&&e.key==="Enter"){e.preventDefault();button.click()}else if(clickOnEscape&&e.key==="Escape"){e.preventDefault();button.click()}});dialog.addEventListener("cancel",function(e){e.preventDefault()});button.onmouseenter=function(e){button.style.backgroundColor=hovered};button.onmouseleave=function(e){button.style.backgroundColor=background};button.onclick=function(e){dialog.close(responseId)};dialog.append(button);return true},189061:$0=>{var dialog_id=UTF8ToString($0);var dialog=document.getElementById(dialog_id);if(!dialog){return false}return dialog.open},189199:$0=>{var dialog_id=UTF8ToString($0);var dialog=document.getElementById(dialog_id);if(!dialog){return 0}try{return parseInt(dialog.returnValue)}catch(e){return 0}},189381:($0,$1)=>{alert(UTF8ToString($0)+"\n\n"+UTF8ToString($1))}};function SDL_GetEmscriptenJoystickVendor(device_index){let gamepad=navigator["getGamepads"]()[device_index];let vendor_str="Vendor: ";if(gamepad["id"]["indexOf"](vendor_str)>0){let vendor_str_index=gamepad["id"]["indexOf"](vendor_str)+vendor_str["length"];return parseInt(gamepad["id"]["substr"](vendor_str_index,4),16)}let id_split=gamepad["id"]["split"]("-");if(id_split["length"]>1&&!isNaN(parseInt(id_split[0],16))){return parseInt(id_split[0],16)}return 0}function SDL_GetEmscriptenJoystickProduct(device_index){let gamepad=navigator["getGamepads"]()[device_index];let product_str="Product: ";if(gamepad["id"]["indexOf"](product_str)>0){let product_str_index=gamepad["id"]["indexOf"](product_str)+product_str["length"];return parseInt(gamepad["id"]["substr"](product_str_index,4),16)}let id_split=gamepad["id"]["split"]("-");if(id_split["length"]>1&&!isNaN(parseInt(id_split[1],16))){return parseInt(id_split[1],16)}return 0}function SDL_IsEmscriptenJoystickXInput(device_index){let gamepad=navigator["getGamepads"]()[device_index];return gamepad["id"]["toLowerCase"]()["indexOf"]("xinput")>=0}var _main,_SDL_free,_SDL_malloc,_SDL_calloc,_Emscripten_force_free,_SDL_realloc,_malloc,_SDLEmscriptenCameraPermissionOutcome,_SDLEmscriptenThreadIterate,_Emscripten_HandlePointerEnter,_Emscripten_HandlePointerLeave,_Emscripten_HandlePointerGeneric,_Emscripten_HandleMouseButtonUpGlobal,_Emscripten_SendDragEvent,_Emscripten_SendDragCompleteEvent,_Emscripten_SendDragTextEvent,_Emscripten_SendDragFileEvent,_Emscripten_HandleLockKeysCheck,_Emscripten_SendSystemThemeChangedEvent,_requestFullscreenThroughSDL,_htons,_ntohs,__emscripten_stack_restore,__emscripten_stack_alloc,_emscripten_stack_get_current,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){_main=Module["_main"]=wasmExports["vf"];_SDL_free=Module["_SDL_free"]=wasmExports["xf"];_SDL_malloc=Module["_SDL_malloc"]=wasmExports["yf"];_SDL_calloc=Module["_SDL_calloc"]=wasmExports["zf"];_Emscripten_force_free=Module["_Emscripten_force_free"]=wasmExports["Af"];_SDL_realloc=Module["_SDL_realloc"]=wasmExports["Bf"];_malloc=wasmExports["Cf"];_SDLEmscriptenCameraPermissionOutcome=Module["_SDLEmscriptenCameraPermissionOutcome"]=wasmExports["Df"];_SDLEmscriptenThreadIterate=Module["_SDLEmscriptenThreadIterate"]=wasmExports["Ef"];_Emscripten_HandlePointerEnter=Module["_Emscripten_HandlePointerEnter"]=wasmExports["Ff"];_Emscripten_HandlePointerLeave=Module["_Emscripten_HandlePointerLeave"]=wasmExports["Gf"];_Emscripten_HandlePointerGeneric=Module["_Emscripten_HandlePointerGeneric"]=wasmExports["Hf"];_Emscripten_HandleMouseButtonUpGlobal=Module["_Emscripten_HandleMouseButtonUpGlobal"]=wasmExports["If"];_Emscripten_SendDragEvent=Module["_Emscripten_SendDragEvent"]=wasmExports["Jf"];_Emscripten_SendDragCompleteEvent=Module["_Emscripten_SendDragCompleteEvent"]=wasmExports["Kf"];_Emscripten_SendDragTextEvent=Module["_Emscripten_SendDragTextEvent"]=wasmExports["Lf"];_Emscripten_SendDragFileEvent=Module["_Emscripten_SendDragFileEvent"]=wasmExports["Mf"];_Emscripten_HandleLockKeysCheck=Module["_Emscripten_HandleLockKeysCheck"]=wasmExports["Nf"];_Emscripten_SendSystemThemeChangedEvent=Module["_Emscripten_SendSystemThemeChangedEvent"]=wasmExports["Of"];_requestFullscreenThroughSDL=Module["_requestFullscreenThroughSDL"]=wasmExports["Pf"];_htons=wasmExports["Qf"];_ntohs=wasmExports["Rf"];__emscripten_stack_restore=wasmExports["Sf"];__emscripten_stack_alloc=wasmExports["Tf"];_emscripten_stack_get_current=wasmExports["Uf"];memory=wasmMemory=wasmExports["tf"];__indirect_function_table=wasmTable=wasmExports["wf"]}var wasmImports={T:SDL_GetEmscriptenJoystickProduct,ca:SDL_GetEmscriptenJoystickVendor,qf:SDL_IsEmscriptenJoystickXInput,Ua:___syscall_accept4,Ta:___syscall_bind,b:___syscall_fcntl64,$a:___syscall_fdatasync,Za:___syscall_ioctl,Sa:___syscall_listen,_a:___syscall_openat,Ra:___syscall_recvfrom,Qa:___syscall_sendto,Pa:___syscall_socket,Wa:___syscall_stat64,bb:__abort_js,ab:_clock_time_get,E:_emscripten_asm_const_double_sync_on_main_thread,g:_emscripten_asm_const_int,a:_emscripten_asm_const_int_sync_on_main_thread,_c:_emscripten_asm_const_ptr_sync_on_main_thread,rf:_emscripten_cancel_main_loop,w:_emscripten_date_now,Ka:_emscripten_exit_fullscreen,Fc:_emscripten_exit_pointerlock,f:_emscripten_get_device_pixel_ratio,c:_emscripten_get_element_css_size,Ma:_emscripten_get_fullscreen_status,n:_emscripten_get_gamepad_status,re:_emscripten_get_main_loop_timing,k:_emscripten_get_now,Oa:_emscripten_get_num_gamepads,td:_emscripten_get_screen_size,qa:_emscripten_glActiveTexture,pa:_emscripten_glAttachShader,Uc:_emscripten_glBeginQuery,Ga:_emscripten_glBeginQueryEXT,Ac:_emscripten_glBeginTransformFeedback,oa:_emscripten_glBindAttribLocation,na:_emscripten_glBindBuffer,xc:_emscripten_glBindBufferBase,yc:_emscripten_glBindBufferRange,ma:_emscripten_glBindFramebuffer,la:_emscripten_glBindRenderbuffer,Fb:_emscripten_glBindSampler,ka:_emscripten_glBindTexture,xb:_emscripten_glBindTransformFeedback,Gc:_emscripten_glBindVertexArray,ya:_emscripten_glBindVertexArrayOES,ja:_emscripten_glBlendColor,ia:_emscripten_glBlendEquation,ha:_emscripten_glBlendEquationSeparate,ga:_emscripten_glBlendFunc,fa:_emscripten_glBlendFuncSeparate,Jc:_emscripten_glBlitFramebuffer,ea:_emscripten_glBufferData,da:_emscripten_glBufferSubData,ba:_emscripten_glCheckFramebufferStatus,aa:_emscripten_glClear,ac:_emscripten_glClearBufferfi,bc:_emscripten_glClearBufferfv,dc:_emscripten_glClearBufferiv,cc:_emscripten_glClearBufferuiv,$:_emscripten_glClearColor,_:_emscripten_glClearDepthf,Z:_emscripten_glClearStencil,Ob:_emscripten_glClientWaitSync,jd:_emscripten_glClipControlEXT,Y:_emscripten_glColorMask,X:_emscripten_glCompileShader,W:_emscripten_glCompressedTexImage2D,Zc:_emscripten_glCompressedTexImage3D,V:_emscripten_glCompressedTexSubImage2D,Yc:_emscripten_glCompressedTexSubImage3D,_b:_emscripten_glCopyBufferSubData,U:_emscripten_glCopyTexImage2D,S:_emscripten_glCopyTexSubImage2D,$c:_emscripten_glCopyTexSubImage3D,R:_emscripten_glCreateProgram,Q:_emscripten_glCreateShader,P:_emscripten_glCullFace,O:_emscripten_glDeleteBuffers,N:_emscripten_glDeleteFramebuffers,M:_emscripten_glDeleteProgram,Wc:_emscripten_glDeleteQueries,Ia:_emscripten_glDeleteQueriesEXT,L:_emscripten_glDeleteRenderbuffers,Hb:_emscripten_glDeleteSamplers,K:_emscripten_glDeleteShader,Pb:_emscripten_glDeleteSync,J:_emscripten_glDeleteTextures,wb:_emscripten_glDeleteTransformFeedbacks,Ec:_emscripten_glDeleteVertexArrays,xa:_emscripten_glDeleteVertexArraysOES,pf:_emscripten_glDepthFunc,of:_emscripten_glDepthMask,nf:_emscripten_glDepthRangef,mf:_emscripten_glDetachShader,lf:_emscripten_glDisable,kf:_emscripten_glDisableVertexAttribArray,jf:_emscripten_glDrawArrays,Tb:_emscripten_glDrawArraysInstanced,ta:_emscripten_glDrawArraysInstancedANGLE,gb:_emscripten_glDrawArraysInstancedARB,gd:_emscripten_glDrawArraysInstancedEXT,hb:_emscripten_glDrawArraysInstancedNV,Qc:_emscripten_glDrawBuffers,ed:_emscripten_glDrawBuffersEXT,ua:_emscripten_glDrawBuffersWEBGL,hf:_emscripten_glDrawElements,Sb:_emscripten_glDrawElementsInstanced,sa:_emscripten_glDrawElementsInstancedANGLE,eb:_emscripten_glDrawElementsInstancedARB,fb:_emscripten_glDrawElementsInstancedEXT,fd:_emscripten_glDrawElementsInstancedNV,cd:_emscripten_glDrawRangeElements,gf:_emscripten_glEnable,ff:_emscripten_glEnableVertexAttribArray,Tc:_emscripten_glEndQuery,Fa:_emscripten_glEndQueryEXT,zc:_emscripten_glEndTransformFeedback,Rb:_emscripten_glFenceSync,ef:_emscripten_glFinish,df:_emscripten_glFlush,cf:_emscripten_glFramebufferRenderbuffer,bf:_emscripten_glFramebufferTexture2D,Hc:_emscripten_glFramebufferTextureLayer,af:_emscripten_glFrontFace,$e:_emscripten_glGenBuffers,Ze:_emscripten_glGenFramebuffers,Xc:_emscripten_glGenQueries,Ja:_emscripten_glGenQueriesEXT,Ye:_emscripten_glGenRenderbuffers,Ib:_emscripten_glGenSamplers,Xe:_emscripten_glGenTextures,vb:_emscripten_glGenTransformFeedbacks,Dc:_emscripten_glGenVertexArrays,wa:_emscripten_glGenVertexArraysOES,_e:_emscripten_glGenerateMipmap,We:_emscripten_glGetActiveAttrib,Ve:_emscripten_glGetActiveUniform,Vb:_emscripten_glGetActiveUniformBlockName,Wb:_emscripten_glGetActiveUniformBlockiv,Yb:_emscripten_glGetActiveUniformsiv,Ue:_emscripten_glGetAttachedShaders,Te:_emscripten_glGetAttribLocation,Se:_emscripten_glGetBooleanv,Jb:_emscripten_glGetBufferParameteri64v,Re:_emscripten_glGetBufferParameteriv,Qe:_emscripten_glGetError,Pe:_emscripten_glGetFloatv,mc:_emscripten_glGetFragDataLocation,Oe:_emscripten_glGetFramebufferAttachmentParameteriv,Kb:_emscripten_glGetInteger64i_v,Mb:_emscripten_glGetInteger64v,Bc:_emscripten_glGetIntegeri_v,Ne:_emscripten_glGetIntegerv,kb:_emscripten_glGetInternalformativ,rb:_emscripten_glGetProgramBinary,Ke:_emscripten_glGetProgramInfoLog,Le:_emscripten_glGetProgramiv,Aa:_emscripten_glGetQueryObjecti64vEXT,Ca:_emscripten_glGetQueryObjectivEXT,za:_emscripten_glGetQueryObjectui64vEXT,Rc:_emscripten_glGetQueryObjectuiv,Ba:_emscripten_glGetQueryObjectuivEXT,Sc:_emscripten_glGetQueryiv,Da:_emscripten_glGetQueryivEXT,Je:_emscripten_glGetRenderbufferParameteriv,zb:_emscripten_glGetSamplerParameterfv,Ab:_emscripten_glGetSamplerParameteriv,He:_emscripten_glGetShaderInfoLog,Ge:_emscripten_glGetShaderPrecisionFormat,Fe:_emscripten_glGetShaderSource,Ie:_emscripten_glGetShaderiv,Ee:_emscripten_glGetString,$b:_emscripten_glGetStringi,Lb:_emscripten_glGetSynciv,De:_emscripten_glGetTexParameterfv,Ce:_emscripten_glGetTexParameteriv,vc:_emscripten_glGetTransformFeedbackVarying,Xb:_emscripten_glGetUniformBlockIndex,Zb:_emscripten_glGetUniformIndices,ze:_emscripten_glGetUniformLocation,Be:_emscripten_glGetUniformfv,Ae:_emscripten_glGetUniformiv,nc:_emscripten_glGetUniformuiv,tc:_emscripten_glGetVertexAttribIiv,sc:_emscripten_glGetVertexAttribIuiv,we:_emscripten_glGetVertexAttribPointerv,ye:_emscripten_glGetVertexAttribfv,xe:_emscripten_glGetVertexAttribiv,ve:_emscripten_glHint,ob:_emscripten_glInvalidateFramebuffer,nb:_emscripten_glInvalidateSubFramebuffer,ue:_emscripten_glIsBuffer,te:_emscripten_glIsEnabled,se:_emscripten_glIsFramebuffer,qe:_emscripten_glIsProgram,Vc:_emscripten_glIsQuery,Ha:_emscripten_glIsQueryEXT,pe:_emscripten_glIsRenderbuffer,Gb:_emscripten_glIsSampler,oe:_emscripten_glIsShader,Qb:_emscripten_glIsSync,ne:_emscripten_glIsTexture,ub:_emscripten_glIsTransformFeedback,Cc:_emscripten_glIsVertexArray,va:_emscripten_glIsVertexArrayOES,me:_emscripten_glLineWidth,le:_emscripten_glLinkProgram,tb:_emscripten_glPauseTransformFeedback,ke:_emscripten_glPixelStorei,id:_emscripten_glPolygonModeWEBGL,je:_emscripten_glPolygonOffset,kd:_emscripten_glPolygonOffsetClampEXT,qb:_emscripten_glProgramBinary,pb:_emscripten_glProgramParameteri,Ea:_emscripten_glQueryCounterEXT,dd:_emscripten_glReadBuffer,ie:_emscripten_glReadPixels,he:_emscripten_glReleaseShaderCompiler,fe:_emscripten_glRenderbufferStorage,Ic:_emscripten_glRenderbufferStorageMultisample,sb:_emscripten_glResumeTransformFeedback,ee:_emscripten_glSampleCoverage,Cb:_emscripten_glSamplerParameterf,Bb:_emscripten_glSamplerParameterfv,Eb:_emscripten_glSamplerParameteri,Db:_emscripten_glSamplerParameteriv,de:_emscripten_glScissor,ce:_emscripten_glShaderBinary,be:_emscripten_glShaderSource,ae:_emscripten_glStencilFunc,$d:_emscripten_glStencilFuncSeparate,_d:_emscripten_glStencilMask,Zd:_emscripten_glStencilMaskSeparate,Yd:_emscripten_glStencilOp,Xd:_emscripten_glStencilOpSeparate,Wd:_emscripten_glTexImage2D,bd:_emscripten_glTexImage3D,Vd:_emscripten_glTexParameterf,Ud:_emscripten_glTexParameterfv,Td:_emscripten_glTexParameteri,Sd:_emscripten_glTexParameteriv,mb:_emscripten_glTexStorage2D,lb:_emscripten_glTexStorage3D,Rd:_emscripten_glTexSubImage2D,ad:_emscripten_glTexSubImage3D,wc:_emscripten_glTransformFeedbackVaryings,Qd:_emscripten_glUniform1f,Pd:_emscripten_glUniform1fv,Od:_emscripten_glUniform1i,Nd:_emscripten_glUniform1iv,lc:_emscripten_glUniform1ui,hc:_emscripten_glUniform1uiv,Md:_emscripten_glUniform2f,Ld:_emscripten_glUniform2fv,Kd:_emscripten_glUniform2i,Jd:_emscripten_glUniform2iv,kc:_emscripten_glUniform2ui,gc:_emscripten_glUniform2uiv,Id:_emscripten_glUniform3f,Hd:_emscripten_glUniform3fv,Gd:_emscripten_glUniform3i,Fd:_emscripten_glUniform3iv,jc:_emscripten_glUniform3ui,fc:_emscripten_glUniform3uiv,Ed:_emscripten_glUniform4f,Dd:_emscripten_glUniform4fv,Cd:_emscripten_glUniform4i,Bd:_emscripten_glUniform4iv,ic:_emscripten_glUniform4ui,ec:_emscripten_glUniform4uiv,Ub:_emscripten_glUniformBlockBinding,Ad:_emscripten_glUniformMatrix2fv,Pc:_emscripten_glUniformMatrix2x3fv,Nc:_emscripten_glUniformMatrix2x4fv,zd:_emscripten_glUniformMatrix3fv,Oc:_emscripten_glUniformMatrix3x2fv,Lc:_emscripten_glUniformMatrix3x4fv,yd:_emscripten_glUniformMatrix4fv,Mc:_emscripten_glUniformMatrix4x2fv,Kc:_emscripten_glUniformMatrix4x3fv,xd:_emscripten_glUseProgram,wd:_emscripten_glValidateProgram,vd:_emscripten_glVertexAttrib1f,ud:_emscripten_glVertexAttrib1fv,sd:_emscripten_glVertexAttrib2f,rd:_emscripten_glVertexAttrib2fv,qd:_emscripten_glVertexAttrib3f,pd:_emscripten_glVertexAttrib3fv,od:_emscripten_glVertexAttrib4f,nd:_emscripten_glVertexAttrib4fv,yb:_emscripten_glVertexAttribDivisor,ra:_emscripten_glVertexAttribDivisorANGLE,ib:_emscripten_glVertexAttribDivisorARB,hd:_emscripten_glVertexAttribDivisorEXT,jb:_emscripten_glVertexAttribDivisorNV,rc:_emscripten_glVertexAttribI4i,pc:_emscripten_glVertexAttribI4iv,qc:_emscripten_glVertexAttribI4ui,oc:_emscripten_glVertexAttribI4uiv,uc:_emscripten_glVertexAttribIPointer,md:_emscripten_glVertexAttribPointer,ld:_emscripten_glViewport,Nb:_emscripten_glWaitSync,j:_emscripten_has_asyncify,La:_emscripten_request_fullscreen_strategy,F:_emscripten_request_pointerlock,Va:_emscripten_resize_heap,A:_emscripten_sample_gamepad_data,p:_emscripten_set_beforeunload_callback_on_thread,C:_emscripten_set_blur_callback_on_thread,d:_emscripten_set_canvas_element_size,Me:_emscripten_set_devicemotion_callback_on_thread,e:_emscripten_set_element_css_size,D:_emscripten_set_focus_callback_on_thread,z:_emscripten_set_fullscreenchange_callback_on_thread,m:_emscripten_set_gamepadconnected_callback_on_thread,l:_emscripten_set_gamepaddisconnected_callback_on_thread,t:_emscripten_set_keydown_callback_on_thread,r:_emscripten_set_keypress_callback_on_thread,s:_emscripten_set_keyup_callback_on_thread,sf:_emscripten_set_main_loop,I:_emscripten_set_main_loop_timing,v:_emscripten_set_orientationchange_callback_on_thread,B:_emscripten_set_pointerlockchange_callback_on_thread,y:_emscripten_set_resize_callback_on_thread,q:_emscripten_set_visibilitychange_callback_on_thread,x:_emscripten_set_wheel_callback_on_thread,Na:_emscripten_set_window_title,i:_emscripten_sleep,ge:_emscripten_webgl_create_context,G:_emscripten_webgl_destroy_context,H:_emscripten_webgl_make_context_current,cb:_environ_get,db:_environ_sizes_get,h:_fd_close,Ya:_fd_read,Xa:_fd_seek,u:_fd_write,o:_getnameinfo};function callMain(args=[]){var entryFunction=_main;args.unshift(thisProgram);var argc=args.length;var argv=stackAlloc((argc+1)*4);var argv_ptr=argv;for(var arg of args){HEAPU32[argv_ptr>>2]=stringToUTF8OnStack(arg);argv_ptr+=4}HEAPU32[argv_ptr>>2]=0;try{var ret=entryFunction(argc,argv);exitJS(ret,true);return ret}catch(e){return handleException(e)}}function run(args=arguments_){if(runDependencies>0){dependenciesFulfilled=run;return}preRun();if(runDependencies>0){dependenciesFulfilled=run;return}function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();Module["onRuntimeInitialized"]?.();var noInitialRun=Module["noInitialRun"]||false;if(!noInitialRun)callMain(args);postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;createWasm();run(); diff --git a/build-web/sdl3_flecs_template.wasm b/build-web/sdl3_flecs_template.wasm new file mode 100755 index 0000000..1d1d166 Binary files /dev/null and b/build-web/sdl3_flecs_template.wasm differ diff --git a/build_web.sh b/build_web.sh new file mode 100755 index 0000000..de601e6 --- /dev/null +++ b/build_web.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Build the Render Pipeline Visualizer for the web using Emscripten +set -e + +# Source emsdk if not already in PATH +if ! command -v emcc &> /dev/null; then + source ~/emsdk/emsdk_env.sh +fi + +BUILD_DIR="build-web" +mkdir -p "$BUILD_DIR" +cd "$BUILD_DIR" + +emcmake cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DSDL_SHARED=OFF \ + -DSDL_STATIC=ON \ + -DSDL_TEST=OFF \ + -DSDL_X11=OFF \ + -DSDL_WAYLAND=OFF + +emmake make -j$(nproc) + +echo "" +echo "Build complete! Files are in $BUILD_DIR/" +echo "To test locally: cd $BUILD_DIR && python3 -m http.server 8080" +echo "Then open http://localhost:8080/sdl3_flecs_template.html" diff --git a/include/components.hpp b/include/components.hpp new file mode 100644 index 0000000..8c2b415 --- /dev/null +++ b/include/components.hpp @@ -0,0 +1,11 @@ +/** + * @file components.hpp + * @brief Master include for all ECS components + */ + +#pragma once + +#include "components/transform.hpp" +#include "components/render.hpp" +#include "components/input.hpp" +#include "components/common.hpp" diff --git a/include/components/common.hpp b/include/components/common.hpp new file mode 100644 index 0000000..69aa282 --- /dev/null +++ b/include/components/common.hpp @@ -0,0 +1,24 @@ +/** + * @file common.hpp + * @brief Common/utility components + */ + +#pragma once + +#include + +struct Name { + std::string value; +}; + +// Tag component - empty struct marks entities for destruction +struct ToDestroy {}; + +struct Lifetime { + float remaining = 0.0f; +}; + +struct Health { + float current = 100.0f; + float max = 100.0f; +}; diff --git a/include/components/input.hpp b/include/components/input.hpp new file mode 100644 index 0000000..9f2f2dc --- /dev/null +++ b/include/components/input.hpp @@ -0,0 +1,21 @@ +/** + * @file input.hpp + * @brief Input-related components + */ + +#pragma once + +struct PlayerControlled { + int player_id = 0; +}; + +struct InputState { + bool move_up = false; + bool move_down = false; + bool move_left = false; + bool move_right = false; + bool action_primary = false; + bool action_secondary = false; + float mouse_x = 0.0f; + float mouse_y = 0.0f; +}; diff --git a/include/components/render.hpp b/include/components/render.hpp new file mode 100644 index 0000000..6c6ff3d --- /dev/null +++ b/include/components/render.hpp @@ -0,0 +1,32 @@ +/** + * @file render.hpp + * @brief Rendering-related components + */ + +#pragma once + +#include +#include + +struct Color { + uint8_t r = 255; + uint8_t g = 255; + uint8_t b = 255; + uint8_t a = 255; +}; + +struct Sprite { + SDL_Texture* texture = nullptr; + SDL_FRect src_rect{}; + int z_order = 0; + bool visible = true; +}; + +struct RectShape { + float width = 0.0f; + float height = 0.0f; +}; + +struct CircleShape { + float radius = 0.0f; +}; diff --git a/include/components/transform.hpp b/include/components/transform.hpp new file mode 100644 index 0000000..f70cc24 --- /dev/null +++ b/include/components/transform.hpp @@ -0,0 +1,25 @@ +/** + * @file transform.hpp + * @brief Transform components for position, velocity, and rotation + */ + +#pragma once + +struct Position { + float x = 0.0f; + float y = 0.0f; +}; + +struct Velocity { + float x = 0.0f; + float y = 0.0f; +}; + +struct Rotation { + float angle = 0.0f; +}; + +struct Scale { + float x = 1.0f; + float y = 1.0f; +}; diff --git a/include/game.hpp b/include/game.hpp new file mode 100644 index 0000000..d7f1127 --- /dev/null +++ b/include/game.hpp @@ -0,0 +1,52 @@ +/** + * @file game.hpp + * @brief Main game state and context + */ + +#pragma once + +#include +#include + +struct WindowConfig { + const char *title = "RIDGE RACER 37"; + int width = 1280; + int height = 720; + SDL_WindowFlags flags = 0; +}; + +struct PipelineState; + +/** + * @brief Game context containing all core resources + */ +struct GameContext { + // SDL Resources + SDL_Window *window = nullptr; + SDL_Renderer *renderer = nullptr; + + // FLECS World (C++ wrapper) + flecs::world ecs; + + // Timing + uint64_t last_time = 0; + float delta_time = 0.0f; + + // State + bool running = true; + bool paused = false; + + // Window info + int window_width = 0; + int window_height = 0; + + // Pipeline visualization + PipelineState *pipeline = nullptr; +}; + +bool game_init(GameContext &ctx, const WindowConfig &config); +void game_shutdown(GameContext &ctx); +void game_process_events(GameContext &ctx); +void game_update(GameContext &ctx); +void game_render(GameContext &ctx); +bool game_loop(GameContext &ctx); diff --git a/include/math3d.hpp b/include/math3d.hpp new file mode 100644 index 0000000..0591893 --- /dev/null +++ b/include/math3d.hpp @@ -0,0 +1,129 @@ +/** + * @file math3d.hpp + * @brief Minimal 3D math library for render pipeline visualization + */ + +#pragma once + +#include +#include + +constexpr float PI = 3.14159265358979323846f; + +struct Vec3 { + float x = 0, y = 0, z = 0; + Vec3 operator+(Vec3 o) const { return {x+o.x, y+o.y, z+o.z}; } + Vec3 operator-(Vec3 o) const { return {x-o.x, y-o.y, z-o.z}; } + Vec3 operator*(float s) const { return {x*s, y*s, z*s}; } + float dot(Vec3 o) const { return x*o.x + y*o.y + z*o.z; } + Vec3 cross(Vec3 o) const { return {y*o.z - z*o.y, z*o.x - x*o.z, x*o.y - y*o.x}; } + float length() const { return std::sqrt(x*x + y*y + z*z); } + Vec3 normalized() const { + float l = length(); + return l > 0 ? Vec3{x/l, y/l, z/l} : Vec3{0, 0, 0}; + } +}; + +inline Vec3 lerp(Vec3 a, Vec3 b, float t) { + return {a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t}; +} + +inline float smoothstep(float t) { + return t * t * (3.0f - 2.0f * t); +} + +struct Vec4 { + float x = 0, y = 0, z = 0, w = 1; + Vec3 xyz() const { return {x, y, z}; } + Vec3 perspDiv() const { + return w != 0 ? Vec3{x/w, y/w, z/w} : Vec3{x, y, z}; + } +}; + +struct Mat4 { + float m[4][4] = {}; + + static Mat4 identity() { + Mat4 r; + r.m[0][0] = r.m[1][1] = r.m[2][2] = r.m[3][3] = 1; + return r; + } + + Vec4 operator*(Vec4 v) const { + return { + m[0][0]*v.x + m[0][1]*v.y + m[0][2]*v.z + m[0][3]*v.w, + m[1][0]*v.x + m[1][1]*v.y + m[1][2]*v.z + m[1][3]*v.w, + m[2][0]*v.x + m[2][1]*v.y + m[2][2]*v.z + m[2][3]*v.w, + m[3][0]*v.x + m[3][1]*v.y + m[3][2]*v.z + m[3][3]*v.w, + }; + } + + Mat4 operator*(const Mat4& o) const { + Mat4 r; + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + for (int k = 0; k < 4; k++) + r.m[i][j] += m[i][k] * o.m[k][j]; + return r; + } + + static Mat4 translate(float x, float y, float z) { + Mat4 r = identity(); + r.m[0][3] = x; r.m[1][3] = y; r.m[2][3] = z; + return r; + } + + static Mat4 scale(float sx, float sy, float sz) { + Mat4 r = identity(); + r.m[0][0] = sx; r.m[1][1] = sy; r.m[2][2] = sz; + return r; + } + + static Mat4 rotateX(float angle) { + float c = std::cos(angle), s = std::sin(angle); + Mat4 r = identity(); + r.m[1][1] = c; r.m[1][2] = -s; + r.m[2][1] = s; r.m[2][2] = c; + return r; + } + + static Mat4 rotateY(float angle) { + float c = std::cos(angle), s = std::sin(angle); + Mat4 r = identity(); + r.m[0][0] = c; r.m[0][2] = s; + r.m[2][0] = -s; r.m[2][2] = c; + return r; + } + + static Mat4 rotateZ(float angle) { + float c = std::cos(angle), s = std::sin(angle); + Mat4 r = identity(); + r.m[0][0] = c; r.m[0][1] = -s; + r.m[1][0] = s; r.m[1][1] = c; + return r; + } + + // Standard OpenGL-style perspective (right-handed, -Z forward) + static Mat4 perspective(float fov, float aspect, float near, float far) { + float t = std::tan(fov * 0.5f); + Mat4 r; + r.m[0][0] = 1.0f / (aspect * t); + r.m[1][1] = 1.0f / t; + r.m[2][2] = -(far + near) / (far - near); + r.m[2][3] = -(2.0f * far * near) / (far - near); + r.m[3][2] = -1.0f; + return r; + } + + // Standard lookAt (right-handed) + static Mat4 lookAt(Vec3 eye, Vec3 target, Vec3 up) { + Vec3 f = (target - eye).normalized(); + Vec3 r = f.cross(up).normalized(); + Vec3 u = r.cross(f); + Mat4 mat = identity(); + mat.m[0][0] = r.x; mat.m[0][1] = r.y; mat.m[0][2] = r.z; mat.m[0][3] = -r.dot(eye); + mat.m[1][0] = u.x; mat.m[1][1] = u.y; mat.m[1][2] = u.z; mat.m[1][3] = -u.dot(eye); + mat.m[2][0] = -f.x; mat.m[2][1] = -f.y; mat.m[2][2] = -f.z; mat.m[2][3] = f.dot(eye); + return mat; + } +}; diff --git a/include/pipeline.hpp b/include/pipeline.hpp new file mode 100644 index 0000000..9061b3c --- /dev/null +++ b/include/pipeline.hpp @@ -0,0 +1,129 @@ +/** + * @file pipeline.hpp + * @brief Render pipeline visualization state and interface + */ + +#pragma once + +#include "math3d.hpp" +#include + +struct GameContext; + +constexpr int NUM_CUBE_VERTS = 8; +constexpr int NUM_CUBE_EDGES = 12; +constexpr int NUM_TRI_VERTS = 3; +constexpr int NUM_TRI_EDGES = 3; + +struct PipelineState { + int current_stage = 0; // 0-5 + int prev_stage = 0; + float transition_t = 1.0f; // 0→1 animation progress (1 = complete) + float fade_alpha = 1.0f; // for 3D↔2D mode transitions + + // Interpolated vertices for rendering during transitions + Vec3 display_verts[NUM_CUBE_VERTS]; + + // Vertex selection + int selected_vertex = -1; // -1 = none, 0-7 = selected vertex index + bool pending_click = false; + float click_x = 0, click_y = 0; + + // Object transform parameters (user-adjustable) + float obj_rot_y = 0.0f; + float obj_pos_x = 2.0f; + float obj_pos_y = 1.0f; + float obj_pos_z = -3.0f; + float obj_scale = 1.0f; + bool auto_rotate = false; + + // Display camera (orbit around scene for visualization) + float cam_orbit_pitch = 25.0f; // degrees + float cam_orbit_yaw = -30.0f; // degrees + float cam_distance = 10.0f; + + // Pipeline camera (the one being visualized in the pipeline) + float pipe_cam_x = 0.0f; + float pipe_cam_y = 2.0f; + float pipe_cam_z = 5.0f; + float pipe_cam_fov = 1.0f; // radians (~57 degrees) + float pipe_cam_near = 0.1f; + float pipe_cam_far = 100.0f; + + // Mouse drag state + bool mouse_down = false; + float mouse_start_x = 0, mouse_start_y = 0; + float orbit_start_pitch = 0, orbit_start_yaw = 0; + + // Slider drag state + int dragging_slider = -1; // -1 = none, 0+ = slider index + bool show_sliders = true; + + // First-person camera view (picture-in-picture) + bool show_first_person = false; + + // Matrix color breakdown + bool show_matrix_breakdown = false; + + // Vertex shader simulation + bool shader_mode = false; + float shader_amplitude = 0.3f; + float shader_frequency = 2.0f; + float shader_time = 0.0f; + + // Help overlay + bool show_help = false; + + // Clipping visualizer + bool show_clipping = false; + struct ClippedFace { + static constexpr int MAX_VERTS = 24; + Vec4 verts[MAX_VERTS]; + int count = 0; + bool was_clipped = false; + }; + ClippedFace clipped_faces[6]; + + // Depth buffer / Z-fighting demo + bool show_depth_viz = false; + bool show_zfight_plane = false; + float zfight_plane_z = -3.0f; + static constexpr int NUM_PLANE_VERTS = 4; + Vec4 plane_world[4]; + Vec3 plane_screen[4]; + Vec3 plane_display[4]; + + // Input flags (set per event, cleared after processing) + bool key_left = false; + bool key_right = false; + bool key_space = false; + bool key_r = false; + bool key_num[6] = {}; + + // Computed transforms + Mat4 model_matrix; + Mat4 view_matrix; + Mat4 proj_matrix; + + // Transformed vertices at each pipeline stage (cube) + Vec4 verts_object[NUM_CUBE_VERTS]; + Vec4 verts_world[NUM_CUBE_VERTS]; + Vec4 verts_view[NUM_CUBE_VERTS]; + Vec4 verts_clip[NUM_CUBE_VERTS]; + Vec3 verts_ndc[NUM_CUBE_VERTS]; + Vec3 verts_screen[NUM_CUBE_VERTS]; + + // Second shape: triangle + bool show_triangle = false; + Vec4 tri_object[NUM_TRI_VERTS]; + Vec4 tri_world[NUM_TRI_VERTS]; + Vec4 tri_view[NUM_TRI_VERTS]; + Vec4 tri_clip[NUM_TRI_VERTS]; + Vec3 tri_ndc[NUM_TRI_VERTS]; + Vec3 tri_screen[NUM_TRI_VERTS]; + Vec3 tri_display[NUM_TRI_VERTS]; +}; + +void pipeline_handle_event(PipelineState& ps, const SDL_Event& event); +void pipeline_update(PipelineState& ps, GameContext& ctx); +void pipeline_render(PipelineState& ps, GameContext& ctx); diff --git a/include/systems.hpp b/include/systems.hpp new file mode 100644 index 0000000..73bee91 --- /dev/null +++ b/include/systems.hpp @@ -0,0 +1,16 @@ +/** + * @file systems.hpp + * @brief ECS System registration + */ + +#pragma once + +#include + +// Forward declaration +struct GameContext; + +/** + * @brief Register all components and systems with the ECS world + */ +void register_systems(flecs::world& ecs, GameContext* ctx); diff --git a/remove_comments.py b/remove_comments.py new file mode 100644 index 0000000..1c1f4ae --- /dev/null +++ b/remove_comments.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""Remove all C/C++ comments from a file.""" + +import sys +import re + + +def remove_comments(source): + pattern = re.compile( + r'//.*?$|/\*.*?\*/|"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'', + re.DOTALL | re.MULTILINE, + ) + + def replacer(match): + s = match.group(0) + if s.startswith("/"): + return " " if s.startswith("/*") else "" + return s + + return pattern.sub(replacer, source) + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print(f"Usage: {sys.argv[0]} [--inplace]", file=sys.stderr) + sys.exit(1) + + path = sys.argv[1] + inplace = "--inplace" in sys.argv + + with open(path) as f: + result = remove_comments(f.read()) + + if inplace: + with open(path, "w") as f: + f.write(result) + else: + print(result, end="") diff --git a/renderpip-web.zip b/renderpip-web.zip new file mode 100644 index 0000000..f1ecda8 Binary files /dev/null and b/renderpip-web.zip differ diff --git a/src/game.cpp b/src/game.cpp new file mode 100644 index 0000000..53a978f --- /dev/null +++ b/src/game.cpp @@ -0,0 +1,155 @@ +/** + * @file game.cpp + * @brief Main game implementation + */ + +#include "game.hpp" +#include "systems.hpp" +#include "components.hpp" +#include "pipeline.hpp" + +#include + +bool game_init(GameContext& ctx, const WindowConfig& config) { + // Initialize SDL3 + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { + SDL_Log("Failed to initialize SDL: %s", SDL_GetError()); + return false; + } + + // Create window + ctx.window = SDL_CreateWindow( + config.title, + config.width, + config.height, + config.flags + ); + + if (!ctx.window) { + SDL_Log("Failed to create window: %s", SDL_GetError()); + game_shutdown(ctx); + return false; + } + + ctx.window_width = config.width; + ctx.window_height = config.height; + + // Create renderer + ctx.renderer = SDL_CreateRenderer(ctx.window, nullptr); + if (!ctx.renderer) { + SDL_Log("Failed to create renderer: %s", SDL_GetError()); + game_shutdown(ctx); + return false; + } + + // Enable VSync + SDL_SetRenderVSync(ctx.renderer, 1); + + // Register components and systems + register_systems(ctx.ecs, &ctx); + + // Initialize timing + ctx.last_time = SDL_GetPerformanceCounter(); + ctx.delta_time = 0.0f; + + // Set running state + ctx.running = true; + ctx.paused = false; + + SDL_Log("Game initialized successfully"); + return true; +} + +void game_shutdown(GameContext& ctx) { + if (ctx.renderer) { + SDL_DestroyRenderer(ctx.renderer); + ctx.renderer = nullptr; + } + + if (ctx.window) { + SDL_DestroyWindow(ctx.window); + ctx.window = nullptr; + } + + SDL_Quit(); + SDL_Log("Game shutdown complete"); +} + +void game_process_events(GameContext& ctx) { + SDL_Event event; + + while (SDL_PollEvent(&event)) { + // Forward events to pipeline visualization (skip while paused to avoid queuing) + if (ctx.pipeline && !ctx.paused) { + pipeline_handle_event(*ctx.pipeline, event); + } + + switch (event.type) { + case SDL_EVENT_QUIT: + ctx.running = false; + break; + + case SDL_EVENT_KEY_DOWN: + if (event.key.key == SDLK_ESCAPE) { + ctx.running = false; + } + if (event.key.key == SDLK_P) { + ctx.paused = !ctx.paused; + } + break; + + case SDL_EVENT_WINDOW_RESIZED: + ctx.window_width = event.window.data1; + ctx.window_height = event.window.data2; + break; + + default: + break; + } + } +} + +void game_update(GameContext& ctx) { + // Calculate delta time + uint64_t current_time = SDL_GetPerformanceCounter(); + uint64_t freq = SDL_GetPerformanceFrequency(); + ctx.delta_time = static_cast(current_time - ctx.last_time) / static_cast(freq); + ctx.last_time = current_time; + + // Clamp delta time to prevent spiral of death + if (ctx.delta_time > 0.25f) { + ctx.delta_time = 0.25f; + } + + // Clear screen + SDL_SetRenderDrawColor(ctx.renderer, 30, 30, 40, 255); + SDL_RenderClear(ctx.renderer); + + if (!ctx.paused) { + // Update pipeline and ECS only when not paused + if (ctx.pipeline) { + pipeline_update(*ctx.pipeline, ctx); + } + ctx.ecs.progress(ctx.delta_time); + } + + // Always render so the display doesn't blank while paused + if (ctx.pipeline) { + pipeline_render(*ctx.pipeline, ctx); + } + + SDL_RenderPresent(ctx.renderer); +} + +void game_render(GameContext& ctx) { + // Rendering is handled in game_update() + (void)ctx; +} + +bool game_loop(GameContext& ctx) { + game_process_events(ctx); + game_update(ctx); + game_render(ctx); + + return ctx.running; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..6816fe0 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,82 @@ +/** + * @file main.cpp + * @brief Godot Render Pipeline Visualizer + * + * Interactive tool to visualize the 6 stages of the render pipeline: + * Object Space -> World Space -> View Space -> Clip Space -> NDC -> Screen Space + * + * Controls: + * Left/Right arrows: Navigate between pipeline stages + * 1-6: Jump to a specific stage + * WASD: Move object (X/Z plane) + * Q/E: Move object (Y axis) + * Mouse drag: Orbit the display camera + * Scroll wheel: Zoom in/out + * Space: Toggle auto-rotation + * R: Reset all transforms + * P: Pause + * ESC: Quit + */ + +#define SDL_MAIN_HANDLED +#include +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +#include "game.hpp" +#include "pipeline.hpp" + +#include +#include + +// Global context for emscripten callback +static GameContext* g_ctx = nullptr; + +#ifdef __EMSCRIPTEN__ +static void em_main_loop() { + if (!game_loop(*g_ctx)) { + emscripten_cancel_main_loop(); + } +} +#endif + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + SDL_SetMainReady(); + + static GameContext ctx; + static PipelineState pipeline; + ctx.pipeline = &pipeline; + g_ctx = &ctx; + + WindowConfig config{ + .title = "Render Pipeline Visualizer", + .width = 1280, + .height = 720, + .flags = SDL_WINDOW_RESIZABLE + }; + + if (!game_init(ctx, config)) { + std::fprintf(stderr, "Failed to initialize\n"); + return EXIT_FAILURE; + } + + std::printf("Render Pipeline Visualizer running.\n"); + std::printf("Use Left/Right arrows to navigate stages, WASD/QE to move object.\n"); + +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(em_main_loop, 0, 1); +#else + while (game_loop(ctx)) { + // Game loop handles everything + } +#endif + + game_shutdown(ctx); + return EXIT_SUCCESS; +} diff --git a/src/pipeline.cpp b/src/pipeline.cpp new file mode 100644 index 0000000..0cdd0c9 --- /dev/null +++ b/src/pipeline.cpp @@ -0,0 +1,2661 @@ + + +#include "pipeline.hpp" +#include "game.hpp" +#include +#include +#include +#include +#include + + + + + +struct StageInfo { + const char* name; + const char* description; + const char* detail; + const char* godot_api; +}; + +static const StageInfo stage_info[] = { + {"1. Object Space", + "Local space: vertices defined relative to the model's pivot.", + "Identity transform - raw vertex data as authored.", + "Godot: MeshInstance3D / ArrayMesh vertex data"}, + {"2. World Space", + "Object placed in the scene via the Model Matrix.", + "Model Matrix = Translation * Rotation * Scale", + "Godot: Node3D.global_transform"}, + {"3. View Space", + "Scene transformed to the camera's frame of reference.", + "View Matrix = inverse(Camera Transform)", + "Godot: Camera3D.get_camera_transform().inverse()"}, + {"4. Clip Space", + "Homogeneous coordinates after projection. W != 1.", + "Projection Matrix maps frustum to clip volume.", + "Godot: Camera3D.get_camera_projection()"}, + {"5. NDC (Normalized Device Coordinates)", + "After perspective divide (xyz / w). Range: [-1, 1].", + "Vertices outside [-1,1] are clipped.", + "Godot: RenderingServer (GPU perspective divide)"}, + {"6. Screen Space", + "Final pixel coordinates for rasterization.", + "Viewport transform maps NDC to screen resolution.", + "Godot: Viewport.get_visible_rect() / Camera3D.unproject_position()"}, +}; + + + + + +static const Vec3 cube_verts[NUM_CUBE_VERTS] = { + {-0.7f, -0.7f, -0.7f}, + { 0.7f, -0.7f, -0.7f}, + { 0.7f, 0.7f, -0.7f}, + {-0.7f, 0.7f, -0.7f}, + {-0.7f, -0.7f, 0.7f}, + { 0.7f, -0.7f, 0.7f}, + { 0.7f, 0.7f, 0.7f}, + {-0.7f, 0.7f, 0.7f}, +}; + +static const int cube_edges[NUM_CUBE_EDGES][2] = { + {0,1}, {1,2}, {2,3}, {3,0}, + {4,5}, {5,6}, {6,7}, {7,4}, + {0,4}, {1,5}, {2,6}, {3,7}, +}; + + + + + +static const Vec3 tri_verts[NUM_TRI_VERTS] = { + { 0.0f, 1.0f, 0.0f}, + {-0.8f, -0.6f, 0.0f}, + { 0.8f, -0.6f, 0.0f}, +}; + +static const int tri_edges[NUM_TRI_EDGES][2] = { + {0, 1}, {1, 2}, {2, 0}, +}; + + +// Z-fighting demo plane (flat quad) +static const Vec3 plane_verts[4] = { + {-1.5f, -1.5f, 0.0f}, + { 1.5f, -1.5f, 0.0f}, + { 1.5f, 1.5f, 0.0f}, + {-1.5f, 1.5f, 0.0f}, +}; +static const int plane_edges[4][2] = {{0,1}, {1,2}, {2,3}, {3,0}}; + +struct RGBA { uint8_t r, g, b, a; }; +static const RGBA vert_colors[NUM_CUBE_VERTS] = { + {255, 80, 80, 255}, + { 80, 255, 80, 255}, + { 80, 80, 255, 255}, + {255, 255, 80, 255}, + {255, 80, 255, 255}, + { 80, 255, 255, 255}, + {255, 180, 80, 255}, + {180, 80, 255, 255}, +}; + +static const RGBA tri_colors[NUM_TRI_VERTS] = { + {255, 255, 255, 255}, + {180, 220, 255, 255}, + {255, 200, 180, 255}, +}; + + +static const RGBA stage_colors[] = { + {255, 120, 80, 255}, + {255, 200, 80, 255}, + {120, 255, 80, 255}, + { 80, 200, 255, 255}, + {120, 120, 255, 255}, + {200, 80, 255, 255}, +}; + +static const char* stage_short_names[] = { + "Object", "World", "View", "Clip", "NDC", "Screen" +}; + + + + + +enum SliderID { + SLIDER_POS_X, SLIDER_POS_Y, SLIDER_POS_Z, + SLIDER_ROT_Y, SLIDER_SCALE, + SLIDER_FOV, SLIDER_NEAR, SLIDER_FAR, + SLIDER_SHADER_AMP, SLIDER_SHADER_FREQ, + SLIDER_PLANE_Z, + SLIDER_COUNT +}; + +struct SliderDef { + const char* label; + float min_val, max_val; +}; + +static const SliderDef slider_defs[SLIDER_COUNT] = { + {"Pos X", -10.0f, 10.0f}, + {"Pos Y", -5.0f, 5.0f}, + {"Pos Z", -10.0f, 10.0f}, + {"Rot Y", 0.0f, 360.0f}, + {"Scale", 0.1f, 3.0f}, + {"FOV", 20.0f, 120.0f}, + {"Near", 0.01f, 5.0f}, + {"Far", 5.0f, 50.0f}, + {"Amp", 0.0f, 1.0f}, + {"Freq", 0.5f, 8.0f}, + {"Plane Z", -10.0f, 10.0f}, +}; + +static float slider_get(const PipelineState& ps, int id) { + switch (id) { + case SLIDER_POS_X: return ps.obj_pos_x; + case SLIDER_POS_Y: return ps.obj_pos_y; + case SLIDER_POS_Z: return ps.obj_pos_z; + case SLIDER_ROT_Y: return ps.obj_rot_y * 180.0f / PI; + case SLIDER_SCALE: return ps.obj_scale; + case SLIDER_FOV: return ps.pipe_cam_fov * 180.0f / PI; + case SLIDER_NEAR: return ps.pipe_cam_near; + case SLIDER_FAR: return ps.pipe_cam_far; + case SLIDER_SHADER_AMP: return ps.shader_amplitude; + case SLIDER_SHADER_FREQ: return ps.shader_frequency; + case SLIDER_PLANE_Z: return ps.zfight_plane_z; + default: return 0; + } +} + +static void slider_set(PipelineState& ps, int id, float val) { + switch (id) { + case SLIDER_POS_X: ps.obj_pos_x = val; break; + case SLIDER_POS_Y: ps.obj_pos_y = val; break; + case SLIDER_POS_Z: ps.obj_pos_z = val; break; + case SLIDER_ROT_Y: ps.obj_rot_y = val * PI / 180.0f; break; + case SLIDER_SCALE: ps.obj_scale = val; break; + case SLIDER_FOV: ps.pipe_cam_fov = val * PI / 180.0f; break; + case SLIDER_NEAR: ps.pipe_cam_near = val; break; + case SLIDER_FAR: ps.pipe_cam_far = val; break; + case SLIDER_SHADER_AMP: ps.shader_amplitude = val; break; + case SLIDER_SHADER_FREQ: ps.shader_frequency = val; break; + case SLIDER_PLANE_Z: ps.zfight_plane_z = val; break; + } +} + + +static constexpr float SLIDER_PANEL_X = 24.0f; +static constexpr float SLIDER_PANEL_Y_OFFSET = 210.0f; +static constexpr float SLIDER_W = 140.0f; +static constexpr float SLIDER_H = 14.0f; +static constexpr float SLIDER_SPACING = 26.0f; +static constexpr float SLIDER_HANDLE_W = 8.0f; +static constexpr float SLIDER_HANDLE_H = 14.0f; +static constexpr float SLIDER_SECTION_GAP = 18.0f; + + +static constexpr float SLIDER_TOP_PAD = 36.0f; // room for header + section label + +static SDL_FRect slider_track_rect(float vp_y, int idx) { + float x = SLIDER_PANEL_X + 52; + float y = vp_y + SLIDER_PANEL_Y_OFFSET + SLIDER_TOP_PAD + idx * SLIDER_SPACING; + // Extra gap before Camera and Shader sections + if (idx >= SLIDER_FOV) y += SLIDER_SECTION_GAP; + if (idx >= SLIDER_SHADER_AMP) y += SLIDER_SECTION_GAP; + if (idx >= SLIDER_PLANE_Z) y += SLIDER_SECTION_GAP; + return {x, y, SLIDER_W, SLIDER_H}; +} + + +static float slider_handle_x(const SDL_FRect& track, float val, float min_v, float max_v) { + float t = (val - min_v) / (max_v - min_v); + t = std::clamp(t, 0.0f, 1.0f); + return track.x + t * track.w; +} + + +// Check if a slider should be visible based on current toggles +static bool slider_visible(const PipelineState& ps, int id) { + if ((id == SLIDER_SHADER_AMP || id == SLIDER_SHADER_FREQ) && !ps.shader_mode) return false; + if (id == SLIDER_PLANE_Z && !ps.show_zfight_plane) return false; + return true; +} + +static int slider_hit_test(const PipelineState& ps, float mx, float my, float vp_y) { + if (!ps.show_sliders) return -1; + for (int i = 0; i < SLIDER_COUNT; i++) { + if (!slider_visible(ps, i)) continue; + SDL_FRect track = slider_track_rect(vp_y, i); + float val = slider_get(ps, i); + float hx = slider_handle_x(track, val, slider_defs[i].min_val, slider_defs[i].max_val); + float hy = track.y + track.h * 0.5f; + + if (mx >= hx - 10 && mx <= hx + 10 && + my >= hy - 10 && my <= hy + 10) { + return i; + } + } + return -1; +} + + +static const int cube_faces[6][4] = { + {0, 1, 2, 3}, + {4, 5, 6, 7}, + {0, 3, 7, 4}, + {1, 2, 6, 5}, + {0, 1, 5, 4}, + {2, 3, 7, 6}, +}; + + +static bool is_3d_stage(int stage) { return stage <= 3; } + + +static void get_stage_verts(const PipelineState& ps, int stage, Vec3 out[NUM_CUBE_VERTS]) { + for (int i = 0; i < NUM_CUBE_VERTS; i++) { + switch (stage) { + case 0: out[i] = ps.verts_object[i].xyz(); break; + case 1: out[i] = ps.verts_world[i].xyz(); break; + case 2: out[i] = ps.verts_view[i].xyz(); break; + case 3: out[i] = ps.verts_clip[i].xyz(); break; + case 4: out[i] = ps.verts_ndc[i]; break; + case 5: out[i] = ps.verts_screen[i]; break; + } + } +} + +static void get_tri_stage_verts(const PipelineState& ps, int stage, Vec3 out[NUM_TRI_VERTS]) { + for (int i = 0; i < NUM_TRI_VERTS; i++) { + switch (stage) { + case 0: out[i] = ps.tri_object[i].xyz(); break; + case 1: out[i] = ps.tri_world[i].xyz(); break; + case 2: out[i] = ps.tri_view[i].xyz(); break; + case 3: out[i] = ps.tri_clip[i].xyz(); break; + case 4: out[i] = ps.tri_ndc[i]; break; + case 5: out[i] = ps.tri_screen[i]; break; + } + } +} + + + + + + +static Vec3 project_to_viewport(Vec3 p, float pitch, float yaw, float distance, + float vp_x, float vp_y, float vp_w, float vp_h) { + float ax = pitch * PI / 180.0f; + float ay = yaw * PI / 180.0f; + + Vec3 eye = { + distance * std::cos(ax) * std::sin(ay), + distance * std::sin(ax), + distance * std::cos(ax) * std::cos(ay) + }; + + Mat4 view = Mat4::lookAt(eye, {0, 0, 0}, {0, 1, 0}); + Mat4 proj = Mat4::perspective(0.8f, vp_w / vp_h, 0.1f, 100.0f); + Mat4 vp = proj * view; + + Vec4 clip = vp * Vec4{p.x, p.y, p.z, 1.0f}; + if (clip.w <= 0.01f) return {-10000, -10000, 0}; + + Vec3 ndc = clip.perspDiv(); + float sx = vp_x + (ndc.x * 0.5f + 0.5f) * vp_w; + float sy = vp_y + (1.0f - (ndc.y * 0.5f + 0.5f)) * vp_h; + return {sx, sy, ndc.z}; +} + +static void draw_line_3d(SDL_Renderer* r, Vec3 a, Vec3 b, + float pitch, float yaw, float dist, + float vp_x, float vp_y, float vp_w, float vp_h) { + Vec3 sa = project_to_viewport(a, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + Vec3 sb = project_to_viewport(b, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + if (sa.x < -5000 || sb.x < -5000) return; + SDL_RenderLine(r, sa.x, sa.y, sb.x, sb.y); +} + +static void draw_axes_3d(SDL_Renderer* r, Vec3 origin, float length, + float pitch, float yaw, float dist, + float vp_x, float vp_y, float vp_w, float vp_h) { + + SDL_SetRenderDrawColor(r, 255, 60, 60, 255); + draw_line_3d(r, origin, {origin.x + length, origin.y, origin.z}, + pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + + SDL_SetRenderDrawColor(r, 60, 255, 60, 255); + draw_line_3d(r, origin, {origin.x, origin.y + length, origin.z}, + pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + + SDL_SetRenderDrawColor(r, 60, 60, 255, 255); + draw_line_3d(r, origin, {origin.x, origin.y, origin.z + length}, + pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); +} + +static void draw_grid_3d(SDL_Renderer* r, float size, int divisions, + float pitch, float yaw, float dist, + float vp_x, float vp_y, float vp_w, float vp_h) { + SDL_SetRenderDrawColor(r, 45, 45, 55, 255); + float half = size * 0.5f; + float step = size / divisions; + for (int i = 0; i <= divisions; i++) { + float p = -half + i * step; + draw_line_3d(r, {p, 0, -half}, {p, 0, half}, + pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + draw_line_3d(r, {-half, 0, p}, {half, 0, p}, + pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + } +} + + +static void draw_wireframe_3d(SDL_Renderer* r, const Vec3 verts[], + float pitch, float yaw, float dist, + float vp_x, float vp_y, float vp_w, float vp_h) { + + for (int i = 0; i < NUM_CUBE_EDGES; i++) { + int a = cube_edges[i][0], b = cube_edges[i][1]; + uint8_t cr = (uint8_t)((vert_colors[a].r + vert_colors[b].r) / 2); + uint8_t cg = (uint8_t)((vert_colors[a].g + vert_colors[b].g) / 2); + uint8_t cb = (uint8_t)((vert_colors[a].b + vert_colors[b].b) / 2); + SDL_SetRenderDrawColor(r, cr, cg, cb, 255); + draw_line_3d(r, verts[a], verts[b], + pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + } + + + for (int i = 0; i < NUM_CUBE_VERTS; i++) { + Vec3 sp = project_to_viewport(verts[i], pitch, yaw, dist, + vp_x, vp_y, vp_w, vp_h); + if (sp.x < -5000) continue; + SDL_SetRenderDrawColor(r, vert_colors[i].r, vert_colors[i].g, vert_colors[i].b, 255); + SDL_FRect dot = {sp.x - 3, sp.y - 3, 6, 6}; + SDL_RenderFillRect(r, &dot); + } +} + + +static void draw_wireframe_2d(SDL_Renderer* r, const Vec3 verts[], + float vp_x, float vp_y, float vp_w, float vp_h, + float coord_min, float coord_max) { + float range = coord_max - coord_min; + + auto to_screen = [&](Vec3 v) -> Vec3 { + float sx = vp_x + ((v.x - coord_min) / range) * vp_w; + float sy = vp_y + (1.0f - (v.y - coord_min) / range) * vp_h; + return {sx, sy, v.z}; + }; + + for (int i = 0; i < NUM_CUBE_EDGES; i++) { + int a = cube_edges[i][0], b = cube_edges[i][1]; + uint8_t cr = (uint8_t)((vert_colors[a].r + vert_colors[b].r) / 2); + uint8_t cg = (uint8_t)((vert_colors[a].g + vert_colors[b].g) / 2); + uint8_t cb = (uint8_t)((vert_colors[a].b + vert_colors[b].b) / 2); + SDL_SetRenderDrawColor(r, cr, cg, cb, 255); + Vec3 sa = to_screen(verts[a]); + Vec3 sb = to_screen(verts[b]); + SDL_RenderLine(r, sa.x, sa.y, sb.x, sb.y); + } + + for (int i = 0; i < NUM_CUBE_VERTS; i++) { + Vec3 sp = to_screen(verts[i]); + SDL_SetRenderDrawColor(r, vert_colors[i].r, vert_colors[i].g, vert_colors[i].b, 255); + SDL_FRect dot = {sp.x - 3, sp.y - 3, 6, 6}; + SDL_RenderFillRect(r, &dot); + } +} + + +static void draw_faces_3d(SDL_Renderer* r, const Vec3 verts[], + float pitch, float yaw, float dist, + float vp_x, float vp_y, float vp_w, float vp_h, + uint8_t face_alpha) { + + Vec3 screen[NUM_CUBE_VERTS]; + for (int i = 0; i < NUM_CUBE_VERTS; i++) { + screen[i] = project_to_viewport(verts[i], pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + } + + + struct FaceSort { int idx; float avg_z; }; + FaceSort sorted[6]; + for (int f = 0; f < 6; f++) { + sorted[f].idx = f; + float z_sum = 0; + for (int v = 0; v < 4; v++) z_sum += screen[cube_faces[f][v]].z; + sorted[f].avg_z = z_sum / 4.0f; + } + + + std::sort(sorted, sorted + 6, [](const FaceSort& a, const FaceSort& b) { + return a.avg_z > b.avg_z; + }); + + + for (int si = 0; si < 6; si++) { + int f = sorted[si].idx; + const int* fi = cube_faces[f]; + + + Vec3 s0 = screen[fi[0]], s1 = screen[fi[1]], s2 = screen[fi[2]]; + float cross_z = (s1.x - s0.x) * (s2.y - s0.y) - (s1.y - s0.y) * (s2.x - s0.x); + if (cross_z > 0) continue; + + + int avg_r = 0, avg_g = 0, avg_b = 0; + for (int v = 0; v < 4; v++) { + avg_r += vert_colors[fi[v]].r; + avg_g += vert_colors[fi[v]].g; + avg_b += vert_colors[fi[v]].b; + } + SDL_FColor col = { + (avg_r / 4) / 255.0f, + (avg_g / 4) / 255.0f, + (avg_b / 4) / 255.0f, + face_alpha / 255.0f + }; + + + SDL_Vertex sdl_verts[4]; + for (int v = 0; v < 4; v++) { + sdl_verts[v].position = {screen[fi[v]].x, screen[fi[v]].y}; + sdl_verts[v].color = col; + } + int indices[6] = {0, 1, 2, 0, 2, 3}; + SDL_RenderGeometry(r, nullptr, sdl_verts, 4, indices, 6); + } +} + + +static void draw_faces_2d(SDL_Renderer* r, const Vec3 verts[], + float vp_x, float vp_y, float vp_w, float vp_h, + float coord_min, float coord_max, uint8_t face_alpha) { + float range = coord_max - coord_min; + + auto to_screen = [&](Vec3 v) -> Vec3 { + float sx = vp_x + ((v.x - coord_min) / range) * vp_w; + float sy = vp_y + (1.0f - (v.y - coord_min) / range) * vp_h; + return {sx, sy, v.z}; + }; + + + struct FaceSort { int idx; float avg_z; }; + FaceSort sorted[6]; + for (int f = 0; f < 6; f++) { + sorted[f].idx = f; + float z_sum = 0; + for (int v = 0; v < 4; v++) z_sum += verts[cube_faces[f][v]].z; + sorted[f].avg_z = z_sum / 4.0f; + } + std::sort(sorted, sorted + 6, [](const FaceSort& a, const FaceSort& b) { + return a.avg_z > b.avg_z; + }); + + for (int si = 0; si < 6; si++) { + int f = sorted[si].idx; + const int* fi = cube_faces[f]; + + Vec3 s[4]; + for (int v = 0; v < 4; v++) s[v] = to_screen(verts[fi[v]]); + + + float cross_z = (s[1].x - s[0].x) * (s[2].y - s[0].y) - (s[1].y - s[0].y) * (s[2].x - s[0].x); + if (cross_z > 0) continue; + + int avg_r = 0, avg_g = 0, avg_b = 0; + for (int v = 0; v < 4; v++) { + avg_r += vert_colors[fi[v]].r; + avg_g += vert_colors[fi[v]].g; + avg_b += vert_colors[fi[v]].b; + } + SDL_FColor col = { + (avg_r / 4) / 255.0f, + (avg_g / 4) / 255.0f, + (avg_b / 4) / 255.0f, + face_alpha / 255.0f + }; + + SDL_Vertex sdl_verts[4]; + for (int v = 0; v < 4; v++) { + sdl_verts[v].position = {s[v].x, s[v].y}; + sdl_verts[v].color = col; + } + int indices[6] = {0, 1, 2, 0, 2, 3}; + SDL_RenderGeometry(r, nullptr, sdl_verts, 4, indices, 6); + } +} + + +static void draw_faces_screen(SDL_Renderer* r, const Vec3 verts[], + float w, float h, + float sx, float sy, float sw, float sh, + uint8_t face_alpha) { + + Vec3 mapped[NUM_CUBE_VERTS]; + for (int i = 0; i < NUM_CUBE_VERTS; i++) { + mapped[i] = { + sx + (verts[i].x / w) * sw, + sy + (verts[i].y / h) * sh, + verts[i].z + }; + } + + struct FaceSort { int idx; float avg_z; }; + FaceSort sorted[6]; + for (int f = 0; f < 6; f++) { + sorted[f].idx = f; + float z_sum = 0; + for (int v = 0; v < 4; v++) z_sum += verts[cube_faces[f][v]].z; + sorted[f].avg_z = z_sum / 4.0f; + } + std::sort(sorted, sorted + 6, [](const FaceSort& a, const FaceSort& b) { + return a.avg_z > b.avg_z; + }); + + for (int si = 0; si < 6; si++) { + int f = sorted[si].idx; + const int* fi = cube_faces[f]; + + + float cross_z = (mapped[fi[1]].x - mapped[fi[0]].x) * (mapped[fi[2]].y - mapped[fi[0]].y) + - (mapped[fi[1]].y - mapped[fi[0]].y) * (mapped[fi[2]].x - mapped[fi[0]].x); + if (cross_z > 0) continue; + + int avg_r = 0, avg_g = 0, avg_b = 0; + for (int v = 0; v < 4; v++) { + avg_r += vert_colors[fi[v]].r; + avg_g += vert_colors[fi[v]].g; + avg_b += vert_colors[fi[v]].b; + } + SDL_FColor col = { + (avg_r / 4) / 255.0f, + (avg_g / 4) / 255.0f, + (avg_b / 4) / 255.0f, + face_alpha / 255.0f + }; + + SDL_Vertex sdl_verts[4]; + for (int v = 0; v < 4; v++) { + sdl_verts[v].position = {mapped[fi[v]].x, mapped[fi[v]].y}; + sdl_verts[v].color = col; + } + int indices[6] = {0, 1, 2, 0, 2, 3}; + SDL_RenderGeometry(r, nullptr, sdl_verts, 4, indices, 6); + } +} + + + + + + +static void draw_tri_wireframe_3d(SDL_Renderer* r, const Vec3 verts[], + float pitch, float yaw, float dist, + float vp_x, float vp_y, float vp_w, float vp_h) { + for (int i = 0; i < NUM_TRI_EDGES; i++) { + int a = tri_edges[i][0], b = tri_edges[i][1]; + uint8_t cr = (uint8_t)((tri_colors[a].r + tri_colors[b].r) / 2); + uint8_t cg = (uint8_t)((tri_colors[a].g + tri_colors[b].g) / 2); + uint8_t cb = (uint8_t)((tri_colors[a].b + tri_colors[b].b) / 2); + SDL_SetRenderDrawColor(r, cr, cg, cb, 220); + draw_line_3d(r, verts[a], verts[b], pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + } + for (int i = 0; i < NUM_TRI_VERTS; i++) { + Vec3 sp = project_to_viewport(verts[i], pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + if (sp.x < -5000) continue; + SDL_SetRenderDrawColor(r, tri_colors[i].r, tri_colors[i].g, tri_colors[i].b, 255); + SDL_FRect dot = {sp.x - 3, sp.y - 3, 6, 6}; + SDL_RenderFillRect(r, &dot); + } +} + + +static void draw_tri_face_3d(SDL_Renderer* r, const Vec3 verts[], + float pitch, float yaw, float dist, + float vp_x, float vp_y, float vp_w, float vp_h, + uint8_t face_alpha) { + Vec3 screen[3]; + for (int i = 0; i < 3; i++) + screen[i] = project_to_viewport(verts[i], pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + if (screen[0].x < -5000 || screen[1].x < -5000 || screen[2].x < -5000) return; + + SDL_FColor col = {0.85f, 0.9f, 1.0f, face_alpha / 255.0f}; + SDL_Vertex sdl_verts[3]; + for (int i = 0; i < 3; i++) { + sdl_verts[i].position = {screen[i].x, screen[i].y}; + sdl_verts[i].color = col; + } + int indices[3] = {0, 1, 2}; + SDL_RenderGeometry(r, nullptr, sdl_verts, 3, indices, 3); +} + + +static void draw_tri_wireframe_2d(SDL_Renderer* r, const Vec3 verts[], + float vp_x, float vp_y, float vp_w, float vp_h, + float coord_min, float coord_max) { + float range = coord_max - coord_min; + auto to_screen = [&](Vec3 v) -> Vec3 { + float sx = vp_x + ((v.x - coord_min) / range) * vp_w; + float sy = vp_y + (1.0f - (v.y - coord_min) / range) * vp_h; + return {sx, sy, v.z}; + }; + + for (int i = 0; i < NUM_TRI_EDGES; i++) { + int a = tri_edges[i][0], b = tri_edges[i][1]; + uint8_t cr = (uint8_t)((tri_colors[a].r + tri_colors[b].r) / 2); + uint8_t cg = (uint8_t)((tri_colors[a].g + tri_colors[b].g) / 2); + uint8_t cb = (uint8_t)((tri_colors[a].b + tri_colors[b].b) / 2); + SDL_SetRenderDrawColor(r, cr, cg, cb, 220); + Vec3 sa = to_screen(verts[a]), sb = to_screen(verts[b]); + SDL_RenderLine(r, sa.x, sa.y, sb.x, sb.y); + } + for (int i = 0; i < NUM_TRI_VERTS; i++) { + Vec3 sp = to_screen(verts[i]); + SDL_SetRenderDrawColor(r, tri_colors[i].r, tri_colors[i].g, tri_colors[i].b, 255); + SDL_FRect dot = {sp.x - 3, sp.y - 3, 6, 6}; + SDL_RenderFillRect(r, &dot); + } +} + + +static void draw_tri_face_2d(SDL_Renderer* r, const Vec3 verts[], + float vp_x, float vp_y, float vp_w, float vp_h, + float coord_min, float coord_max, uint8_t face_alpha) { + float range = coord_max - coord_min; + SDL_FColor col = {0.85f, 0.9f, 1.0f, face_alpha / 255.0f}; + SDL_Vertex sdl_verts[3]; + for (int i = 0; i < 3; i++) { + float sx = vp_x + ((verts[i].x - coord_min) / range) * vp_w; + float sy = vp_y + (1.0f - (verts[i].y - coord_min) / range) * vp_h; + sdl_verts[i].position = {sx, sy}; + sdl_verts[i].color = col; + } + int indices[3] = {0, 1, 2}; + SDL_RenderGeometry(r, nullptr, sdl_verts, 3, indices, 3); +} + + +static void draw_text(SDL_Renderer* r, float x, float y, const char* text, float s = 1.0f); + + + + + + +static bool is_inside_clip_volume(Vec4 clip) { + return clip.w > 0 && + clip.x >= -clip.w && clip.x <= clip.w && + clip.y >= -clip.w && clip.y <= clip.w && + clip.z >= -clip.w && clip.z <= clip.w; +} + + +// ============================================================================ +// Sutherland-Hodgman clipping +// ============================================================================ + +// Test if a clip-space point is inside a specific clip plane +// Planes: 0=+X (x<=w), 1=-X (x>=-w), 2=+Y, 3=-Y, 4=+Z, 5=-Z +static bool inside_clip_plane(Vec4 v, int plane) { + switch (plane) { + case 0: return v.x <= v.w; + case 1: return v.x >= -v.w; + case 2: return v.y <= v.w; + case 3: return v.y >= -v.w; + case 4: return v.z <= v.w; + case 5: return v.z >= -v.w; + default: return true; + } +} + +// Compute intersection of edge (a→b) with a clip plane +static Vec4 clip_intersect(Vec4 a, Vec4 b, int plane) { + // Compute signed distances to the plane + float da, db; + switch (plane) { + case 0: da = a.w - a.x; db = b.w - b.x; break; + case 1: da = a.w + a.x; db = b.w + b.x; break; + case 2: da = a.w - a.y; db = b.w - b.y; break; + case 3: da = a.w + a.y; db = b.w + b.y; break; + case 4: da = a.w - a.z; db = b.w - b.z; break; + case 5: da = a.w + a.z; db = b.w + b.z; break; + default: da = 1; db = 1; break; + } + float t = da / (da - db); + return { + a.x + (b.x - a.x) * t, + a.y + (b.y - a.y) * t, + a.z + (b.z - a.z) * t, + a.w + (b.w - a.w) * t, + }; +} + +// Clip a polygon against all 6 frustum planes +static void sutherland_hodgman(const Vec4* in_verts, int in_count, + Vec4* out_verts, int& out_count) { + constexpr int MAX = PipelineState::ClippedFace::MAX_VERTS; + Vec4 buf_a[MAX], buf_b[MAX]; + int count_a = std::min(in_count, MAX); + for (int i = 0; i < count_a; i++) buf_a[i] = in_verts[i]; + + Vec4* src = buf_a; + Vec4* dst = buf_b; + int src_count = count_a; + + for (int plane = 0; plane < 6; plane++) { + int dst_count = 0; + if (src_count == 0) break; + + for (int i = 0; i < src_count; i++) { + Vec4 cur = src[i]; + Vec4 nxt = src[(i + 1) % src_count]; + bool cur_in = inside_clip_plane(cur, plane); + bool nxt_in = inside_clip_plane(nxt, plane); + + if (cur_in && nxt_in) { + if (dst_count < MAX) dst[dst_count++] = nxt; + } else if (cur_in && !nxt_in) { + if (dst_count < MAX) dst[dst_count++] = clip_intersect(cur, nxt, plane); + } else if (!cur_in && nxt_in) { + if (dst_count < MAX) dst[dst_count++] = clip_intersect(cur, nxt, plane); + if (dst_count < MAX) dst[dst_count++] = nxt; + } + // both outside: skip + } + + // Swap buffers + std::swap(src, dst); + src_count = dst_count; + } + + out_count = src_count; + for (int i = 0; i < out_count; i++) out_verts[i] = src[i]; +} + +static void compute_clipped_geometry(PipelineState& ps) { + for (int f = 0; f < 6; f++) { + const int* fi = cube_faces[f]; + Vec4 face_verts[4]; + for (int v = 0; v < 4; v++) face_verts[v] = ps.verts_clip[fi[v]]; + + // Check if any vertex is outside + bool any_outside = false; + for (int v = 0; v < 4; v++) { + if (!is_inside_clip_volume(face_verts[v])) { any_outside = true; break; } + } + + sutherland_hodgman(face_verts, 4, ps.clipped_faces[f].verts, ps.clipped_faces[f].count); + ps.clipped_faces[f].was_clipped = any_outside; + } +} + +// Draw clipped geometry in clip space view +static void draw_clipped_geometry_3d(SDL_Renderer* r, const PipelineState& ps, + float pitch, float yaw, float dist, + float vp_x, float vp_y, float vp_w, float vp_h) { + for (int f = 0; f < 6; f++) { + const auto& cf = ps.clipped_faces[f]; + if (cf.count < 2) continue; + if (!cf.was_clipped) continue; // only draw clipped faces + + // Draw clipped polygon edges in bright yellow + SDL_SetRenderDrawColor(r, 255, 220, 50, 255); + for (int i = 0; i < cf.count; i++) { + Vec3 a = cf.verts[i].xyz(); + Vec3 b = cf.verts[(i + 1) % cf.count].xyz(); + draw_line_3d(r, a, b, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + } + + // Draw intersection vertices as diamonds + for (int i = 0; i < cf.count; i++) { + Vec3 sp = project_to_viewport(cf.verts[i].xyz(), pitch, yaw, dist, + vp_x, vp_y, vp_w, vp_h); + if (sp.x < -5000) continue; + + // Check if this is a new vertex (not one of the original face verts) + bool is_original = false; + const int* fi = cube_faces[f]; + for (int v = 0; v < 4; v++) { + Vec4 orig = ps.verts_clip[fi[v]]; + float dx = cf.verts[i].x - orig.x; + float dy = cf.verts[i].y - orig.y; + float dz = cf.verts[i].z - orig.z; + if (dx*dx + dy*dy + dz*dz < 0.001f) { is_original = true; break; } + } + + if (!is_original) { + // Diamond marker for clipped vertices + SDL_SetRenderDrawColor(r, 255, 255, 50, 255); + float s = 5; + SDL_RenderLine(r, sp.x, sp.y - s, sp.x + s, sp.y); + SDL_RenderLine(r, sp.x + s, sp.y, sp.x, sp.y + s); + SDL_RenderLine(r, sp.x, sp.y + s, sp.x - s, sp.y); + SDL_RenderLine(r, sp.x - s, sp.y, sp.x, sp.y - s); + } + } + } +} + +static void draw_clip_status_3d(SDL_Renderer* r, const PipelineState& ps, + float pitch, float yaw, float dist, + float vp_x, float vp_y, float vp_w, float vp_h) { + for (int i = 0; i < NUM_CUBE_VERTS; i++) { + Vec3 sp = project_to_viewport(ps.display_verts[i], pitch, yaw, dist, + vp_x, vp_y, vp_w, vp_h); + if (sp.x < -5000) continue; + + bool inside = is_inside_clip_volume(ps.verts_clip[i]); + if (inside) { + SDL_SetRenderDrawColor(r, 60, 255, 60, 180); + } else { + SDL_SetRenderDrawColor(r, 255, 60, 60, 220); + } + + float rad = 8.0f; + int segs = 16; + for (int s = 0; s < segs; s++) { + float a1 = (float)s / segs * 2.0f * PI; + float a2 = (float)(s + 1) / segs * 2.0f * PI; + SDL_RenderLine(r, sp.x + std::cos(a1) * rad, sp.y + std::sin(a1) * rad, + sp.x + std::cos(a2) * rad, sp.y + std::sin(a2) * rad); + } + + draw_text(r, sp.x + 10, sp.y - 10, inside ? "IN" : "OUT"); + } + + + if (ps.show_triangle) { + for (int i = 0; i < NUM_TRI_VERTS; i++) { + Vec3 sp = project_to_viewport(ps.tri_display[i], pitch, yaw, dist, + vp_x, vp_y, vp_w, vp_h); + if (sp.x < -5000) continue; + + bool inside = is_inside_clip_volume(ps.tri_clip[i]); + SDL_SetRenderDrawColor(r, inside ? 60 : 255, inside ? 255 : 60, 60, 180); + float rad = 8.0f; + int segs = 16; + for (int s = 0; s < segs; s++) { + float a1 = (float)s / segs * 2.0f * PI; + float a2 = (float)(s + 1) / segs * 2.0f * PI; + SDL_RenderLine(r, sp.x + std::cos(a1) * rad, sp.y + std::sin(a1) * rad, + sp.x + std::cos(a2) * rad, sp.y + std::sin(a2) * rad); + } + } + } +} + + +static void draw_near_plane_3d(SDL_Renderer* r, const PipelineState& ps, + float pitch, float yaw, float dist, + float vp_x, float vp_y, float vp_w, float vp_h) { + + + + + Vec3 cam_eye = {ps.pipe_cam_x, ps.pipe_cam_y, ps.pipe_cam_z}; + Vec3 cam_target = {0, 0, 0}; + Vec3 forward = (cam_target - cam_eye).normalized(); + Vec3 right_v = forward.cross({0, 1, 0}).normalized(); + Vec3 up_v = right_v.cross(forward); + + float near = ps.pipe_cam_near; + float half_h = near * std::tan(ps.pipe_cam_fov * 0.5f); + float aspect = ps.proj_matrix.m[1][1] / ps.proj_matrix.m[0][0]; // extract from live projection + float half_w = half_h * aspect; + + Vec3 center = cam_eye + forward * near; + Vec3 corners[4] = { + center + up_v * half_h - right_v * half_w, + center + up_v * half_h + right_v * half_w, + center - up_v * half_h + right_v * half_w, + center - up_v * half_h - right_v * half_w, + }; + + + Vec3 sc[4]; + for (int i = 0; i < 4; i++) { + sc[i] = project_to_viewport(corners[i], pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + if (sc[i].x < -5000) return; + } + + + SDL_FColor col = {0.8f, 0.2f, 0.2f, 0.12f}; + SDL_Vertex verts[4]; + for (int i = 0; i < 4; i++) { + verts[i].position = {sc[i].x, sc[i].y}; + verts[i].color = col; + } + int indices[6] = {0, 1, 2, 0, 2, 3}; + SDL_RenderGeometry(r, nullptr, verts, 4, indices, 6); + + + SDL_SetRenderDrawColor(r, 255, 80, 80, 120); + for (int i = 0; i < 4; i++) { + int j = (i + 1) % 4; + SDL_RenderLine(r, sc[i].x, sc[i].y, sc[j].x, sc[j].y); + } + + + Vec3 label_pos = project_to_viewport(center + up_v * (half_h + 0.3f), + pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + if (label_pos.x > -5000) { + SDL_SetRenderDrawColor(r, 255, 100, 100, 200); + draw_text(r, label_pos.x - 28, label_pos.y, "NEAR PLANE"); + } +} + + +static bool point_in_tri(float px, float py, + float x0, float y0, float x1, float y1, float x2, float y2) { + float d1 = (px - x1) * (y0 - y1) - (x0 - x1) * (py - y1); + float d2 = (px - x2) * (y1 - y2) - (x1 - x2) * (py - y2); + float d3 = (px - x0) * (y2 - y0) - (x2 - x0) * (py - y0); + bool has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0); + bool has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0); + return !(has_neg && has_pos); +} + + +static void draw_raster_grid(SDL_Renderer* r, const PipelineState& ps, + float win_w, float win_h, + float sx, float sy, float sw, float sh) { + constexpr int GRID_X = 40; + constexpr int GRID_Y = 28; + float cell_w = sw / GRID_X; + float cell_h = sh / GRID_Y; + + + + struct Tri2D { float x[3], y[3], z[3]; uint8_t cr, cg, cb; }; + Tri2D tris[12 + 1 + 2]; // cube faces + triangle + plane + int num_tris = 0; + + for (int f = 0; f < 6; f++) { + const int* fi = cube_faces[f]; + float fx[4], fy[4], fz[4]; + for (int v = 0; v < 4; v++) { + fx[v] = ps.verts_screen[fi[v]].x; + fy[v] = ps.verts_screen[fi[v]].y; + fz[v] = ps.verts_screen[fi[v]].z; + } + int avg_r = 0, avg_g = 0, avg_b = 0; + for (int v = 0; v < 4; v++) { + avg_r += vert_colors[fi[v]].r; + avg_g += vert_colors[fi[v]].g; + avg_b += vert_colors[fi[v]].b; + } + uint8_t cr = (uint8_t)(avg_r / 4); + uint8_t cg = (uint8_t)(avg_g / 4); + uint8_t cb = (uint8_t)(avg_b / 4); + tris[num_tris++] = {{fx[0], fx[1], fx[2]}, {fy[0], fy[1], fy[2]}, {fz[0], fz[1], fz[2]}, cr, cg, cb}; + tris[num_tris++] = {{fx[0], fx[2], fx[3]}, {fy[0], fy[2], fy[3]}, {fz[0], fz[2], fz[3]}, cr, cg, cb}; + } + + if (ps.show_triangle) { + tris[num_tris++] = { + {ps.tri_screen[0].x, ps.tri_screen[1].x, ps.tri_screen[2].x}, + {ps.tri_screen[0].y, ps.tri_screen[1].y, ps.tri_screen[2].y}, + {ps.tri_screen[0].z, ps.tri_screen[1].z, ps.tri_screen[2].z}, + 200, 220, 255 + }; + } + + // Z-fighting plane (2 triangles for the quad) + if (ps.show_zfight_plane) { + Vec3 ps0 = ps.plane_screen[0], ps1 = ps.plane_screen[1]; + Vec3 ps2 = ps.plane_screen[2], ps3 = ps.plane_screen[3]; + tris[num_tris++] = { + {ps0.x, ps1.x, ps2.x}, {ps0.y, ps1.y, ps2.y}, {ps0.z, ps1.z, ps2.z}, + 255, 180, 60 + }; + tris[num_tris++] = { + {ps0.x, ps2.x, ps3.x}, {ps0.y, ps2.y, ps3.y}, {ps0.z, ps2.z, ps3.z}, + 255, 180, 60 + }; + } + + // Z-fighting epsilon: proportional to depth range + float zfight_eps = 0.005f / std::max(ps.pipe_cam_far - ps.pipe_cam_near, 0.1f); + + for (int gy = 0; gy < GRID_Y; gy++) { + for (int gx = 0; gx < GRID_X; gx++) { + float px = (gx + 0.5f) / GRID_X * win_w; + float py = (gy + 0.5f) / GRID_Y * win_h; + + uint8_t hit_r = 0, hit_g = 0, hit_b = 0; + bool hit = false; + float hit_z = 1e30f; + float second_z = 1e30f; // for Z-fighting detection + for (int t = 0; t < num_tris; t++) { + if (point_in_tri(px, py, + tris[t].x[0], tris[t].y[0], + tris[t].x[1], tris[t].y[1], + tris[t].x[2], tris[t].y[2])) { + float dx0 = tris[t].x[1] - tris[t].x[0], dy0 = tris[t].y[1] - tris[t].y[0]; + float dx1 = tris[t].x[2] - tris[t].x[0], dy1 = tris[t].y[2] - tris[t].y[0]; + float dpx = px - tris[t].x[0], dpy = py - tris[t].y[0]; + float denom = dx0 * dy1 - dx1 * dy0; + if (std::fabs(denom) < 1e-6f) continue; + float u = (dpx * dy1 - dx1 * dpy) / denom; + float v = (dx0 * dpy - dpx * dy0) / denom; + float z = tris[t].z[0] + u * (tris[t].z[1] - tris[t].z[0]) + v * (tris[t].z[2] - tris[t].z[0]); + + if (z < hit_z) { + second_z = hit_z; + hit_z = z; + hit_r = tris[t].cr; + hit_g = tris[t].cg; + hit_b = tris[t].cb; + hit = true; + } else if (z < second_z) { + second_z = z; + } + } + } + + float cx = sx + gx * cell_w; + float cy = sy + gy * cell_h; + + if (hit) { + // Z-fighting: two surfaces nearly same depth + bool zfighting = (second_z < 1e29f) && (std::fabs(hit_z - second_z) < zfight_eps); + + if (zfighting) { + // Alternating red/yellow checkerboard to show the fight + if ((gx + gy) % 2 == 0) { + SDL_SetRenderDrawColor(r, 255, 60, 60, 80); + } else { + SDL_SetRenderDrawColor(r, 255, 255, 60, 80); + } + } else if (ps.show_depth_viz) { + // Depth grayscale: closer = brighter + int g = 255 - (int)std::clamp((hit_z * 0.5f + 0.5f) * 255.0f, 0.0f, 255.0f); + SDL_SetRenderDrawColor(r, (uint8_t)g, (uint8_t)g, (uint8_t)g, 70); + } else { + SDL_SetRenderDrawColor(r, hit_r, hit_g, hit_b, 50); + } + SDL_FRect cell = {cx + 0.5f, cy + 0.5f, cell_w - 1.0f, cell_h - 1.0f}; + SDL_RenderFillRect(r, &cell); + } + } + } + + + SDL_SetRenderDrawColor(r, 60, 60, 80, 40); + for (int gx = 0; gx <= GRID_X; gx++) { + float lx = sx + gx * cell_w; + SDL_RenderLine(r, lx, sy, lx, sy + sh); + } + for (int gy = 0; gy <= GRID_Y; gy++) { + float ly = sy + gy * cell_h; + SDL_RenderLine(r, sx, ly, sx + sw, ly); + } +} + + + + + + +static void draw_text(SDL_Renderer* r, float x, float y, const char* text, float s) { + SDL_SetRenderScale(r, s, s); + SDL_RenderDebugText(r, x / s, y / s, text); + SDL_SetRenderScale(r, 1.0f, 1.0f); +} + + +static void draw_matrix(SDL_Renderer* r, const Mat4& mat, float x, float y, + const char* label, bool breakdown) { + SDL_SetRenderDrawColor(r, 140, 140, 160, 255); + draw_text(r, x, y, label); + + if (!breakdown) { + // Original uniform color rendering + SDL_SetRenderDrawColor(r, 100, 110, 130, 255); + char buf[80]; + for (int row = 0; row < 4; row++) { + std::snprintf(buf, sizeof(buf), "[%6.2f %6.2f %6.2f %6.2f]", + mat.m[row][0], mat.m[row][1], mat.m[row][2], mat.m[row][3]); + draw_text(r, x, y + 16 + row * 12, buf); + } + return; + } + + // Color-coded breakdown: render each cell individually + float cell_w = 56; // width per column (~7 chars * 8px) + float row_y = y + 16; + + for (int row = 0; row < 4; row++) { + // Opening bracket + SDL_SetRenderDrawColor(r, 70, 70, 85, 255); + draw_text(r, x, row_y, "["); + + for (int col = 0; col < 4; col++) { + // Pick color by matrix region + if (row == 3) { + SDL_SetRenderDrawColor(r, 255, 100, 180, 255); // pink: projection/W row + } else if (col == 3) { + SDL_SetRenderDrawColor(r, 80, 255, 120, 255); // green: translation + } else if (row == col) { + SDL_SetRenderDrawColor(r, 255, 200, 80, 255); // yellow: scale (diagonal) + } else { + SDL_SetRenderDrawColor(r, 100, 180, 255, 255); // blue: rotation + } + + char val[16]; + std::snprintf(val, sizeof(val), "%6.2f", mat.m[row][col]); + draw_text(r, x + 8 + col * cell_w, row_y, val); + } + + // Closing bracket + SDL_SetRenderDrawColor(r, 70, 70, 85, 255); + draw_text(r, x + 8 + 4 * cell_w, row_y, "]"); + + row_y += 12; + } + + // Legend below the matrix + float ly = row_y + 6; + auto draw_swatch = [&](float lx, float ly, uint8_t cr, uint8_t cg, uint8_t cb, const char* lbl) { + SDL_SetRenderDrawColor(r, cr, cg, cb, 255); + SDL_FRect sw = {lx, ly + 2, 6, 6}; + SDL_RenderFillRect(r, &sw); + SDL_SetRenderDrawColor(r, 120, 120, 140, 255); + draw_text(r, lx + 10, ly, lbl); + }; + draw_swatch(x, ly, 100, 180, 255, "Rotation"); + draw_swatch(x + 80, ly, 255, 200, 80, "Scale"); + draw_swatch(x, ly + 14, 80, 255, 120, "Translation"); + draw_swatch(x + 80, ly + 14, 255, 100, 180, "Proj/W"); + + SDL_SetRenderDrawColor(r, 70, 70, 90, 255); + draw_text(r, x, ly + 30, "(M: toggle)"); +} + + +static void draw_frustum_3d(SDL_Renderer* r, + Vec3 cam_eye, Vec3 cam_target, + float fov, float aspect, float near_dist, float far_dist, + float pitch, float yaw, float dist, + float vp_x, float vp_y, float vp_w, float vp_h) { + + Vec3 forward = (cam_target - cam_eye).normalized(); + Vec3 right = forward.cross({0, 1, 0}).normalized(); + Vec3 up = right.cross(forward); + + + float near_h = near_dist * std::tan(fov * 0.5f); + float near_w = near_h * aspect; + + float vis_far = std::min(far_dist, 15.0f); + float vis_far_h = vis_far * std::tan(fov * 0.5f); + float vis_far_w = vis_far_h * aspect; + + + Vec3 nc = cam_eye + forward * near_dist; + Vec3 fc = cam_eye + forward * vis_far; + + + Vec3 ntl = nc + up * near_h - right * near_w; + Vec3 ntr = nc + up * near_h + right * near_w; + Vec3 nbr = nc - up * near_h + right * near_w; + Vec3 nbl = nc - up * near_h - right * near_w; + + Vec3 ftl = fc + up * vis_far_h - right * vis_far_w; + Vec3 ftr = fc + up * vis_far_h + right * vis_far_w; + Vec3 fbr = fc - up * vis_far_h + right * vis_far_w; + Vec3 fbl = fc - up * vis_far_h - right * vis_far_w; + + + SDL_SetRenderDrawColor(r, 255, 255, 100, 80); + + + draw_line_3d(r, ntl, ntr, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + draw_line_3d(r, ntr, nbr, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + draw_line_3d(r, nbr, nbl, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + draw_line_3d(r, nbl, ntl, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + + + draw_line_3d(r, ftl, ftr, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + draw_line_3d(r, ftr, fbr, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + draw_line_3d(r, fbr, fbl, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + draw_line_3d(r, fbl, ftl, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + + + SDL_SetRenderDrawColor(r, 255, 255, 100, 50); + draw_line_3d(r, ntl, ftl, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + draw_line_3d(r, ntr, ftr, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + draw_line_3d(r, nbr, fbr, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); + draw_line_3d(r, nbl, fbl, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h); +} + + +static void draw_info_panel(SDL_Renderer* r, const PipelineState& ps, int stage, + float x, float y) { + char buf[128]; + float line_h = 14.0f; + float ix = x + 8; // inner content x + float cy = y; + + Vec3 v_obj = ps.verts_object[0].xyz(); + Vec3 v_world = ps.verts_world[0].xyz(); + Vec3 v_view = ps.verts_view[0].xyz(); + Vec4 v_clip4 = ps.verts_clip[0]; + Vec3 v_ndc = ps.verts_ndc[0]; + Vec3 v_scr = ps.verts_screen[0]; + RGBA vc = vert_colors[0]; + + // --- Measure panel height first so we can draw the background --- + // Rough height per stage: header(20) + formula(14-28) + gap(6) + input(14) + arrow(14) + output(14) + pad(8) + float panel_h = 0; + switch (stage) { + case 0: panel_h = 20 + 14 + 6 + 14 + 10; break; + case 5: panel_h = 20 + 28 + 6 + 14 + 14 + 14 + 10; break; + default: panel_h = 20 + 14 + 14 + 6 + 14 + 14 + 14 + 10; break; + } + + // Background card + SDL_SetRenderDrawColor(r, 18, 18, 28, 210); + SDL_FRect bg = {x, y, 280, panel_h}; + SDL_RenderFillRect(r, &bg); + SDL_SetRenderDrawColor(r, 55, 55, 70, 180); + SDL_RenderRect(r, &bg); + + // Header: colored dot + title + SDL_SetRenderDrawColor(r, vc.r, vc.g, vc.b, 255); + SDL_FRect dot = {ix, cy + 4, 8, 8}; + SDL_RenderFillRect(r, &dot); + SDL_SetRenderDrawColor(r, 190, 190, 210, 255); + draw_text(r, ix + 12, cy + 2, "Vertex 0 - Step by Step"); + cy += 20; + + // Helpers + auto draw_formula = [&](const char* text) { + SDL_SetRenderDrawColor(r, 170, 200, 255, 255); + draw_text(r, ix, cy, text); + cy += line_h; + }; + auto draw_dim_label = [&](const char* text) { + SDL_SetRenderDrawColor(r, 110, 120, 140, 255); + draw_text(r, ix, cy, text); + cy += line_h; + }; + auto draw_input = [&](const char* label, const char* val) { + SDL_SetRenderDrawColor(r, 130, 140, 160, 255); + draw_text(r, ix + 4, cy, label); + SDL_SetRenderDrawColor(r, vc.r, vc.g, vc.b, 200); + draw_text(r, ix + 60, cy, val); + cy += line_h; + }; + auto draw_arrow = [&](const char* op) { + SDL_SetRenderDrawColor(r, 80, 180, 120, 255); + draw_text(r, ix + 16, cy, op); + cy += line_h; + }; + auto draw_output = [&](const char* label, const char* val) { + SDL_SetRenderDrawColor(r, 200, 210, 230, 255); + draw_text(r, ix + 4, cy, label); + SDL_SetRenderDrawColor(r, vc.r, vc.g, vc.b, 255); + draw_text(r, ix + 60, cy, val); + cy += line_h; + }; + + switch (stage) { + case 0: + if (ps.shader_mode) { + draw_formula("v = authored + sin(t*f + y*4)*A"); + } else { + draw_formula("v_local = authored position"); + } + cy += 6; + std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v_obj.x, v_obj.y, v_obj.z); + draw_output("v0 =", buf); + break; + + case 1: + draw_formula("v_world = M_model * v_local"); + draw_dim_label("M = Translate * Rotate * Scale"); + cy += 6; + std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v_obj.x, v_obj.y, v_obj.z); + draw_input("Input:", buf); + draw_arrow("* Model Matrix"); + std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v_world.x, v_world.y, v_world.z); + draw_output("Output:", buf); + break; + + case 2: + draw_formula("v_view = M_view * v_world"); + draw_dim_label("M_view = lookAt(eye, target, up)"); + cy += 6; + std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v_world.x, v_world.y, v_world.z); + draw_input("Input:", buf); + draw_arrow("* View Matrix"); + std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v_view.x, v_view.y, v_view.z); + draw_output("Output:", buf); + break; + + case 3: + draw_formula("v_clip = M_proj * v_view"); + draw_dim_label("Perspective projection"); + cy += 6; + std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v_view.x, v_view.y, v_view.z); + draw_input("Input:", buf); + draw_arrow("* Projection Matrix"); + std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f, w=%.2f)", v_clip4.x, v_clip4.y, v_clip4.z, v_clip4.w); + draw_output("Output:", buf); + break; + + case 4: + draw_formula("v_ndc = v_clip.xyz / v_clip.w"); + draw_dim_label("Perspective divide"); + cy += 6; + std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f, w=%.2f)", v_clip4.x, v_clip4.y, v_clip4.z, v_clip4.w); + draw_input("Input:", buf); + std::snprintf(buf, sizeof(buf), "/ w = %.2f", v_clip4.w); + draw_arrow(buf); + std::snprintf(buf, sizeof(buf), "(%.3f, %.3f, %.3f)", v_ndc.x, v_ndc.y, v_ndc.z); + draw_output("Output:", buf); + break; + + case 5: + draw_formula("x = (ndc.x*0.5+0.5) * W"); + draw_formula("y = (1-(ndc.y*0.5+0.5)) * H"); + cy += 6; + std::snprintf(buf, sizeof(buf), "(%.3f, %.3f)", v_ndc.x, v_ndc.y); + draw_input("Input:", buf); + draw_arrow("=> viewport transform"); + std::snprintf(buf, sizeof(buf), "(%.0f, %.0f) px", v_scr.x, v_scr.y); + draw_output("Output:", buf); + break; + } +} + + +static void draw_vertex_highlight(SDL_Renderer* r, float sx, float sy, RGBA col, float time) { + float pulse = 6.0f + 2.0f * std::sin(time * 4.0f); + int segments = 20; + SDL_SetRenderDrawColor(r, col.r, col.g, col.b, 255); + for (int i = 0; i < segments; i++) { + float a1 = (float)i / segments * 2.0f * PI; + float a2 = (float)(i + 1) / segments * 2.0f * PI; + SDL_RenderLine(r, + sx + std::cos(a1) * pulse, sy + std::sin(a1) * pulse, + sx + std::cos(a2) * pulse, sy + std::sin(a2) * pulse); + } + + SDL_SetRenderDrawColor(r, col.r, col.g, col.b, 200); + SDL_FRect dot = {sx - 4, sy - 4, 8, 8}; + SDL_RenderFillRect(r, &dot); +} + + +static void draw_vertex_sidebar(SDL_Renderer* r, const PipelineState& ps, + int vi, int current_stage, + float x, float y, float panel_w) { + RGBA col = vert_colors[vi]; + char buf[96]; + float line_h = 13.0f; + float cy = y; + + + SDL_SetRenderDrawColor(r, 20, 20, 30, 200); + SDL_FRect bg = {x - 4, y - 4, panel_w + 8, 130}; + SDL_RenderFillRect(r, &bg); + SDL_SetRenderDrawColor(r, col.r, col.g, col.b, 120); + SDL_RenderRect(r, &bg); + + + static const char* vert_names[] = { + "v0 (front-bot-L)", "v1 (front-bot-R)", "v2 (front-top-R)", "v3 (front-top-L)", + "v4 (back-bot-L)", "v5 (back-bot-R)", "v6 (back-top-R)", "v7 (back-top-L)" + }; + SDL_SetRenderDrawColor(r, col.r, col.g, col.b, 255); + draw_text(r, x, cy, vert_names[vi], 1.2f); + cy += 18; + + + struct StageEntry { + const char* label; + Vec3 v3; + Vec4 v4; + bool use_v4; + bool is_screen; + }; + + StageEntry entries[6] = { + {"Object", ps.verts_object[vi].xyz(), {}, false, false}, + {"World", ps.verts_world[vi].xyz(), {}, false, false}, + {"View", ps.verts_view[vi].xyz(), {}, false, false}, + {"Clip", {}, ps.verts_clip[vi], true, false}, + {"NDC", ps.verts_ndc[vi], {}, false, false}, + {"Screen", ps.verts_screen[vi], {}, false, true}, + }; + + float val_x = x + 56; // align all coordinate values at a fixed column + + for (int s = 0; s < 6; s++) { + // Highlight current stage row + if (s == current_stage) { + SDL_SetRenderDrawColor(r, col.r, col.g, col.b, 40); + SDL_FRect highlight = {x - 2, cy - 1, panel_w + 4, line_h + 2}; + SDL_RenderFillRect(r, &highlight); + } + + // Stage label + RGBA sc = stage_colors[s]; + SDL_SetRenderDrawColor(r, sc.r, sc.g, sc.b, (uint8_t)(s == current_stage ? 255 : 160)); + draw_text(r, x, cy, entries[s].label); + + // Coordinate value on the same line, at fixed column + SDL_SetRenderDrawColor(r, col.r, col.g, col.b, (uint8_t)(s == current_stage ? 255 : 150)); + if (entries[s].use_v4) { + Vec4 c = entries[s].v4; + std::snprintf(buf, sizeof(buf), "(%.1f, %.1f, %.1f, w=%.1f)", + c.x, c.y, c.z, c.w); + } else if (entries[s].is_screen) { + Vec3 v = entries[s].v3; + std::snprintf(buf, sizeof(buf), "(%.0f, %.0f)", v.x, v.y); + } else { + Vec3 v = entries[s].v3; + std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v.x, v.y, v.z); + } + draw_text(r, val_x, cy, buf); + cy += line_h + 4; + } +} + + +static void draw_slider_panel(SDL_Renderer* r, const PipelineState& ps, float vp_y) { + float panel_x = SLIDER_PANEL_X; + float panel_y = vp_y + SLIDER_PANEL_Y_OFFSET - 20; + + // Count visible sliders for background height + int visible_count = 0; + int section_gaps = 1; // Camera section always has a gap + for (int i = 0; i < SLIDER_COUNT; i++) { + if (slider_visible(ps, i)) visible_count++; + } + if (ps.shader_mode) section_gaps++; + if (ps.show_zfight_plane) section_gaps++; + + SDL_SetRenderDrawColor(r, 18, 18, 28, 220); + float bg_h = SLIDER_TOP_PAD + (float)visible_count * SLIDER_SPACING + + section_gaps * SLIDER_SECTION_GAP + 18; + SDL_FRect bg = {panel_x - 6, panel_y - 4, SLIDER_W + 112, bg_h}; + SDL_RenderFillRect(r, &bg); + SDL_SetRenderDrawColor(r, 55, 55, 70, 200); + SDL_RenderRect(r, &bg); + + + SDL_SetRenderDrawColor(r, 160, 160, 185, 255); + draw_text(r, panel_x, panel_y + 2, "Sliders (T: toggle)"); + + + // Section labels positioned with enough clearance + { + SDL_FRect first_track = slider_track_rect(vp_y, SLIDER_POS_X); + SDL_SetRenderDrawColor(r, 100, 100, 125, 180); + draw_text(r, panel_x - 2, first_track.y - 14, "Object"); + + SDL_FRect cam_track = slider_track_rect(vp_y, SLIDER_FOV); + draw_text(r, panel_x - 2, cam_track.y - 14, "Camera"); + + if (ps.shader_mode) { + SDL_FRect shader_track = slider_track_rect(vp_y, SLIDER_SHADER_AMP); + draw_text(r, panel_x - 2, shader_track.y - 14, "Shader (V)"); + } + + if (ps.show_zfight_plane) { + SDL_FRect plane_track = slider_track_rect(vp_y, SLIDER_PLANE_Z); + draw_text(r, panel_x - 2, plane_track.y - 14, "Depth (X)"); + } + } + + for (int i = 0; i < SLIDER_COUNT; i++) { + if (!slider_visible(ps, i)) continue; + SDL_FRect track = slider_track_rect(vp_y, i); + float val = slider_get(ps, i); + float hx = slider_handle_x(track, val, slider_defs[i].min_val, slider_defs[i].max_val); + float hy = track.y + track.h * 0.5f; + + + SDL_SetRenderDrawColor(r, 130, 130, 155, 255); + draw_text(r, panel_x, track.y + 1, slider_defs[i].label); + + + SDL_SetRenderDrawColor(r, 40, 40, 55, 255); + SDL_FRect track_bg = {track.x, hy - 2, track.w, 4}; + SDL_RenderFillRect(r, &track_bg); + + + SDL_SetRenderDrawColor(r, 80, 120, 180, 180); + SDL_FRect filled = {track.x, hy - 2, hx - track.x, 4}; + SDL_RenderFillRect(r, &filled); + + + bool active = (ps.dragging_slider == i); + if (active) { + SDL_SetRenderDrawColor(r, 120, 180, 255, 255); + } else { + SDL_SetRenderDrawColor(r, 180, 180, 200, 255); + } + SDL_FRect handle = {hx - SLIDER_HANDLE_W * 0.5f, hy - SLIDER_HANDLE_H * 0.5f, + SLIDER_HANDLE_W, SLIDER_HANDLE_H}; + SDL_RenderFillRect(r, &handle); + + + char buf[32]; + if (slider_defs[i].max_val >= 100) { + std::snprintf(buf, sizeof(buf), "%.0f", val); + } else if (slider_defs[i].max_val <= 1) { + std::snprintf(buf, sizeof(buf), "%.2f", val); + } else { + std::snprintf(buf, sizeof(buf), "%.1f", val); + } + SDL_SetRenderDrawColor(r, 180, 180, 200, 220); + draw_text(r, track.x + track.w + 6, track.y + 1, buf); + } +} + + +// ============================================================================ +// First-person camera view (split-screen) +// ============================================================================ + +// Project a 3D world-space point through the pipeline camera to a viewport +static Vec3 project_first_person(Vec3 p, const Mat4& view, const Mat4& proj, + float vp_x, float vp_y, float vp_w, float vp_h) { + Vec4 view_pt = view * Vec4{p.x, p.y, p.z, 1.0f}; + Vec4 clip = proj * view_pt; + if (clip.w <= 0.01f) return {-10000, -10000, 0}; + Vec3 ndc = clip.perspDiv(); + if (ndc.x < -1.5f || ndc.x > 1.5f || ndc.y < -1.5f || ndc.y > 1.5f) + return {-10000, -10000, 0}; + float sx = vp_x + (ndc.x * 0.5f + 0.5f) * vp_w; + float sy = vp_y + (1.0f - (ndc.y * 0.5f + 0.5f)) * vp_h; + return {sx, sy, ndc.z}; +} + +static void draw_first_person_view(SDL_Renderer* r, const PipelineState& ps, + float area_x, float area_y, float area_w, float area_h) { + // Letterbox to preserve aspect ratio (use pipeline camera aspect) + float cam_aspect = 16.0f / 9.0f; + float area_aspect = area_w / area_h; + float rv_x, rv_y, rv_w, rv_h; + if (area_aspect > cam_aspect) { + // Area is wider than camera - pillarbox + rv_h = area_h; + rv_w = area_h * cam_aspect; + rv_x = area_x + (area_w - rv_w) * 0.5f; + rv_y = area_y; + } else { + // Area is taller than camera - letterbox + rv_w = area_w; + rv_h = area_w / cam_aspect; + rv_x = area_x; + rv_y = area_y + (area_h - rv_h) * 0.5f; + } + + // Background + SDL_SetRenderDrawColor(r, 22, 22, 32, 255); + SDL_FRect bg = {rv_x, rv_y, rv_w, rv_h}; + SDL_RenderFillRect(r, &bg); + + // Border + SDL_SetRenderDrawColor(r, 255, 255, 100, 120); + SDL_RenderRect(r, &bg); + + // Label + SDL_SetRenderDrawColor(r, 255, 255, 100, 255); + draw_text(r, rv_x + 4, rv_y + 4, "Camera View (F: toggle)"); + + // Use the pipeline camera's projection but with the render viewport's aspect + float rv_aspect = rv_w / rv_h; + Mat4 fp_proj = Mat4::perspective(ps.pipe_cam_fov, rv_aspect, ps.pipe_cam_near, ps.pipe_cam_far); + const Mat4& view = ps.view_matrix; + + // Ground grid + SDL_SetRenderDrawColor(r, 50, 50, 65, 180); + float half = 6.0f; + int divs = 12; + float step = (half * 2) / divs; + for (int i = 0; i <= divs; i++) { + float p = -half + i * step; + Vec3 a = project_first_person({p, 0, -half}, view, fp_proj, rv_x, rv_y, rv_w, rv_h); + Vec3 b = project_first_person({p, 0, half}, view, fp_proj, rv_x, rv_y, rv_w, rv_h); + if (a.x > -5000 && b.x > -5000) SDL_RenderLine(r, a.x, a.y, b.x, b.y); + a = project_first_person({-half, 0, p}, view, fp_proj, rv_x, rv_y, rv_w, rv_h); + b = project_first_person({half, 0, p}, view, fp_proj, rv_x, rv_y, rv_w, rv_h); + if (a.x > -5000 && b.x > -5000) SDL_RenderLine(r, a.x, a.y, b.x, b.y); + } + + // Axes + auto draw_fp_axis = [&](Vec3 from, Vec3 to, uint8_t cr, uint8_t cg, uint8_t cb) { + Vec3 a = project_first_person(from, view, fp_proj, rv_x, rv_y, rv_w, rv_h); + Vec3 b = project_first_person(to, view, fp_proj, rv_x, rv_y, rv_w, rv_h); + if (a.x > -5000 && b.x > -5000) { + SDL_SetRenderDrawColor(r, cr, cg, cb, 200); + SDL_RenderLine(r, a.x, a.y, b.x, b.y); + } + }; + draw_fp_axis({0,0,0}, {2,0,0}, 255, 60, 60); + draw_fp_axis({0,0,0}, {0,2,0}, 60, 255, 60); + draw_fp_axis({0,0,0}, {0,0,2}, 60, 60, 255); + + // World-space cube vertices + Vec3 ws_cube[NUM_CUBE_VERTS]; + for (int i = 0; i < NUM_CUBE_VERTS; i++) + ws_cube[i] = ps.verts_world[i].xyz(); + + // Cube faces + { + Vec3 screen[NUM_CUBE_VERTS]; + for (int i = 0; i < NUM_CUBE_VERTS; i++) + screen[i] = project_first_person(ws_cube[i], view, fp_proj, rv_x, rv_y, rv_w, rv_h); + + struct FaceSort { int idx; float avg_z; }; + FaceSort sorted[6]; + for (int f = 0; f < 6; f++) { + sorted[f].idx = f; + float z_sum = 0; + for (int v = 0; v < 4; v++) z_sum += screen[cube_faces[f][v]].z; + sorted[f].avg_z = z_sum / 4.0f; + } + std::sort(sorted, sorted + 6, [](const FaceSort& a, const FaceSort& b) { + return a.avg_z > b.avg_z; + }); + + for (int si = 0; si < 6; si++) { + int f = sorted[si].idx; + const int* fi = cube_faces[f]; + bool all_visible = true; + for (int v = 0; v < 4; v++) + if (screen[fi[v]].x < -5000) { all_visible = false; break; } + if (!all_visible) continue; + + float cross_z = (screen[fi[1]].x - screen[fi[0]].x) * (screen[fi[2]].y - screen[fi[0]].y) + - (screen[fi[1]].y - screen[fi[0]].y) * (screen[fi[2]].x - screen[fi[0]].x); + if (cross_z > 0) continue; + + int avg_r = 0, avg_g = 0, avg_b = 0; + for (int v = 0; v < 4; v++) { + avg_r += vert_colors[fi[v]].r; + avg_g += vert_colors[fi[v]].g; + avg_b += vert_colors[fi[v]].b; + } + SDL_FColor col = {(avg_r / 4) / 255.0f, (avg_g / 4) / 255.0f, + (avg_b / 4) / 255.0f, 0.3f}; + SDL_Vertex sv[4]; + for (int v = 0; v < 4; v++) { + sv[v].position = {screen[fi[v]].x, screen[fi[v]].y}; + sv[v].color = col; + } + int indices[6] = {0, 1, 2, 0, 2, 3}; + SDL_RenderGeometry(r, nullptr, sv, 4, indices, 6); + } + } + + // Cube wireframe + for (int i = 0; i < NUM_CUBE_EDGES; i++) { + int a = cube_edges[i][0], b = cube_edges[i][1]; + Vec3 sa = project_first_person(ws_cube[a], view, fp_proj, rv_x, rv_y, rv_w, rv_h); + Vec3 sb = project_first_person(ws_cube[b], view, fp_proj, rv_x, rv_y, rv_w, rv_h); + if (sa.x < -5000 || sb.x < -5000) continue; + uint8_t cr = (uint8_t)((vert_colors[a].r + vert_colors[b].r) / 2); + uint8_t cg = (uint8_t)((vert_colors[a].g + vert_colors[b].g) / 2); + uint8_t cb = (uint8_t)((vert_colors[a].b + vert_colors[b].b) / 2); + SDL_SetRenderDrawColor(r, cr, cg, cb, 255); + SDL_RenderLine(r, sa.x, sa.y, sb.x, sb.y); + } + + // Cube vertex dots + for (int i = 0; i < NUM_CUBE_VERTS; i++) { + Vec3 sp = project_first_person(ws_cube[i], view, fp_proj, rv_x, rv_y, rv_w, rv_h); + if (sp.x < -5000) continue; + SDL_SetRenderDrawColor(r, vert_colors[i].r, vert_colors[i].g, vert_colors[i].b, 255); + SDL_FRect dot = {sp.x - 3, sp.y - 3, 6, 6}; + SDL_RenderFillRect(r, &dot); + } + + // Triangle + if (ps.show_triangle) { + Vec3 ws_tri[NUM_TRI_VERTS]; + for (int i = 0; i < NUM_TRI_VERTS; i++) + ws_tri[i] = ps.tri_world[i].xyz(); + + Vec3 ts[3]; + bool all_vis = true; + for (int i = 0; i < 3; i++) { + ts[i] = project_first_person(ws_tri[i], view, fp_proj, rv_x, rv_y, rv_w, rv_h); + if (ts[i].x < -5000) all_vis = false; + } + if (all_vis) { + SDL_FColor tcol = {0.85f, 0.9f, 1.0f, 0.25f}; + SDL_Vertex tri_sv[3]; + for (int i = 0; i < 3; i++) { + tri_sv[i].position = {ts[i].x, ts[i].y}; + tri_sv[i].color = tcol; + } + int ti[3] = {0, 1, 2}; + SDL_RenderGeometry(r, nullptr, tri_sv, 3, ti, 3); + + for (int i = 0; i < NUM_TRI_EDGES; i++) { + int a = tri_edges[i][0], b = tri_edges[i][1]; + uint8_t cr = (uint8_t)((tri_colors[a].r + tri_colors[b].r) / 2); + uint8_t cg = (uint8_t)((tri_colors[a].g + tri_colors[b].g) / 2); + uint8_t cb = (uint8_t)((tri_colors[a].b + tri_colors[b].b) / 2); + SDL_SetRenderDrawColor(r, cr, cg, cb, 220); + SDL_RenderLine(r, ts[a].x, ts[a].y, ts[b].x, ts[b].y); + } + for (int i = 0; i < NUM_TRI_VERTS; i++) { + SDL_SetRenderDrawColor(r, tri_colors[i].r, tri_colors[i].g, tri_colors[i].b, 255); + SDL_FRect dot = {ts[i].x - 3, ts[i].y - 3, 6, 6}; + SDL_RenderFillRect(r, &dot); + } + } + } +} + +// ============================================================================ +// Stage indicator +// ============================================================================ + +// ============================================================================ +// Help overlay +// ============================================================================ + +static void draw_help_overlay(SDL_Renderer* r, const PipelineState& ps, float w, float h) { + // Dark background + SDL_SetRenderDrawColor(r, 10, 10, 20, 220); + SDL_FRect bg = {0, 0, w, h}; + SDL_RenderFillRect(r, &bg); + + float cx = w * 0.5f; + float top = 40; + + // Title + SDL_SetRenderDrawColor(r, 240, 240, 250, 255); + draw_text(r, cx - 60, top, "Controls", 2.0f); + top += 50; + + // Helper: draw a toggle row with on/off indicator + auto draw_toggle = [&](float x, float y, const char* key, const char* label, bool active) { + // Dot indicator + if (active) { + SDL_SetRenderDrawColor(r, 80, 255, 80, 255); + } else { + SDL_SetRenderDrawColor(r, 60, 60, 70, 255); + } + SDL_FRect dot = {x, y + 3, 6, 6}; + SDL_RenderFillRect(r, &dot); + + SDL_SetRenderDrawColor(r, 200, 200, 220, 255); + draw_text(r, x + 12, y, key); + SDL_SetRenderDrawColor(r, 140, 140, 160, 255); + draw_text(r, x + 80, y, label); + }; + + auto draw_row = [&](float x, float y, const char* key, const char* label) { + SDL_SetRenderDrawColor(r, 200, 200, 220, 255); + draw_text(r, x, y, key); + SDL_SetRenderDrawColor(r, 140, 140, 160, 255); + draw_text(r, x + 100, y, label); + }; + + float col1 = cx - 280; + float col2 = cx + 40; + float line_h = 18; + float y = top; + + // Left column: Navigation + SDL_SetRenderDrawColor(r, 180, 200, 255, 255); + draw_text(r, col1, y, "Navigation", 1.3f); + y += 24; + draw_row(col1, y, "Left/Right", "Change pipeline stage"); y += line_h; + draw_row(col1, y, "1 - 6", "Jump to specific stage"); y += line_h; + draw_row(col1, y, "WASD", "Move object (XZ plane)"); y += line_h; + draw_row(col1, y, "Q / E", "Move object (Y axis)"); y += line_h; + draw_row(col1, y, "Mouse drag", "Orbit display camera"); y += line_h; + draw_row(col1, y, "Scroll", "Zoom in/out"); y += line_h; + draw_row(col1, y, "Right-click","Select vertex"); y += line_h; + draw_row(col1, y, "Space", "Toggle auto-rotation"); y += line_h; + draw_row(col1, y, "R", "Reset all transforms"); y += line_h; + draw_row(col1, y, "P", "Pause/unpause"); y += line_h; + draw_row(col1, y, "ESC", "Quit"); + + // Right column: Toggles + y = top; + SDL_SetRenderDrawColor(r, 180, 200, 255, 255); + draw_text(r, col2, y, "Toggles", 1.3f); + y += 24; + draw_toggle(col2, y, "T", "Sliders panel", ps.show_sliders); y += line_h; + draw_toggle(col2, y, "G", "Triangle shape", ps.show_triangle); y += line_h; + draw_toggle(col2, y, "F", "Camera split view", ps.show_first_person); y += line_h; + draw_toggle(col2, y, "M", "Matrix breakdown", ps.show_matrix_breakdown); y += line_h; + draw_toggle(col2, y, "V", "Vertex shader sim", ps.shader_mode); y += line_h; + draw_toggle(col2, y, "C", "Clipping visualizer", ps.show_clipping); y += line_h; + draw_toggle(col2, y, "Z", "Depth visualization", ps.show_depth_viz); y += line_h; + draw_toggle(col2, y, "X", "Z-fighting plane", ps.show_zfight_plane); y += line_h; + + // Footer + SDL_SetRenderDrawColor(r, 100, 100, 120, 255); + draw_text(r, cx - 60, h - 50, "Press H to close"); +} + +static void draw_stage_indicator(SDL_Renderer* r, int current, float w, float h) { + float box_w = 90; + float gap = 36; + float total_w = 6 * box_w + 5 * gap; + float start_x = (w - total_w) / 2.0f; + float y = h - 58; + + for (int i = 0; i < 6; i++) { + float bx = start_x + i * (box_w + gap); + RGBA sc = stage_colors[i]; + + if (i == current) { + SDL_SetRenderDrawColor(r, sc.r, sc.g, sc.b, 255); + SDL_FRect box = {bx, y, box_w, 32}; + SDL_RenderFillRect(r, &box); + SDL_SetRenderDrawColor(r, 10, 10, 15, 255); + } else { + SDL_SetRenderDrawColor(r, 40, 40, 50, 255); + SDL_FRect box = {bx, y, box_w, 32}; + SDL_RenderFillRect(r, &box); + SDL_SetRenderDrawColor(r, sc.r, sc.g, sc.b, 180); + SDL_RenderRect(r, &box); + SDL_SetRenderDrawColor(r, 160, 160, 175, 255); + } + + float name_len = (float)std::strlen(stage_short_names[i]); + float tx = bx + (box_w - name_len * 8) / 2.0f; + draw_text(r, tx, y + 12, stage_short_names[i]); + + + if (i < 5) { + SDL_SetRenderDrawColor(r, 70, 70, 85, 255); + float ax = bx + box_w + 4; + float ay = y + 16; + float ae = bx + box_w + gap - 4; + SDL_RenderLine(r, ax, ay, ae, ay); + SDL_RenderLine(r, ae - 6, ay - 4, ae, ay); + SDL_RenderLine(r, ae - 6, ay + 4, ae, ay); + } + } +} + + + + + +void pipeline_handle_event(PipelineState& ps, const SDL_Event& event) { + switch (event.type) { + case SDL_EVENT_KEY_DOWN: + switch (event.key.key) { + case SDLK_LEFT: ps.key_left = true; break; + case SDLK_RIGHT: ps.key_right = true; break; + case SDLK_SPACE: ps.key_space = true; break; + case SDLK_R: ps.key_r = true; break; + case SDLK_T: ps.show_sliders = !ps.show_sliders; break; + case SDLK_G: ps.show_triangle = !ps.show_triangle; break; + case SDLK_F: ps.show_first_person = !ps.show_first_person; break; + case SDLK_M: ps.show_matrix_breakdown = !ps.show_matrix_breakdown; break; + case SDLK_V: ps.shader_mode = !ps.shader_mode; break; + case SDLK_H: ps.show_help = !ps.show_help; break; + case SDLK_C: ps.show_clipping = !ps.show_clipping; break; + case SDLK_Z: ps.show_depth_viz = !ps.show_depth_viz; break; + case SDLK_X: ps.show_zfight_plane = !ps.show_zfight_plane; break; + case SDLK_1: ps.key_num[0] = true; break; + case SDLK_2: ps.key_num[1] = true; break; + case SDLK_3: ps.key_num[2] = true; break; + case SDLK_4: ps.key_num[3] = true; break; + case SDLK_5: ps.key_num[4] = true; break; + case SDLK_6: ps.key_num[5] = true; break; + default: break; + } + break; + + case SDL_EVENT_MOUSE_BUTTON_DOWN: + if (event.button.button == SDL_BUTTON_RIGHT) { + ps.pending_click = true; + ps.click_x = event.button.x; + ps.click_y = event.button.y; + } + break; + + case SDL_EVENT_MOUSE_WHEEL: + ps.cam_distance -= event.wheel.y * 0.5f; + ps.cam_distance = std::clamp(ps.cam_distance, 3.0f, 30.0f); + break; + + default: + break; + } +} + + + + + +void pipeline_update(PipelineState& ps, GameContext& ctx) { + float dt = ctx.delta_time; + + + int new_stage = ps.current_stage; + if (ps.key_left && ps.current_stage > 0) new_stage = ps.current_stage - 1; + if (ps.key_right && ps.current_stage < 5) new_stage = ps.current_stage + 1; + if (ps.key_space) ps.auto_rotate = !ps.auto_rotate; + if (ps.key_r) { + ps.obj_rot_y = 0; + ps.obj_pos_x = 2; ps.obj_pos_y = 1; ps.obj_pos_z = -3; + ps.obj_scale = 1; + ps.cam_orbit_pitch = 25; ps.cam_orbit_yaw = -30; + ps.cam_distance = 10; + ps.auto_rotate = true; + } + for (int i = 0; i < 6; i++) { + if (ps.key_num[i]) new_stage = i; + } + + if (new_stage != ps.current_stage) { + ps.prev_stage = ps.current_stage; + ps.current_stage = new_stage; + ps.transition_t = 0.0f; + ps.fade_alpha = 1.0f; + } + + ps.key_left = ps.key_right = ps.key_space = ps.key_r = false; + for (int i = 0; i < 6; i++) ps.key_num[i] = false; + + + float mx, my; + auto buttons = SDL_GetMouseState(&mx, &my); + float vp_y_calc = 92.0f; + + if (buttons & SDL_BUTTON_LMASK) { + if (!ps.mouse_down) { + ps.mouse_down = true; + ps.mouse_start_x = mx; + ps.mouse_start_y = my; + + + int hit = slider_hit_test(ps, mx, my, vp_y_calc); + if (hit >= 0) { + ps.dragging_slider = hit; + } else { + ps.dragging_slider = -1; + ps.orbit_start_pitch = ps.cam_orbit_pitch; + ps.orbit_start_yaw = ps.cam_orbit_yaw; + } + } + + if (ps.dragging_slider >= 0) { + + int si = ps.dragging_slider; + SDL_FRect track = slider_track_rect(vp_y_calc, si); + float t = (mx - track.x) / track.w; + t = std::clamp(t, 0.0f, 1.0f); + float val = slider_defs[si].min_val + t * (slider_defs[si].max_val - slider_defs[si].min_val); + slider_set(ps, si, val); + } else { + + ps.cam_orbit_yaw = ps.orbit_start_yaw + (mx - ps.mouse_start_x) * 0.3f; + ps.cam_orbit_pitch = ps.orbit_start_pitch + (my - ps.mouse_start_y) * 0.3f; + ps.cam_orbit_pitch = std::clamp(ps.cam_orbit_pitch, -89.0f, 89.0f); + } + } else { + ps.mouse_down = false; + ps.dragging_slider = -1; + } + + + const bool* keys = SDL_GetKeyboardState(nullptr); + float move_speed = 2.5f * dt; + if (keys[SDL_SCANCODE_W]) ps.obj_pos_z -= move_speed; + if (keys[SDL_SCANCODE_S]) ps.obj_pos_z += move_speed; + if (keys[SDL_SCANCODE_A]) ps.obj_pos_x -= move_speed; + if (keys[SDL_SCANCODE_D]) ps.obj_pos_x += move_speed; + if (keys[SDL_SCANCODE_Q]) ps.obj_pos_y -= move_speed; + if (keys[SDL_SCANCODE_E]) ps.obj_pos_y += move_speed; + + + if (ps.auto_rotate) { + ps.obj_rot_y += dt * 0.5f; + if (ps.obj_rot_y > 2.0f * PI) ps.obj_rot_y -= 2.0f * PI; + } + + + + + ps.model_matrix = Mat4::translate(ps.obj_pos_x, ps.obj_pos_y, ps.obj_pos_z) + * Mat4::rotateY(ps.obj_rot_y) + * Mat4::scale(ps.obj_scale, ps.obj_scale, ps.obj_scale); + + + Vec3 cam_eye = {ps.pipe_cam_x, ps.pipe_cam_y, ps.pipe_cam_z}; + Vec3 cam_target = {0, 0, 0}; + ps.view_matrix = Mat4::lookAt(cam_eye, cam_target, {0, 1, 0}); + + + // Clamp near < far to prevent division by zero in perspective matrix + if (ps.pipe_cam_near >= ps.pipe_cam_far) { + ps.pipe_cam_far = ps.pipe_cam_near + 0.1f; + } + float aspect = (float)ctx.window_width / std::max((float)ctx.window_height, 1.0f); + ps.proj_matrix = Mat4::perspective(ps.pipe_cam_fov, aspect, ps.pipe_cam_near, ps.pipe_cam_far); + + + // Vertex shader simulation + if (ps.shader_mode) { + ps.shader_time += dt; + if (ps.shader_frequency > 0) + ps.shader_time = std::fmod(ps.shader_time, 2.0f * PI / ps.shader_frequency); + } + + for (int i = 0; i < NUM_CUBE_VERTS; i++) { + Vec3 v = cube_verts[i]; + if (ps.shader_mode) { + v.x += std::sin(ps.shader_time * ps.shader_frequency + v.y * 4.0f) * ps.shader_amplitude; + } + + ps.verts_object[i] = {v.x, v.y, v.z, 1.0f}; + ps.verts_world[i] = ps.model_matrix * ps.verts_object[i]; + ps.verts_view[i] = ps.view_matrix * ps.verts_world[i]; + ps.verts_clip[i] = ps.proj_matrix * ps.verts_view[i]; + ps.verts_ndc[i] = ps.verts_clip[i].perspDiv(); + + float sw = (float)ctx.window_width; + float sh = (float)ctx.window_height; + ps.verts_screen[i] = { + (ps.verts_ndc[i].x * 0.5f + 0.5f) * sw, + (1.0f - (ps.verts_ndc[i].y * 0.5f + 0.5f)) * sh, + ps.verts_ndc[i].z + }; + } + + for (int i = 0; i < NUM_TRI_VERTS; i++) { + Vec3 v = tri_verts[i]; + if (ps.shader_mode) { + v.x += std::sin(ps.shader_time * ps.shader_frequency + v.y * 4.0f) * ps.shader_amplitude; + } + ps.tri_object[i] = {v.x, v.y, v.z, 1.0f}; + ps.tri_world[i] = ps.model_matrix * ps.tri_object[i]; + ps.tri_view[i] = ps.view_matrix * ps.tri_world[i]; + ps.tri_clip[i] = ps.proj_matrix * ps.tri_view[i]; + ps.tri_ndc[i] = ps.tri_clip[i].perspDiv(); + + float sw = (float)ctx.window_width; + float sh = (float)ctx.window_height; + ps.tri_screen[i] = { + (ps.tri_ndc[i].x * 0.5f + 0.5f) * sw, + (1.0f - (ps.tri_ndc[i].y * 0.5f + 0.5f)) * sh, + ps.tri_ndc[i].z + }; + } + + // Compute clipped geometry for the clip visualizer + if (ps.show_clipping) { + compute_clipped_geometry(ps); + } + + // Z-fighting plane transform + if (ps.show_zfight_plane) { + Mat4 plane_model = Mat4::translate(ps.obj_pos_x, ps.obj_pos_y, ps.zfight_plane_z) + * Mat4::rotateY(ps.obj_rot_y) + * Mat4::scale(1.0f, 1.0f, 1.0f); + for (int i = 0; i < 4; i++) { + Vec3 v = plane_verts[i]; + Vec4 obj = {v.x, v.y, v.z, 1.0f}; + ps.plane_world[i] = plane_model * obj; + Vec4 view = ps.view_matrix * ps.plane_world[i]; + Vec4 clip = ps.proj_matrix * view; + Vec3 ndc = clip.perspDiv(); + float sw = (float)ctx.window_width; + float sh = (float)ctx.window_height; + ps.plane_screen[i] = { + (ndc.x * 0.5f + 0.5f) * sw, + (1.0f - (ndc.y * 0.5f + 0.5f)) * sh, + ndc.z + }; + ps.plane_display[i] = ps.plane_world[i].xyz(); // for 3D rendering + } + } + + if (ps.transition_t < 1.0f) { + ps.transition_t = std::min(1.0f, ps.transition_t + dt * 3.0f); + } + + + bool mode_change = is_3d_stage(ps.prev_stage) != is_3d_stage(ps.current_stage); + float t = smoothstep(ps.transition_t); + + if (mode_change) { + + + if (ps.transition_t < 0.5f) { + ps.fade_alpha = 1.0f - ps.transition_t * 2.0f; + get_stage_verts(ps, ps.prev_stage, ps.display_verts); + get_tri_stage_verts(ps, ps.prev_stage, ps.tri_display); + } else { + ps.fade_alpha = (ps.transition_t - 0.5f) * 2.0f; + get_stage_verts(ps, ps.current_stage, ps.display_verts); + get_tri_stage_verts(ps, ps.current_stage, ps.tri_display); + } + } else if (ps.transition_t >= 1.0f) { + ps.fade_alpha = 1.0f; + get_stage_verts(ps, ps.current_stage, ps.display_verts); + get_tri_stage_verts(ps, ps.current_stage, ps.tri_display); + } else { + ps.fade_alpha = 1.0f; + Vec3 prev_v[NUM_CUBE_VERTS], cur_v[NUM_CUBE_VERTS]; + get_stage_verts(ps, ps.prev_stage, prev_v); + get_stage_verts(ps, ps.current_stage, cur_v); + for (int i = 0; i < NUM_CUBE_VERTS; i++) { + ps.display_verts[i] = lerp(prev_v[i], cur_v[i], t); + } + Vec3 prev_t[NUM_TRI_VERTS], cur_t[NUM_TRI_VERTS]; + get_tri_stage_verts(ps, ps.prev_stage, prev_t); + get_tri_stage_verts(ps, ps.current_stage, cur_t); + for (int i = 0; i < NUM_TRI_VERTS; i++) { + ps.tri_display[i] = lerp(prev_t[i], cur_t[i], t); + } + } + + + if (ps.pending_click) { + ps.pending_click = false; + + float w = (float)ctx.window_width; + float h = (float)ctx.window_height; + float full_vp_w = w - 40; + float vp_x = 20, vp_y = 92; + float vp_h = h - 92 - 80; + float vp_w = ps.show_first_person ? (full_vp_w - 10.0f) * 0.5f : full_vp_w; + + + float best_dist = 20.0f; + int best = -1; + + for (int i = 0; i < NUM_CUBE_VERTS; i++) { + float sx_proj, sy_proj; + + if (is_3d_stage(ps.current_stage)) { + Vec3 sp = project_to_viewport(ps.display_verts[i], + ps.cam_orbit_pitch, ps.cam_orbit_yaw, ps.cam_distance, + vp_x, vp_y, vp_w, vp_h); + if (sp.x < -5000) continue; + sx_proj = sp.x; + sy_proj = sp.y; + } else if (ps.current_stage == 4) { + + float ndc_size = std::min(vp_w, vp_h) * 0.38f; + float cx = vp_x + vp_w * 0.5f; + float cy = vp_y + vp_h * 0.5f; + Vec3 v = ps.display_verts[i]; + sx_proj = cx - ndc_size + ((v.x + 1.0f) / 2.0f) * ndc_size * 2; + sy_proj = cy - ndc_size + (1.0f - (v.y + 1.0f) / 2.0f) * ndc_size * 2; + } else { + + float screen_w = vp_w * 0.7f; + float screen_h = vp_h * 0.7f; + float sx = vp_x + (vp_w - screen_w) * 0.5f; + float sy = vp_y + (vp_h - screen_h) * 0.5f; + sx_proj = sx + (ps.display_verts[i].x / w) * screen_w; + sy_proj = sy + (ps.display_verts[i].y / h) * screen_h; + } + + float dx = sx_proj - ps.click_x; + float dy = sy_proj - ps.click_y; + float dist = std::sqrt(dx * dx + dy * dy); + if (dist < best_dist) { + best_dist = dist; + best = i; + } + } + + + if (best == ps.selected_vertex) { + ps.selected_vertex = -1; + } else { + ps.selected_vertex = best; + } + } +} + + + + + +void pipeline_render(PipelineState& ps, GameContext& ctx) { + SDL_Renderer* r = ctx.renderer; + float w = (float)ctx.window_width; + float h = (float)ctx.window_height; + + SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND); + + int stage = ps.current_stage; + + // Viewport layout: split when first-person view is active + float full_vp_w = w - 40; + float vp_x = 20; + float vp_y = 92; + float vp_h = h - 92 - 80; + float vp_w, cam_vp_x, cam_vp_w; + constexpr float SPLIT_GAP = 10.0f; + + if (ps.show_first_person) { + vp_w = (full_vp_w - SPLIT_GAP) * 0.5f; + cam_vp_x = vp_x + vp_w + SPLIT_GAP; + cam_vp_w = full_vp_w - vp_w - SPLIT_GAP; + } else { + vp_w = full_vp_w; + cam_vp_x = 0; + cam_vp_w = 0; + } + + // Pipeline viewport border + SDL_SetRenderDrawColor(r, 50, 50, 60, 255); + SDL_FRect vp_rect = {vp_x, vp_y, vp_w, vp_h}; + SDL_RenderRect(r, &vp_rect); + + + SDL_SetRenderDrawColor(r, 240, 240, 250, 255); + draw_text(r, 20, 8, stage_info[stage].name, 3.0f); + + SDL_SetRenderDrawColor(r, 155, 155, 175, 255); + draw_text(r, 20, 40, stage_info[stage].description, 2.0f); + SDL_SetRenderDrawColor(r, 110, 110, 130, 255); + draw_text(r, 20, 68, stage_info[stage].detail, 1.2f); + float detail_end = 20 + (float)std::strlen(stage_info[stage].detail) * 8.0f * 1.2f; + SDL_SetRenderDrawColor(r, 80, 80, 95, 255); + draw_text(r, detail_end + 8, 68, "-"); + SDL_SetRenderDrawColor(r, 100, 160, 120, 255); + draw_text(r, detail_end + 24, 68, stage_info[stage].godot_api, 1.2f); + + // Shader mode badge + if (ps.shader_mode) { + SDL_SetRenderDrawColor(r, 255, 140, 60, 255); + draw_text(r, vp_x + vp_w - 180, vp_y + vp_h - 60, "VERTEX SHADER ACTIVE"); + } + + bool in_transition = ps.transition_t < 1.0f; + bool mode_change = in_transition && (is_3d_stage(ps.prev_stage) != is_3d_stage(ps.current_stage)); + + int render_stage = stage; + if (mode_change) { + render_stage = (ps.transition_t < 0.5f) ? ps.prev_stage : ps.current_stage; + } + + uint8_t alpha = (uint8_t)(ps.fade_alpha * 255.0f); + uint8_t face_alpha = (uint8_t)(ps.fade_alpha * 40.0f); + + + if (is_3d_stage(render_stage)) { + float p = ps.cam_orbit_pitch, y_angle = ps.cam_orbit_yaw, d = ps.cam_distance; + + + draw_grid_3d(r, 12, 12, p, y_angle, d, vp_x, vp_y, vp_w, vp_h); + + + draw_axes_3d(r, {0, 0, 0}, 2.0f, p, y_angle, d, vp_x, vp_y, vp_w, vp_h); + + + auto label_axis = [&](Vec3 pos, const char* lbl, uint8_t cr, uint8_t cg, uint8_t cb) { + Vec3 sp = project_to_viewport(pos, p, y_angle, d, vp_x, vp_y, vp_w, vp_h); + if (sp.x > -5000) { + SDL_SetRenderDrawColor(r, cr, cg, cb, alpha); + draw_text(r, sp.x + 4, sp.y - 4, lbl); + } + }; + label_axis({2.2f, 0, 0}, "X", 255, 60, 60); + label_axis({0, 2.2f, 0}, "Y", 60, 255, 60); + label_axis({0, 0, 2.2f}, "Z", 60, 60, 255); + + + draw_faces_3d(r, ps.display_verts, p, y_angle, d, vp_x, vp_y, vp_w, vp_h, face_alpha); + + + draw_wireframe_3d(r, ps.display_verts, p, y_angle, d, vp_x, vp_y, vp_w, vp_h); + + + if (ps.show_triangle) { + draw_tri_face_3d(r, ps.tri_display, p, y_angle, d, vp_x, vp_y, vp_w, vp_h, face_alpha); + draw_tri_wireframe_3d(r, ps.tri_display, p, y_angle, d, vp_x, vp_y, vp_w, vp_h); + } + + // Z-fighting plane wireframe (only in world/view stages where it makes sense) + if (ps.show_zfight_plane && stage <= 1) { + SDL_SetRenderDrawColor(r, 255, 180, 60, 180); + for (int i = 0; i < 4; i++) { + int a = plane_edges[i][0], b = plane_edges[i][1]; + draw_line_3d(r, ps.plane_display[a], ps.plane_display[b], + p, y_angle, d, vp_x, vp_y, vp_w, vp_h); + } + // Semi-transparent face + Vec3 screen_p[4]; + for (int i = 0; i < 4; i++) + screen_p[i] = project_to_viewport(ps.plane_display[i], p, y_angle, d, vp_x, vp_y, vp_w, vp_h); + bool all_vis = true; + for (int i = 0; i < 4; i++) if (screen_p[i].x < -5000) all_vis = false; + if (all_vis) { + SDL_FColor pcol = {1.0f, 0.7f, 0.24f, 0.15f}; + SDL_Vertex sv[4]; + for (int i = 0; i < 4; i++) { + sv[i].position = {screen_p[i].x, screen_p[i].y}; + sv[i].color = pcol; + } + int idx[6] = {0, 1, 2, 0, 2, 3}; + SDL_RenderGeometry(r, nullptr, sv, 4, idx, 6); + } + } + + if (stage >= 1) { + Vec3 cam = {ps.pipe_cam_x, ps.pipe_cam_y, ps.pipe_cam_z}; + Vec3 cam_target = {0, 0, 0}; + + + float aspect = (float)ctx.window_width / std::max((float)ctx.window_height, 1.0f); + draw_frustum_3d(r, cam, cam_target, + ps.pipe_cam_fov, aspect, ps.pipe_cam_near, ps.pipe_cam_far, + p, y_angle, d, vp_x, vp_y, vp_w, vp_h); + + + Vec3 sp = project_to_viewport(cam, p, y_angle, d, vp_x, vp_y, vp_w, vp_h); + if (sp.x > -5000) { + SDL_SetRenderDrawColor(r, 255, 255, 100, 255); + SDL_FRect cam_dot = {sp.x - 5, sp.y - 5, 10, 10}; + SDL_RenderFillRect(r, &cam_dot); + draw_text(r, sp.x + 8, sp.y - 4, "CAM"); + + + SDL_SetRenderDrawColor(r, 255, 255, 100, 100); + draw_line_3d(r, cam, cam_target, p, y_angle, d, vp_x, vp_y, vp_w, vp_h); + } + } + + // Matrix display - anchored to pipeline viewport right edge + float mat_x = vp_x + vp_w - 270; + float mat_y = vp_y + 10; + bool bd = ps.show_matrix_breakdown; + if (stage == 1) draw_matrix(r, ps.model_matrix, mat_x, mat_y, "Model Matrix:", bd); + if (stage == 2) draw_matrix(r, ps.view_matrix, mat_x, mat_y, "View Matrix:", bd); + if (stage == 3) draw_matrix(r, ps.proj_matrix, mat_x, mat_y, "Projection Matrix:", bd); + + + if (stage == 2) { + draw_near_plane_3d(r, ps, p, y_angle, d, vp_x, vp_y, vp_w, vp_h); + } + + + if (stage == 3) { + draw_clip_status_3d(r, ps, p, y_angle, d, vp_x, vp_y, vp_w, vp_h); + if (ps.show_clipping) { + draw_clipped_geometry_3d(r, ps, p, y_angle, d, vp_x, vp_y, vp_w, vp_h); + } + } + + + } else if (render_stage == 4) { + float ndc_size = std::min(vp_w, vp_h) * 0.38f; + float cx = vp_x + vp_w * 0.5f; + float cy = vp_y + vp_h * 0.5f; + + + SDL_SetRenderDrawColor(r, 55, 55, 75, 255); + SDL_FRect ndc_box = {cx - ndc_size, cy - ndc_size, ndc_size * 2, ndc_size * 2}; + SDL_RenderRect(r, &ndc_box); + + + SDL_SetRenderDrawColor(r, 45, 45, 60, 255); + SDL_RenderLine(r, cx - ndc_size, cy, cx + ndc_size, cy); + SDL_RenderLine(r, cx, cy - ndc_size, cx, cy + ndc_size); + + + SDL_SetRenderDrawColor(r, 80, 80, 100, 255); + draw_text(r, cx - ndc_size - 24, cy - 4, "-1"); + draw_text(r, cx + ndc_size + 6, cy - 4, "+1"); + draw_text(r, cx - 8, cy - ndc_size - 14, "+1"); + draw_text(r, cx - 8, cy + ndc_size + 4, "-1"); + draw_text(r, cx + 4, cy + 4, "0"); + + + draw_faces_2d(r, ps.display_verts, + cx - ndc_size, cy - ndc_size, ndc_size * 2, ndc_size * 2, + -1.0f, 1.0f, face_alpha); + + + draw_wireframe_2d(r, ps.display_verts, + cx - ndc_size, cy - ndc_size, ndc_size * 2, ndc_size * 2, + -1.0f, 1.0f); + + + if (ps.show_triangle) { + draw_tri_face_2d(r, ps.tri_display, + cx - ndc_size, cy - ndc_size, ndc_size * 2, ndc_size * 2, + -1.0f, 1.0f, face_alpha); + draw_tri_wireframe_2d(r, ps.tri_display, + cx - ndc_size, cy - ndc_size, ndc_size * 2, ndc_size * 2, + -1.0f, 1.0f); + } + + + } else { + float screen_w = vp_w * 0.7f; + float screen_h = vp_h * 0.7f; + float sx = vp_x + (vp_w - screen_w) * 0.5f; + float sy = vp_y + (vp_h - screen_h) * 0.5f; + + + SDL_SetRenderDrawColor(r, 38, 38, 52, 255); + SDL_FRect screen_bg = {sx, sy, screen_w, screen_h}; + SDL_RenderFillRect(r, &screen_bg); + SDL_SetRenderDrawColor(r, 75, 75, 95, 255); + SDL_RenderRect(r, &screen_bg); + + + SDL_SetRenderDrawColor(r, 80, 80, 100, 255); + draw_text(r, sx + 2, sy + 2, "(0, 0)"); + char buf[48]; + std::snprintf(buf, sizeof(buf), "(%d, %d)", ctx.window_width, ctx.window_height); + draw_text(r, sx + screen_w - (float)std::strlen(buf) * 8 - 4, sy + screen_h - 14, buf); + + + draw_raster_grid(r, ps, w, h, sx, sy, screen_w, screen_h); + + + draw_faces_screen(r, ps.display_verts, w, h, sx, sy, screen_w, screen_h, face_alpha); + + + for (int i = 0; i < NUM_CUBE_EDGES; i++) { + int a = cube_edges[i][0], b = cube_edges[i][1]; + Vec3 va = ps.display_verts[a]; + Vec3 vb = ps.display_verts[b]; + + float ax = sx + (va.x / w) * screen_w; + float ay = sy + (va.y / h) * screen_h; + float bx = sx + (vb.x / w) * screen_w; + float by = sy + (vb.y / h) * screen_h; + + uint8_t cr = (uint8_t)((vert_colors[a].r + vert_colors[b].r) / 2); + uint8_t cg = (uint8_t)((vert_colors[a].g + vert_colors[b].g) / 2); + uint8_t cb = (uint8_t)((vert_colors[a].b + vert_colors[b].b) / 2); + SDL_SetRenderDrawColor(r, cr, cg, cb, alpha); + SDL_RenderLine(r, ax, ay, bx, by); + } + + + for (int i = 0; i < NUM_CUBE_VERTS; i++) { + float vx = sx + (ps.display_verts[i].x / w) * screen_w; + float vy = sy + (ps.display_verts[i].y / h) * screen_h; + SDL_SetRenderDrawColor(r, vert_colors[i].r, vert_colors[i].g, vert_colors[i].b, alpha); + SDL_FRect dot = {vx - 3, vy - 3, 6, 6}; + SDL_RenderFillRect(r, &dot); + } + + + if (ps.show_triangle) { + + SDL_FColor tcol = {0.85f, 0.9f, 1.0f, face_alpha / 255.0f}; + SDL_Vertex tri_sv[3]; + for (int i = 0; i < 3; i++) { + tri_sv[i].position = {sx + (ps.tri_display[i].x / w) * screen_w, + sy + (ps.tri_display[i].y / h) * screen_h}; + tri_sv[i].color = tcol; + } + int tri_idx[3] = {0, 1, 2}; + SDL_RenderGeometry(r, nullptr, tri_sv, 3, tri_idx, 3); + + + for (int i = 0; i < NUM_TRI_EDGES; i++) { + int a = tri_edges[i][0], b = tri_edges[i][1]; + uint8_t cr = (uint8_t)((tri_colors[a].r + tri_colors[b].r) / 2); + uint8_t cg = (uint8_t)((tri_colors[a].g + tri_colors[b].g) / 2); + uint8_t cb = (uint8_t)((tri_colors[a].b + tri_colors[b].b) / 2); + SDL_SetRenderDrawColor(r, cr, cg, cb, alpha); + SDL_RenderLine(r, tri_sv[a].position.x, tri_sv[a].position.y, + tri_sv[b].position.x, tri_sv[b].position.y); + } + + for (int i = 0; i < NUM_TRI_VERTS; i++) { + SDL_SetRenderDrawColor(r, tri_colors[i].r, tri_colors[i].g, tri_colors[i].b, alpha); + SDL_FRect dot = {tri_sv[i].position.x - 3, tri_sv[i].position.y - 3, 6, 6}; + SDL_RenderFillRect(r, &dot); + } + } + } + + + if (ps.selected_vertex >= 0) { + int vi = ps.selected_vertex; + float time = (float)SDL_GetTicks() / 1000.0f; + float sx_sel = 0, sy_sel = 0; + bool visible = true; + + if (is_3d_stage(render_stage)) { + Vec3 sp = project_to_viewport(ps.display_verts[vi], + ps.cam_orbit_pitch, ps.cam_orbit_yaw, ps.cam_distance, + vp_x, vp_y, vp_w, vp_h); + if (sp.x < -5000) visible = false; + sx_sel = sp.x; + sy_sel = sp.y; + } else if (render_stage == 4) { + float ndc_size = std::min(vp_w, vp_h) * 0.38f; + float cx = vp_x + vp_w * 0.5f; + float cy = vp_y + vp_h * 0.5f; + Vec3 v = ps.display_verts[vi]; + sx_sel = cx - ndc_size + ((v.x + 1.0f) / 2.0f) * ndc_size * 2; + sy_sel = cy - ndc_size + (1.0f - (v.y + 1.0f) / 2.0f) * ndc_size * 2; + } else { + float screen_w = vp_w * 0.7f; + float screen_h = vp_h * 0.7f; + float sx = vp_x + (vp_w - screen_w) * 0.5f; + float sy = vp_y + (vp_h - screen_h) * 0.5f; + sx_sel = sx + (ps.display_verts[vi].x / w) * screen_w; + sy_sel = sy + (ps.display_verts[vi].y / h) * screen_h; + } + + if (visible) { + draw_vertex_highlight(r, sx_sel, sy_sel, vert_colors[vi], time); + } + + // Vertex sidebar - right edge, anchored just above vertex coordinates panel + float sidebar_h = 130; + float sidebar_y = vp_y + vp_h - 130 - sidebar_h - 10; // 130=vert coords height, 10=gap + draw_vertex_sidebar(r, ps, vi, stage, vp_x + vp_w - 250, sidebar_y, 240); + } + + // First-person camera split view + if (ps.show_first_person) { + draw_first_person_view(r, ps, cam_vp_x, vp_y, cam_vp_w, vp_h); + } + + + draw_stage_indicator(r, ps.current_stage, w, h); + + + SDL_SetRenderDrawColor(r, 70, 70, 90, 255); + draw_text(r, 20, h - 16, + "Left/Right: Stage WASD: Move Drag: Orbit RClick: Select Space: Auto-rotate H: Help"); + + + draw_info_panel(r, ps, stage, vp_x + 8, vp_y + 10); + + + if (ps.show_sliders) { + draw_slider_panel(r, ps, vp_y); + } + + + { + char buf[64]; + float px = 20; + float py = vp_y + vp_h - 44; + SDL_SetRenderDrawColor(r, 90, 90, 110, 255); + std::snprintf(buf, sizeof(buf), "Object: pos(%.1f, %.1f, %.1f) rot(%.0f deg)", + ps.obj_pos_x, ps.obj_pos_y, ps.obj_pos_z, + ps.obj_rot_y * 180.0f / PI); + draw_text(r, px, py, buf); + std::snprintf(buf, sizeof(buf), "Camera: pos(%.1f, %.1f, %.1f) fov(%.0f deg)", + ps.pipe_cam_x, ps.pipe_cam_y, ps.pipe_cam_z, + ps.pipe_cam_fov * 180.0f / PI); + draw_text(r, px, py + 14, buf); + } + + // Vertex coordinates - anchored to pipeline viewport right edge + { + float panel_x = vp_x + vp_w - 225; + float panel_y = vp_y + vp_h - 155; + SDL_SetRenderDrawColor(r, 110, 110, 130, 255); + draw_text(r, panel_x, panel_y, "Vertex Coordinates:", 1.2f); + + for (int i = 0; i < NUM_CUBE_VERTS; i++) { + char buf[80]; + if (stage == 3) { + Vec4 c = ps.verts_clip[i]; + std::snprintf(buf, sizeof(buf), "(%5.1f,%5.1f,%5.1f, w=%4.1f)", + c.x, c.y, c.z, c.w); + } else { + Vec3 v; + switch (stage) { + case 0: v = ps.verts_object[i].xyz(); break; + case 1: v = ps.verts_world[i].xyz(); break; + case 2: v = ps.verts_view[i].xyz(); break; + case 4: v = ps.verts_ndc[i]; break; + case 5: v = ps.verts_screen[i]; break; + default: v = {}; break; + } + if (stage == 5) { + std::snprintf(buf, sizeof(buf), "(%6.0f, %6.0f)", v.x, v.y); + } else { + std::snprintf(buf, sizeof(buf), "(%5.2f, %5.2f, %5.2f)", v.x, v.y, v.z); + } + } + SDL_SetRenderDrawColor(r, vert_colors[i].r, vert_colors[i].g, + vert_colors[i].b, 220); + draw_text(r, panel_x, panel_y + 18 + i * 15, buf, 1.2f); + } + } + + // Help hint - top right corner of window + if (!ps.show_help) { + float hw = 136, hh = 18; + float hx = w - hw - 2, hy = 2; + SDL_SetRenderDrawColor(r, 50, 50, 65, 140); + SDL_FRect help_bg = {hx - 4, hy - 2, hw, hh}; + SDL_RenderFillRect(r, &help_bg); + SDL_SetRenderDrawColor(r, 70, 70, 90, 160); + SDL_RenderRect(r, &help_bg); + SDL_SetRenderDrawColor(r, 100, 100, 120, 200); + draw_text(r, hx, hy + 1, "Press H for help"); + } + + // Help overlay (drawn last, on top of everything) + if (ps.show_help) { + draw_help_overlay(r, ps, w, h); + } +} diff --git a/src/systems.cpp b/src/systems.cpp new file mode 100644 index 0000000..ba95188 --- /dev/null +++ b/src/systems.cpp @@ -0,0 +1,72 @@ +/** + * @file systems.cpp + * @brief ECS System implementations using FLECS C++ API + */ + +#include "systems.hpp" +#include "components.hpp" +#include "game.hpp" + +void register_systems(flecs::world &ecs, GameContext *ctx) { + // Movement system - updates positions based on velocities + ecs.system("MovementSystem") + .kind(flecs::OnUpdate) + .each([](flecs::iter &it, size_t, Position &pos, const Velocity &vel) { + pos.x += vel.x * it.delta_time(); + pos.y += vel.y * it.delta_time(); + }); + + // Lifetime system - decrements lifetime and destroys expired entities + ecs.system("LifetimeSystem") + .kind(flecs::OnUpdate) + .each([](flecs::iter &it, size_t i, Lifetime &life) { + life.remaining -= it.delta_time(); + if (life.remaining <= 0.0f) { + it.entity(i).add(); + } + }); + + // Cleanup system - destroys entities marked with ToDestroy + ecs.system("CleanupSystem") + .kind(flecs::PostUpdate) + .each([](flecs::entity e, const ToDestroy &) { e.destruct(); }); + + // Render rect system + ecs.system("RenderRectSystem") + .kind(flecs::OnStore) + .ctx(ctx) + .each([](flecs::iter &it, size_t, const Position &pos, + const RectShape &rect, const Color &color) { + auto *game_ctx = static_cast(it.ctx()); + if (!game_ctx || !game_ctx->renderer) + return; + + SDL_SetRenderDrawColor(game_ctx->renderer, color.r, color.g, color.b, + color.a); + + SDL_FRect dst{pos.x - rect.width / 2.0f, pos.y - rect.height / 2.0f, + rect.width, rect.height}; + + SDL_RenderFillRect(game_ctx->renderer, &dst); + }); + + // Render sprite system + ecs.system("RenderSpriteSystem") + .kind(flecs::OnStore) + .ctx(ctx) + .each([](flecs::iter &it, size_t, const Position &pos, + const Sprite &sprite) { + auto *game_ctx = static_cast(it.ctx()); + if (!game_ctx || !game_ctx->renderer) + return; + if (!sprite.visible || !sprite.texture) + return; + + SDL_FRect dst{pos.x - sprite.src_rect.w / 2.0f, + pos.y - sprite.src_rect.h / 2.0f, sprite.src_rect.w, + sprite.src_rect.h}; + + SDL_RenderTexture(game_ctx->renderer, sprite.texture, &sprite.src_rect, + &dst); + }); +} diff --git a/web/shell.html b/web/shell.html new file mode 100644 index 0000000..0b54b25 --- /dev/null +++ b/web/shell.html @@ -0,0 +1,68 @@ + + + + + +Render Pipeline Visualizer + + + + +
Loading...
+ + + + {{{ SCRIPT }}} + +