From bf4b3a5c35152c1292757134123b3363d0f81bf6 Mon Sep 17 00:00:00 2001 From: Matt Kosarek Date: Mon, 29 Dec 2025 09:34:00 -0500 Subject: Renamed PascalCase files to snake_case --- themes/dist/output.js | 929 +++++++++++++++-------------- themes/dist/output.wasm | Bin 171370 -> 159329 bytes themes/src/Logger.cpp | 123 ---- themes/src/Logger.h | 43 -- themes/src/MainLoop.cpp | 31 - themes/src/MainLoop.h | 29 - themes/src/Renderer2d.cpp | 126 ---- themes/src/Renderer2d.h | 61 -- themes/src/Renderer3d.cpp | 239 -------- themes/src/Renderer3d.h | 56 -- themes/src/Shader.cpp | 61 -- themes/src/Shader.h | 64 -- themes/src/WebglContext.cpp | 46 -- themes/src/WebglContext.h | 18 - themes/src/autumn/AutumnTheme.cpp | 73 --- themes/src/autumn/AutumnTheme.hpp | 33 - themes/src/autumn/LeafParticleRender.cpp | 166 ------ themes/src/autumn/LeafParticleRender.h | 58 -- themes/src/autumn/TreeShape.cpp | 214 ------- themes/src/autumn/TreeShape.h | 74 --- themes/src/autumn/autumn_theme.cpp | 73 +++ themes/src/autumn/autumn_theme.hpp | 33 + themes/src/autumn/leaf_particle_render.cpp | 166 ++++++ themes/src/autumn/leaf_particle_render.h | 58 ++ themes/src/autumn/tree_shape.cpp | 214 +++++++ themes/src/autumn/tree_shape.h | 74 +++ themes/src/list.h | 2 +- themes/src/logger.cpp | 123 ++++ themes/src/logger.h | 43 ++ themes/src/main.cpp | 14 +- themes/src/main_loop.cpp | 31 + themes/src/main_loop.h | 29 + themes/src/renderer_2d.cpp | 126 ++++ themes/src/renderer_2d.h | 61 ++ themes/src/renderer_3d.cpp | 239 ++++++++ themes/src/renderer_3d.h | 56 ++ themes/src/shader.cpp | 61 ++ themes/src/shader.h | 64 ++ themes/src/shapes_2d.cpp | 2 +- themes/src/shapes_2d.h | 2 +- themes/src/spring/GrassRenderer.cpp | 29 - themes/src/spring/GrassRenderer.hpp | 33 - themes/src/spring/SpringTheme.cpp | 208 ------- themes/src/spring/SpringTheme.hpp | 45 -- themes/src/spring/grass_renderer.cpp | 29 + themes/src/spring/grass_renderer.hpp | 33 + themes/src/spring/spring_theme.cpp | 208 +++++++ themes/src/spring/spring_theme.hpp | 45 ++ themes/src/summer/SummerTheme.cpp | 82 --- themes/src/summer/SummerTheme.h | 29 - themes/src/summer/summer_theme.cpp | 82 +++ themes/src/summer/summer_theme.h | 29 + themes/src/webgl_context.cpp | 46 ++ themes/src/webgl_context.h | 18 + themes/src/winter/Snowflake.cpp | 189 ------ themes/src/winter/Snowflake.h | 48 -- themes/src/winter/Windfield.cpp | 28 - themes/src/winter/Windfield.hpp | 39 -- themes/src/winter/WinterTheme.cpp | 32 - themes/src/winter/WinterTheme.hpp | 25 - themes/src/winter/snowflake.cpp | 189 ++++++ themes/src/winter/snowflake.h | 48 ++ themes/src/winter/windfield.cpp | 28 + themes/src/winter/windfield.hpp | 39 ++ themes/src/winter/winter_theme.cpp | 32 + themes/src/winter/winter_theme.hpp | 25 + 66 files changed, 2803 insertions(+), 2750 deletions(-) delete mode 100644 themes/src/Logger.cpp delete mode 100644 themes/src/Logger.h delete mode 100644 themes/src/MainLoop.cpp delete mode 100644 themes/src/MainLoop.h delete mode 100644 themes/src/Renderer2d.cpp delete mode 100644 themes/src/Renderer2d.h delete mode 100644 themes/src/Renderer3d.cpp delete mode 100644 themes/src/Renderer3d.h delete mode 100644 themes/src/Shader.cpp delete mode 100644 themes/src/Shader.h delete mode 100644 themes/src/WebglContext.cpp delete mode 100644 themes/src/WebglContext.h delete mode 100644 themes/src/autumn/AutumnTheme.cpp delete mode 100644 themes/src/autumn/AutumnTheme.hpp delete mode 100644 themes/src/autumn/LeafParticleRender.cpp delete mode 100644 themes/src/autumn/LeafParticleRender.h delete mode 100644 themes/src/autumn/TreeShape.cpp delete mode 100644 themes/src/autumn/TreeShape.h create mode 100644 themes/src/autumn/autumn_theme.cpp create mode 100644 themes/src/autumn/autumn_theme.hpp create mode 100644 themes/src/autumn/leaf_particle_render.cpp create mode 100644 themes/src/autumn/leaf_particle_render.h create mode 100644 themes/src/autumn/tree_shape.cpp create mode 100644 themes/src/autumn/tree_shape.h create mode 100644 themes/src/logger.cpp create mode 100644 themes/src/logger.h create mode 100644 themes/src/main_loop.cpp create mode 100644 themes/src/main_loop.h create mode 100644 themes/src/renderer_2d.cpp create mode 100644 themes/src/renderer_2d.h create mode 100644 themes/src/renderer_3d.cpp create mode 100644 themes/src/renderer_3d.h create mode 100644 themes/src/shader.cpp create mode 100644 themes/src/shader.h delete mode 100644 themes/src/spring/GrassRenderer.cpp delete mode 100644 themes/src/spring/GrassRenderer.hpp delete mode 100644 themes/src/spring/SpringTheme.cpp delete mode 100644 themes/src/spring/SpringTheme.hpp create mode 100644 themes/src/spring/grass_renderer.cpp create mode 100644 themes/src/spring/grass_renderer.hpp create mode 100644 themes/src/spring/spring_theme.cpp create mode 100644 themes/src/spring/spring_theme.hpp delete mode 100644 themes/src/summer/SummerTheme.cpp delete mode 100644 themes/src/summer/SummerTheme.h create mode 100644 themes/src/summer/summer_theme.cpp create mode 100644 themes/src/summer/summer_theme.h create mode 100644 themes/src/webgl_context.cpp create mode 100644 themes/src/webgl_context.h delete mode 100644 themes/src/winter/Snowflake.cpp delete mode 100644 themes/src/winter/Snowflake.h delete mode 100644 themes/src/winter/Windfield.cpp delete mode 100644 themes/src/winter/Windfield.hpp delete mode 100644 themes/src/winter/WinterTheme.cpp delete mode 100644 themes/src/winter/WinterTheme.hpp create mode 100644 themes/src/winter/snowflake.cpp create mode 100644 themes/src/winter/snowflake.h create mode 100644 themes/src/winter/windfield.cpp create mode 100644 themes/src/winter/windfield.hpp create mode 100644 themes/src/winter/winter_theme.cpp create mode 100644 themes/src/winter/winter_theme.hpp diff --git a/themes/dist/output.js b/themes/dist/output.js index 9ff02be..de4c0ed 100644 --- a/themes/dist/output.js +++ b/themes/dist/output.js @@ -19,13 +19,17 @@ var Module = typeof Module != 'undefined' ? Module : {}; // Attempt to auto-detect the environment var ENVIRONMENT_IS_WEB = typeof window == 'object'; -var ENVIRONMENT_IS_WORKER = typeof WorkerGlobalScope != 'undefined'; +var ENVIRONMENT_IS_WORKER = typeof importScripts == 'function'; // N.b. Electron.js environment is simultaneously a NODE-environment, but // also a web environment. var ENVIRONMENT_IS_NODE = typeof process == 'object' && typeof process.versions == 'object' && typeof process.versions.node == 'string' && process.type != 'renderer'; var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; if (ENVIRONMENT_IS_NODE) { + // `require()` is no-op in an ESM module, use `createRequire()` to construct + // the require()` function. This is only necessary for multi-environment + // builds, `-sENVIRONMENT=node` emits a static import declaration instead. + // TODO: Swap all `require()`'s with `import()`'s? } @@ -78,19 +82,23 @@ if (ENVIRONMENT_IS_NODE) { // include: node_shell_read.js readBinary = (filename) => { - // We need to re-wrap `file://` strings to URLs. - filename = isFileURI(filename) ? new URL(filename) : filename; + // We need to re-wrap `file://` strings to URLs. Normalizing isn't + // necessary in that case, the path should already be absolute. + filename = isFileURI(filename) ? new URL(filename) : nodePath.normalize(filename); var ret = fs.readFileSync(filename); - assert(Buffer.isBuffer(ret)); + assert(ret.buffer); return ret; }; -readAsync = async (filename, binary = true) => { +readAsync = (filename, binary = true) => { // See the comment in the `readBinary` function. - filename = isFileURI(filename) ? new URL(filename) : filename; - var ret = fs.readFileSync(filename, binary ? undefined : 'utf8'); - assert(binary ? Buffer.isBuffer(ret) : typeof ret == 'string'); - return ret; + filename = isFileURI(filename) ? new URL(filename) : nodePath.normalize(filename); + return new Promise((resolve, reject) => { + fs.readFile(filename, binary ? undefined : 'utf8', (err, data) => { + if (err) reject(err); + else resolve(binary ? data.buffer : data); + }); + }); }; // end include: node_shell_read.js if (!Module['thisProgram'] && process.argv.length > 1) { @@ -111,7 +119,7 @@ readAsync = async (filename, binary = true) => { } else if (ENVIRONMENT_IS_SHELL) { - if ((typeof process == 'object' && typeof require === 'function') || typeof window == 'object' || typeof WorkerGlobalScope != 'undefined') throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + if ((typeof process == 'object' && typeof require === 'function') || typeof window == 'object' || typeof importScripts == 'function') throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); } else @@ -133,10 +141,10 @@ if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { if (scriptDirectory.startsWith('blob:')) { scriptDirectory = ''; } else { - scriptDirectory = scriptDirectory.slice(0, scriptDirectory.replace(/[?#].*/, '').lastIndexOf('/')+1); + scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, '').lastIndexOf('/')+1); } - if (!(typeof window == 'object' || typeof WorkerGlobalScope != 'undefined')) throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + if (!(typeof window == 'object' || typeof importScripts == 'function')) throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); { // include: web_or_worker_shell_read.js @@ -150,7 +158,7 @@ if (ENVIRONMENT_IS_WORKER) { }; } - readAsync = async (url) => { + readAsync = (url) => { // Fetch has some additional restrictions over XHR, like it can't be used on a file:// url. // See https://github.com/github/fetch/pull/92#issuecomment-140665932 // Cordova or Electron apps are typically loaded from a file:// url. @@ -171,11 +179,13 @@ if (ENVIRONMENT_IS_WORKER) { xhr.send(null); }); } - var response = await fetch(url, { credentials: 'same-origin' }); - if (response.ok) { - return response.arrayBuffer(); - } - throw new Error(response.status + ' : ' + response.url); + return fetch(url, { credentials: 'same-origin' }) + .then((response) => { + if (response.ok) { + return response.arrayBuffer(); + } + return Promise.reject(new Error(response.status + ' : ' + response.url)); + }) }; // end include: web_or_worker_shell_read.js } @@ -297,24 +307,32 @@ var HEAP, HEAPU32, /** @type {!Float32Array} */ HEAPF32, -/* BigInt64Array type is not correctly defined in closure -/** not-@type {!BigInt64Array} */ - HEAP64, -/* BigUint64Array type is not correctly defined in closure -/** not-t@type {!BigUint64Array} */ - HEAPU64, /** @type {!Float64Array} */ HEAPF64; -var runtimeInitialized = false; +// include: runtime_shared.js +function updateMemoryViews() { + var b = wasmMemory.buffer; + Module['HEAP8'] = HEAP8 = new Int8Array(b); + Module['HEAP16'] = HEAP16 = new Int16Array(b); + Module['HEAPU8'] = HEAPU8 = new Uint8Array(b); + Module['HEAPU16'] = HEAPU16 = new Uint16Array(b); + Module['HEAP32'] = HEAP32 = new Int32Array(b); + Module['HEAPU32'] = HEAPU32 = new Uint32Array(b); + Module['HEAPF32'] = HEAPF32 = new Float32Array(b); + Module['HEAPF64'] = HEAPF64 = new Float64Array(b); +} -/** - * Indicates whether filename is delivered via file protocol (as opposed to http/https) - * @noinline - */ -var isFileURI = (filename) => filename.startsWith('file://'); +// end include: runtime_shared.js +assert(!Module['STACK_SIZE'], 'STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time') + +assert(typeof Int32Array != 'undefined' && typeof Float64Array !== 'undefined' && Int32Array.prototype.subarray != undefined && Int32Array.prototype.set != undefined, + 'JS engine does not provide full typed array support'); + +// If memory is defined in wasm, the user can't provide it, or set INITIAL_MEMORY +assert(!Module['wasmMemory'], 'Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally'); +assert(!Module['INITIAL_MEMORY'], 'Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically'); -// include: runtime_shared.js // include: runtime_stack_check.js // Initializes the stack cookie. Called at the startup of main and at the startup of each thread in pthreads mode. function writeStackCookie() { @@ -353,194 +371,84 @@ function checkStackCookie() { } } // end include: runtime_stack_check.js -// include: runtime_exceptions.js -// end include: runtime_exceptions.js -// include: runtime_debug.js -// Endianness check -(() => { - var h16 = new Int16Array(1); - var h8 = new Int8Array(h16.buffer); - h16[0] = 0x6373; - if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)'; -})(); - -if (Module['ENVIRONMENT']) { - throw new Error('Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)'); -} +var __ATPRERUN__ = []; // functions called before the runtime is initialized +var __ATINIT__ = []; // functions called during startup +var __ATMAIN__ = []; // functions called when main() is to be run +var __ATEXIT__ = []; // functions called during shutdown +var __ATPOSTRUN__ = []; // functions called after the main() is called -function legacyModuleProp(prop, newName, incoming=true) { - if (!Object.getOwnPropertyDescriptor(Module, prop)) { - Object.defineProperty(Module, prop, { - configurable: true, - get() { - let extra = incoming ? ' (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)' : ''; - abort(`\`Module.${prop}\` has been replaced by \`${newName}\`` + extra); +var runtimeInitialized = false; - } - }); +function preRun() { + var preRuns = Module['preRun']; + if (preRuns) { + if (typeof preRuns == 'function') preRuns = [preRuns]; + preRuns.forEach(addOnPreRun); } + callRuntimeCallbacks(__ATPRERUN__); } -function ignoredModuleProp(prop) { - if (Object.getOwnPropertyDescriptor(Module, prop)) { - abort(`\`Module.${prop}\` was supplied but \`${prop}\` not included in INCOMING_MODULE_JS_API`); - } -} +function initRuntime() { + assert(!runtimeInitialized); + runtimeInitialized = true; -// forcing the filesystem exports a few things by default -function isExportedByForceFilesystem(name) { - return name === 'FS_createPath' || - name === 'FS_createDataFile' || - name === 'FS_createPreloadedFile' || - name === 'FS_unlink' || - name === 'addRunDependency' || - // The old FS has some functionality that WasmFS lacks. - name === 'FS_createLazyFile' || - name === 'FS_createDevice' || - name === 'removeRunDependency'; -} + checkStackCookie(); -/** - * Intercept access to a global symbol. This enables us to give informative - * warnings/errors when folks attempt to use symbols they did not include in - * their build, or no symbols that no longer exist. - */ -function hookGlobalSymbolAccess(sym, func) { - if (typeof globalThis != 'undefined' && !Object.getOwnPropertyDescriptor(globalThis, sym)) { - Object.defineProperty(globalThis, sym, { - configurable: true, - get() { - func(); - return undefined; - } - }); - } + + callRuntimeCallbacks(__ATINIT__); } -function missingGlobal(sym, msg) { - hookGlobalSymbolAccess(sym, () => { - warnOnce(`\`${sym}\` is not longer defined by emscripten. ${msg}`); - }); +function preMain() { + checkStackCookie(); + + callRuntimeCallbacks(__ATMAIN__); } -missingGlobal('buffer', 'Please use HEAP8.buffer or wasmMemory.buffer'); -missingGlobal('asm', 'Please use wasmExports instead'); +function postRun() { + checkStackCookie(); -function missingLibrarySymbol(sym) { - hookGlobalSymbolAccess(sym, () => { - // Can't `abort()` here because it would break code that does runtime - // checks. e.g. `if (typeof SDL === 'undefined')`. - var msg = `\`${sym}\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`; - // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in - // library.js, which means $name for a JS name with no prefix, or name - // for a JS name like _name. - var librarySymbol = sym; - if (!librarySymbol.startsWith('_')) { - librarySymbol = '$' + sym; - } - msg += ` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${librarySymbol}')`; - if (isExportedByForceFilesystem(sym)) { - msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; - } - warnOnce(msg); - }); + var postRuns = Module['postRun']; + if (postRuns) { + if (typeof postRuns == 'function') postRuns = [postRuns]; + postRuns.forEach(addOnPostRun); + } - // Any symbol that is not included from the JS library is also (by definition) - // not exported on the Module object. - unexportedRuntimeSymbol(sym); + callRuntimeCallbacks(__ATPOSTRUN__); } -function unexportedRuntimeSymbol(sym) { - if (!Object.getOwnPropertyDescriptor(Module, sym)) { - Object.defineProperty(Module, sym, { - configurable: true, - get() { - var msg = `'${sym}' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the Emscripten FAQ)`; - if (isExportedByForceFilesystem(sym)) { - msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; - } - abort(msg); - } - }); - } +function addOnPreRun(cb) { + __ATPRERUN__.unshift(cb); } -// Used by XXXXX_DEBUG settings to output debug messages. -function dbg(...args) { - // TODO(sbc): Make this configurable somehow. Its not always convenient for - // logging to show up as warnings. - console.warn(...args); +function addOnInit(cb) { + __ATINIT__.unshift(cb); } -// end include: runtime_debug.js -// include: memoryprofiler.js -// end include: memoryprofiler.js - -function updateMemoryViews() { - var b = wasmMemory.buffer; - Module['HEAP8'] = HEAP8 = new Int8Array(b); - Module['HEAP16'] = HEAP16 = new Int16Array(b); - Module['HEAPU8'] = HEAPU8 = new Uint8Array(b); - Module['HEAPU16'] = HEAPU16 = new Uint16Array(b); - Module['HEAP32'] = HEAP32 = new Int32Array(b); - Module['HEAPU32'] = HEAPU32 = new Uint32Array(b); - Module['HEAPF32'] = HEAPF32 = new Float32Array(b); - Module['HEAPF64'] = HEAPF64 = new Float64Array(b); - Module['HEAP64'] = HEAP64 = new BigInt64Array(b); - Module['HEAPU64'] = HEAPU64 = new BigUint64Array(b); +function addOnPreMain(cb) { + __ATMAIN__.unshift(cb); } -// end include: runtime_shared.js -assert(!Module['STACK_SIZE'], 'STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time') - -assert(typeof Int32Array != 'undefined' && typeof Float64Array !== 'undefined' && Int32Array.prototype.subarray != undefined && Int32Array.prototype.set != undefined, - 'JS engine does not provide full typed array support'); - -// If memory is defined in wasm, the user can't provide it, or set INITIAL_MEMORY -assert(!Module['wasmMemory'], 'Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally'); -assert(!Module['INITIAL_MEMORY'], 'Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically'); - -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 addOnExit(cb) { } -function initRuntime() { - assert(!runtimeInitialized); - runtimeInitialized = true; - - checkStackCookie(); - - - - wasmExports['__wasm_call_ctors'](); - - +function addOnPostRun(cb) { + __ATPOSTRUN__.unshift(cb); } -function preMain() { - checkStackCookie(); - -} +// include: runtime_math.js +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul -function postRun() { - checkStackCookie(); +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround - if (Module['postRun']) { - if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']]; - while (Module['postRun'].length) { - addOnPostRun(Module['postRun'].shift()); - } - } +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32 - callRuntimeCallbacks(onPostRuns); -} +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc +assert(Math.imul, 'This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); +assert(Math.fround, 'This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); +assert(Math.clz32, 'This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); +assert(Math.trunc, 'This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); +// end include: runtime_math.js // A counter of dependencies for calling run(). If we need to // do asynchronous work before running, increment this and // decrement it. Incrementing must happen in a place like @@ -549,9 +457,9 @@ function postRun() { // it happens right before run - run will be postponed until // the dependencies are met. var runDependencies = 0; +var runDependencyWatcher = null; var dependenciesFulfilled = null; // overridden to take different actions when all run dependencies are fulfilled var runDependencyTracking = {}; -var runDependencyWatcher = null; function getUniqueRunDependency(id) { var orig = id; @@ -652,6 +560,8 @@ function abort(what) { throw e; } +// include: memoryprofiler.js +// end include: memoryprofiler.js // show errors on likely calls to FS when it was not included var FS = { error() { @@ -671,6 +581,22 @@ var FS = { Module['FS_createDataFile'] = FS.createDataFile; Module['FS_createPreloadedFile'] = FS.createPreloadedFile; +// include: URIUtils.js +// Prefix of data URIs emitted by SINGLE_FILE and related options. +var dataURIPrefix = 'data:application/octet-stream;base64,'; + +/** + * Indicates whether filename is a base64 data URI. + * @noinline + */ +var isDataURI = (filename) => filename.startsWith(dataURIPrefix); + +/** + * Indicates whether filename is delivered via file protocol (as opposed to http/https) + * @noinline + */ +var isFileURI = (filename) => filename.startsWith('file://'); +// end include: URIUtils.js function createExportWrapper(name, nargs) { return (...args) => { assert(runtimeInitialized, `native function \`${name}\` called before runtime initialization`); @@ -682,11 +608,18 @@ function createExportWrapper(name, nargs) { }; } -var wasmBinaryFile; +// include: runtime_exceptions.js +// end include: runtime_exceptions.js function findWasmBinary() { - return locateFile('output.wasm'); + var f = 'output.wasm'; + if (!isDataURI(f)) { + return locateFile(f); + } + return f; } +var wasmBinaryFile; + function getBinarySync(file) { if (file == wasmBinaryFile && wasmBinary) { return new Uint8Array(wasmBinary); @@ -697,28 +630,26 @@ function getBinarySync(file) { throw 'both async and sync fetching of the wasm failed'; } -async function getWasmBinary(binaryFile) { +function getBinaryPromise(binaryFile) { // If we don't have the binary yet, load it asynchronously using readAsync. - if (!wasmBinary) { + if (!wasmBinary + ) { // Fetch the binary using readAsync - try { - var response = await readAsync(binaryFile); - return new Uint8Array(response); - } catch { - // Fall back to getBinarySync below; - } + return readAsync(binaryFile).then( + (response) => new Uint8Array(/** @type{!ArrayBuffer} */(response)), + // Fall back to getBinarySync if readAsync fails + () => getBinarySync(binaryFile) + ); } // Otherwise, getBinarySync should be able to get it synchronously - return getBinarySync(binaryFile); + return Promise.resolve().then(() => getBinarySync(binaryFile)); } -async function instantiateArrayBuffer(binaryFile, imports) { - try { - var binary = await getWasmBinary(binaryFile); - var instance = await WebAssembly.instantiate(binary, imports); - return instance; - } catch (reason) { +function instantiateArrayBuffer(binaryFile, imports, receiver) { + return getBinaryPromise(binaryFile).then((binary) => { + return WebAssembly.instantiate(binary, imports); + }).then(receiver, (reason) => { err(`failed to asynchronously prepare wasm: ${reason}`); // Warn on some common problems. @@ -726,34 +657,43 @@ async function instantiateArrayBuffer(binaryFile, imports) { err(`warning: Loading from a file URI (${wasmBinaryFile}) is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing`); } abort(reason); - } + }); } -async function instantiateAsync(binary, binaryFile, imports) { - if (!binary && typeof WebAssembly.instantiateStreaming == 'function' +function instantiateAsync(binary, binaryFile, imports, callback) { + if (!binary && + typeof WebAssembly.instantiateStreaming == 'function' && + !isDataURI(binaryFile) && // Don't use streaming for file:// delivered objects in a webview, fetch them synchronously. - && !isFileURI(binaryFile) + !isFileURI(binaryFile) && // Avoid instantiateStreaming() on Node.js environment for now, as while // Node.js v18.1.0 implements it, it does not have a full fetch() // implementation yet. // // Reference: // https://github.com/emscripten-core/emscripten/pull/16917 - && !ENVIRONMENT_IS_NODE - ) { - try { - var response = fetch(binaryFile, { credentials: 'same-origin' }); - var instantiationResult = await WebAssembly.instantiateStreaming(response, imports); - return instantiationResult; - } catch (reason) { - // We expect the most common failure cause to be a bad MIME type for the binary, - // in which case falling back to ArrayBuffer instantiation should work. - err(`wasm streaming compile failed: ${reason}`); - err('falling back to ArrayBuffer instantiation'); - // fall back of instantiateArrayBuffer below - }; + !ENVIRONMENT_IS_NODE && + typeof fetch == 'function') { + return fetch(binaryFile, { credentials: 'same-origin' }).then((response) => { + // Suppress closure warning here since the upstream definition for + // instantiateStreaming only allows Promise rather than + // an actual Response. + // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure is fixed. + /** @suppress {checkTypes} */ + var result = WebAssembly.instantiateStreaming(response, imports); + + return result.then( + callback, + function(reason) { + // We expect the most common failure cause to be a bad MIME type for the binary, + // in which case falling back to ArrayBuffer instantiation should work. + err(`wasm streaming compile failed: ${reason}`); + err('falling back to ArrayBuffer instantiation'); + return instantiateArrayBuffer(binaryFile, imports, callback); + }); + }); } - return instantiateArrayBuffer(binaryFile, imports); + return instantiateArrayBuffer(binaryFile, imports, callback); } function getWasmImports() { @@ -766,7 +706,8 @@ function getWasmImports() { // Create the wasm instance. // Receives the wasm imports, returns the exports. -async function createWasm() { +function createWasm() { + var info = getWasmImports(); // Load the wasm module and create an instance of using native support in the JS engine. // handle a generated wasm instance, receiving its exports and // performing other necessary setup @@ -785,6 +726,8 @@ async function createWasm() { assert(wasmTable, 'table not found in wasm exports'); + addOnInit(wasmExports['__wasm_call_ctors']); + removeRunDependency('wasm-instantiate'); return wasmExports; } @@ -803,62 +746,166 @@ async function createWasm() { trueModule = null; // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line. // When the regression is fixed, can restore the above PTHREADS-enabled path. - return receiveInstance(result['instance']); + receiveInstance(result['instance']); } - var info = getWasmImports(); + // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback + // to manually instantiate the Wasm module themselves. This allows pages to + // run the instantiation parallel to any other async startup actions they are + // performing. + // Also pthreads and wasm workers initialize the wasm instance through this + // path. + if (Module['instantiateWasm']) { + try { + return Module['instantiateWasm'](info, receiveInstance); + } catch(e) { + err(`Module.instantiateWasm callback failed with error: ${e}`); + return false; + } + } + + wasmBinaryFile ??= findWasmBinary(); + + instantiateAsync(wasmBinary, wasmBinaryFile, info, receiveInstantiationResult); + return {}; // no exports yet; we'll fill them in later +} + +// Globals used by JS i64 conversions (see makeSetValue) +var tempDouble; +var tempI64; + +// include: runtime_debug.js +// Endianness check +(() => { + var h16 = new Int16Array(1); + var h8 = new Int8Array(h16.buffer); + h16[0] = 0x6373; + if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)'; +})(); + +if (Module['ENVIRONMENT']) { + throw new Error('Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)'); +} + +function legacyModuleProp(prop, newName, incoming=true) { + if (!Object.getOwnPropertyDescriptor(Module, prop)) { + Object.defineProperty(Module, prop, { + configurable: true, + get() { + let extra = incoming ? ' (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)' : ''; + abort(`\`Module.${prop}\` has been replaced by \`${newName}\`` + extra); + + } + }); + } +} + +function ignoredModuleProp(prop) { + if (Object.getOwnPropertyDescriptor(Module, prop)) { + abort(`\`Module.${prop}\` was supplied but \`${prop}\` not included in INCOMING_MODULE_JS_API`); + } +} + +// forcing the filesystem exports a few things by default +function isExportedByForceFilesystem(name) { + return name === 'FS_createPath' || + name === 'FS_createDataFile' || + name === 'FS_createPreloadedFile' || + name === 'FS_unlink' || + name === 'addRunDependency' || + // The old FS has some functionality that WasmFS lacks. + name === 'FS_createLazyFile' || + name === 'FS_createDevice' || + name === 'removeRunDependency'; +} + +/** + * Intercept access to a global symbol. This enables us to give informative + * warnings/errors when folks attempt to use symbols they did not include in + * their build, or no symbols that no longer exist. + */ +function hookGlobalSymbolAccess(sym, func) { + if (typeof globalThis != 'undefined' && !Object.getOwnPropertyDescriptor(globalThis, sym)) { + Object.defineProperty(globalThis, sym, { + configurable: true, + get() { + func(); + return undefined; + } + }); + } +} + +function missingGlobal(sym, msg) { + hookGlobalSymbolAccess(sym, () => { + warnOnce(`\`${sym}\` is not longer defined by emscripten. ${msg}`); + }); +} + +missingGlobal('buffer', 'Please use HEAP8.buffer or wasmMemory.buffer'); +missingGlobal('asm', 'Please use wasmExports instead'); + +function missingLibrarySymbol(sym) { + hookGlobalSymbolAccess(sym, () => { + // Can't `abort()` here because it would break code that does runtime + // checks. e.g. `if (typeof SDL === 'undefined')`. + var msg = `\`${sym}\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`; + // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in + // library.js, which means $name for a JS name with no prefix, or name + // for a JS name like _name. + var librarySymbol = sym; + if (!librarySymbol.startsWith('_')) { + librarySymbol = '$' + sym; + } + msg += ` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${librarySymbol}')`; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + warnOnce(msg); + }); + + // Any symbol that is not included from the JS library is also (by definition) + // not exported on the Module object. + unexportedRuntimeSymbol(sym); +} - // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback - // to manually instantiate the Wasm module themselves. This allows pages to - // run the instantiation parallel to any other async startup actions they are - // performing. - // Also pthreads and wasm workers initialize the wasm instance through this - // path. - if (Module['instantiateWasm']) { - return new Promise((resolve, reject) => { - try { - Module['instantiateWasm'](info, (mod, inst) => { - receiveInstance(mod, inst); - resolve(mod.exports); - }); - } catch(e) { - err(`Module.instantiateWasm callback failed with error: ${e}`); - reject(e); +function unexportedRuntimeSymbol(sym) { + if (!Object.getOwnPropertyDescriptor(Module, sym)) { + Object.defineProperty(Module, sym, { + configurable: true, + get() { + var msg = `'${sym}' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the Emscripten FAQ)`; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + abort(msg); } }); } - - wasmBinaryFile ??= findWasmBinary(); - - var result = await instantiateAsync(wasmBinary, wasmBinaryFile, info); - var exports = receiveInstantiationResult(result); - return exports; } +// Used by XXXXX_DEBUG settings to output debug messages. +function dbg(...args) { + // TODO(sbc): Make this configurable somehow. Its not always convenient for + // logging to show up as warnings. + console.warn(...args); +} +// end include: runtime_debug.js // === Body === // end include: preamble.js - class ExitStatus { - name = 'ExitStatus'; - constructor(status) { - this.message = `Program terminated with exit(${status})`; - this.status = status; - } + /** @constructor */ + function ExitStatus(status) { + this.name = 'ExitStatus'; + this.message = `Program terminated with exit(${status})`; + this.status = status; } var callRuntimeCallbacks = (callbacks) => { - while (callbacks.length > 0) { - // Pass the module as the first argument. - callbacks.shift()(Module); - } + // Pass the module as the first argument. + callbacks.forEach((f) => f(Module)); }; - var onPostRuns = []; - var addOnPostRun = (cb) => onPostRuns.unshift(cb); - - var onPreRuns = []; - var addOnPreRun = (cb) => onPreRuns.unshift(cb); - /** @@ -872,7 +919,7 @@ async function createWasm() { case 'i8': return HEAP8[ptr]; case 'i16': return HEAP16[((ptr)>>1)]; case 'i32': return HEAP32[((ptr)>>2)]; - case 'i64': return HEAP64[((ptr)>>3)]; + case 'i64': abort('to do getValue(i64) use WASM_BIGINT'); case 'float': return HEAPF32[((ptr)>>2)]; case 'double': return HEAPF64[((ptr)>>3)]; case '*': return HEAPU32[((ptr)>>2)]; @@ -902,7 +949,7 @@ async function createWasm() { 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 'i64': abort('to do setValue(i64) use WASM_BIGINT'); case 'float': HEAPF32[((ptr)>>2)] = value; break; case 'double': HEAPF64[((ptr)>>3)] = value; break; case '*': HEAPU32[((ptr)>>2)] = value; break; @@ -923,8 +970,99 @@ async function createWasm() { } }; - var __abort_js = () => + var UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder() : undefined; + + /** + * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given + * array that contains uint8 values, returns a copy of that string as a + * Javascript String object. + * heapOrArray is either a regular array, or a JavaScript typed array view. + * @param {number=} idx + * @param {number=} maxBytesToRead + * @return {string} + */ + var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead = NaN) => { + var endIdx = idx + maxBytesToRead; + var endPtr = idx; + // TextDecoder needs to know the byte length in advance, it doesn't stop on + // null terminator by itself. Also, use the length info to avoid running tiny + // strings through TextDecoder, since .subarray() allocates garbage. + // (As a tiny code save trick, compare endPtr against endIdx using a negation, + // so that undefined/NaN means Infinity) + while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; + + if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { + return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); + } + var str = ''; + // If building with TextDecoder, we have already computed the string length + // above, so test loop end condition against that + while (idx < endPtr) { + // For UTF8 byte structure, see: + // http://en.wikipedia.org/wiki/UTF-8#Description + // https://www.ietf.org/rfc/rfc2279.txt + // https://tools.ietf.org/html/rfc3629 + var u0 = heapOrArray[idx++]; + if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } + var u1 = heapOrArray[idx++] & 63; + if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } + var u2 = heapOrArray[idx++] & 63; + if ((u0 & 0xF0) == 0xE0) { + u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; + } else { + if ((u0 & 0xF8) != 0xF0) warnOnce('Invalid UTF-8 leading byte ' + ptrToString(u0) + ' encountered when deserializing a UTF-8 string in wasm memory to a JS string!'); + u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); + } + + if (u0 < 0x10000) { + str += String.fromCharCode(u0); + } else { + var ch = u0 - 0x10000; + str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); + } + } + return str; + }; + + /** + * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the + * emscripten HEAP, returns a copy of that string as a Javascript String object. + * + * @param {number} ptr + * @param {number=} maxBytesToRead - An optional length that specifies the + * maximum number of bytes to read. You can omit this parameter to scan the + * string until the first 0 byte. If maxBytesToRead is passed, and the string + * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the + * string will cut short at that byte index (i.e. maxBytesToRead will not + * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing + * frequent uses of UTF8ToString() with and without maxBytesToRead may throw + * JS JIT optimizations off, so it is worth to consider consistently using one + * @return {string} + */ + var UTF8ToString = (ptr, maxBytesToRead) => { + assert(typeof ptr == 'number', `UTF8ToString expects a number (got ${typeof ptr})`); + return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ''; + }; + var ___assert_fail = (condition, filename, line, func) => { + abort(`Assertion failed: ${UTF8ToString(condition)}, at: ` + [filename ? UTF8ToString(filename) : 'unknown filename', line, func ? UTF8ToString(func) : 'unknown function']); + }; + + var __abort_js = () => { abort('native code called abort()'); + }; + + function __emscripten_fetch_free(id) { + if (Fetch.xhrs.has(id)) { + var xhr = Fetch.xhrs.get(id); + Fetch.xhrs.free(id); + // check if fetch is still in progress and should be aborted + if (xhr.readyState > 0 && xhr.readyState < 4) { + xhr.abort(); + } + } + } + + var __emscripten_memcpy_js = (dest, src, num) => HEAPU8.copyWithin(dest, src, src + num); var isLeapYear = (year) => year%4 === 0 && (year%100 !== 0 || year%400 === 0); @@ -939,12 +1077,13 @@ async function createWasm() { return yday; }; - var INT53_MAX = 9007199254740992; - - var INT53_MIN = -9007199254740992; - var bigintToI53Checked = (num) => (num < INT53_MIN || num > INT53_MAX) ? NaN : Number(num); - function __localtime_js(time, tmPtr) { - time = bigintToI53Checked(time); + var convertI32PairToI53Checked = (lo, hi) => { + assert(lo == (lo >>> 0) || lo == (lo|0)); // lo should either be a i32 or a u32 + assert(hi === (hi|0)); // hi should be a i32 + return ((hi + 0x200000) >>> 0 < 0x400001 - !!lo) ? (lo >>> 0) + hi * 4294967296 : NaN; + }; + function __localtime_js(time_low, time_high,tmPtr) { + var time = convertI32PairToI53Checked(time_low, time_high); var date = new Date(time*1000); @@ -1096,23 +1235,7 @@ async function createWasm() { var _emscripten_date_now = () => Date.now(); - function _emscripten_fetch_free(id) { - if (Fetch.xhrs.has(id)) { - var xhr = Fetch.xhrs.get(id); - Fetch.xhrs.free(id); - // check if fetch is still in progress and should be aborted - if (xhr.readyState > 0 && xhr.readyState < 4) { - xhr.abort(); - } - } - } - - var onExits = []; - var addOnExit = (cb) => onExits.unshift(cb); var JSEvents = { - memcpy(target, src, size) { - HEAP8.set(HEAP8.subarray(src, src + size), target); - }, removeAllEventListeners() { while (JSEvents.eventHandlers.length) { JSEvents._removeHandler(JSEvents.eventHandlers.length - 1); @@ -1233,79 +1356,6 @@ async function createWasm() { }, }; - var UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder() : undefined; - - /** - * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given - * array that contains uint8 values, returns a copy of that string as a - * Javascript String object. - * heapOrArray is either a regular array, or a JavaScript typed array view. - * @param {number=} idx - * @param {number=} maxBytesToRead - * @return {string} - */ - var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead = NaN) => { - var endIdx = idx + maxBytesToRead; - var endPtr = idx; - // TextDecoder needs to know the byte length in advance, it doesn't stop on - // null terminator by itself. Also, use the length info to avoid running tiny - // strings through TextDecoder, since .subarray() allocates garbage. - // (As a tiny code save trick, compare endPtr against endIdx using a negation, - // so that undefined/NaN means Infinity) - while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; - - if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { - return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); - } - var str = ''; - // If building with TextDecoder, we have already computed the string length - // above, so test loop end condition against that - while (idx < endPtr) { - // For UTF8 byte structure, see: - // http://en.wikipedia.org/wiki/UTF-8#Description - // https://www.ietf.org/rfc/rfc2279.txt - // https://tools.ietf.org/html/rfc3629 - var u0 = heapOrArray[idx++]; - if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } - var u1 = heapOrArray[idx++] & 63; - if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } - var u2 = heapOrArray[idx++] & 63; - if ((u0 & 0xF0) == 0xE0) { - u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; - } else { - if ((u0 & 0xF8) != 0xF0) warnOnce('Invalid UTF-8 leading byte ' + ptrToString(u0) + ' encountered when deserializing a UTF-8 string in wasm memory to a JS string!'); - u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); - } - - if (u0 < 0x10000) { - str += String.fromCharCode(u0); - } else { - var ch = u0 - 0x10000; - str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); - } - } - return str; - }; - - /** - * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the - * emscripten HEAP, returns a copy of that string as a Javascript String object. - * - * @param {number} ptr - * @param {number=} maxBytesToRead - An optional length that specifies the - * maximum number of bytes to read. You can omit this parameter to scan the - * string until the first 0 byte. If maxBytesToRead is passed, and the string - * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the - * string will cut short at that byte index (i.e. maxBytesToRead will not - * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing - * frequent uses of UTF8ToString() with and without maxBytesToRead may throw - * JS JIT optimizations off, so it is worth to consider consistently using one - * @return {string} - */ - var UTF8ToString = (ptr, maxBytesToRead) => { - assert(typeof ptr == 'number', `UTF8ToString expects a number (got ${typeof ptr})`); - return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ''; - }; var maybeCStringToJsString = (cString) => { // "cString > 2" checks if the input is a number, and isn't of the special // values we accept here, EMSCRIPTEN_EVENT_TARGET_* (which map to 0, 1, 2). @@ -1318,7 +1368,7 @@ async function createWasm() { var specialHTMLTargets = [0, typeof document != 'undefined' ? document : 0, typeof window != 'undefined' ? window : 0]; var findEventTarget = (target) => { target = maybeCStringToJsString(target); - var domElement = specialHTMLTargets[target] || (typeof document != 'undefined' ? document.querySelector(target) : null); + var domElement = specialHTMLTargets[target] || (typeof document != 'undefined' ? document.querySelector(target) : undefined); return domElement; }; @@ -1345,10 +1395,8 @@ async function createWasm() { var func = wasmTableMirror[funcPtr]; if (!func) { if (funcPtr >= wasmTableMirror.length) wasmTableMirror.length = funcPtr + 1; - /** @suppress {checkTypes} */ wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr); } - /** @suppress {checkTypes} */ assert(wasmTable.get(funcPtr) == func, 'JavaScript-side Wasm function table mirror is out of date!'); return func; }; @@ -1556,8 +1604,12 @@ async function createWasm() { class HandleAllocator { - allocated = [undefined]; - freelist = []; + constructor() { + // TODO(https://github.com/emscripten-core/emscripten/issues/21414): + // Use inline field declarations. + this.allocated = [undefined]; + this.freelist = []; + } get(id) { assert(this.allocated[id] !== undefined, `invalid handle: ${id}`); return this.allocated[id]; @@ -1804,7 +1856,6 @@ async function createWasm() { quit_(code, new ExitStatus(code)); }; - /** @suppress {duplicate } */ /** @param {boolean|number=} implicit */ var exitJS = (status, implicit) => { @@ -2132,18 +2183,22 @@ async function createWasm() { return !!(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_polygon_offset_clamp = (ctx) => { + return !!(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_EXT_clip_control = (ctx) => { + return !!(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_polygon_mode = (ctx) => { + return !!(ctx.webglPolygonMode = ctx.getExtension('WEBGL_polygon_mode')); + }; - var webgl_enable_WEBGL_multi_draw = (ctx) => + var webgl_enable_WEBGL_multi_draw = (ctx) => { // Closure is expected to be allowed to minify the '.multiDrawWebgl' property, so not accessing it quoted. - !!(ctx.multiDrawWebgl = ctx.getExtension('WEBGL_multi_draw')); + return !!(ctx.multiDrawWebgl = ctx.getExtension('WEBGL_multi_draw')); + }; var getEmscriptenSupportedExtensions = (ctx) => { // Restrict the list of advertised extensions to those that we actually @@ -2242,11 +2297,6 @@ async function createWasm() { for (var i = table.length; i < ret; i++) { table[i] = null; } - // Skip over any non-null elements that might have been created by - // glBindBuffer. - while (table[ret]) { - ret = GL.counter++; - } return ret; }, genObject:(n, buffers, createFunction, objectTable @@ -2480,7 +2530,7 @@ async function createWasm() { // Active Emscripten GL layer context object. GL.currentContext = GL.contexts[contextHandle]; // Active WebGL context object. - Module['ctx'] = GLctx = GL.currentContext?.GLctx; + Module.ctx = GLctx = GL.currentContext?.GLctx; return !(contextHandle && !GLctx); }, getContext:(contextHandle) => { @@ -2497,7 +2547,7 @@ async function createWasm() { } // Make sure the canvas object no longer refers to the context object so // there are no GC surprises. - if (GL.contexts[contextHandle]?.GLctx.canvas) { + if (GL.contexts[contextHandle] && GL.contexts[contextHandle].GLctx.canvas) { GL.contexts[contextHandle].GLctx.canvas.GLctxObject = undefined; } GL.contexts[contextHandle] = null; @@ -2583,11 +2633,6 @@ async function createWasm() { renderViaOffscreenBackBuffer: HEAP8[attributes + 32] }; - // TODO: Make these into hard errors at some point in the future - if (contextAttributes.majorVersion !== 1 && contextAttributes.majorVersion !== 2) { - err(`Invalid WebGL version requested: ${contextAttributes.majorVersion}`); - } - var canvas = findCanvasEventTarget(target); if (!canvas) { @@ -2619,8 +2664,8 @@ async function createWasm() { abort('fd_close called without SYSCALLS_REQUIRE_FILESYSTEM'); }; - function _fd_seek(fd, offset, whence, newOffset) { - offset = bigintToI53Checked(offset); + function _fd_seek(fd,offset_low, offset_high,whence,newOffset) { + var offset = convertI32PairToI53Checked(offset_low, offset_high); return 70; @@ -2669,14 +2714,6 @@ async function createWasm() { }; var _glBindBuffer = (target, buffer) => { - // Calling glBindBuffer with an unknown buffer will implicitly create a - // new one. Here we bypass `GL.counter` and directly using the ID passed - // in. - if (buffer && !GL.buffers[buffer]) { - var b = GLctx.createBuffer(); - b.name = buffer; - GL.buffers[buffer] = b; - } if (target == 0x8892 /*GL_ARRAY_BUFFER*/) { GLctx.currentArrayBufferBinding = buffer; } else if (target == 0x8893 /*GL_ELEMENT_ARRAY_BUFFER*/) { @@ -2905,8 +2942,9 @@ async function createWasm() { }; - var _glGetAttribLocation = (program, name) => - GLctx.getAttribLocation(GL.programs[program], UTF8ToString(name)); + var _glGetAttribLocation = (program, name) => { + return GLctx.getAttribLocation(GL.programs[program], UTF8ToString(name)); + }; var _glGetProgramInfoLog = (program, maxLength, length, infoLog) => { var log = GLctx.getProgramInfoLog(GL.programs[program]); @@ -3220,17 +3258,21 @@ function checkIncomingModuleAPI() { ignoredModuleProp('fetchSettings'); } var wasmImports = { + /** @export */ + __assert_fail: ___assert_fail, /** @export */ _abort_js: __abort_js, /** @export */ + _emscripten_fetch_free: __emscripten_fetch_free, + /** @export */ + _emscripten_memcpy_js: __emscripten_memcpy_js, + /** @export */ _localtime_js: __localtime_js, /** @export */ _tzset_js: __tzset_js, /** @export */ emscripten_date_now: _emscripten_date_now, /** @export */ - emscripten_fetch_free: _emscripten_fetch_free, - /** @export */ emscripten_get_element_css_size: _emscripten_get_element_css_size, /** @export */ emscripten_is_main_browser_thread: _emscripten_is_main_browser_thread, @@ -3329,14 +3371,15 @@ var wasmImports = { /** @export */ glVertexAttribPointer: _glVertexAttribPointer }; -var wasmExports; -createWasm(); +var wasmExports = createWasm(); var ___wasm_call_ctors = createExportWrapper('__wasm_call_ctors', 0); -var _free = createExportWrapper('free', 1); var _malloc = createExportWrapper('malloc', 1); +var _free = createExportWrapper('free', 1); var _main = Module['_main'] = createExportWrapper('main', 2); var _fflush = createExportWrapper('fflush', 1); var _strerror = createExportWrapper('strerror', 1); +var __emscripten_tempret_set = createExportWrapper('_emscripten_tempret_set', 1); +var __emscripten_tempret_get = createExportWrapper('_emscripten_tempret_get', 0); var _emscripten_stack_init = () => (_emscripten_stack_init = wasmExports['emscripten_stack_init'])(); var _emscripten_stack_get_free = () => (_emscripten_stack_get_free = wasmExports['emscripten_stack_get_free'])(); var _emscripten_stack_get_base = () => (_emscripten_stack_get_base = wasmExports['emscripten_stack_get_base'])(); @@ -3344,6 +3387,7 @@ var _emscripten_stack_get_end = () => (_emscripten_stack_get_end = wasmExports[' var __emscripten_stack_restore = (a0) => (__emscripten_stack_restore = wasmExports['_emscripten_stack_restore'])(a0); var __emscripten_stack_alloc = (a0) => (__emscripten_stack_alloc = wasmExports['_emscripten_stack_alloc'])(a0); var _emscripten_stack_get_current = () => (_emscripten_stack_get_current = wasmExports['emscripten_stack_get_current'])(); +var dynCall_jiji = Module['dynCall_jiji'] = createExportWrapper('dynCall_jiji', 5); // include: postamble.js @@ -3355,7 +3399,6 @@ var missingLibrarySymbols = [ 'writeI53ToU64Clamped', 'writeI53ToU64Signaling', 'convertI32PairToI53', - 'convertI32PairToI53Checked', 'convertU32PairToI53', 'stackAlloc', 'getTempRet0', @@ -3368,11 +3411,14 @@ var missingLibrarySymbols = [ 'inetNtop6', 'readSockaddr', 'writeSockaddr', + 'initRandomFill', + 'randomFill', 'emscriptenLog', 'readEmAsmArgs', 'getExecutableName', 'listenOnce', 'autoResumeAudioContext', + 'dynCallLegacy', 'getDynCaller', 'dynCall', 'runtimeKeepalivePush', @@ -3381,9 +3427,6 @@ var missingLibrarySymbols = [ 'asyncLoad', 'mmapAlloc', 'getNativeTypeSize', - 'addOnInit', - 'addOnPostCtor', - 'addOnPreMain', 'STACK_SIZE', 'STACK_ALIGN', 'POINTER_SIZE', @@ -3460,12 +3503,11 @@ var missingLibrarySymbols = [ 'checkWasiClock', 'wasiRightsToMuslOFlags', 'wasiOFlagsToMuslOFlags', - 'initRandomFill', - 'randomFill', + 'createDyncallWrapper', 'safeSetTimeout', 'setImmediateWrapped', - 'safeRequestAnimationFrame', 'clearImmediateWrapped', + 'polyfillSetImmediate', 'registerPostMainLoop', 'getPromise', 'makePromise', @@ -3474,6 +3516,7 @@ var missingLibrarySymbols = [ 'ExceptionInfo', 'findMatchingCatch', 'Browser_asyncPrepareDataCounter', + 'safeRequestAnimationFrame', 'arraySum', 'addDays', 'getSocketFromFD', @@ -3514,6 +3557,11 @@ missingLibrarySymbols.forEach(missingLibrarySymbol) var unexportedSymbols = [ 'run', + 'addOnPreRun', + 'addOnInit', + 'addOnPreMain', + 'addOnExit', + 'addOnPostRun', 'addRunDependency', 'removeRunDependency', 'out', @@ -3527,9 +3575,7 @@ var unexportedSymbols = [ 'writeI53ToI64', 'readI53FromI64', 'readI53FromU64', - 'INT53_MAX', - 'INT53_MIN', - 'bigintToI53Checked', + 'convertI32PairToI53Checked', 'stackSave', 'stackRestore', 'ptrToString', @@ -3554,9 +3600,6 @@ var unexportedSymbols = [ 'HandleAllocator', 'wasmTable', 'noExitRuntime', - 'addOnPreRun', - 'addOnExit', - 'addOnPostRun', 'freeTableIndexes', 'functionsInTableMap', 'setValue', @@ -3584,9 +3627,6 @@ var unexportedSymbols = [ 'UNWIND_CACHE', 'ExitStatus', 'flush_NO_FILESYSTEM', - 'emSetImmediate', - 'emClearImmediate_deps', - 'emClearImmediate', 'registerPreMainLoop', 'promiseMap', 'uncaughtExceptionCount', @@ -3651,10 +3691,17 @@ unexportedSymbols.forEach(unexportedRuntimeSymbol); var calledRun; +var calledPrerun; + +dependenciesFulfilled = function runCaller() { + // If run has never been called, and we should call run (INVOKE_RUN is true, and Module.noInitialRun is not false) + if (!calledRun) run(); + if (!calledRun) dependenciesFulfilled = runCaller; // try this again later, after new deps are fulfilled +}; function callMain() { assert(runDependencies == 0, 'cannot call main when async dependencies remain! (listen on Module["onRuntimeInitialized"])'); - assert(typeof onPreRuns === 'undefined' || onPreRuns.length == 0, 'cannot call main when preRun functions remain to be called'); + assert(calledPrerun, 'cannot call main without calling preRun first'); var entryFunction = _main; @@ -3668,7 +3715,8 @@ function callMain() { // if we're not running an evented main loop, it's time to exit exitJS(ret, /* implicit = */ true); return ret; - } catch (e) { + } + catch (e) { return handleException(e); } } @@ -3685,26 +3733,27 @@ function stackCheckInit() { function run() { if (runDependencies > 0) { - dependenciesFulfilled = run; return; } - stackCheckInit(); + stackCheckInit(); - preRun(); + if (!calledPrerun) { + calledPrerun = 1; + preRun(); - // a preRun added a dependency, run will be called later - if (runDependencies > 0) { - dependenciesFulfilled = run; - return; + // a preRun added a dependency, run will be called later + if (runDependencies > 0) { + return; + } } function doRun() { // run may have just been called through dependencies being fulfilled just in this very frame, // or while the async setStatus time below was happening - assert(!calledRun); - calledRun = true; - Module['calledRun'] = true; + if (calledRun) return; + calledRun = 1; + Module['calledRun'] = 1; if (ABORT) return; @@ -3714,8 +3763,7 @@ function run() { Module['onRuntimeInitialized']?.(); - var noInitialRun = Module['noInitialRun'];legacyModuleProp('noInitialRun', 'noInitialRun'); - if (!noInitialRun) callMain(); + if (shouldRunNow) callMain(); postRun(); } @@ -3769,6 +3817,11 @@ if (Module['preInit']) { } } +// shouldRunNow refers to calling main(), not run(). +var shouldRunNow = true; + +if (Module['noInitialRun']) shouldRunNow = false; + run(); // end include: postamble.js diff --git a/themes/dist/output.wasm b/themes/dist/output.wasm index 9754410..7c0155e 100755 Binary files a/themes/dist/output.wasm and b/themes/dist/output.wasm differ diff --git a/themes/src/Logger.cpp b/themes/src/Logger.cpp deleted file mode 100644 index 1068d88..0000000 --- a/themes/src/Logger.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "Logger.h" -#include -#include -#include - -namespace Logger { - LogLevel gLogLevel = LogLevel_Debug; - FILE* gFilePointer = NULL; - - void initialize(LoggerOptions options) { - setLevel(options.level); - if (options.logToFile) { -#ifdef WIN32 - fopen_s(&gFilePointer, options.filePath, "a"); -#else - gFilePointer = fopen(options.filePath, "a"); -#endif - } - } - - void setLevel(LogLevel level) { - gLogLevel = level; - } - - LogLevel getLevel() { - return gLogLevel; - } - - void printHeader(const char* levelStr, const char* fileName, int lineNumber) { - time_t t = time(0); - tm now; -#ifdef WIN32 - localtime_s(&now, &t); -#else - now = *localtime(&t); -#endif - - printf("%s:%d [%d-%d-%d %d:%d:%d] %s: ", fileName, lineNumber, (now.tm_year + 1900), (now.tm_mon + 1), now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, levelStr); - if (gFilePointer != NULL) { - fprintf(gFilePointer, "[%d-%d-%d %d:%d:%d] %s: ", (now.tm_year + 1900), (now.tm_mon + 1), now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, levelStr); - } - } - - void logInternal(const char* file, int lineNumber,LogLevel level, const char* format, va_list args) { - if (level < gLogLevel) { - return; - } - - - const char* levelStr; - switch (level) { - case LogLevel_Debug: - levelStr = "Debug"; - break; - case LogLevel_Info: - levelStr = "Info"; - break; - case LogLevel_Warn: - levelStr = "Warning"; - break; - case LogLevel_Error: - levelStr = "Error"; - break; - default: - levelStr = "Unknown"; - break; - } - - if (gFilePointer != NULL) { - va_list fileArgs; - va_copy(fileArgs, args); - vfprintf(gFilePointer, format, fileArgs); - fprintf(gFilePointer, "\n"); - } - - printHeader(levelStr, file, lineNumber); - - vprintf(format, args); - printf("\n"); - } - - void doLog(const char* file, int lineNumber,LogLevel level, const char* format, ...) { - va_list args; - va_start(args, format); - logInternal(file, lineNumber, level, format, args); - va_end(args); - } - - void doDebug(const char* file, int lineNumber,const char* format, ...) { - va_list args; - va_start(args, format); - logInternal(file, lineNumber, LogLevel_Debug, format, args); - va_end(args); - } - - void doInfo(const char* file, int lineNumber,const char* format, ...) { - va_list args; - va_start(args, format); - logInternal(file, lineNumber, LogLevel_Info, format, args); - va_end(args); - } - - void doWarning(const char* file, int lineNumber,const char* format, ...) { - va_list args; - va_start(args, format); - logInternal(file, lineNumber, LogLevel_Warn, format, args); - va_end(args); - } - - void doError(const char* file, int lineNumber,const char* format, ...) { - va_list args; - va_start(args, format); - logInternal(file, lineNumber, LogLevel_Error, format, args); - va_end(args); - } - - void free() { - if (gFilePointer) { - fclose(gFilePointer); - gFilePointer = NULL; - } - } -} diff --git a/themes/src/Logger.h b/themes/src/Logger.h deleted file mode 100644 index 7596b6f..0000000 --- a/themes/src/Logger.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef LOGGER_H -#define LOGGER_H - -#include - -enum LogLevel { - LogLevel_Debug = 0, - LogLevel_Info = 1, - LogLevel_Warn = 2, - LogLevel_Error = 3 -}; - -struct LoggerOptions { - LogLevel level = LogLevel_Debug; - bool logToFile = false; - const char* filePath = "debug.log"; -}; - -namespace Logger { - void initialize(LoggerOptions options); - void setLevel(LogLevel level); - LogLevel getLevel(); - void doLog(const char* file, int lineNumber, LogLevel level, const char* format, ...); - void doDebug(const char* file, int lineNumber, const char* format, ...); - void doInfo(const char* file, int lineNumber, const char* format, ...); - void doWarning(const char* file, int lineNumber, const char* format, ...); - void doError(const char* file, int lineNumber, const char* format, ...); - void free(); -}; - -#if WIN32 -#define __FILENAME__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) -#else -#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) -#endif - -#define logger_log(level, format, ...) Logger::doLog(__FILENAME__, __LINE__, level, format, ## __VA_ARGS__) -#define logger_debug(format, ...) Logger::doDebug(__FILENAME__, __LINE__, format, ## __VA_ARGS__) -#define logger_info(format, ...) Logger::doInfo(__FILENAME__, __LINE__, format, ## __VA_ARGS__) -#define logger_warning(format, ...) Logger::doWarning(__FILENAME__, __LINE__, format, ## __VA_ARGS__) -#define logger_error(format, ...) Logger::doError(__FILENAME__, __LINE__, format, ## __VA_ARGS__) - -#endif diff --git a/themes/src/MainLoop.cpp b/themes/src/MainLoop.cpp deleted file mode 100644 index 09aa643..0000000 --- a/themes/src/MainLoop.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "MainLoop.h" -#include -#include - -EM_BOOL loop(double time, void* loop) { - MainLoop* mainLoop = (MainLoop*) loop; - if (!mainLoop->isRunning) { - return false; - } - - if (mainLoop->lastTime == 0) { - mainLoop->lastTime = time; - return true; - } - - long deltaTime = time - mainLoop->lastTime; - mainLoop->lastTime = time; - mainLoop->elapsedTime += deltaTime; - mainLoop->numFrames++; - float deltaTimeSeconds = static_cast(deltaTime) / 1000.f; - - if (mainLoop->elapsedTime >= 1000.0) { - printf("FPS: %d\n", mainLoop->numFrames); - - mainLoop->elapsedTime = 0.0; - mainLoop->numFrames = 0; - } - - mainLoop->updateFunc(deltaTimeSeconds, NULL); - return true; -} \ No newline at end of file diff --git a/themes/src/MainLoop.h b/themes/src/MainLoop.h deleted file mode 100644 index 07520a2..0000000 --- a/themes/src/MainLoop.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -EM_BOOL loop(double time, void* loop); - -struct MainLoop { - bool isRunning = false; - double lastTime = 0, elapsedTime = 0; - int numFrames = 0; - void (*updateFunc)(float dtSeconds, void *userData); - - void run(void (*cb)(float dtSeconds, void *userData)) { - isRunning = true; - lastTime = 0; - elapsedTime = 0; - numFrames = 0; - updateFunc = cb; - - emscripten_request_animation_frame_loop(loop, this); - } - - void stop() { - isRunning = false; - } -}; diff --git a/themes/src/Renderer2d.cpp b/themes/src/Renderer2d.cpp deleted file mode 100644 index f1d78e3..0000000 --- a/themes/src/Renderer2d.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "Renderer2d.h" -#include "Shader.h" -#include "WebglContext.h" -#include "mathlib.h" -#include -#include "shaders/renderer2d_vert.h" -#include "shaders/renderer2d_frag.h" - -// Note: In the 'transform' attribute, the transform.x is the scale, -// transform.y is the rotation, and transform.zw is the translation. - -void Renderer2d::load(WebglContext* inContext, const char* inVertexShader, const char* inFragmentShader) { - auto vertexShader = inVertexShader ? inVertexShader : shader_renderer2d_vert; - auto fragmentShader = inFragmentShader ? inFragmentShader : shader_renderer2d_frag; - context = inContext; - printf("Compiling Renderer2d shader...\n"); - shader = loadShader(vertexShader, fragmentShader); - - useShader(shader); - attributes.position = getShaderAttribute(shader, "position"); - attributes.color = getShaderAttribute(shader, "color"); - attributes.vMatrix = getShaderAttribute(shader, "vMatrix"); - uniforms.projection = getShaderUniform(shader, "projection"); - uniforms.model = getShaderUniform(shader, "model"); - projection = Mat4x4().getOrthographicMatrix(0, context->width, 0, context->height); - - printf("Renderer2d shader compiled.\n"); -} - -void Renderer2d::render() { - projection = Mat4x4().getOrthographicMatrix(0, context->width, 0, context->height); - - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LEQUAL); - glDepthMask(GL_TRUE); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - glClearColor(clearColor.x, clearColor.y, clearColor.z, clearColor.w); - glClearDepth(1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - useShader(shader); - setShaderMat4(uniforms.projection, projection); -} - -void Renderer2d::unload() { - glClearColor(0.f, 0.f, 0.f, 0.f); - glClear(GL_COLOR_BUFFER_BIT); - glDeleteProgram(shader); -} - -f32 Renderer2d::get_width() { - return context->width; -} - -f32 Renderer2d::get_height() { - return context->height; -} - - -void Mesh2D::load(Vertex2D* inVertices, u32 inNumVertices, Renderer2d* renderer) { - ebo = 0; - numVertices = inNumVertices; - useShader(renderer->shader); - - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, inNumVertices * sizeof(Vertex2D), &inVertices[0], GL_STATIC_DRAW); - - glEnableVertexAttribArray(renderer->attributes.position); - glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0); - - glEnableVertexAttribArray(renderer->attributes.color); - glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color)); - - for (i32 idx = 0; idx < 4; idx++) { - glEnableVertexAttribArray(renderer->attributes.vMatrix + idx); - glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)(offsetof(Vertex2D, vMatrix) + (idx * 16))); - } - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); -} - - -void Mesh2D::load(Vertex2D* vertices, - u32 numVertices, - u32* indices, - u32 inNumIndices, - Renderer2d* renderer) { - load(vertices, numVertices, renderer); - glBindVertexArray(vao); - glGenBuffers(1, &ebo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, inNumIndices * sizeof(u32), &indices[0], GL_STATIC_DRAW); - numIndices = inNumIndices; - glBindVertexArray(0); -} - -void Mesh2D::render(Renderer2d* renderer, GLenum drawType) { - setShaderMat4(renderer->uniforms.model, model); - - if (ebo == 0) { - glBindVertexArray(vao); - glDrawArrays(drawType, 0, numVertices); - glBindVertexArray(0); - } - else { - glBindVertexArray(vao); - glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0); - glBindVertexArray(0); - } -} - -void Mesh2D::unload() { - glDeleteVertexArrays(1, &vao); - glDeleteBuffers(1, &vbo); - if (ebo != 0) { - glDeleteBuffers(1, &ebo); - ebo = 0; - } - vao = 0; - vbo = 0; -} diff --git a/themes/src/Renderer2d.h b/themes/src/Renderer2d.h deleted file mode 100644 index 7432894..0000000 --- a/themes/src/Renderer2d.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include "WebglContext.h" -#include "types.h" -#include "Shader.h" -#include "mathlib.h" - -struct WebglContext; - -/// Responsible for rendering Mesh2Ds -struct Renderer2d { - WebglContext* context = NULL; - Mat4x4 projection; - u32 shader; - Vector4 clearColor; - - struct { - i32 position; - i32 color; - - // TODO: vMatrix is not standard and does not belong here - i32 vMatrix; - } attributes; - - struct { - i32 projection; - i32 model; - } uniforms; - - /// Load with the provided context and shader programs. If the shaders are NULL, the default - /// shader is used - void load(WebglContext* context, const char* vertexShader = NULL, const char* fragmentShader = NULL); - void render(); - void unload(); - f32 get_width(); - f32 get_height(); -}; - -struct Vertex2D { - Vector2 position; - Vector4 color; - Mat4x4 vMatrix; -}; - -struct Mesh2D { - u32 vao; - u32 vbo; - u32 ebo = 0; - u32 numVertices = 0; - u32 numIndices = 0; - Mat4x4 model; - - void load(Vertex2D* vertices, u32 numVertices, Renderer2d* renderer); - void load(Vertex2D* vertices, - u32 numVertices, - u32* indices, - u32 numIndices, - Renderer2d* renderer); - void render(Renderer2d* renderer, GLenum drawType = GL_TRIANGLES); - void unload(); -}; diff --git a/themes/src/Renderer3d.cpp b/themes/src/Renderer3d.cpp deleted file mode 100644 index 00315de..0000000 --- a/themes/src/Renderer3d.cpp +++ /dev/null @@ -1,239 +0,0 @@ -#include "Renderer3d.h" -#include "Shader.h" -#include "list.h" -#include "mathlib.h" -#include "WebglContext.h" -#include "Logger.h" -#include - -// Note: In the 'transform' attribute, the transform.x is the scale, -// transform.y is the rotation, and transform.zw is the translatiob. -EM_BOOL onScreenSizeChanged_3D(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) { - Renderer3d* renderer = (Renderer3d*)userData; - - EMSCRIPTEN_RESULT result = emscripten_set_canvas_element_size( renderer->context->query, uiEvent->documentBodyClientWidth, uiEvent->documentBodyClientHeight); - if (result != EMSCRIPTEN_RESULT_SUCCESS) { - logger_error("Failed to resize element at query: %s\n", renderer->context->query); - } - //renderer->projection = Mat4x4().getOrthographicMatrix(0, renderer->context->width, 0, renderer->context->height); - - return true; -} - -void Renderer3d::load(WebglContext* inContext, const char* vertexShader, const char* fragmentShader) { - context = inContext; - printf("Compiling Renderer3d shader...\n"); - shader = loadShader(vertexShader, fragmentShader); - - useShader(shader); - attributes.position = getShaderAttribute(shader, "position"); - attributes.color = getShaderAttribute(shader, "color"); - attributes.normal = getShaderAttribute(shader, "normal"); - uniforms.projection = getShaderUniform(shader, "projection"); - uniforms.view = getShaderUniform(shader, "view"); - uniforms.model = getShaderUniform(shader, "model"); - projection = Mat4x4().getPerspectiveProjection(0.1, 1000.f, 0.872f, static_cast(context->width) / static_cast(context->height)); - view = Mat4x4().getLookAt({ 0, 25, 75 }, { 0, 15, 0 }, { 0, 1, 0 }); - - logger_info("Renderer3d shader compiled.\n"); - - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, onScreenSizeChanged_3D); -} - -void Renderer3d::render() { - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LEQUAL); - glDepthMask(GL_TRUE); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - glClearColor(clearColor.x, clearColor.y, clearColor.z, clearColor.w); - glClearDepth(1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - useShader(shader); - setShaderMat4(uniforms.projection, projection); - setShaderMat4(uniforms.view, view); -} - -void Renderer3d::unload() { - glClearColor(0.f, 0.f, 0.f, 0.f); - glClear(GL_COLOR_BUFFER_BIT); - glDeleteProgram(shader); -} - -enum LineType { - LineType_None, - LineType_Comment, - LineType_mtl, - LineType_v, - LineType_f, - LineType_Unsupported -}; - -struct LineItem { - LineType type = LineType_None; - - i32 idx = 0; - - union { - f32 vertices[3]; - i32 indices[3]; - } v; -}; - -inline i32 readPastSpaces(i32 i, const char* content) { - while (content[i] == ' ') i++; - return i; -} - -inline i32 readPastLine(i32 i, const char* content) { - while (content[i] != '\n' && content[i] != '\0') i++; - return i; -} - -inline i32 readToken(i32 i, const char* content, char* output) { - i32 tidx = 0; - i = readPastSpaces(i, content); - while (content[i] != ' ' && content[i] != '\n' && content[i] != '\0') { - output[tidx] = content[i]; - i++; - tidx++; - } - output[tidx] = '\0'; - return i; -} - -Mesh3d Mesh3d_fromObj(Renderer3d* renderer, const char* content, const i32 len) { - Mesh3d result; - result.vertices.allocate(2048); - result.indices.allocate(2048); - - LineItem lt; - lt.type = LineType_None; - i32 lineNumber = 0; - i32 i = 0; - while (content[i] != '\0') { - i = readPastSpaces(i, content); - if (lt.type == LineType_None) { - lineNumber++; - char type[32]; - i = readToken(i, content, type); - - if (strncmp(type, "#", 1) == 0) { - lt.type = LineType_Comment; - } - else if (strncmp(type, "mtllib", 6) == 0) { - lt.type = LineType_mtl; - } - else if (strncmp(type, "v", 1) == 0) { - lt.type = LineType_v; - } - else if (strncmp(type, "f", 1) == 0) { - lt.type = LineType_f; - } - else { - i++; - //lt.type = LineType_Unsupported; - //logger_error("Unknown type %s, %d", type, lineNumber); - } - } - else { - char buffer[32]; - switch (lt.type) { - case LineType_mtl: - i = readToken(i, content, buffer); - break; - case LineType_v: { - while (content[i] != '\n' && content[i] != '\0') { - i = readToken(i, content, buffer); - lt.v.vertices[lt.idx] = atof(buffer); - lt.idx++; - } - - float fColor = randomFloatBetween(0.8, 1); - result.vertices.add({ - Vector4(lt.v.vertices[0], lt.v.vertices[1], lt.v.vertices[2], 1.f), - Vector4(fColor, fColor, fColor, 1) - }); - break; - } - case LineType_f: { - while (content[i] != '\n' && content[i] != '\0') { - i = readToken(i, content, buffer); - lt.v.indices[lt.idx] = atoi(buffer); - lt.idx++; - } - - auto v1idx = lt.v.indices[0] - 1; - auto v2idx = lt.v.indices[1] - 1; - auto v3idx = lt.v.indices[2] - 1; - - result.indices.add(v1idx); - result.indices.add(v2idx); - result.indices.add(v3idx); - - auto& v1 = result.vertices[v1idx]; - auto& v2 = result.vertices[v2idx]; - auto& v3 = result.vertices[v3idx]; - Vector3 normal = (v1.position - v2.position).cross(v1.position - v3.position).toVector3().normalize(); - v1.normal = normal; - v2.normal = normal; - v3.normal = normal; - break; - } - default: - i = readPastLine(i, content); - break; - } - - lt = LineItem(); - } - } - - printf("Completed Mesh3d loading.\n"); - result.load(renderer); - return result; -} - -void Mesh3d::load(Renderer3d* renderer) { - glGenVertexArrays(1, &vao); - glGenBuffers(1, &vbo); - glGenBuffers(1, &ebo); - - glBindVertexArray(vao); - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, vertices.numElements * sizeof(Vertex3d), &vertices.data[0], GL_STATIC_DRAW); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.numElements * sizeof(GLuint), &indices.data[0], GL_STATIC_DRAW); - - // Position - glEnableVertexAttribArray(renderer->attributes.position); - glVertexAttribPointer(renderer->attributes.position, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex3d), (GLvoid *)0); - - // Color - glEnableVertexAttribArray(renderer->attributes.color); - glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex3d), (GLvoid *)offsetof(Vertex3d, color)); - - // Normal - glEnableVertexAttribArray(renderer->attributes.normal); - glVertexAttribPointer(renderer->attributes.normal, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex3d), (GLvoid *)offsetof(Vertex3d, normal)); - - glBindVertexArray(0); -} - -void Mesh3d::unload() { - if (vao) glDeleteVertexArrays(1, &vao); - if (vbo) glDeleteBuffers(1, &vbo); - if (ebo) glDeleteBuffers(1, &ebo); - vertices.deallocate(); - indices.deallocate(); -} - -void Mesh3d::render(Renderer3d* renderer) { - setShaderMat4(renderer->uniforms.model, model); - - glBindVertexArray(vao); - glDrawElements(GL_TRIANGLES, indices.numElements, GL_UNSIGNED_INT, 0); - glBindVertexArray(0); -} diff --git a/themes/src/Renderer3d.h b/themes/src/Renderer3d.h deleted file mode 100644 index 5b2c8c8..0000000 --- a/themes/src/Renderer3d.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef RENDERER3D_H -#define RENDERER3D_H -#include "mathlib.h" -#include "list.h" -#include "types.h" -#include - -struct Renderer3d; - -struct Vertex3d { - Vector4 position; - Vector4 color; - Vector4 normal; -}; - -struct Mesh3d { - u32 vao; - u32 vbo; - u32 ebo; - matte::List vertices; - matte::List indices; - Mat4x4 model; - - void load(Renderer3d* renderer); - void render(Renderer3d* renderer); - void unload(); -}; - -struct WebglContext; -struct Renderer3d { - WebglContext* context = NULL; - Mat4x4 projection; - Mat4x4 view; - u32 shader; - Vector4 clearColor; - - struct { - i32 position; - i32 color; - i32 normal; - } attributes; - - struct { - i32 projection; - i32 view; - i32 model; - } uniforms; - - void load(WebglContext* context, const char* vertexShader, const char* fragmentShader); - void render(); - void unload(); -}; - -Mesh3d Mesh3d_fromObj(Renderer3d* renderer, const char* content, const i32 len); - -#endif diff --git a/themes/src/Shader.cpp b/themes/src/Shader.cpp deleted file mode 100644 index 5f2b00e..0000000 --- a/themes/src/Shader.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "Shader.h" -#include - -GLuint loadIndividualShader(GLenum shaderType, const GLchar* cCode) { - GLuint shader = glCreateShader(shaderType); - glShaderSource(shader, 1, &cCode, 0); - glCompileShader(shader); - GLint success; - glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - if (!success) { - GLchar infoLog[512]; - glGetShaderInfoLog(shader, 512, 0, infoLog); - printf("Failed to load shader: %s, Shader =%s\n", infoLog, cCode); - return 0; - } - - return shader; -} - -void attachShaders(Shader& retVal, const GLchar* vertexShader, const GLchar* fragmentShader) { - GLuint vertex = 0, fragment = 0, geometry = 0; - if (vertexShader) { - vertex = loadIndividualShader(GL_VERTEX_SHADER, vertexShader); - glAttachShader(retVal, vertex); - } - - if (fragmentShader) { - fragment = loadIndividualShader(GL_FRAGMENT_SHADER, fragmentShader); - glAttachShader(retVal, fragment); - } - - glLinkProgram(retVal); - GLint isLinked = 0; - glGetProgramiv(retVal, GL_LINK_STATUS, (int*)&isLinked); - if (isLinked == GL_FALSE) { - GLint maxLength = 0; - glGetProgramiv(retVal, GL_INFO_LOG_LENGTH, &maxLength); - - // The maxLength includes the NULL character - GLchar* infoLog = new GLchar[maxLength]; - glGetProgramInfoLog(retVal, maxLength, &maxLength, infoLog); - glDeleteProgram(retVal); - printf("Error. Could not initialize shader with vertex=%s, error=%s\n", vertexShader, infoLog); - delete []infoLog; - } - - if (vertexShader) - glDeleteShader(vertex); - if (fragmentShader) - glDeleteShader(fragment); -} - -Shader loadShader(const GLchar* vertexShader, const GLchar* fragmentShader) { - Shader retVal; - retVal = glCreateProgram(); - - attachShaders(retVal, vertexShader, fragmentShader); - useShader(retVal); - - return retVal; -} diff --git a/themes/src/Shader.h b/themes/src/Shader.h deleted file mode 100644 index bc81764..0000000 --- a/themes/src/Shader.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include -#include -#include "mathlib.h" - -typedef GLuint Shader; - -Shader loadShader(const GLchar* vertexShader, const GLchar* fragmentShader); - -inline GLint getShaderUniform(const Shader& shader, const GLchar *name) { - GLint uid = glGetUniformLocation(shader, name); - if (uid < 0) { - return -1; - } - return uid; -} - -inline GLint getShaderAttribute(const Shader& shader, const GLchar *name) { - printf("Getting attribute for shader, name: %d, %s\n", shader, name); - GLint uid = glGetAttribLocation(shader, name); - if (uid < 0) { - printf("Unable to get attribute %s for shader %d\n", name, shader); - return -1; - } - return uid; -} - -inline void useShader(const Shader& shader) { - glUseProgram(shader); -} - -inline void setShaderFloat(GLint location, GLfloat value) { - glUniform1f(location, value); -} - -inline void setShaderInt(GLint location, GLint value) { - glUniform1i(location, value); -} - -inline void setShaderUint(GLint location, GLuint value) { - glUniform1ui(location, value); -} - -inline void setShaderVec2(GLint location, const Vector2& value) { - glUniform2f(location, value.x, value.y); -} - -inline void setShaderMat4(GLint location, const Mat4x4& matrix) { - glUniformMatrix4fv(location, 1, GL_FALSE, matrix.m); -} - -inline void setShaderBVec3(GLint location, bool first, bool second, bool third) { - glUniform3i(location, first, second, third); -} - -inline void setShaderBVec4(GLint location, bool first, bool second, bool third, bool fourth) { - glUniform4i(location, first, second, third, fourth); -} - -inline void setShaderBool(GLint location, bool value) { - glUniform1i(location, value); -} diff --git a/themes/src/WebglContext.cpp b/themes/src/WebglContext.cpp deleted file mode 100644 index df49c2d..0000000 --- a/themes/src/WebglContext.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "WebglContext.h" -#include - - -EM_BOOL onResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) { - WebglContext* context = (WebglContext*)userData; - - f64 inWidth, inHeight; - emscripten_get_element_css_size(context->query, &inWidth, &inHeight); - - context->width = static_cast(inWidth); - context->height = static_cast(inHeight); - - return true; -} - -void WebglContext::init(const char* inQuery) { - strcpy(query, inQuery); - f64 inWidth, inHeight; - emscripten_get_element_css_size(query, &inWidth, &inHeight); - width = static_cast(inWidth); - height = static_cast(inHeight); - emscripten_set_canvas_element_size( query, width, height); - - EmscriptenWebGLContextAttributes attrs; - emscripten_webgl_init_context_attributes(&attrs); - - attrs.enableExtensionsByDefault = 1; - attrs.majorVersion = 3; - attrs.minorVersion = 0; - - context = emscripten_webgl_create_context(query, &attrs); - makeCurrentContext(); - - glClearColor(0, 0, 0, 0.0f); - - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, onResize); -}; - -void WebglContext::makeCurrentContext() { - emscripten_webgl_make_context_current(context); -}; - -void WebglContext::destroy() { - emscripten_webgl_destroy_context(context); -} diff --git a/themes/src/WebglContext.h b/themes/src/WebglContext.h deleted file mode 100644 index 1956092..0000000 --- a/themes/src/WebglContext.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include "types.h" -#include -#include -#include -#include -#include - -struct WebglContext { - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context; - f32 width = 800; - f32 height = 600; - char query[128];; - - void init(const char* inQuery); - void makeCurrentContext(); - void destroy() ; -}; diff --git a/themes/src/autumn/AutumnTheme.cpp b/themes/src/autumn/AutumnTheme.cpp deleted file mode 100644 index 4b7a2e2..0000000 --- a/themes/src/autumn/AutumnTheme.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "AutumnTheme.hpp" -#include "../shapes_2d.h" -#include - -namespace -{ - const int NUM_HILLS = 3; -} - -AutumnTheme::AutumnTheme(WebglContext* context) -{ - renderer.load(context); - load(); -} - -AutumnTheme::~AutumnTheme() -{ - unload(); -} - -void AutumnTheme::load() { - renderer.clearColor = Vector4(252, 210, 153, 255).toNormalizedColor(); - auto lr = tree.load(&renderer); - leafParticles.load(&renderer, &lr); - background = new RectangularGradient( - renderer, - Vector4{135, 206, 235, 255}.toNormalizedColor(), - Vector4(252, 210, 153, 255).toNormalizedColor(), - renderer.get_width(), - renderer.get_height(), - {0, 0}); - - - background_hill = new Circleish( - renderer, - 1000, - Vector4(137, 129, 33, 255).toNormalizedColor(), - 100, - 0, - 50); - background_hill->mesh.model = background_hill->mesh.model.translateByVec2({1200, -700}); - - tree_hill = new Circleish( - renderer, - 500, - Vector4{ 76, 75, 22, 255 }.toNormalizedColor(), - 100, - 0, - 50); - tree_hill->mesh.model = tree_hill->mesh.model.translateByVec2(Vector2(300, -290)); -} - -void AutumnTheme::update(f32 dtSeconds) { - tree.update(dtSeconds); - leafParticles.update(dtSeconds); -} - -void AutumnTheme::render() { - renderer.render(); - background->render(); - background_hill->render(); - tree.render(&renderer); - tree_hill->render(); - leafParticles.render(&renderer); -} - -void AutumnTheme::unload() { - tree.unload(); - leafParticles.unload(); - delete background; - delete background_hill; - delete tree_hill; -} diff --git a/themes/src/autumn/AutumnTheme.hpp b/themes/src/autumn/AutumnTheme.hpp deleted file mode 100644 index e3f5748..0000000 --- a/themes/src/autumn/AutumnTheme.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef AUTUMN_THEME_HPP -#define AUTUMN_THEME_HPP - -#include "TreeShape.h" -#include "LeafParticleRender.h" -#include "../types.h" -#include "../theme.h" -#include "../Renderer2d.h" -#include -#include - -class RectangularGradient; -class Circleish; - -class AutumnTheme : public Theme { -public: - AutumnTheme(WebglContext*); - ~AutumnTheme(); - TreeShape tree; - LeafParticleRender leafParticles; - RectangularGradient* background; - Circleish* tree_hill; - Circleish* background_hill; - - void load(); - void update(f32 dtSeconds); - void render(); - void unload(); -private: - Renderer2d renderer; -}; - -#endif diff --git a/themes/src/autumn/LeafParticleRender.cpp b/themes/src/autumn/LeafParticleRender.cpp deleted file mode 100644 index fee3df2..0000000 --- a/themes/src/autumn/LeafParticleRender.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#include "LeafParticleRender.h" -#include "../Renderer2d.h" -#include "../mathlib.h" -#include "TreeShape.h" -#include "../types.h" -#include - -const i32 verticesPerLeaf = 6; -const f32 leafRadius = 3.f; -const i32 fallChanceMax = 100; - -inline void updateLeaf(Vertex2D* vertices, Vector2 position, Vector4 color, f32 scale) { - f32 radius = scale * leafRadius; - Vector2 bottomLeft = Vector2(-radius, -radius) + position; - Vector2 bottomRight = Vector2(radius, -radius) + position; - Vector2 topLeft = Vector2(-radius, radius) + position; - Vector2 topRight = Vector2(radius, radius) + position; - - vertices[0] = { bottomLeft, color, Mat4x4() }; - vertices[1] = { bottomRight, color, Mat4x4() }; - vertices[2] = { topLeft, color, Mat4x4() }; - vertices[3] = { topLeft, color, Mat4x4() }; - vertices[4] = { topRight, color, Mat4x4() }; - vertices[5] = { bottomRight, color, Mat4x4() }; -} - -void LeafParticleRender::load(Renderer2d *renderer, TreeShapeLoadResult* lr) { - LeafParticleLoadData ld; - ld.numLeaves = 256; - numLeaves = ld.numLeaves; - numVertices = ld.numLeaves * verticesPerLeaf; - - updateData = new LeafParticleUpdateData[numLeaves]; - vertices = new Vertex2D[numVertices]; - - for (i32 leafIdx = 0; leafIdx < numLeaves; leafIdx++) { - i32 randomBranch = randomIntBetween(0, lr->numBranches); - i32 randomVertex = randomIntBetween(0, 6); // TODO: Manually entering num vertices per branch. - updateData[leafIdx].vertexToFollow = &lr->updateData[randomBranch].vertices[randomVertex]; - updateData[leafIdx].fallChance = randomIntBetween(0, fallChanceMax); - updateData[leafIdx].color = Vector4(randomFloatBetween(0.3, 0.9), randomFloatBetween(0.1, 0.6), 0, 1); - updateData[leafIdx].vertexPtr = &vertices[leafIdx * verticesPerLeaf]; - updateData[leafIdx].resetTime = randomFloatBetween(4.f, 6.f); - } - - useShader(renderer->shader); - - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Vertex2D), &vertices[0], GL_DYNAMIC_DRAW); - - glEnableVertexAttribArray(renderer->attributes.position); - glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0); - - glEnableVertexAttribArray(renderer->attributes.color); - glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color)); - - for (i32 idx = 0; idx < 4; idx++) { - glEnableVertexAttribArray(renderer->attributes.vMatrix + idx); - glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)(offsetof(Vertex2D, vMatrix) + (idx * 16))); - } - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); -} - -void LeafParticleRender::update(f32 dtSeconds) { - elapsedTimeSeconds += dtSeconds; - - // Every time the fallIntervalSeconds passes, we remove one leaf - // from the tree and send it barrelling towards the earth. - i32 fallRoll; - bool didGenerateFall = false; - if (elapsedTimeSeconds >= fallIntervalSeconds) { - fallRoll = randomIntBetween(0, fallChanceMax); - didGenerateFall = true; - elapsedTimeSeconds = 0; - } - - for (i32 leafIdx = 0; leafIdx < numLeaves; leafIdx++) { - auto updateDataItem = &updateData[leafIdx]; - - if (didGenerateFall) { - if (updateDataItem->state == LeafParticleState::OnTree && updateDataItem->fallChance == fallRoll) { - updateDataItem->state = LeafParticleState::Falling; - updateDataItem->fallPosition = updateDataItem->vertexToFollow->position; - updateDataItem->fallVerticalVelocity = -randomFloatBetween(15.f, 25.f); - updateDataItem->fallHorizontalFrequency = randomFloatBetween(3.f, 5.f); - } - } - - switch (updateDataItem->state) { - case (LeafParticleState::Remerging): { - updateDataItem->timeElapsedSeconds += dtSeconds; - - if (updateDataItem->timeElapsedSeconds >= updateDataItem->resetTime) { - updateDataItem->timeElapsedSeconds = 0.f; - updateDataItem->state = LeafParticleState::OnTree; - updateDataItem->color.w = 1.f; - updateDataItem->scale = 1.f; - } - else { - updateDataItem->color.w = (updateDataItem->timeElapsedSeconds / updateDataItem->resetTime); - updateDataItem->scale = (updateDataItem->timeElapsedSeconds / updateDataItem->resetTime); - } - - updateLeaf(updateDataItem->vertexPtr, updateDataItem->vertexToFollow->position, updateDataItem->color, updateDataItem->scale); - break; - } - case (LeafParticleState::OnGround): { - updateDataItem->timeElapsedSeconds += dtSeconds; - - if (updateDataItem->timeElapsedSeconds >= updateDataItem->resetTime) { - updateDataItem->timeElapsedSeconds = 0.f; - updateDataItem->color.w = 0.f; - updateDataItem->state = LeafParticleState::Remerging; - } - else { - updateDataItem->color.w = 1.f - (updateDataItem->timeElapsedSeconds / updateDataItem->resetTime); - updateLeaf(updateDataItem->vertexPtr, updateDataItem->fallPosition, updateDataItem->color, updateDataItem->scale); - } - break; - } - case (LeafParticleState::Falling): { - updateDataItem->timeElapsedSeconds += dtSeconds; - const f32 xPosUpdate = cosf(updateDataItem->fallHorizontalFrequency * updateDataItem->timeElapsedSeconds); - updateDataItem->fallPosition.x += xPosUpdate; - updateDataItem->fallPosition.y += updateDataItem->fallVerticalVelocity * dtSeconds; - if (updateDataItem->fallPosition.y <= 50.f) { // TODO: Hardcoded ground for now - updateDataItem->fallPosition.y = 50.f; - updateDataItem->state = LeafParticleState::OnGround; - updateDataItem->timeElapsedSeconds = 0; - updateDataItem->resetTime = randomFloatBetween(2.f, 5.f); // TODO: Hardcoded reset interval - } - updateLeaf(updateDataItem->vertexPtr, updateDataItem->fallPosition, updateDataItem->color, updateDataItem->scale); - break; - } - case (LeafParticleState::OnTree): { - updateLeaf(updateDataItem->vertexPtr, updateDataItem->vertexToFollow->position, updateDataItem->color, updateDataItem->scale); - break; - } - } - } -} - -void LeafParticleRender::render(Renderer2d *renderer) { - setShaderMat4(renderer->uniforms.model, model); - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices * sizeof(Vertex2D), &vertices[0]); - - glBindVertexArray(vao); - glDrawArrays(GL_TRIANGLES, 0, numVertices); - glBindVertexArray(0); -} - -void LeafParticleRender::unload() { - glDeleteVertexArrays(1, &vao); - glDeleteBuffers(1, &vbo); - delete [] vertices; - - elapsedTimeSeconds = 0; -} diff --git a/themes/src/autumn/LeafParticleRender.h b/themes/src/autumn/LeafParticleRender.h deleted file mode 100644 index f6efe1f..0000000 --- a/themes/src/autumn/LeafParticleRender.h +++ /dev/null @@ -1,58 +0,0 @@ -#include "../Renderer2d.h" -#include "../mathlib.h" -#include "../types.h" - -struct TreeShapeLoadResult; - -struct LeafParticleLoadData { - Vector2 initPosition; - Vector4 initColor; - int numLeaves = 48; -}; - -enum LeafParticleState { - OnTree, - Falling, - OnGround, - Remerging -}; - -struct LeafParticleUpdateData { - LeafParticleState state = LeafParticleState::Remerging; - - Vertex2D* vertexToFollow = NULL; - Vector4 color = Vector4(1.f, 0.f, 0.f, 0.f); - f32 scale = 1.f; - - f32 timeElapsedSeconds = 0.f; - i32 fallChance = -1; - Vector2 fallPosition; - f32 fallVerticalVelocity; - f32 fallHorizontalFrequency; - - f32 resetTime = 0.f; - - Vertex2D* vertexPtr = NULL; -}; - -struct LeafParticleRender { - f32 elapsedTimeSeconds = 0.5; - f32 fallIntervalSeconds = 1.f; - - // Update data - i32 numLeaves = 0; - - LeafParticleUpdateData* updateData = NULL; - Vertex2D* vertices = NULL; - - // Render data - u32 vao; - u32 vbo; - u32 numVertices = 0; - Mat4x4 model; - - void load(Renderer2d* renderer, TreeShapeLoadResult* lr); - void update(f32 dtSeconds); - void render(Renderer2d* renderer); - void unload(); -}; \ No newline at end of file diff --git a/themes/src/autumn/TreeShape.cpp b/themes/src/autumn/TreeShape.cpp deleted file mode 100644 index 7c80929..0000000 --- a/themes/src/autumn/TreeShape.cpp +++ /dev/null @@ -1,214 +0,0 @@ -#include "TreeShape.h" -#include "../mathlib.h" -#include -#include -#include -#include - -void TreeBranchLoadData::fillVertices(Vertex2D* vertices, int branchTier) { - bottomLeft = Vector2 { position.x - width / 2.f, position.y }.rotateAround(rotation, position); - bottomRight = Vector2 { position.x + width / 2.f, position.y }.rotateAround(rotation, position); - topLeft = (Vector2 { position.x - width / 2.f, position.y + height }).rotateAround(rotation, position); - topRight = (Vector2 { position.x + width / 2.f, position.y + height }).rotateAround(rotation, position); - - topMidpoint = topLeft + (topRight - topLeft) / 2.f; - - vertices[0] = { bottomLeft, color}; - vertices[1] = { bottomRight, color}; - vertices[2] = { topLeft, color}; - vertices[3] = { topLeft, color}; - vertices[4] = { topRight, color}; - vertices[5] = { bottomRight, color}; -}; - -TreeShapeLoadResult TreeShape::load(Renderer2d* renderer) { - srand ( time(NULL) ); - - timeElapsedSeconds = 0; - - TreeLoadData ld; - - numBranches = pow(ld.divisionsPerBranch, ld.numBranchLevels + 1); - numVertices = 6 * numBranches; - - TreeBranchLoadData* generationData = new TreeBranchLoadData[numBranches]; - updateData = new TreeBranchUpdateData[numBranches]; - vertices = new Vertex2D[numVertices]; - - // The load result will contain information that we can pass on to our leaf renderer. - TreeShapeLoadResult lr; - lr.lowerBounds = Vector2(FLT_MAX, FLT_MAX); - lr.upperBounds = Vector2(FLT_MIN, FLT_MIN); - lr.updateData = updateData; - lr.numBranches = numBranches; - i32 branchIndex = 0; - createBranch(&ld, generationData, numBranches, &branchIndex, 0, ld.trunkWidth, ld.trunkHeight, Vector2 { 300.f, 200.f }, 0, NULL, vertices, &lr); - - useShader(renderer->shader); - - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Vertex2D), &vertices[0], GL_DYNAMIC_DRAW); - - glEnableVertexAttribArray(renderer->attributes.position); - glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0); - - glEnableVertexAttribArray(renderer->attributes.color); - glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color)); - - for (i32 idx = 0; idx < 4; idx++) { - glEnableVertexAttribArray(renderer->attributes.vMatrix + idx); - glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)(offsetof(Vertex2D, vMatrix) + (idx * 16))); - glVertexAttribDivisor(renderer->attributes.vMatrix + idx, 1); - } - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); - - delete [] generationData; - - return lr; -} - -const f32 ninetyDegreeRotation = PI / 2.f; - -void TreeShape::createBranch(TreeLoadData* ld, TreeBranchLoadData* generationData, i32 numBranches, i32* branchIndex, - i32 branchLevel, f32 width, f32 height, Vector2 position, f32 rotation, - TreeBranchUpdateData* parent, Vertex2D* vertices, TreeShapeLoadResult* lr) { - TreeBranchLoadData* branchLoadData = &generationData[*branchIndex]; - branchLoadData->width = width; - branchLoadData->height = height; - branchLoadData->position = position; - branchLoadData->rotation = rotation; - branchLoadData->fillVertices(&vertices[(*branchIndex) * 6], branchLevel); - - // Fil in the bounds for the LeafRenderer later. - if (branchLoadData->topMidpoint.x > lr->upperBounds.x) { - lr->upperBounds.x = branchLoadData->topMidpoint.x; - } - if (branchLoadData->topMidpoint.y > lr->upperBounds.y) { - lr->upperBounds.y = branchLoadData->topMidpoint.y; - } - if (branchLoadData->topMidpoint.x < lr->lowerBounds.x) { - lr->lowerBounds.x = branchLoadData->topMidpoint.x; - } - if (branchLoadData->topMidpoint.y < lr->lowerBounds.y) { - lr->lowerBounds.y = branchLoadData->topMidpoint.y; - } - - TreeBranchUpdateData* branchUpdateData = &updateData[*branchIndex]; - branchUpdateData->tier = branchLevel; - branchUpdateData->periodOffset = randomFloatBetween(0.f, 2.f * PI); - branchUpdateData->period = randomFloatBetween(3.f, 5.f); - branchUpdateData->amplitude = randomFloatBetween(0.01f, 0.05f); - branchUpdateData->branchToFollow = parent; - branchUpdateData->vertices = &vertices[(*branchIndex) * 6]; - - if (branchLevel == ld->numBranchLevels) { - return; - } - - for (int division = 0; division < ld->divisionsPerBranch; division++) { - // Weight between [0, 1] - float weight = static_cast(division) / static_cast(ld->divisionsPerBranch - 1); - - // Normalize the weight between [-1, 1] - f32 normalizedWeight = (0.5f - (weight)) * 2.f; - - // We want a rotation that takes the current rotation of the branch, and averages it between the two branches. - f32 branchRotationAmount = randomFloatBetween(PI / 8.f, PI / 3.f); - f32 branchRotation = branchLoadData->rotation + (normalizedWeight * branchRotationAmount); - - // Since trees are taller vertically, we will find a normalized value that describes how far the direction is from - // being horizontal. If it is closer to 1, we will make the branch taller on average. - f32 verticalHeightScaler = (fabs(fabs(branchRotation) - ninetyDegreeRotation) / ninetyDegreeRotation) * 0.1; - f32 branchWidth = width * randomFloatBetween(ld->trunkWidthScalerMin, ld->trunkWidthScalerMax); - f32 branchHeight = height * randomFloatBetween(ld->trunkHeightScalerMin + verticalHeightScaler, ld->trunkHeightScalerMax + verticalHeightScaler); - - - // We want the branch to start within the previous branch, so we drop it down into it based off of the rotation. - Vector2 branchOffsetVertical = Vector2{ 0, branchWidth }.rotate(branchRotation); - - Vector2 branchPosition = branchLoadData->topLeft + ((branchLoadData->topRight - branchLoadData->topLeft) * weight) - branchOffsetVertical; // Position of branch along the top of the parent branch - - (*branchIndex)++; - createBranch(ld, generationData, numBranches, branchIndex, branchLevel + 1, branchWidth, branchHeight, branchPosition, branchRotation, branchUpdateData, vertices, lr); - } -} - -void TreeShape::update(f32 dtSeconds) { - timeElapsedSeconds += dtSeconds; - - for (i32 bIdx = 0; bIdx < numBranches; bIdx++) { - TreeBranchUpdateData* branchUpdataData = &updateData[bIdx]; - - // Fade in simulation. We fade in based on the tier. - f32 animationStart = (branchUpdataData->tier * animateStaggerPerTier); - f32 animationEnd = animationStart + animateTimePerTier; - - f32 alpha = 0.f; - if (timeElapsedSeconds < animationStart) { - alpha = 0.f; - } - else if (timeElapsedSeconds > animationEnd) { - alpha = 1.f; - } - else { - alpha = (1.f - (animationEnd - timeElapsedSeconds)) / animateTimePerTier; - } - - i32 startParentIndex = bIdx * 6; - - branchUpdataData->currentOffset.x = branchUpdataData->amplitude * cosf(branchUpdataData->periodOffset + branchUpdataData->period * timeElapsedSeconds); - branchUpdataData->currentOffset.y = branchUpdataData->amplitude * sinf(branchUpdataData->periodOffset + branchUpdataData->period * timeElapsedSeconds); - - if (branchUpdataData->branchToFollow != NULL) { - branchUpdataData->currentOffset += branchUpdataData->branchToFollow->currentOffset; - - // The root of the branch only moves according to the change of the end of the parent. - branchUpdataData->vertices[0].color.w = alpha; - branchUpdataData->vertices[0].position += branchUpdataData->branchToFollow->currentOffset; - branchUpdataData->vertices[1].color.w = alpha; - branchUpdataData->vertices[1].position += branchUpdataData->branchToFollow->currentOffset; - branchUpdataData->vertices[5].color.w = alpha; - branchUpdataData->vertices[5].position += branchUpdataData->branchToFollow->currentOffset; - } - else { - branchUpdataData->vertices[0].color.w = alpha; - branchUpdataData->vertices[1].color.w = alpha; - branchUpdataData->vertices[5].color.w = alpha; - } - - - branchUpdataData->vertices[2].color.w = alpha; - branchUpdataData->vertices[2].position += branchUpdataData->currentOffset; - branchUpdataData->vertices[3].color.w = alpha; - branchUpdataData->vertices[3].position += branchUpdataData->currentOffset; - branchUpdataData->vertices[4].color.w = alpha; - branchUpdataData->vertices[4].position += branchUpdataData->currentOffset; - } -} - -void TreeShape::render(Renderer2d* renderer) { - setShaderMat4(renderer->uniforms.model, model); - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices * sizeof(Vertex2D), &vertices[0]); - - glBindVertexArray(vao); - glDrawArrays(GL_TRIANGLES, 0, numVertices); - glBindVertexArray(0); -} - -void TreeShape::unload() { - glDeleteVertexArrays(1, &vao); - glDeleteBuffers(1, &vbo); - delete[] vertices; - delete [] updateData; - timeElapsedSeconds = 0; - vertices = NULL; - updateData = NULL; -} diff --git a/themes/src/autumn/TreeShape.h b/themes/src/autumn/TreeShape.h deleted file mode 100644 index fc0d11e..0000000 --- a/themes/src/autumn/TreeShape.h +++ /dev/null @@ -1,74 +0,0 @@ -#include "../Renderer2d.h" -#include "../types.h" -#include "../mathlib.h" - -struct TreeLoadData { - f32 trunkHeight = 96.f; // Height of the trunk start - f32 trunkWidth = 32.f; // Width of the trunk start - f32 trunkHeightScalerMin = 0.7f; - f32 trunkHeightScalerMax = 0.8f; - f32 trunkWidthScalerMin = 0.35f; - f32 trunkWidthScalerMax = 0.75f; - i32 divisionsPerBranch = 2; // How many branches to split into at each branch split - i32 numBranchLevels = 8; // How many branch levels to display -}; - -struct TreeBranchLoadData { - f32 width = 0.f; - f32 height = 0.f; - Vector2 position; // Center point - f32 rotation = 0; // How much we are rotated off of the center point in radians - Vector4 color = Vector4(101,56,24, 1.f).toNormalizedColor(); - - // Calculated while filling in vertices - Vector2 bottomLeft; - Vector2 bottomRight; - Vector2 topLeft; - Vector2 topRight; - Vector2 topMidpoint; - - void fillVertices(Vertex2D* vertices, int branchTier); -}; - -struct TreeBranchUpdateData { - i32 tier = 0; - f32 periodOffset = 0; - f32 period = 0; - f32 amplitude = 0; - Vector2 currentOffset; - Vertex2D* vertices = NULL; - TreeBranchUpdateData* branchToFollow = NULL; -}; - -struct TreeShapeLoadResult { - Vector2 lowerBounds; - Vector2 upperBounds; - Vector2 center; - TreeBranchUpdateData* updateData; - u32 numBranches = 0; -}; - -struct TreeShape { - // Update data - TreeBranchUpdateData* updateData = NULL; - Vertex2D* vertices = NULL; - f32 timeElapsedSeconds = 0.f; - f32 animateTimePerTier = 1.f; - f32 animateStaggerPerTier = 0.2f; - u32 numBranches = 0; - - // Render data - u32 vao; - u32 vbo; - u32 numVertices = 0; - Mat4x4 model; - - TreeShapeLoadResult load(Renderer2d* renderer); - void createBranch(TreeLoadData* ld, TreeBranchLoadData* branchList, i32 numBranches, - i32* branchIndex, i32 branchLevel, f32 width, f32 height, - Vector2 position, f32 rotation, TreeBranchUpdateData* parent, Vertex2D* vertices, TreeShapeLoadResult* lr); - void update(f32 dtSeconds); - void render(Renderer2d* renderer); - void unload(); -}; - diff --git a/themes/src/autumn/autumn_theme.cpp b/themes/src/autumn/autumn_theme.cpp new file mode 100644 index 0000000..d88b265 --- /dev/null +++ b/themes/src/autumn/autumn_theme.cpp @@ -0,0 +1,73 @@ +#include "autumn_theme.hpp" +#include "../shapes_2d.h" +#include + +namespace +{ + const int NUM_HILLS = 3; +} + +AutumnTheme::AutumnTheme(WebglContext* context) +{ + renderer.load(context); + load(); +} + +AutumnTheme::~AutumnTheme() +{ + unload(); +} + +void AutumnTheme::load() { + renderer.clearColor = Vector4(252, 210, 153, 255).toNormalizedColor(); + auto lr = tree.load(&renderer); + leafParticles.load(&renderer, &lr); + background = new RectangularGradient( + renderer, + Vector4{135, 206, 235, 255}.toNormalizedColor(), + Vector4(252, 210, 153, 255).toNormalizedColor(), + renderer.get_width(), + renderer.get_height(), + {0, 0}); + + + background_hill = new Circleish( + renderer, + 1000, + Vector4(137, 129, 33, 255).toNormalizedColor(), + 100, + 0, + 50); + background_hill->mesh.model = background_hill->mesh.model.translateByVec2({1200, -700}); + + tree_hill = new Circleish( + renderer, + 500, + Vector4{ 76, 75, 22, 255 }.toNormalizedColor(), + 100, + 0, + 50); + tree_hill->mesh.model = tree_hill->mesh.model.translateByVec2(Vector2(300, -290)); +} + +void AutumnTheme::update(f32 dtSeconds) { + tree.update(dtSeconds); + leafParticles.update(dtSeconds); +} + +void AutumnTheme::render() { + renderer.render(); + background->render(); + background_hill->render(); + tree.render(&renderer); + tree_hill->render(); + leafParticles.render(&renderer); +} + +void AutumnTheme::unload() { + tree.unload(); + leafParticles.unload(); + delete background; + delete background_hill; + delete tree_hill; +} diff --git a/themes/src/autumn/autumn_theme.hpp b/themes/src/autumn/autumn_theme.hpp new file mode 100644 index 0000000..b61c0f3 --- /dev/null +++ b/themes/src/autumn/autumn_theme.hpp @@ -0,0 +1,33 @@ +#ifndef AUTUMN_THEME_HPP +#define AUTUMN_THEME_HPP + +#include "tree_shape.h" +#include "leaf_particle_render.h" +#include "../types.h" +#include "../theme.h" +#include "../renderer_2d.h" +#include +#include + +class RectangularGradient; +class Circleish; + +class AutumnTheme : public Theme { +public: + AutumnTheme(WebglContext*); + ~AutumnTheme(); + TreeShape tree; + LeafParticleRender leafParticles; + RectangularGradient* background; + Circleish* tree_hill; + Circleish* background_hill; + + void load(); + void update(f32 dtSeconds); + void render(); + void unload(); +private: + Renderer2d renderer; +}; + +#endif diff --git a/themes/src/autumn/leaf_particle_render.cpp b/themes/src/autumn/leaf_particle_render.cpp new file mode 100644 index 0000000..569bb2d --- /dev/null +++ b/themes/src/autumn/leaf_particle_render.cpp @@ -0,0 +1,166 @@ +#include "leaf_particle_render.h" +#include "../renderer_2d.h" +#include "../mathlib.h" +#include "tree_shape.h" +#include "../types.h" +#include + +const i32 verticesPerLeaf = 6; +const f32 leafRadius = 3.f; +const i32 fallChanceMax = 100; + +inline void updateLeaf(Vertex2D* vertices, Vector2 position, Vector4 color, f32 scale) { + f32 radius = scale * leafRadius; + Vector2 bottomLeft = Vector2(-radius, -radius) + position; + Vector2 bottomRight = Vector2(radius, -radius) + position; + Vector2 topLeft = Vector2(-radius, radius) + position; + Vector2 topRight = Vector2(radius, radius) + position; + + vertices[0] = { bottomLeft, color, Mat4x4() }; + vertices[1] = { bottomRight, color, Mat4x4() }; + vertices[2] = { topLeft, color, Mat4x4() }; + vertices[3] = { topLeft, color, Mat4x4() }; + vertices[4] = { topRight, color, Mat4x4() }; + vertices[5] = { bottomRight, color, Mat4x4() }; +} + +void LeafParticleRender::load(Renderer2d *renderer, TreeShapeLoadResult* lr) { + LeafParticleLoadData ld; + ld.numLeaves = 256; + numLeaves = ld.numLeaves; + numVertices = ld.numLeaves * verticesPerLeaf; + + updateData = new LeafParticleUpdateData[numLeaves]; + vertices = new Vertex2D[numVertices]; + + for (i32 leafIdx = 0; leafIdx < numLeaves; leafIdx++) { + i32 randomBranch = randomIntBetween(0, lr->numBranches); + i32 randomVertex = randomIntBetween(0, 6); // TODO: Manually entering num vertices per branch. + updateData[leafIdx].vertexToFollow = &lr->updateData[randomBranch].vertices[randomVertex]; + updateData[leafIdx].fallChance = randomIntBetween(0, fallChanceMax); + updateData[leafIdx].color = Vector4(randomFloatBetween(0.3, 0.9), randomFloatBetween(0.1, 0.6), 0, 1); + updateData[leafIdx].vertexPtr = &vertices[leafIdx * verticesPerLeaf]; + updateData[leafIdx].resetTime = randomFloatBetween(4.f, 6.f); + } + + useShader(renderer->shader); + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Vertex2D), &vertices[0], GL_DYNAMIC_DRAW); + + glEnableVertexAttribArray(renderer->attributes.position); + glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0); + + glEnableVertexAttribArray(renderer->attributes.color); + glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color)); + + for (i32 idx = 0; idx < 4; idx++) { + glEnableVertexAttribArray(renderer->attributes.vMatrix + idx); + glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)(offsetof(Vertex2D, vMatrix) + (idx * 16))); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} + +void LeafParticleRender::update(f32 dtSeconds) { + elapsedTimeSeconds += dtSeconds; + + // Every time the fallIntervalSeconds passes, we remove one leaf + // from the tree and send it barrelling towards the earth. + i32 fallRoll; + bool didGenerateFall = false; + if (elapsedTimeSeconds >= fallIntervalSeconds) { + fallRoll = randomIntBetween(0, fallChanceMax); + didGenerateFall = true; + elapsedTimeSeconds = 0; + } + + for (i32 leafIdx = 0; leafIdx < numLeaves; leafIdx++) { + auto updateDataItem = &updateData[leafIdx]; + + if (didGenerateFall) { + if (updateDataItem->state == LeafParticleState::OnTree && updateDataItem->fallChance == fallRoll) { + updateDataItem->state = LeafParticleState::Falling; + updateDataItem->fallPosition = updateDataItem->vertexToFollow->position; + updateDataItem->fallVerticalVelocity = -randomFloatBetween(15.f, 25.f); + updateDataItem->fallHorizontalFrequency = randomFloatBetween(3.f, 5.f); + } + } + + switch (updateDataItem->state) { + case (LeafParticleState::Remerging): { + updateDataItem->timeElapsedSeconds += dtSeconds; + + if (updateDataItem->timeElapsedSeconds >= updateDataItem->resetTime) { + updateDataItem->timeElapsedSeconds = 0.f; + updateDataItem->state = LeafParticleState::OnTree; + updateDataItem->color.w = 1.f; + updateDataItem->scale = 1.f; + } + else { + updateDataItem->color.w = (updateDataItem->timeElapsedSeconds / updateDataItem->resetTime); + updateDataItem->scale = (updateDataItem->timeElapsedSeconds / updateDataItem->resetTime); + } + + updateLeaf(updateDataItem->vertexPtr, updateDataItem->vertexToFollow->position, updateDataItem->color, updateDataItem->scale); + break; + } + case (LeafParticleState::OnGround): { + updateDataItem->timeElapsedSeconds += dtSeconds; + + if (updateDataItem->timeElapsedSeconds >= updateDataItem->resetTime) { + updateDataItem->timeElapsedSeconds = 0.f; + updateDataItem->color.w = 0.f; + updateDataItem->state = LeafParticleState::Remerging; + } + else { + updateDataItem->color.w = 1.f - (updateDataItem->timeElapsedSeconds / updateDataItem->resetTime); + updateLeaf(updateDataItem->vertexPtr, updateDataItem->fallPosition, updateDataItem->color, updateDataItem->scale); + } + break; + } + case (LeafParticleState::Falling): { + updateDataItem->timeElapsedSeconds += dtSeconds; + const f32 xPosUpdate = cosf(updateDataItem->fallHorizontalFrequency * updateDataItem->timeElapsedSeconds); + updateDataItem->fallPosition.x += xPosUpdate; + updateDataItem->fallPosition.y += updateDataItem->fallVerticalVelocity * dtSeconds; + if (updateDataItem->fallPosition.y <= 50.f) { // TODO: Hardcoded ground for now + updateDataItem->fallPosition.y = 50.f; + updateDataItem->state = LeafParticleState::OnGround; + updateDataItem->timeElapsedSeconds = 0; + updateDataItem->resetTime = randomFloatBetween(2.f, 5.f); // TODO: Hardcoded reset interval + } + updateLeaf(updateDataItem->vertexPtr, updateDataItem->fallPosition, updateDataItem->color, updateDataItem->scale); + break; + } + case (LeafParticleState::OnTree): { + updateLeaf(updateDataItem->vertexPtr, updateDataItem->vertexToFollow->position, updateDataItem->color, updateDataItem->scale); + break; + } + } + } +} + +void LeafParticleRender::render(Renderer2d *renderer) { + setShaderMat4(renderer->uniforms.model, model); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices * sizeof(Vertex2D), &vertices[0]); + + glBindVertexArray(vao); + glDrawArrays(GL_TRIANGLES, 0, numVertices); + glBindVertexArray(0); +} + +void LeafParticleRender::unload() { + glDeleteVertexArrays(1, &vao); + glDeleteBuffers(1, &vbo); + delete [] vertices; + + elapsedTimeSeconds = 0; +} diff --git a/themes/src/autumn/leaf_particle_render.h b/themes/src/autumn/leaf_particle_render.h new file mode 100644 index 0000000..1209e1b --- /dev/null +++ b/themes/src/autumn/leaf_particle_render.h @@ -0,0 +1,58 @@ +#include "../renderer_2d.h" +#include "../mathlib.h" +#include "../types.h" + +struct TreeShapeLoadResult; + +struct LeafParticleLoadData { + Vector2 initPosition; + Vector4 initColor; + int numLeaves = 48; +}; + +enum LeafParticleState { + OnTree, + Falling, + OnGround, + Remerging +}; + +struct LeafParticleUpdateData { + LeafParticleState state = LeafParticleState::Remerging; + + Vertex2D* vertexToFollow = NULL; + Vector4 color = Vector4(1.f, 0.f, 0.f, 0.f); + f32 scale = 1.f; + + f32 timeElapsedSeconds = 0.f; + i32 fallChance = -1; + Vector2 fallPosition; + f32 fallVerticalVelocity; + f32 fallHorizontalFrequency; + + f32 resetTime = 0.f; + + Vertex2D* vertexPtr = NULL; +}; + +struct LeafParticleRender { + f32 elapsedTimeSeconds = 0.5; + f32 fallIntervalSeconds = 1.f; + + // Update data + i32 numLeaves = 0; + + LeafParticleUpdateData* updateData = NULL; + Vertex2D* vertices = NULL; + + // Render data + u32 vao; + u32 vbo; + u32 numVertices = 0; + Mat4x4 model; + + void load(Renderer2d* renderer, TreeShapeLoadResult* lr); + void update(f32 dtSeconds); + void render(Renderer2d* renderer); + void unload(); +}; \ No newline at end of file diff --git a/themes/src/autumn/tree_shape.cpp b/themes/src/autumn/tree_shape.cpp new file mode 100644 index 0000000..622751b --- /dev/null +++ b/themes/src/autumn/tree_shape.cpp @@ -0,0 +1,214 @@ +#include "tree_shape.h" +#include "../mathlib.h" +#include +#include +#include +#include + +void TreeBranchLoadData::fillVertices(Vertex2D* vertices, int branchTier) { + bottomLeft = Vector2 { position.x - width / 2.f, position.y }.rotateAround(rotation, position); + bottomRight = Vector2 { position.x + width / 2.f, position.y }.rotateAround(rotation, position); + topLeft = (Vector2 { position.x - width / 2.f, position.y + height }).rotateAround(rotation, position); + topRight = (Vector2 { position.x + width / 2.f, position.y + height }).rotateAround(rotation, position); + + topMidpoint = topLeft + (topRight - topLeft) / 2.f; + + vertices[0] = { bottomLeft, color}; + vertices[1] = { bottomRight, color}; + vertices[2] = { topLeft, color}; + vertices[3] = { topLeft, color}; + vertices[4] = { topRight, color}; + vertices[5] = { bottomRight, color}; +}; + +TreeShapeLoadResult TreeShape::load(Renderer2d* renderer) { + srand ( time(NULL) ); + + timeElapsedSeconds = 0; + + TreeLoadData ld; + + numBranches = pow(ld.divisionsPerBranch, ld.numBranchLevels + 1); + numVertices = 6 * numBranches; + + TreeBranchLoadData* generationData = new TreeBranchLoadData[numBranches]; + updateData = new TreeBranchUpdateData[numBranches]; + vertices = new Vertex2D[numVertices]; + + // The load result will contain information that we can pass on to our leaf renderer. + TreeShapeLoadResult lr; + lr.lowerBounds = Vector2(FLT_MAX, FLT_MAX); + lr.upperBounds = Vector2(FLT_MIN, FLT_MIN); + lr.updateData = updateData; + lr.numBranches = numBranches; + i32 branchIndex = 0; + createBranch(&ld, generationData, numBranches, &branchIndex, 0, ld.trunkWidth, ld.trunkHeight, Vector2 { 300.f, 200.f }, 0, NULL, vertices, &lr); + + useShader(renderer->shader); + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Vertex2D), &vertices[0], GL_DYNAMIC_DRAW); + + glEnableVertexAttribArray(renderer->attributes.position); + glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0); + + glEnableVertexAttribArray(renderer->attributes.color); + glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color)); + + for (i32 idx = 0; idx < 4; idx++) { + glEnableVertexAttribArray(renderer->attributes.vMatrix + idx); + glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)(offsetof(Vertex2D, vMatrix) + (idx * 16))); + glVertexAttribDivisor(renderer->attributes.vMatrix + idx, 1); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + delete [] generationData; + + return lr; +} + +const f32 ninetyDegreeRotation = PI / 2.f; + +void TreeShape::createBranch(TreeLoadData* ld, TreeBranchLoadData* generationData, i32 numBranches, i32* branchIndex, + i32 branchLevel, f32 width, f32 height, Vector2 position, f32 rotation, + TreeBranchUpdateData* parent, Vertex2D* vertices, TreeShapeLoadResult* lr) { + TreeBranchLoadData* branchLoadData = &generationData[*branchIndex]; + branchLoadData->width = width; + branchLoadData->height = height; + branchLoadData->position = position; + branchLoadData->rotation = rotation; + branchLoadData->fillVertices(&vertices[(*branchIndex) * 6], branchLevel); + + // Fil in the bounds for the LeafRenderer later. + if (branchLoadData->topMidpoint.x > lr->upperBounds.x) { + lr->upperBounds.x = branchLoadData->topMidpoint.x; + } + if (branchLoadData->topMidpoint.y > lr->upperBounds.y) { + lr->upperBounds.y = branchLoadData->topMidpoint.y; + } + if (branchLoadData->topMidpoint.x < lr->lowerBounds.x) { + lr->lowerBounds.x = branchLoadData->topMidpoint.x; + } + if (branchLoadData->topMidpoint.y < lr->lowerBounds.y) { + lr->lowerBounds.y = branchLoadData->topMidpoint.y; + } + + TreeBranchUpdateData* branchUpdateData = &updateData[*branchIndex]; + branchUpdateData->tier = branchLevel; + branchUpdateData->periodOffset = randomFloatBetween(0.f, 2.f * PI); + branchUpdateData->period = randomFloatBetween(3.f, 5.f); + branchUpdateData->amplitude = randomFloatBetween(0.01f, 0.05f); + branchUpdateData->branchToFollow = parent; + branchUpdateData->vertices = &vertices[(*branchIndex) * 6]; + + if (branchLevel == ld->numBranchLevels) { + return; + } + + for (int division = 0; division < ld->divisionsPerBranch; division++) { + // Weight between [0, 1] + float weight = static_cast(division) / static_cast(ld->divisionsPerBranch - 1); + + // Normalize the weight between [-1, 1] + f32 normalizedWeight = (0.5f - (weight)) * 2.f; + + // We want a rotation that takes the current rotation of the branch, and averages it between the two branches. + f32 branchRotationAmount = randomFloatBetween(PI / 8.f, PI / 3.f); + f32 branchRotation = branchLoadData->rotation + (normalizedWeight * branchRotationAmount); + + // Since trees are taller vertically, we will find a normalized value that describes how far the direction is from + // being horizontal. If it is closer to 1, we will make the branch taller on average. + f32 verticalHeightScaler = (fabs(fabs(branchRotation) - ninetyDegreeRotation) / ninetyDegreeRotation) * 0.1; + f32 branchWidth = width * randomFloatBetween(ld->trunkWidthScalerMin, ld->trunkWidthScalerMax); + f32 branchHeight = height * randomFloatBetween(ld->trunkHeightScalerMin + verticalHeightScaler, ld->trunkHeightScalerMax + verticalHeightScaler); + + + // We want the branch to start within the previous branch, so we drop it down into it based off of the rotation. + Vector2 branchOffsetVertical = Vector2{ 0, branchWidth }.rotate(branchRotation); + + Vector2 branchPosition = branchLoadData->topLeft + ((branchLoadData->topRight - branchLoadData->topLeft) * weight) - branchOffsetVertical; // Position of branch along the top of the parent branch + + (*branchIndex)++; + createBranch(ld, generationData, numBranches, branchIndex, branchLevel + 1, branchWidth, branchHeight, branchPosition, branchRotation, branchUpdateData, vertices, lr); + } +} + +void TreeShape::update(f32 dtSeconds) { + timeElapsedSeconds += dtSeconds; + + for (i32 bIdx = 0; bIdx < numBranches; bIdx++) { + TreeBranchUpdateData* branchUpdataData = &updateData[bIdx]; + + // Fade in simulation. We fade in based on the tier. + f32 animationStart = (branchUpdataData->tier * animateStaggerPerTier); + f32 animationEnd = animationStart + animateTimePerTier; + + f32 alpha = 0.f; + if (timeElapsedSeconds < animationStart) { + alpha = 0.f; + } + else if (timeElapsedSeconds > animationEnd) { + alpha = 1.f; + } + else { + alpha = (1.f - (animationEnd - timeElapsedSeconds)) / animateTimePerTier; + } + + i32 startParentIndex = bIdx * 6; + + branchUpdataData->currentOffset.x = branchUpdataData->amplitude * cosf(branchUpdataData->periodOffset + branchUpdataData->period * timeElapsedSeconds); + branchUpdataData->currentOffset.y = branchUpdataData->amplitude * sinf(branchUpdataData->periodOffset + branchUpdataData->period * timeElapsedSeconds); + + if (branchUpdataData->branchToFollow != NULL) { + branchUpdataData->currentOffset += branchUpdataData->branchToFollow->currentOffset; + + // The root of the branch only moves according to the change of the end of the parent. + branchUpdataData->vertices[0].color.w = alpha; + branchUpdataData->vertices[0].position += branchUpdataData->branchToFollow->currentOffset; + branchUpdataData->vertices[1].color.w = alpha; + branchUpdataData->vertices[1].position += branchUpdataData->branchToFollow->currentOffset; + branchUpdataData->vertices[5].color.w = alpha; + branchUpdataData->vertices[5].position += branchUpdataData->branchToFollow->currentOffset; + } + else { + branchUpdataData->vertices[0].color.w = alpha; + branchUpdataData->vertices[1].color.w = alpha; + branchUpdataData->vertices[5].color.w = alpha; + } + + + branchUpdataData->vertices[2].color.w = alpha; + branchUpdataData->vertices[2].position += branchUpdataData->currentOffset; + branchUpdataData->vertices[3].color.w = alpha; + branchUpdataData->vertices[3].position += branchUpdataData->currentOffset; + branchUpdataData->vertices[4].color.w = alpha; + branchUpdataData->vertices[4].position += branchUpdataData->currentOffset; + } +} + +void TreeShape::render(Renderer2d* renderer) { + setShaderMat4(renderer->uniforms.model, model); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices * sizeof(Vertex2D), &vertices[0]); + + glBindVertexArray(vao); + glDrawArrays(GL_TRIANGLES, 0, numVertices); + glBindVertexArray(0); +} + +void TreeShape::unload() { + glDeleteVertexArrays(1, &vao); + glDeleteBuffers(1, &vbo); + delete[] vertices; + delete [] updateData; + timeElapsedSeconds = 0; + vertices = NULL; + updateData = NULL; +} diff --git a/themes/src/autumn/tree_shape.h b/themes/src/autumn/tree_shape.h new file mode 100644 index 0000000..0d18415 --- /dev/null +++ b/themes/src/autumn/tree_shape.h @@ -0,0 +1,74 @@ +#include "../renderer_2d.h" +#include "../types.h" +#include "../mathlib.h" + +struct TreeLoadData { + f32 trunkHeight = 96.f; // Height of the trunk start + f32 trunkWidth = 32.f; // Width of the trunk start + f32 trunkHeightScalerMin = 0.7f; + f32 trunkHeightScalerMax = 0.8f; + f32 trunkWidthScalerMin = 0.35f; + f32 trunkWidthScalerMax = 0.75f; + i32 divisionsPerBranch = 2; // How many branches to split into at each branch split + i32 numBranchLevels = 8; // How many branch levels to display +}; + +struct TreeBranchLoadData { + f32 width = 0.f; + f32 height = 0.f; + Vector2 position; // Center point + f32 rotation = 0; // How much we are rotated off of the center point in radians + Vector4 color = Vector4(101,56,24, 1.f).toNormalizedColor(); + + // Calculated while filling in vertices + Vector2 bottomLeft; + Vector2 bottomRight; + Vector2 topLeft; + Vector2 topRight; + Vector2 topMidpoint; + + void fillVertices(Vertex2D* vertices, int branchTier); +}; + +struct TreeBranchUpdateData { + i32 tier = 0; + f32 periodOffset = 0; + f32 period = 0; + f32 amplitude = 0; + Vector2 currentOffset; + Vertex2D* vertices = NULL; + TreeBranchUpdateData* branchToFollow = NULL; +}; + +struct TreeShapeLoadResult { + Vector2 lowerBounds; + Vector2 upperBounds; + Vector2 center; + TreeBranchUpdateData* updateData; + u32 numBranches = 0; +}; + +struct TreeShape { + // Update data + TreeBranchUpdateData* updateData = NULL; + Vertex2D* vertices = NULL; + f32 timeElapsedSeconds = 0.f; + f32 animateTimePerTier = 1.f; + f32 animateStaggerPerTier = 0.2f; + u32 numBranches = 0; + + // Render data + u32 vao; + u32 vbo; + u32 numVertices = 0; + Mat4x4 model; + + TreeShapeLoadResult load(Renderer2d* renderer); + void createBranch(TreeLoadData* ld, TreeBranchLoadData* branchList, i32 numBranches, + i32* branchIndex, i32 branchLevel, f32 width, f32 height, + Vector2 position, f32 rotation, TreeBranchUpdateData* parent, Vertex2D* vertices, TreeShapeLoadResult* lr); + void update(f32 dtSeconds); + void render(Renderer2d* renderer); + void unload(); +}; + diff --git a/themes/src/list.h b/themes/src/list.h index 25b236a..9b6a719 100644 --- a/themes/src/list.h +++ b/themes/src/list.h @@ -1,7 +1,7 @@ #pragma once #include #include -#include "Logger.h" +#include "logger.h" #define FOREACH(list) \ for (i32 idx = 0; idx < list.numElements; idx++) \ diff --git a/themes/src/logger.cpp b/themes/src/logger.cpp new file mode 100644 index 0000000..bead282 --- /dev/null +++ b/themes/src/logger.cpp @@ -0,0 +1,123 @@ +#include "logger.h" +#include +#include +#include + +namespace Logger { + LogLevel gLogLevel = LogLevel_Debug; + FILE* gFilePointer = NULL; + + void initialize(LoggerOptions options) { + setLevel(options.level); + if (options.logToFile) { +#ifdef WIN32 + fopen_s(&gFilePointer, options.filePath, "a"); +#else + gFilePointer = fopen(options.filePath, "a"); +#endif + } + } + + void setLevel(LogLevel level) { + gLogLevel = level; + } + + LogLevel getLevel() { + return gLogLevel; + } + + void printHeader(const char* levelStr, const char* fileName, int lineNumber) { + time_t t = time(0); + tm now; +#ifdef WIN32 + localtime_s(&now, &t); +#else + now = *localtime(&t); +#endif + + printf("%s:%d [%d-%d-%d %d:%d:%d] %s: ", fileName, lineNumber, (now.tm_year + 1900), (now.tm_mon + 1), now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, levelStr); + if (gFilePointer != NULL) { + fprintf(gFilePointer, "[%d-%d-%d %d:%d:%d] %s: ", (now.tm_year + 1900), (now.tm_mon + 1), now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, levelStr); + } + } + + void logInternal(const char* file, int lineNumber,LogLevel level, const char* format, va_list args) { + if (level < gLogLevel) { + return; + } + + + const char* levelStr; + switch (level) { + case LogLevel_Debug: + levelStr = "Debug"; + break; + case LogLevel_Info: + levelStr = "Info"; + break; + case LogLevel_Warn: + levelStr = "Warning"; + break; + case LogLevel_Error: + levelStr = "Error"; + break; + default: + levelStr = "Unknown"; + break; + } + + if (gFilePointer != NULL) { + va_list fileArgs; + va_copy(fileArgs, args); + vfprintf(gFilePointer, format, fileArgs); + fprintf(gFilePointer, "\n"); + } + + printHeader(levelStr, file, lineNumber); + + vprintf(format, args); + printf("\n"); + } + + void doLog(const char* file, int lineNumber,LogLevel level, const char* format, ...) { + va_list args; + va_start(args, format); + logInternal(file, lineNumber, level, format, args); + va_end(args); + } + + void doDebug(const char* file, int lineNumber,const char* format, ...) { + va_list args; + va_start(args, format); + logInternal(file, lineNumber, LogLevel_Debug, format, args); + va_end(args); + } + + void doInfo(const char* file, int lineNumber,const char* format, ...) { + va_list args; + va_start(args, format); + logInternal(file, lineNumber, LogLevel_Info, format, args); + va_end(args); + } + + void doWarning(const char* file, int lineNumber,const char* format, ...) { + va_list args; + va_start(args, format); + logInternal(file, lineNumber, LogLevel_Warn, format, args); + va_end(args); + } + + void doError(const char* file, int lineNumber,const char* format, ...) { + va_list args; + va_start(args, format); + logInternal(file, lineNumber, LogLevel_Error, format, args); + va_end(args); + } + + void free() { + if (gFilePointer) { + fclose(gFilePointer); + gFilePointer = NULL; + } + } +} diff --git a/themes/src/logger.h b/themes/src/logger.h new file mode 100644 index 0000000..7596b6f --- /dev/null +++ b/themes/src/logger.h @@ -0,0 +1,43 @@ +#ifndef LOGGER_H +#define LOGGER_H + +#include + +enum LogLevel { + LogLevel_Debug = 0, + LogLevel_Info = 1, + LogLevel_Warn = 2, + LogLevel_Error = 3 +}; + +struct LoggerOptions { + LogLevel level = LogLevel_Debug; + bool logToFile = false; + const char* filePath = "debug.log"; +}; + +namespace Logger { + void initialize(LoggerOptions options); + void setLevel(LogLevel level); + LogLevel getLevel(); + void doLog(const char* file, int lineNumber, LogLevel level, const char* format, ...); + void doDebug(const char* file, int lineNumber, const char* format, ...); + void doInfo(const char* file, int lineNumber, const char* format, ...); + void doWarning(const char* file, int lineNumber, const char* format, ...); + void doError(const char* file, int lineNumber, const char* format, ...); + void free(); +}; + +#if WIN32 +#define __FILENAME__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) +#else +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#endif + +#define logger_log(level, format, ...) Logger::doLog(__FILENAME__, __LINE__, level, format, ## __VA_ARGS__) +#define logger_debug(format, ...) Logger::doDebug(__FILENAME__, __LINE__, format, ## __VA_ARGS__) +#define logger_info(format, ...) Logger::doInfo(__FILENAME__, __LINE__, format, ## __VA_ARGS__) +#define logger_warning(format, ...) Logger::doWarning(__FILENAME__, __LINE__, format, ## __VA_ARGS__) +#define logger_error(format, ...) Logger::doError(__FILENAME__, __LINE__, format, ## __VA_ARGS__) + +#endif diff --git a/themes/src/main.cpp b/themes/src/main.cpp index d9cfc01..60e6aed 100644 --- a/themes/src/main.cpp +++ b/themes/src/main.cpp @@ -1,13 +1,13 @@ -#include "WebglContext.h" -#include "MainLoop.h" -#include "Renderer2d.h" +#include "webgl_context.h" +#include "main_loop.h" +#include "renderer_2d.h" #include "mathlib.h" #include "theme.h" #include "types.h" -#include "summer/SummerTheme.h" -#include "autumn/AutumnTheme.hpp" -#include "spring/SpringTheme.hpp" -#include "winter/WinterTheme.hpp" +#include "summer/summer_theme.h" +#include "autumn/autumn_theme.hpp" +#include "spring/spring_theme.hpp" +#include "winter/winter_theme.hpp" #include #include diff --git a/themes/src/main_loop.cpp b/themes/src/main_loop.cpp new file mode 100644 index 0000000..e5397ca --- /dev/null +++ b/themes/src/main_loop.cpp @@ -0,0 +1,31 @@ +#include "main_loop.h" +#include +#include + +EM_BOOL loop(double time, void* loop) { + MainLoop* mainLoop = (MainLoop*) loop; + if (!mainLoop->isRunning) { + return false; + } + + if (mainLoop->lastTime == 0) { + mainLoop->lastTime = time; + return true; + } + + long deltaTime = time - mainLoop->lastTime; + mainLoop->lastTime = time; + mainLoop->elapsedTime += deltaTime; + mainLoop->numFrames++; + float deltaTimeSeconds = static_cast(deltaTime) / 1000.f; + + if (mainLoop->elapsedTime >= 1000.0) { + printf("FPS: %d\n", mainLoop->numFrames); + + mainLoop->elapsedTime = 0.0; + mainLoop->numFrames = 0; + } + + mainLoop->updateFunc(deltaTimeSeconds, NULL); + return true; +} \ No newline at end of file diff --git a/themes/src/main_loop.h b/themes/src/main_loop.h new file mode 100644 index 0000000..07520a2 --- /dev/null +++ b/themes/src/main_loop.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include + +EM_BOOL loop(double time, void* loop); + +struct MainLoop { + bool isRunning = false; + double lastTime = 0, elapsedTime = 0; + int numFrames = 0; + void (*updateFunc)(float dtSeconds, void *userData); + + void run(void (*cb)(float dtSeconds, void *userData)) { + isRunning = true; + lastTime = 0; + elapsedTime = 0; + numFrames = 0; + updateFunc = cb; + + emscripten_request_animation_frame_loop(loop, this); + } + + void stop() { + isRunning = false; + } +}; diff --git a/themes/src/renderer_2d.cpp b/themes/src/renderer_2d.cpp new file mode 100644 index 0000000..7200669 --- /dev/null +++ b/themes/src/renderer_2d.cpp @@ -0,0 +1,126 @@ +#include "renderer_2d.h" +#include "shader.h" +#include "webgl_context.h" +#include "mathlib.h" +#include +#include "shaders/renderer2d_vert.h" +#include "shaders/renderer2d_frag.h" + +// Note: In the 'transform' attribute, the transform.x is the scale, +// transform.y is the rotation, and transform.zw is the translation. + +void Renderer2d::load(WebglContext* inContext, const char* inVertexShader, const char* inFragmentShader) { + auto vertexShader = inVertexShader ? inVertexShader : shader_renderer2d_vert; + auto fragmentShader = inFragmentShader ? inFragmentShader : shader_renderer2d_frag; + context = inContext; + printf("Compiling Renderer2d shader...\n"); + shader = loadShader(vertexShader, fragmentShader); + + useShader(shader); + attributes.position = getShaderAttribute(shader, "position"); + attributes.color = getShaderAttribute(shader, "color"); + attributes.vMatrix = getShaderAttribute(shader, "vMatrix"); + uniforms.projection = getShaderUniform(shader, "projection"); + uniforms.model = getShaderUniform(shader, "model"); + projection = Mat4x4().getOrthographicMatrix(0, context->width, 0, context->height); + + printf("Renderer2d shader compiled.\n"); +} + +void Renderer2d::render() { + projection = Mat4x4().getOrthographicMatrix(0, context->width, 0, context->height); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glDepthMask(GL_TRUE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + glClearColor(clearColor.x, clearColor.y, clearColor.z, clearColor.w); + glClearDepth(1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + useShader(shader); + setShaderMat4(uniforms.projection, projection); +} + +void Renderer2d::unload() { + glClearColor(0.f, 0.f, 0.f, 0.f); + glClear(GL_COLOR_BUFFER_BIT); + glDeleteProgram(shader); +} + +f32 Renderer2d::get_width() { + return context->width; +} + +f32 Renderer2d::get_height() { + return context->height; +} + + +void Mesh2D::load(Vertex2D* inVertices, u32 inNumVertices, Renderer2d* renderer) { + ebo = 0; + numVertices = inNumVertices; + useShader(renderer->shader); + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, inNumVertices * sizeof(Vertex2D), &inVertices[0], GL_STATIC_DRAW); + + glEnableVertexAttribArray(renderer->attributes.position); + glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0); + + glEnableVertexAttribArray(renderer->attributes.color); + glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color)); + + for (i32 idx = 0; idx < 4; idx++) { + glEnableVertexAttribArray(renderer->attributes.vMatrix + idx); + glVertexAttribPointer(renderer->attributes.vMatrix + idx, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)(offsetof(Vertex2D, vMatrix) + (idx * 16))); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} + + +void Mesh2D::load(Vertex2D* vertices, + u32 numVertices, + u32* indices, + u32 inNumIndices, + Renderer2d* renderer) { + load(vertices, numVertices, renderer); + glBindVertexArray(vao); + glGenBuffers(1, &ebo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, inNumIndices * sizeof(u32), &indices[0], GL_STATIC_DRAW); + numIndices = inNumIndices; + glBindVertexArray(0); +} + +void Mesh2D::render(Renderer2d* renderer, GLenum drawType) { + setShaderMat4(renderer->uniforms.model, model); + + if (ebo == 0) { + glBindVertexArray(vao); + glDrawArrays(drawType, 0, numVertices); + glBindVertexArray(0); + } + else { + glBindVertexArray(vao); + glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0); + glBindVertexArray(0); + } +} + +void Mesh2D::unload() { + glDeleteVertexArrays(1, &vao); + glDeleteBuffers(1, &vbo); + if (ebo != 0) { + glDeleteBuffers(1, &ebo); + ebo = 0; + } + vao = 0; + vbo = 0; +} diff --git a/themes/src/renderer_2d.h b/themes/src/renderer_2d.h new file mode 100644 index 0000000..d572533 --- /dev/null +++ b/themes/src/renderer_2d.h @@ -0,0 +1,61 @@ +#pragma once + +#include "webgl_context.h" +#include "types.h" +#include "shader.h" +#include "mathlib.h" + +struct WebglContext; + +/// Responsible for rendering Mesh2Ds +struct Renderer2d { + WebglContext* context = NULL; + Mat4x4 projection; + u32 shader; + Vector4 clearColor; + + struct { + i32 position; + i32 color; + + // TODO: vMatrix is not standard and does not belong here + i32 vMatrix; + } attributes; + + struct { + i32 projection; + i32 model; + } uniforms; + + /// Load with the provided context and shader programs. If the shaders are NULL, the default + /// shader is used + void load(WebglContext* context, const char* vertexShader = NULL, const char* fragmentShader = NULL); + void render(); + void unload(); + f32 get_width(); + f32 get_height(); +}; + +struct Vertex2D { + Vector2 position; + Vector4 color; + Mat4x4 vMatrix; +}; + +struct Mesh2D { + u32 vao; + u32 vbo; + u32 ebo = 0; + u32 numVertices = 0; + u32 numIndices = 0; + Mat4x4 model; + + void load(Vertex2D* vertices, u32 numVertices, Renderer2d* renderer); + void load(Vertex2D* vertices, + u32 numVertices, + u32* indices, + u32 numIndices, + Renderer2d* renderer); + void render(Renderer2d* renderer, GLenum drawType = GL_TRIANGLES); + void unload(); +}; diff --git a/themes/src/renderer_3d.cpp b/themes/src/renderer_3d.cpp new file mode 100644 index 0000000..cc79940 --- /dev/null +++ b/themes/src/renderer_3d.cpp @@ -0,0 +1,239 @@ +#include "renderer_3d.h" +#include "shader.h" +#include "list.h" +#include "mathlib.h" +#include "webgl_context.h" +#include "logger.h" +#include + +// Note: In the 'transform' attribute, the transform.x is the scale, +// transform.y is the rotation, and transform.zw is the translatiob. +EM_BOOL onScreenSizeChanged_3D(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) { + Renderer3d* renderer = (Renderer3d*)userData; + + EMSCRIPTEN_RESULT result = emscripten_set_canvas_element_size( renderer->context->query, uiEvent->documentBodyClientWidth, uiEvent->documentBodyClientHeight); + if (result != EMSCRIPTEN_RESULT_SUCCESS) { + logger_error("Failed to resize element at query: %s\n", renderer->context->query); + } + //renderer->projection = Mat4x4().getOrthographicMatrix(0, renderer->context->width, 0, renderer->context->height); + + return true; +} + +void Renderer3d::load(WebglContext* inContext, const char* vertexShader, const char* fragmentShader) { + context = inContext; + printf("Compiling Renderer3d shader...\n"); + shader = loadShader(vertexShader, fragmentShader); + + useShader(shader); + attributes.position = getShaderAttribute(shader, "position"); + attributes.color = getShaderAttribute(shader, "color"); + attributes.normal = getShaderAttribute(shader, "normal"); + uniforms.projection = getShaderUniform(shader, "projection"); + uniforms.view = getShaderUniform(shader, "view"); + uniforms.model = getShaderUniform(shader, "model"); + projection = Mat4x4().getPerspectiveProjection(0.1, 1000.f, 0.872f, static_cast(context->width) / static_cast(context->height)); + view = Mat4x4().getLookAt({ 0, 25, 75 }, { 0, 15, 0 }, { 0, 1, 0 }); + + logger_info("Renderer3d shader compiled.\n"); + + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, onScreenSizeChanged_3D); +} + +void Renderer3d::render() { + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glDepthMask(GL_TRUE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + glClearColor(clearColor.x, clearColor.y, clearColor.z, clearColor.w); + glClearDepth(1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + useShader(shader); + setShaderMat4(uniforms.projection, projection); + setShaderMat4(uniforms.view, view); +} + +void Renderer3d::unload() { + glClearColor(0.f, 0.f, 0.f, 0.f); + glClear(GL_COLOR_BUFFER_BIT); + glDeleteProgram(shader); +} + +enum LineType { + LineType_None, + LineType_Comment, + LineType_mtl, + LineType_v, + LineType_f, + LineType_Unsupported +}; + +struct LineItem { + LineType type = LineType_None; + + i32 idx = 0; + + union { + f32 vertices[3]; + i32 indices[3]; + } v; +}; + +inline i32 readPastSpaces(i32 i, const char* content) { + while (content[i] == ' ') i++; + return i; +} + +inline i32 readPastLine(i32 i, const char* content) { + while (content[i] != '\n' && content[i] != '\0') i++; + return i; +} + +inline i32 readToken(i32 i, const char* content, char* output) { + i32 tidx = 0; + i = readPastSpaces(i, content); + while (content[i] != ' ' && content[i] != '\n' && content[i] != '\0') { + output[tidx] = content[i]; + i++; + tidx++; + } + output[tidx] = '\0'; + return i; +} + +Mesh3d Mesh3d_fromObj(Renderer3d* renderer, const char* content, const i32 len) { + Mesh3d result; + result.vertices.allocate(2048); + result.indices.allocate(2048); + + LineItem lt; + lt.type = LineType_None; + i32 lineNumber = 0; + i32 i = 0; + while (content[i] != '\0') { + i = readPastSpaces(i, content); + if (lt.type == LineType_None) { + lineNumber++; + char type[32]; + i = readToken(i, content, type); + + if (strncmp(type, "#", 1) == 0) { + lt.type = LineType_Comment; + } + else if (strncmp(type, "mtllib", 6) == 0) { + lt.type = LineType_mtl; + } + else if (strncmp(type, "v", 1) == 0) { + lt.type = LineType_v; + } + else if (strncmp(type, "f", 1) == 0) { + lt.type = LineType_f; + } + else { + i++; + //lt.type = LineType_Unsupported; + //logger_error("Unknown type %s, %d", type, lineNumber); + } + } + else { + char buffer[32]; + switch (lt.type) { + case LineType_mtl: + i = readToken(i, content, buffer); + break; + case LineType_v: { + while (content[i] != '\n' && content[i] != '\0') { + i = readToken(i, content, buffer); + lt.v.vertices[lt.idx] = atof(buffer); + lt.idx++; + } + + float fColor = randomFloatBetween(0.8, 1); + result.vertices.add({ + Vector4(lt.v.vertices[0], lt.v.vertices[1], lt.v.vertices[2], 1.f), + Vector4(fColor, fColor, fColor, 1) + }); + break; + } + case LineType_f: { + while (content[i] != '\n' && content[i] != '\0') { + i = readToken(i, content, buffer); + lt.v.indices[lt.idx] = atoi(buffer); + lt.idx++; + } + + auto v1idx = lt.v.indices[0] - 1; + auto v2idx = lt.v.indices[1] - 1; + auto v3idx = lt.v.indices[2] - 1; + + result.indices.add(v1idx); + result.indices.add(v2idx); + result.indices.add(v3idx); + + auto& v1 = result.vertices[v1idx]; + auto& v2 = result.vertices[v2idx]; + auto& v3 = result.vertices[v3idx]; + Vector3 normal = (v1.position - v2.position).cross(v1.position - v3.position).toVector3().normalize(); + v1.normal = normal; + v2.normal = normal; + v3.normal = normal; + break; + } + default: + i = readPastLine(i, content); + break; + } + + lt = LineItem(); + } + } + + printf("Completed Mesh3d loading.\n"); + result.load(renderer); + return result; +} + +void Mesh3d::load(Renderer3d* renderer) { + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + glGenBuffers(1, &ebo); + + glBindVertexArray(vao); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, vertices.numElements * sizeof(Vertex3d), &vertices.data[0], GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.numElements * sizeof(GLuint), &indices.data[0], GL_STATIC_DRAW); + + // Position + glEnableVertexAttribArray(renderer->attributes.position); + glVertexAttribPointer(renderer->attributes.position, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex3d), (GLvoid *)0); + + // Color + glEnableVertexAttribArray(renderer->attributes.color); + glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex3d), (GLvoid *)offsetof(Vertex3d, color)); + + // Normal + glEnableVertexAttribArray(renderer->attributes.normal); + glVertexAttribPointer(renderer->attributes.normal, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex3d), (GLvoid *)offsetof(Vertex3d, normal)); + + glBindVertexArray(0); +} + +void Mesh3d::unload() { + if (vao) glDeleteVertexArrays(1, &vao); + if (vbo) glDeleteBuffers(1, &vbo); + if (ebo) glDeleteBuffers(1, &ebo); + vertices.deallocate(); + indices.deallocate(); +} + +void Mesh3d::render(Renderer3d* renderer) { + setShaderMat4(renderer->uniforms.model, model); + + glBindVertexArray(vao); + glDrawElements(GL_TRIANGLES, indices.numElements, GL_UNSIGNED_INT, 0); + glBindVertexArray(0); +} diff --git a/themes/src/renderer_3d.h b/themes/src/renderer_3d.h new file mode 100644 index 0000000..5b2c8c8 --- /dev/null +++ b/themes/src/renderer_3d.h @@ -0,0 +1,56 @@ +#ifndef RENDERER3D_H +#define RENDERER3D_H +#include "mathlib.h" +#include "list.h" +#include "types.h" +#include + +struct Renderer3d; + +struct Vertex3d { + Vector4 position; + Vector4 color; + Vector4 normal; +}; + +struct Mesh3d { + u32 vao; + u32 vbo; + u32 ebo; + matte::List vertices; + matte::List indices; + Mat4x4 model; + + void load(Renderer3d* renderer); + void render(Renderer3d* renderer); + void unload(); +}; + +struct WebglContext; +struct Renderer3d { + WebglContext* context = NULL; + Mat4x4 projection; + Mat4x4 view; + u32 shader; + Vector4 clearColor; + + struct { + i32 position; + i32 color; + i32 normal; + } attributes; + + struct { + i32 projection; + i32 view; + i32 model; + } uniforms; + + void load(WebglContext* context, const char* vertexShader, const char* fragmentShader); + void render(); + void unload(); +}; + +Mesh3d Mesh3d_fromObj(Renderer3d* renderer, const char* content, const i32 len); + +#endif diff --git a/themes/src/shader.cpp b/themes/src/shader.cpp new file mode 100644 index 0000000..ed2cab5 --- /dev/null +++ b/themes/src/shader.cpp @@ -0,0 +1,61 @@ +#include "shader.h" +#include + +GLuint loadIndividualShader(GLenum shaderType, const GLchar* cCode) { + GLuint shader = glCreateShader(shaderType); + glShaderSource(shader, 1, &cCode, 0); + glCompileShader(shader); + GLint success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { + GLchar infoLog[512]; + glGetShaderInfoLog(shader, 512, 0, infoLog); + printf("Failed to load shader: %s, Shader =%s\n", infoLog, cCode); + return 0; + } + + return shader; +} + +void attachShaders(Shader& retVal, const GLchar* vertexShader, const GLchar* fragmentShader) { + GLuint vertex = 0, fragment = 0, geometry = 0; + if (vertexShader) { + vertex = loadIndividualShader(GL_VERTEX_SHADER, vertexShader); + glAttachShader(retVal, vertex); + } + + if (fragmentShader) { + fragment = loadIndividualShader(GL_FRAGMENT_SHADER, fragmentShader); + glAttachShader(retVal, fragment); + } + + glLinkProgram(retVal); + GLint isLinked = 0; + glGetProgramiv(retVal, GL_LINK_STATUS, (int*)&isLinked); + if (isLinked == GL_FALSE) { + GLint maxLength = 0; + glGetProgramiv(retVal, GL_INFO_LOG_LENGTH, &maxLength); + + // The maxLength includes the NULL character + GLchar* infoLog = new GLchar[maxLength]; + glGetProgramInfoLog(retVal, maxLength, &maxLength, infoLog); + glDeleteProgram(retVal); + printf("Error. Could not initialize shader with vertex=%s, error=%s\n", vertexShader, infoLog); + delete []infoLog; + } + + if (vertexShader) + glDeleteShader(vertex); + if (fragmentShader) + glDeleteShader(fragment); +} + +Shader loadShader(const GLchar* vertexShader, const GLchar* fragmentShader) { + Shader retVal; + retVal = glCreateProgram(); + + attachShaders(retVal, vertexShader, fragmentShader); + useShader(retVal); + + return retVal; +} diff --git a/themes/src/shader.h b/themes/src/shader.h new file mode 100644 index 0000000..bc81764 --- /dev/null +++ b/themes/src/shader.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include "mathlib.h" + +typedef GLuint Shader; + +Shader loadShader(const GLchar* vertexShader, const GLchar* fragmentShader); + +inline GLint getShaderUniform(const Shader& shader, const GLchar *name) { + GLint uid = glGetUniformLocation(shader, name); + if (uid < 0) { + return -1; + } + return uid; +} + +inline GLint getShaderAttribute(const Shader& shader, const GLchar *name) { + printf("Getting attribute for shader, name: %d, %s\n", shader, name); + GLint uid = glGetAttribLocation(shader, name); + if (uid < 0) { + printf("Unable to get attribute %s for shader %d\n", name, shader); + return -1; + } + return uid; +} + +inline void useShader(const Shader& shader) { + glUseProgram(shader); +} + +inline void setShaderFloat(GLint location, GLfloat value) { + glUniform1f(location, value); +} + +inline void setShaderInt(GLint location, GLint value) { + glUniform1i(location, value); +} + +inline void setShaderUint(GLint location, GLuint value) { + glUniform1ui(location, value); +} + +inline void setShaderVec2(GLint location, const Vector2& value) { + glUniform2f(location, value.x, value.y); +} + +inline void setShaderMat4(GLint location, const Mat4x4& matrix) { + glUniformMatrix4fv(location, 1, GL_FALSE, matrix.m); +} + +inline void setShaderBVec3(GLint location, bool first, bool second, bool third) { + glUniform3i(location, first, second, third); +} + +inline void setShaderBVec4(GLint location, bool first, bool second, bool third, bool fourth) { + glUniform4i(location, first, second, third, fourth); +} + +inline void setShaderBool(GLint location, bool value) { + glUniform1i(location, value); +} diff --git a/themes/src/shapes_2d.cpp b/themes/src/shapes_2d.cpp index d5a29ed..e00c521 100644 --- a/themes/src/shapes_2d.cpp +++ b/themes/src/shapes_2d.cpp @@ -1,5 +1,5 @@ #include "shapes_2d.h" -#include "Renderer2d.h" +#include "renderer_2d.h" #include "mathlib.h" #include "list.h" diff --git a/themes/src/shapes_2d.h b/themes/src/shapes_2d.h index 8e08504..325d525 100644 --- a/themes/src/shapes_2d.h +++ b/themes/src/shapes_2d.h @@ -2,7 +2,7 @@ #define SHAPES_2D #include "mathlib.h" -#include "Renderer2d.h" +#include "renderer_2d.h" #include "types.h" class RectangularGradient diff --git a/themes/src/spring/GrassRenderer.cpp b/themes/src/spring/GrassRenderer.cpp deleted file mode 100644 index b69d111..0000000 --- a/themes/src/spring/GrassRenderer.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "GrassRenderer.hpp" -#include "Renderer3d.h" - -void GrassRenderer::load(GrassRendererLoadData params, Renderer3d* renderer) { - const f32 COLUMN_INCREMENT = GRASS_BLADES_PER_COL / params.area.x; - const f32 ROW_INCREMENT = GRASS_BLADES_PER_ROW / params.area.y; - for (i32 r = 0; r < GRASS_BLADES_PER_ROW; r++) { - i32 indexOffset = r * GRASS_BLADES_PER_ROW; - f32 y = ROW_INCREMENT * r; - for (i32 c = 0; c < GRASS_BLADES_PER_COL; c++) { - f32 x = COLUMN_INCREMENT * c; - i32 index = indexOffset + c; - grassBlades[index].position = Vector3(x, y, 0); - grassBlades[index].top_offset = Vector2(0, 0); - } - } -} - -void GrassRenderer::update(f32 seconds) { - -} - -void GrassRenderer::render(Renderer3d* renderer) { - -} - -void GrassRenderer::unload() { - -} diff --git a/themes/src/spring/GrassRenderer.hpp b/themes/src/spring/GrassRenderer.hpp deleted file mode 100644 index 8c96724..0000000 --- a/themes/src/spring/GrassRenderer.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef GRASS_RENDERER_HPP -#define GRASS_RENDERER_HPP - -#include "Renderer3d.h" -#include "mathlib.h" -#include "types.h" - -const i32 GRASS_BLADES_PER_ROW = 24; -const i32 GRASS_BLADES_PER_COL = 24; -const i32 NUM_GRASS_BLADES = GRASS_BLADES_PER_ROW * GRASS_BLADES_PER_COL; - -struct GrassRendererLoadData { - Vector2 origin = Vector2(0, 0); - Vector2 area = Vector2(480, 480); - f32 grassHeight = 12.f; -}; - -struct GrassUpdateData { - Vector3 position; - Vector2 top_offset; -}; - -struct GrassRenderer { - - GrassUpdateData grassBlades[NUM_GRASS_BLADES]; - - void load(GrassRendererLoadData params, Renderer3d* renderer); - void update(f32 dtSeconds); - void render(Renderer3d* renderer); - void unload(); -}; - -#endif diff --git a/themes/src/spring/SpringTheme.cpp b/themes/src/spring/SpringTheme.cpp deleted file mode 100644 index e39c138..0000000 --- a/themes/src/spring/SpringTheme.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include "SpringTheme.hpp" -#include "../Renderer3d.h" -#include "../shader_fetcher.hpp" -#include -#include - -void onBunnySuccess(emscripten_fetch_t *fetch) { - SpringTheme* springTheme = (SpringTheme*)fetch->userData; - springTheme->state = SpringThemeState::LoadedBunny; - printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url); - const i32 len = fetch->numBytes; - springTheme->bunnyMesh = Mesh3d_fromObj(&springTheme->renderer, fetch->data, len); - // The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1]; - emscripten_fetch_close(fetch); // Free data associated with the fetch. -} - -void onBunnyFail(emscripten_fetch_t *fetch) { - printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status); - emscripten_fetch_close(fetch); // Also free data on failure. -} - -inline void fetch_bunny(SpringTheme* theme) { - emscripten_fetch_attr_t attr; - emscripten_fetch_attr_init(&attr); - strcpy(attr.requestMethod, "GET"); - attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; - attr.onsuccess = onBunnySuccess; - attr.onerror = onBunnyFail; - auto* bunny_fetch = emscripten_fetch(&attr, "themes/resources/bunny.obj"); - bunny_fetch->userData = theme; -} - -inline void on_shaders_loader(ShaderFetchResult* result) { - SpringTheme* theme = (SpringTheme*)result->user_data; - theme->renderer.load(theme->renderer.context, result->vertex.c_str(), result->fragment.c_str()); - theme->state = SpringThemeState::LoadedShader; - fetch_bunny(theme); -} - -SpringTheme::SpringTheme(WebglContext* context) -{ - load(context); -} - -SpringTheme::~SpringTheme() -{ - unload(); -} - -void SpringTheme::load(WebglContext* context) { - state = SpringThemeState::Loading; - renderer.context = context; - renderer.clearColor = Vector4(160, 231, 160, 255.f).toNormalizedColor(); - - fetch_shader( - { - "themes/src/_shaders/renderer3d.vert", - "themes/src/_shaders/renderer3d.frag" - }, - on_shaders_loader, - this - ); -} - -inline Vector3 bunnyLerp(Vector3& start, Vector3& target, f32 t) { - t = 3 * t *t - 2 * t * t * t; - return start + ((target - start) * t); -} - -inline f32 verticalHopLerp(f32 start, f32 target, f32 t) { - f32 ogt = t; - t = 3 * t *t - 2 * t * t * t; - if (ogt >= 0.5f) t = 1.f - t; - return start + ((target - start) * t); -} - -inline f32 rotationLerp(f32 start, f32 target, f32 t) { - return start + ((target - start) * t); -} - -void SpringTheme::update(f32 dtSeconds) { - switch (state) { - case SpringThemeState::Loading: return; - case SpringThemeState::LoadedShader: return; - case SpringThemeState::LoadedBunny: - state = SpringThemeState::Idle; - stateTimer = 0.f; - bunnyHopAnimationTimer = 0.f; - break; - case SpringThemeState::Idle: { - bunnyHopAnimationTimer += dtSeconds; - const f32 HOP_FREQUENCY = 6.f; - - if (bunnyHopAnimationTimer > stateTimer) { - state = SpringThemeState::PreHop; - f32 xDir = 1; - f32 yDir = 1; - if (bunnyTarget.x > 0) xDir = -1; - if (bunnyTarget.z > 0) yDir = -1; - bunnyTarget = bunnyPosition + Vector3(randomFloatBetween(0, xDir * 25), 0, randomFloatBetween(0, yDir * 25)); - auto direction = (bunnyTarget - bunnyPosition); - auto distance = direction.length(); - direction = direction.normalize(); - numHops = ceil(distance / HOP_FREQUENCY); - hopCount = 0; - - targetRotation = PI - atan2(direction.y, direction.x); - stateTimer = ((bunnyTarget - bunnyPosition).length() / bunnySpeed) / numHops; - bunnyHopAnimationTimer = 0.f; - hopIncrement = (bunnyTarget - bunnyPosition) / numHops; - } - break; - } - case SpringThemeState::PreHop: { - const f32 ROTATION_TIME = 0.5f; - bunnyHopAnimationTimer += dtSeconds; - f32 current = bunnyRotation + (targetRotation - bunnyRotation) * (bunnyHopAnimationTimer / ROTATION_TIME); - bunnyMesh.model = Mat4x4().rotate(0, current, 0).translate(bunnyPosition); - - if (bunnyHopAnimationTimer > ROTATION_TIME) { - bunnyRotation = targetRotation; - bunnyHopAnimationTimer = 0; - state = SpringThemeState::Hopping; - } - break; - } - case SpringThemeState::Hopping: { - bunnyHopAnimationTimer += dtSeconds; - f32 t = bunnyHopAnimationTimer / stateTimer; - - Vector3 nextPosition = bunnyPosition + hopIncrement; - auto renderPos = bunnyLerp(bunnyPosition, nextPosition, t); - if ((renderPos - nextPosition).length() < 0.01f) { - hopCount += 1; - bunnyHopAnimationTimer = 0.f; - bunnyPosition = nextPosition; - } - - renderPos.y = verticalHopLerp(0.f, 4.f, t); - - const f32 RMAX = PI / 16.f; - f32 zRotation = 0; - f32 start = 0.f; - f32 end = PI / 8.f; - f32 startTime = 0.f; - f32 endTime = 0.f; - bool disableRot = false; - - if (t >= 0.9f) { - disableRot = true; - } - else if (t >= 0.7f) { - start = -RMAX; - end = 0.f; - startTime = 0.7f; - endTime = 0.9f; - } - else if (t >= 0.50f) { - start = 0.f; - end = -RMAX; - startTime = 0.50f; - endTime = 0.70f; - } - else if (t >= 0.40f) { - disableRot = true; - } - else if (t >= 0.20f) { - start = RMAX; - end = 0.f; - startTime = 0.20f; - endTime = 0.40f; - } - else { - start = 0.f; - end = RMAX; - startTime = 0.f; - endTime = 0.20f; - } - - - if (!disableRot) { - f32 totalTime = endTime - startTime; - zRotation = rotationLerp(start, end, (totalTime - (endTime - t)) / totalTime); - } - - bunnyMesh.model = Mat4x4().getZRotationMatrix(zRotation).rotate(0, bunnyRotation, 0).translate(renderPos); - if (hopCount == numHops) { - bunnyPosition = bunnyTarget; - bunnyHopAnimationTimer = 0.f; - state = SpringThemeState::Idle; - stateTimer = randomFloatBetween(0.5f, 1.f); - } - break; - } - } -} - -void SpringTheme::render() { - renderer.render(); - if (state != SpringThemeState::Loading) { - bunnyMesh.render(&renderer); - } -} - -void SpringTheme::unload() { - renderer.unload(); - bunnyMesh.unload(); -} diff --git a/themes/src/spring/SpringTheme.hpp b/themes/src/spring/SpringTheme.hpp deleted file mode 100644 index 64f9cb5..0000000 --- a/themes/src/spring/SpringTheme.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef SPRING_THEME_HPP -#define SPRING_THEME_HPP - -#include "../mathlib.h" -#include "../types.h" -#include "../Renderer3d.h" -#include "../theme.h" - - -enum class SpringThemeState { - Loading = 0, - LoadedShader, - LoadedBunny, - PreHop, - Hopping, - Idle -}; - -class SpringTheme : public Theme { -public: - SpringTheme(WebglContext*); - ~SpringTheme(); - Renderer3d renderer; - SpringThemeState state; - f32 bunnySpeed = 5.f; - Vector3 bunnyPosition = Vector3(0, 0, 0); - Vector3 bunnyTarget = Vector3(0, 0, 0); - Vector3 hopIncrement = Vector3(0, 0, 0); - - f32 numHops = 0; - f32 hopCount = 0; - f32 bunnyHopAnimationTimer = 0.f; - f32 stateTimer = 0.f; - f32 bunnyRotation = 0.f; - f32 targetRotation = 0.f; - - Mesh3d bunnyMesh; - - void load(WebglContext*); - void update(f32 dtSeconds); - void render(); - void unload(); -}; - -#endif diff --git a/themes/src/spring/grass_renderer.cpp b/themes/src/spring/grass_renderer.cpp new file mode 100644 index 0000000..685f733 --- /dev/null +++ b/themes/src/spring/grass_renderer.cpp @@ -0,0 +1,29 @@ +#include "grass_renderer.hpp" +#include "../renderer_3d.h" + +void GrassRenderer::load(GrassRendererLoadData params, Renderer3d* renderer) { + const f32 COLUMN_INCREMENT = GRASS_BLADES_PER_COL / params.area.x; + const f32 ROW_INCREMENT = GRASS_BLADES_PER_ROW / params.area.y; + for (i32 r = 0; r < GRASS_BLADES_PER_ROW; r++) { + i32 indexOffset = r * GRASS_BLADES_PER_ROW; + f32 y = ROW_INCREMENT * r; + for (i32 c = 0; c < GRASS_BLADES_PER_COL; c++) { + f32 x = COLUMN_INCREMENT * c; + i32 index = indexOffset + c; + grassBlades[index].position = Vector3(x, y, 0); + grassBlades[index].top_offset = Vector2(0, 0); + } + } +} + +void GrassRenderer::update(f32 seconds) { + +} + +void GrassRenderer::render(Renderer3d* renderer) { + +} + +void GrassRenderer::unload() { + +} diff --git a/themes/src/spring/grass_renderer.hpp b/themes/src/spring/grass_renderer.hpp new file mode 100644 index 0000000..88879f3 --- /dev/null +++ b/themes/src/spring/grass_renderer.hpp @@ -0,0 +1,33 @@ +#ifndef GRASS_RENDERER_HPP +#define GRASS_RENDERER_HPP + +#include "../renderer_3d.h" +#include "mathlib.h" +#include "types.h" + +const i32 GRASS_BLADES_PER_ROW = 24; +const i32 GRASS_BLADES_PER_COL = 24; +const i32 NUM_GRASS_BLADES = GRASS_BLADES_PER_ROW * GRASS_BLADES_PER_COL; + +struct GrassRendererLoadData { + Vector2 origin = Vector2(0, 0); + Vector2 area = Vector2(480, 480); + f32 grassHeight = 12.f; +}; + +struct GrassUpdateData { + Vector3 position; + Vector2 top_offset; +}; + +struct GrassRenderer { + + GrassUpdateData grassBlades[NUM_GRASS_BLADES]; + + void load(GrassRendererLoadData params, Renderer3d* renderer); + void update(f32 dtSeconds); + void render(Renderer3d* renderer); + void unload(); +}; + +#endif diff --git a/themes/src/spring/spring_theme.cpp b/themes/src/spring/spring_theme.cpp new file mode 100644 index 0000000..8507194 --- /dev/null +++ b/themes/src/spring/spring_theme.cpp @@ -0,0 +1,208 @@ +#include "spring_theme.hpp" +#include "../renderer_3d.h" +#include "../shader_fetcher.hpp" +#include +#include + +void onBunnySuccess(emscripten_fetch_t *fetch) { + SpringTheme* springTheme = (SpringTheme*)fetch->userData; + springTheme->state = SpringThemeState::LoadedBunny; + printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url); + const i32 len = fetch->numBytes; + springTheme->bunnyMesh = Mesh3d_fromObj(&springTheme->renderer, fetch->data, len); + // The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1]; + emscripten_fetch_close(fetch); // Free data associated with the fetch. +} + +void onBunnyFail(emscripten_fetch_t *fetch) { + printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status); + emscripten_fetch_close(fetch); // Also free data on failure. +} + +inline void fetch_bunny(SpringTheme* theme) { + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + strcpy(attr.requestMethod, "GET"); + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + attr.onsuccess = onBunnySuccess; + attr.onerror = onBunnyFail; + auto* bunny_fetch = emscripten_fetch(&attr, "themes/resources/bunny.obj"); + bunny_fetch->userData = theme; +} + +inline void on_shaders_loader(ShaderFetchResult* result) { + SpringTheme* theme = (SpringTheme*)result->user_data; + theme->renderer.load(theme->renderer.context, result->vertex.c_str(), result->fragment.c_str()); + theme->state = SpringThemeState::LoadedShader; + fetch_bunny(theme); +} + +SpringTheme::SpringTheme(WebglContext* context) +{ + load(context); +} + +SpringTheme::~SpringTheme() +{ + unload(); +} + +void SpringTheme::load(WebglContext* context) { + state = SpringThemeState::Loading; + renderer.context = context; + renderer.clearColor = Vector4(160, 231, 160, 255.f).toNormalizedColor(); + + fetch_shader( + { + "themes/src/_shaders/renderer3d.vert", + "themes/src/_shaders/renderer3d.frag" + }, + on_shaders_loader, + this + ); +} + +inline Vector3 bunnyLerp(Vector3& start, Vector3& target, f32 t) { + t = 3 * t *t - 2 * t * t * t; + return start + ((target - start) * t); +} + +inline f32 verticalHopLerp(f32 start, f32 target, f32 t) { + f32 ogt = t; + t = 3 * t *t - 2 * t * t * t; + if (ogt >= 0.5f) t = 1.f - t; + return start + ((target - start) * t); +} + +inline f32 rotationLerp(f32 start, f32 target, f32 t) { + return start + ((target - start) * t); +} + +void SpringTheme::update(f32 dtSeconds) { + switch (state) { + case SpringThemeState::Loading: return; + case SpringThemeState::LoadedShader: return; + case SpringThemeState::LoadedBunny: + state = SpringThemeState::Idle; + stateTimer = 0.f; + bunnyHopAnimationTimer = 0.f; + break; + case SpringThemeState::Idle: { + bunnyHopAnimationTimer += dtSeconds; + const f32 HOP_FREQUENCY = 6.f; + + if (bunnyHopAnimationTimer > stateTimer) { + state = SpringThemeState::PreHop; + f32 xDir = 1; + f32 yDir = 1; + if (bunnyTarget.x > 0) xDir = -1; + if (bunnyTarget.z > 0) yDir = -1; + bunnyTarget = bunnyPosition + Vector3(randomFloatBetween(0, xDir * 25), 0, randomFloatBetween(0, yDir * 25)); + auto direction = (bunnyTarget - bunnyPosition); + auto distance = direction.length(); + direction = direction.normalize(); + numHops = ceil(distance / HOP_FREQUENCY); + hopCount = 0; + + targetRotation = PI - atan2(direction.y, direction.x); + stateTimer = ((bunnyTarget - bunnyPosition).length() / bunnySpeed) / numHops; + bunnyHopAnimationTimer = 0.f; + hopIncrement = (bunnyTarget - bunnyPosition) / numHops; + } + break; + } + case SpringThemeState::PreHop: { + const f32 ROTATION_TIME = 0.5f; + bunnyHopAnimationTimer += dtSeconds; + f32 current = bunnyRotation + (targetRotation - bunnyRotation) * (bunnyHopAnimationTimer / ROTATION_TIME); + bunnyMesh.model = Mat4x4().rotate(0, current, 0).translate(bunnyPosition); + + if (bunnyHopAnimationTimer > ROTATION_TIME) { + bunnyRotation = targetRotation; + bunnyHopAnimationTimer = 0; + state = SpringThemeState::Hopping; + } + break; + } + case SpringThemeState::Hopping: { + bunnyHopAnimationTimer += dtSeconds; + f32 t = bunnyHopAnimationTimer / stateTimer; + + Vector3 nextPosition = bunnyPosition + hopIncrement; + auto renderPos = bunnyLerp(bunnyPosition, nextPosition, t); + if ((renderPos - nextPosition).length() < 0.01f) { + hopCount += 1; + bunnyHopAnimationTimer = 0.f; + bunnyPosition = nextPosition; + } + + renderPos.y = verticalHopLerp(0.f, 4.f, t); + + const f32 RMAX = PI / 16.f; + f32 zRotation = 0; + f32 start = 0.f; + f32 end = PI / 8.f; + f32 startTime = 0.f; + f32 endTime = 0.f; + bool disableRot = false; + + if (t >= 0.9f) { + disableRot = true; + } + else if (t >= 0.7f) { + start = -RMAX; + end = 0.f; + startTime = 0.7f; + endTime = 0.9f; + } + else if (t >= 0.50f) { + start = 0.f; + end = -RMAX; + startTime = 0.50f; + endTime = 0.70f; + } + else if (t >= 0.40f) { + disableRot = true; + } + else if (t >= 0.20f) { + start = RMAX; + end = 0.f; + startTime = 0.20f; + endTime = 0.40f; + } + else { + start = 0.f; + end = RMAX; + startTime = 0.f; + endTime = 0.20f; + } + + + if (!disableRot) { + f32 totalTime = endTime - startTime; + zRotation = rotationLerp(start, end, (totalTime - (endTime - t)) / totalTime); + } + + bunnyMesh.model = Mat4x4().getZRotationMatrix(zRotation).rotate(0, bunnyRotation, 0).translate(renderPos); + if (hopCount == numHops) { + bunnyPosition = bunnyTarget; + bunnyHopAnimationTimer = 0.f; + state = SpringThemeState::Idle; + stateTimer = randomFloatBetween(0.5f, 1.f); + } + break; + } + } +} + +void SpringTheme::render() { + renderer.render(); + if (state != SpringThemeState::Loading) { + bunnyMesh.render(&renderer); + } +} + +void SpringTheme::unload() { + renderer.unload(); + bunnyMesh.unload(); +} diff --git a/themes/src/spring/spring_theme.hpp b/themes/src/spring/spring_theme.hpp new file mode 100644 index 0000000..6079958 --- /dev/null +++ b/themes/src/spring/spring_theme.hpp @@ -0,0 +1,45 @@ +#ifndef SPRING_THEME_HPP +#define SPRING_THEME_HPP + +#include "../mathlib.h" +#include "../types.h" +#include "../renderer_3d.h" +#include "../theme.h" + + +enum class SpringThemeState { + Loading = 0, + LoadedShader, + LoadedBunny, + PreHop, + Hopping, + Idle +}; + +class SpringTheme : public Theme { +public: + SpringTheme(WebglContext*); + ~SpringTheme(); + Renderer3d renderer; + SpringThemeState state; + f32 bunnySpeed = 5.f; + Vector3 bunnyPosition = Vector3(0, 0, 0); + Vector3 bunnyTarget = Vector3(0, 0, 0); + Vector3 hopIncrement = Vector3(0, 0, 0); + + f32 numHops = 0; + f32 hopCount = 0; + f32 bunnyHopAnimationTimer = 0.f; + f32 stateTimer = 0.f; + f32 bunnyRotation = 0.f; + f32 targetRotation = 0.f; + + Mesh3d bunnyMesh; + + void load(WebglContext*); + void update(f32 dtSeconds); + void render(); + void unload(); +}; + +#endif diff --git a/themes/src/summer/SummerTheme.cpp b/themes/src/summer/SummerTheme.cpp deleted file mode 100644 index 1f76b56..0000000 --- a/themes/src/summer/SummerTheme.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "SummerTheme.h" -#include "../Renderer2d.h" -#include "../list.h" -#include "../mathlib.h" -#include "../shaders/sun_frag.h" -#include "../shaders/sun_vert.h" -#include - -SummerTheme::SummerTheme(WebglContext* context) -{ - renderer.load(context); - load(context); -} - -SummerTheme::~SummerTheme() -{ - unload(); -} - -void SummerTheme::load(WebglContext* context) { - renderer.load(context, shader_sun_vert, shader_sun_frag); - renderer.clearColor = Vector4(0, 181, 286, 255.f).toNormalizedColor(); - sun.sectors = 180; - sun.radius = renderer.context->width / 4.f; - sun.load(&renderer); -} - -void SummerTheme::update(f32 dtSeconds) { - sun.update(dtSeconds); -} - -void SummerTheme::render() { - renderer.render(); - sun.render(&renderer); -} - -void SummerTheme::unload() { - sun.unload(); -} - -void Sun::load(Renderer2d* renderer) { - matte::List vertices; - matte::List indices; - Vector4 sunColor = Vector4(249, 215, 28, 255).toNormalizedColor(); - vertices.add({ Vector2(0, 0), sunColor, Mat4x4() }); - - f32 radiansPerSector = (2.f * PI) / sectors; - for (i32 i = 0; i <= sectors; i++) { - f32 radians = radiansPerSector * i; - f32 cosAngle = cosf(radians); - f32 sinAngle = sinf(radians); - Vector2 vertex = Vector2(radius * cosAngle, radius * sinAngle); - vertices.add({ vertex, sunColor, Mat4x4() }); - - u32 first = i; - u32 second = 0; - u32 third = i + 1; - indices.add(first); - indices.add(second); - indices.add(third); - } - - mesh.load(&vertices.data[0], vertices.numElements, &indices.data[0], indices.numElements, renderer); - mesh.model = Mat4x4().translateByVec2(Vector2(renderer->context->width / 2.f, renderer->context->height / 2.f)); - vertices.deallocate(); - indices.deallocate(); -} - -void Sun::update(f32 dtSeconds) { - -} - -void Sun::render(Renderer2d* renderer) { - setShaderMat4(renderer->uniforms.model, mesh.model); - glBindVertexArray(mesh.vao); - glDrawElements(GL_TRIANGLES, mesh.numIndices, GL_UNSIGNED_INT, 0); - glBindVertexArray(0); -} - -void Sun::unload() { - mesh.unload(); -} diff --git a/themes/src/summer/SummerTheme.h b/themes/src/summer/SummerTheme.h deleted file mode 100644 index 2ce6b7f..0000000 --- a/themes/src/summer/SummerTheme.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include "../types.h" -#include "../Renderer2d.h" -#include "../theme.h" -#include - -struct Sun { - f32 radius = 20.f; - i32 sectors = 180; - Mesh2D mesh; - - void load(Renderer2d* renderer); - void update(f32 dtSeconds); - void render(Renderer2d* renderer); - void unload(); -}; - -class SummerTheme : public Theme { -public: - SummerTheme(WebglContext*); - ~SummerTheme(); - Sun sun; - void load(WebglContext*); - void update(f32 dtSeconds); - void render(); - void unload(); -private: - Renderer2d renderer; -}; diff --git a/themes/src/summer/summer_theme.cpp b/themes/src/summer/summer_theme.cpp new file mode 100644 index 0000000..6935ad1 --- /dev/null +++ b/themes/src/summer/summer_theme.cpp @@ -0,0 +1,82 @@ +#include "summer_theme.h" +#include "../renderer_2d.h" +#include "../list.h" +#include "../mathlib.h" +#include "../shaders/sun_frag.h" +#include "../shaders/sun_vert.h" +#include + +SummerTheme::SummerTheme(WebglContext* context) +{ + renderer.load(context); + load(context); +} + +SummerTheme::~SummerTheme() +{ + unload(); +} + +void SummerTheme::load(WebglContext* context) { + renderer.load(context, shader_sun_vert, shader_sun_frag); + renderer.clearColor = Vector4(0, 181, 286, 255.f).toNormalizedColor(); + sun.sectors = 180; + sun.radius = renderer.context->width / 4.f; + sun.load(&renderer); +} + +void SummerTheme::update(f32 dtSeconds) { + sun.update(dtSeconds); +} + +void SummerTheme::render() { + renderer.render(); + sun.render(&renderer); +} + +void SummerTheme::unload() { + sun.unload(); +} + +void Sun::load(Renderer2d* renderer) { + matte::List vertices; + matte::List indices; + Vector4 sunColor = Vector4(249, 215, 28, 255).toNormalizedColor(); + vertices.add({ Vector2(0, 0), sunColor, Mat4x4() }); + + f32 radiansPerSector = (2.f * PI) / sectors; + for (i32 i = 0; i <= sectors; i++) { + f32 radians = radiansPerSector * i; + f32 cosAngle = cosf(radians); + f32 sinAngle = sinf(radians); + Vector2 vertex = Vector2(radius * cosAngle, radius * sinAngle); + vertices.add({ vertex, sunColor, Mat4x4() }); + + u32 first = i; + u32 second = 0; + u32 third = i + 1; + indices.add(first); + indices.add(second); + indices.add(third); + } + + mesh.load(&vertices.data[0], vertices.numElements, &indices.data[0], indices.numElements, renderer); + mesh.model = Mat4x4().translateByVec2(Vector2(renderer->context->width / 2.f, renderer->context->height / 2.f)); + vertices.deallocate(); + indices.deallocate(); +} + +void Sun::update(f32 dtSeconds) { + +} + +void Sun::render(Renderer2d* renderer) { + setShaderMat4(renderer->uniforms.model, mesh.model); + glBindVertexArray(mesh.vao); + glDrawElements(GL_TRIANGLES, mesh.numIndices, GL_UNSIGNED_INT, 0); + glBindVertexArray(0); +} + +void Sun::unload() { + mesh.unload(); +} diff --git a/themes/src/summer/summer_theme.h b/themes/src/summer/summer_theme.h new file mode 100644 index 0000000..cd25ff5 --- /dev/null +++ b/themes/src/summer/summer_theme.h @@ -0,0 +1,29 @@ +#pragma once +#include "../types.h" +#include "../renderer_2d.h" +#include "../theme.h" +#include + +struct Sun { + f32 radius = 20.f; + i32 sectors = 180; + Mesh2D mesh; + + void load(Renderer2d* renderer); + void update(f32 dtSeconds); + void render(Renderer2d* renderer); + void unload(); +}; + +class SummerTheme : public Theme { +public: + SummerTheme(WebglContext*); + ~SummerTheme(); + Sun sun; + void load(WebglContext*); + void update(f32 dtSeconds); + void render(); + void unload(); +private: + Renderer2d renderer; +}; diff --git a/themes/src/webgl_context.cpp b/themes/src/webgl_context.cpp new file mode 100644 index 0000000..71b983e --- /dev/null +++ b/themes/src/webgl_context.cpp @@ -0,0 +1,46 @@ +#include "webgl_context.h" +#include + + +EM_BOOL onResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) { + WebglContext* context = (WebglContext*)userData; + + f64 inWidth, inHeight; + emscripten_get_element_css_size(context->query, &inWidth, &inHeight); + + context->width = static_cast(inWidth); + context->height = static_cast(inHeight); + + return true; +} + +void WebglContext::init(const char* inQuery) { + strcpy(query, inQuery); + f64 inWidth, inHeight; + emscripten_get_element_css_size(query, &inWidth, &inHeight); + width = static_cast(inWidth); + height = static_cast(inHeight); + emscripten_set_canvas_element_size( query, width, height); + + EmscriptenWebGLContextAttributes attrs; + emscripten_webgl_init_context_attributes(&attrs); + + attrs.enableExtensionsByDefault = 1; + attrs.majorVersion = 3; + attrs.minorVersion = 0; + + context = emscripten_webgl_create_context(query, &attrs); + makeCurrentContext(); + + glClearColor(0, 0, 0, 0.0f); + + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, onResize); +}; + +void WebglContext::makeCurrentContext() { + emscripten_webgl_make_context_current(context); +}; + +void WebglContext::destroy() { + emscripten_webgl_destroy_context(context); +} diff --git a/themes/src/webgl_context.h b/themes/src/webgl_context.h new file mode 100644 index 0000000..1956092 --- /dev/null +++ b/themes/src/webgl_context.h @@ -0,0 +1,18 @@ +#pragma once +#include "types.h" +#include +#include +#include +#include +#include + +struct WebglContext { + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context; + f32 width = 800; + f32 height = 600; + char query[128];; + + void init(const char* inQuery); + void makeCurrentContext(); + void destroy() ; +}; diff --git a/themes/src/winter/Snowflake.cpp b/themes/src/winter/Snowflake.cpp deleted file mode 100644 index 57f1a8f..0000000 --- a/themes/src/winter/Snowflake.cpp +++ /dev/null @@ -1,189 +0,0 @@ -#include "Snowflake.h" -#include "../Renderer2d.h" -#include "../mathlib.h" -#include "../list.h" -#include - -/* - - What else to do? - - - Windstream that blows a certain selection of snowflakes in a loop-dee-loop pattern - - Snowflakes that land on the ground and melt - - Snowflakes that spin along the Y-axis for a three dimensional effect - - */ - -const Vector4 snowColor = Vector4(1.0, 0.98, 0.98, 1); -const Vector2 NUM_ARMS_RANGE = Vector2(6.f, 8.f); -const Vector2 RADIUS_RANGE = Vector2(8.f, 32.f); -const Vector2 VELOCITY_RANGE_X = Vector2(-10.f, 10.f); -const Vector2 VELOCITY_RANGE_Y = Vector2(-100.f, -85.f); -const Vector2 ROTATION_VELOCITY_RANGE = Vector2(-PI / 8.f, PI / 8.f); -const Vector2 WIND_VELOCITY_RANGE_X = Vector2(-3.f, 3.f); -const Vector2 WIND_VELOCITY_RANGE_Y = Vector2(3.f, 10.f); -const f32 GRAVITY = 5.f; - -inline void generateSnowflakeArm(f32 width, f32 height, f32 angle, matte::List* vertices, Mat4x4 transform = Mat4x4()) { - f32 halfWidth = width / 2.f; - Vector2 leftStart = transform * Vector2(-halfWidth, 0).rotate(angle); - Vector2 leftEnd = transform * Vector2(-halfWidth, height).rotate(angle); - Vector2 rightStart = transform * Vector2(halfWidth, 0).rotate(angle); - Vector2 rightEnd = transform * Vector2(halfWidth, height).rotate(angle); - - vertices->add({ leftStart, snowColor, Mat4x4() }); - vertices->add({ leftEnd, snowColor, Mat4x4() }); - vertices->add({ rightEnd, snowColor, Mat4x4() }); - vertices->add({ leftStart, snowColor, Mat4x4() }); - vertices->add({ rightEnd, snowColor, Mat4x4() }); - vertices->add({ rightStart, snowColor, Mat4x4() }); -} - -/** - Fills in the vertices array vertices that represent a snowflake shape. The snowflake shape consists - of numArms jutting out of the center radially. The center of the flake is connected. The radius is - used to determine the length of the arms. The first third of each arm is barren, after which branches - extends on either side of the arm at an angle of about 60 degrees. Each branch can itself have tiny - sub branches jutting out of it, but these should be not nearly as large as the regular branches. - - With all of this in mind, we should be able to build a convincing snowflake. - - :param vertices List of vertices to be filled in - :param numArms Number of arms radially sticking out of the snowflake - :param radius Length of the snowflake arms - */ -inline void generateSnowflakeShape(matte::List* vertices, i32 numArms, f32 radius, f32 armWidthRatio = 0.08f) { - f32 innerRadius = 0; - f32 outerRadius = 2 * radius; - f32 dx = ((2 * PI) / numArms); - for (i32 armIndex = 0; armIndex < numArms; armIndex++) { - f32 armAngle = dx * armIndex; - generateSnowflakeArm(armWidthRatio * radius, radius, armAngle, vertices); - f32 armLeftAngle = DEG_TO_RAD(60.f); - f32 armRightAngle = DEG_TO_RAD(-60.f); - - const i32 NUM_SUB_ARMS = 4; - for (i32 subArmIndex = 0; subArmIndex < NUM_SUB_ARMS; subArmIndex++) { - f32 height = (radius / static_cast(subArmIndex)); - f32 width = (armWidthRatio / (subArmIndex + 1)) * height; - f32 transY = (radius / (NUM_SUB_ARMS + 1)) * (subArmIndex + 1); - Vector2 translation = Vector2(0, transY).rotate(armAngle); - generateSnowflakeArm(width, height, armAngle, vertices, Mat4x4().translateByVec2(translation).rotate2D(armLeftAngle)); - generateSnowflakeArm(width, height, armAngle, vertices, Mat4x4().translateByVec2(translation).rotate2D(armRightAngle)); - } - } -} - -inline void initFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) { - ud->radius = randomFloatBetween(RADIUS_RANGE.x, RADIUS_RANGE.y); - ud->vtxIdx = renderer->vertices.numElements; - generateSnowflakeShape(&renderer->vertices, - randomFloatBetween(NUM_ARMS_RANGE.x, NUM_ARMS_RANGE.y), - ud->radius); - - ud->numVertices = renderer->vertices.numElements - ud->vtxIdx; - ud->velocity = Vector2(randomFloatBetween(VELOCITY_RANGE_X.x, VELOCITY_RANGE_X.y), randomFloatBetween(VELOCITY_RANGE_Y.x, VELOCITY_RANGE_Y.y)); - ud->position = Vector2(randomFloatBetween(0, renderer->xMax), randomFloatBetween(renderer->yMax, 4 * renderer->yMax)); - ud->rotateVelocity = randomFloatBetween(ROTATION_VELOCITY_RANGE.x, ROTATION_VELOCITY_RANGE.y); -} - -void SnowflakeParticleRenderer::load(SnowflakeLoadParameters params, Renderer2d* renderer) { - numSnowflakes = params.numSnowflakes; - - updateData = new SnowflakeUpdateData[params.numSnowflakes]; - - xMax = static_cast(renderer->context->width); - yMax = static_cast(renderer->context->height); - - vertices.deallocate(); - vertices.growDynamically = true; - - // Initialize each snow flake with its shape - for (i32 s = 0; s < numSnowflakes; s++) { - auto ud = &updateData[s]; - initFlake(this, ud); - } - - useShader(renderer->shader); - - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, vertices.numElements * sizeof(Vertex2D), &vertices.data[0], GL_DYNAMIC_DRAW); - - glEnableVertexAttribArray(renderer->attributes.position); - glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0); - - glEnableVertexAttribArray(renderer->attributes.color); - glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color)); - - for (i32 idx = 0; idx < 4; idx++) { - i32 offset = (4 * sizeof(f32)) * idx; - glEnableVertexAttribArray(renderer->attributes.vMatrix + idx); - glVertexAttribPointer(renderer->attributes.vMatrix + idx, - 4, - GL_FLOAT, - GL_FALSE, - sizeof(Vertex2D), - (GLvoid *)(offsetof(Vertex2D, vMatrix) + offset)); - } - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); -} - -inline void resetFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) { - ud->position.y = 2 * renderer->yMax; - ud->velocity = Vector2(randomFloatBetween(-10, 10), randomFloatBetween(-100, -85)); - ud->rotation = 0; -} - -inline void updateFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud, i32 s, f32 dtSeconds) { - ud->velocity = ud->velocity + Vector2(0, -(GRAVITY * dtSeconds)); - //if (addWind) ud->velocity += renderer->windSpeed; - ud->position += ud->velocity * dtSeconds; - ud->rotation += ud->rotateVelocity * dtSeconds; - - Mat4x4 m = Mat4x4().translateByVec2(ud->position).rotate2D(ud->rotation); - for (i32 v = ud->vtxIdx; v < (ud->vtxIdx + ud->numVertices); v++) { - renderer->vertices.data[v].vMatrix = m; - } - - if (ud->position.y <= -ud->radius) { - resetFlake(renderer, ud); - } -} - -void SnowflakeParticleRenderer::update(f32 dtSeconds) { - timeUntilNextWindSeconds -= dtSeconds; - if (timeUntilNextWindSeconds < 0) { - timeUntilNextWindSeconds = randomFloatBetween(2.5f, 10.f); - } - - for (i32 s = 0; s < numSnowflakes; s++) { - SnowflakeUpdateData* ud = &updateData[s]; - updateFlake(this, ud, s, dtSeconds); - } -} - -void SnowflakeParticleRenderer::render(Renderer2d* renderer) { - setShaderMat4(renderer->uniforms.model, model); - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.numElements * sizeof(Vertex2D), &vertices.data[0]); - - glBindVertexArray(vao); - glDrawArrays(GL_TRIANGLES, 0, vertices.numElements); - glBindVertexArray(0); -} - -void SnowflakeParticleRenderer::unload() { - glDeleteVertexArrays(1, &vao); - glDeleteBuffers(1, &vbo); - vao = 0; - vbo = 0; - vertices.deallocate(); - delete [] updateData; -} diff --git a/themes/src/winter/Snowflake.h b/themes/src/winter/Snowflake.h deleted file mode 100644 index ad027f6..0000000 --- a/themes/src/winter/Snowflake.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef SNOWFLAKE_H -#define SNOWFLAKE_H - -#include "../types.h" -#include "../mathlib.h" -#include "../list.h" -#include "Windfield.hpp" - -struct Renderer2d; -struct Vertex2D; - -struct SnowflakeLoadParameters { - i32 numSnowflakes = 480; - f32 windIntervalSeconds = 1.5f; -}; - -struct SnowflakeUpdateData { - Vector2 velocity; - Vector2 position; - f32 rotateVelocity = 0.f; - f32 rotation = 0; - f32 radius; - - i32 vtxIdx = 0; - i32 numVertices = 0; -}; - -struct SnowflakeParticleRenderer { - f32 xMax = 0; - f32 yMax = 0; - f32 windIntervalSeconds = 1.5; - i32 numSnowflakes = 0; - f32 timeUntilNextWindSeconds = 0; - WindField<100, 100, 10> wind; - SnowflakeUpdateData* updateData; - - u32 vao; - u32 vbo; - Mat4x4 model; - matte::List vertices; - - void load(SnowflakeLoadParameters params, Renderer2d* renderer); - void update(f32 dtSeconds); - void render(Renderer2d* renderer); - void unload(); -}; - -#endif diff --git a/themes/src/winter/Windfield.cpp b/themes/src/winter/Windfield.cpp deleted file mode 100644 index 88fb74b..0000000 --- a/themes/src/winter/Windfield.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "Windfield.hpp" - - -template -void WindField::load(f32 cellSizePixels, i32 fieldWithCells, i32 fieldHeightCells, f32 ttl, Vector2 origin) { - this->ttl = ttl; - this->origin = origin; - this->end = this->origin + Vector2(Width * CellDimension, Height * CellDimension); -} - -template -bool WindField::addVector(i32 x, i32 y, Vector2& v) { - field[x][y] = v; - return false; -} - -template -Vector2 WindField::getWindFactor(Vector2& v) { - if (v.x >= origin.x && v.x <= end.x - && v.y >= origin.y && v.y <= end.y) { - Vector2 positionInField = v - this->origin; - i32 cellX = static_cast(Width / positionInField.x); - i32 cellY = static_cast(Height / positionInField.y); - return field[cellX][cellY]; - } - - return Vector2(); -} diff --git a/themes/src/winter/Windfield.hpp b/themes/src/winter/Windfield.hpp deleted file mode 100644 index 5bf0c38..0000000 --- a/themes/src/winter/Windfield.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef WIND_FIELD_HPP -#define WIND_FIELD_HPP -#include "../types.h" -#include "../mathlib.h" - -/** - A Windfield represents a field of vectors in a rectangular region. - The Width and Height are given in units of CellDimenions. The CellDimension - is given in pixels. - */ -template -struct WindField { - f32 ttl = 0.f; - Vector2 origin; - Vector2 end; - - /* - Granularity of each cell in pixels. - */ - const f32 cellDimension = CellDimension; - - /* - Width of the vector field in CellDimensions. - */ - const f32 width = Width; - - /* - Height of the vector vield in CellDimensions. - */ - const f32 height = Height; - - Vector2** field; - - void load(f32 cellSizePixels, i32 fieldWithCells, i32 fieldHeightCells, f32 ttl, Vector2 origin); - bool addVector(i32 x, i32 y, Vector2& v); - Vector2 getWindFactor(Vector2& v); -}; - -#endif diff --git a/themes/src/winter/WinterTheme.cpp b/themes/src/winter/WinterTheme.cpp deleted file mode 100644 index 052670e..0000000 --- a/themes/src/winter/WinterTheme.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "WinterTheme.hpp" -#include "../Renderer2d.h" - -WinterTheme::WinterTheme(WebglContext* context) -{ - renderer.load(context); - load(); -} - -WinterTheme::~WinterTheme() -{ - unload(); -} - -void WinterTheme::load() { - renderer.clearColor = Vector4(200, 229, 239, 255).toNormalizedColor(); - SnowflakeLoadParameters lp; - spr.load(lp, &renderer); -} - -void WinterTheme::update(f32 dtSeconds) { - spr.update(dtSeconds); -} - -void WinterTheme::render() { - renderer.render(); - spr.render(&renderer); -} - -void WinterTheme::unload() { - spr.unload(); -} diff --git a/themes/src/winter/WinterTheme.hpp b/themes/src/winter/WinterTheme.hpp deleted file mode 100644 index 5ba6d94..0000000 --- a/themes/src/winter/WinterTheme.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef WINTER_THEME_HPP -#define WINTER_THEME_HPP - -#include "Snowflake.h" -#include "../types.h" -#include "../theme.h" -#include "../Renderer2d.h" - -struct WebglContext; - -struct WinterTheme : public Theme { -public: - WinterTheme(WebglContext*); - ~WinterTheme(); - SnowflakeParticleRenderer spr; - - void load(); - void update(f32 dtSeconds); - void render(); - void unload(); -private: - Renderer2d renderer; -}; - -#endif diff --git a/themes/src/winter/snowflake.cpp b/themes/src/winter/snowflake.cpp new file mode 100644 index 0000000..4ce8f3a --- /dev/null +++ b/themes/src/winter/snowflake.cpp @@ -0,0 +1,189 @@ +#include "snowflake.h" +#include "../renderer_2d.h" +#include "../mathlib.h" +#include "../list.h" +#include + +/* + + What else to do? + + - Windstream that blows a certain selection of snowflakes in a loop-dee-loop pattern + - Snowflakes that land on the ground and melt + - Snowflakes that spin along the Y-axis for a three dimensional effect + + */ + +const Vector4 snowColor = Vector4(1.0, 0.98, 0.98, 1); +const Vector2 NUM_ARMS_RANGE = Vector2(6.f, 8.f); +const Vector2 RADIUS_RANGE = Vector2(8.f, 32.f); +const Vector2 VELOCITY_RANGE_X = Vector2(-10.f, 10.f); +const Vector2 VELOCITY_RANGE_Y = Vector2(-100.f, -85.f); +const Vector2 ROTATION_VELOCITY_RANGE = Vector2(-PI / 8.f, PI / 8.f); +const Vector2 WIND_VELOCITY_RANGE_X = Vector2(-3.f, 3.f); +const Vector2 WIND_VELOCITY_RANGE_Y = Vector2(3.f, 10.f); +const f32 GRAVITY = 5.f; + +inline void generateSnowflakeArm(f32 width, f32 height, f32 angle, matte::List* vertices, Mat4x4 transform = Mat4x4()) { + f32 halfWidth = width / 2.f; + Vector2 leftStart = transform * Vector2(-halfWidth, 0).rotate(angle); + Vector2 leftEnd = transform * Vector2(-halfWidth, height).rotate(angle); + Vector2 rightStart = transform * Vector2(halfWidth, 0).rotate(angle); + Vector2 rightEnd = transform * Vector2(halfWidth, height).rotate(angle); + + vertices->add({ leftStart, snowColor, Mat4x4() }); + vertices->add({ leftEnd, snowColor, Mat4x4() }); + vertices->add({ rightEnd, snowColor, Mat4x4() }); + vertices->add({ leftStart, snowColor, Mat4x4() }); + vertices->add({ rightEnd, snowColor, Mat4x4() }); + vertices->add({ rightStart, snowColor, Mat4x4() }); +} + +/** + Fills in the vertices array vertices that represent a snowflake shape. The snowflake shape consists + of numArms jutting out of the center radially. The center of the flake is connected. The radius is + used to determine the length of the arms. The first third of each arm is barren, after which branches + extends on either side of the arm at an angle of about 60 degrees. Each branch can itself have tiny + sub branches jutting out of it, but these should be not nearly as large as the regular branches. + + With all of this in mind, we should be able to build a convincing snowflake. + + :param vertices List of vertices to be filled in + :param numArms Number of arms radially sticking out of the snowflake + :param radius Length of the snowflake arms + */ +inline void generateSnowflakeShape(matte::List* vertices, i32 numArms, f32 radius, f32 armWidthRatio = 0.08f) { + f32 innerRadius = 0; + f32 outerRadius = 2 * radius; + f32 dx = ((2 * PI) / numArms); + for (i32 armIndex = 0; armIndex < numArms; armIndex++) { + f32 armAngle = dx * armIndex; + generateSnowflakeArm(armWidthRatio * radius, radius, armAngle, vertices); + f32 armLeftAngle = DEG_TO_RAD(60.f); + f32 armRightAngle = DEG_TO_RAD(-60.f); + + const i32 NUM_SUB_ARMS = 4; + for (i32 subArmIndex = 0; subArmIndex < NUM_SUB_ARMS; subArmIndex++) { + f32 height = (radius / static_cast(subArmIndex)); + f32 width = (armWidthRatio / (subArmIndex + 1)) * height; + f32 transY = (radius / (NUM_SUB_ARMS + 1)) * (subArmIndex + 1); + Vector2 translation = Vector2(0, transY).rotate(armAngle); + generateSnowflakeArm(width, height, armAngle, vertices, Mat4x4().translateByVec2(translation).rotate2D(armLeftAngle)); + generateSnowflakeArm(width, height, armAngle, vertices, Mat4x4().translateByVec2(translation).rotate2D(armRightAngle)); + } + } +} + +inline void initFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) { + ud->radius = randomFloatBetween(RADIUS_RANGE.x, RADIUS_RANGE.y); + ud->vtxIdx = renderer->vertices.numElements; + generateSnowflakeShape(&renderer->vertices, + randomFloatBetween(NUM_ARMS_RANGE.x, NUM_ARMS_RANGE.y), + ud->radius); + + ud->numVertices = renderer->vertices.numElements - ud->vtxIdx; + ud->velocity = Vector2(randomFloatBetween(VELOCITY_RANGE_X.x, VELOCITY_RANGE_X.y), randomFloatBetween(VELOCITY_RANGE_Y.x, VELOCITY_RANGE_Y.y)); + ud->position = Vector2(randomFloatBetween(0, renderer->xMax), randomFloatBetween(renderer->yMax, 4 * renderer->yMax)); + ud->rotateVelocity = randomFloatBetween(ROTATION_VELOCITY_RANGE.x, ROTATION_VELOCITY_RANGE.y); +} + +void SnowflakeParticleRenderer::load(SnowflakeLoadParameters params, Renderer2d* renderer) { + numSnowflakes = params.numSnowflakes; + + updateData = new SnowflakeUpdateData[params.numSnowflakes]; + + xMax = static_cast(renderer->context->width); + yMax = static_cast(renderer->context->height); + + vertices.deallocate(); + vertices.growDynamically = true; + + // Initialize each snow flake with its shape + for (i32 s = 0; s < numSnowflakes; s++) { + auto ud = &updateData[s]; + initFlake(this, ud); + } + + useShader(renderer->shader); + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, vertices.numElements * sizeof(Vertex2D), &vertices.data[0], GL_DYNAMIC_DRAW); + + glEnableVertexAttribArray(renderer->attributes.position); + glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0); + + glEnableVertexAttribArray(renderer->attributes.color); + glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color)); + + for (i32 idx = 0; idx < 4; idx++) { + i32 offset = (4 * sizeof(f32)) * idx; + glEnableVertexAttribArray(renderer->attributes.vMatrix + idx); + glVertexAttribPointer(renderer->attributes.vMatrix + idx, + 4, + GL_FLOAT, + GL_FALSE, + sizeof(Vertex2D), + (GLvoid *)(offsetof(Vertex2D, vMatrix) + offset)); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} + +inline void resetFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) { + ud->position.y = 2 * renderer->yMax; + ud->velocity = Vector2(randomFloatBetween(-10, 10), randomFloatBetween(-100, -85)); + ud->rotation = 0; +} + +inline void updateFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud, i32 s, f32 dtSeconds) { + ud->velocity = ud->velocity + Vector2(0, -(GRAVITY * dtSeconds)); + //if (addWind) ud->velocity += renderer->windSpeed; + ud->position += ud->velocity * dtSeconds; + ud->rotation += ud->rotateVelocity * dtSeconds; + + Mat4x4 m = Mat4x4().translateByVec2(ud->position).rotate2D(ud->rotation); + for (i32 v = ud->vtxIdx; v < (ud->vtxIdx + ud->numVertices); v++) { + renderer->vertices.data[v].vMatrix = m; + } + + if (ud->position.y <= -ud->radius) { + resetFlake(renderer, ud); + } +} + +void SnowflakeParticleRenderer::update(f32 dtSeconds) { + timeUntilNextWindSeconds -= dtSeconds; + if (timeUntilNextWindSeconds < 0) { + timeUntilNextWindSeconds = randomFloatBetween(2.5f, 10.f); + } + + for (i32 s = 0; s < numSnowflakes; s++) { + SnowflakeUpdateData* ud = &updateData[s]; + updateFlake(this, ud, s, dtSeconds); + } +} + +void SnowflakeParticleRenderer::render(Renderer2d* renderer) { + setShaderMat4(renderer->uniforms.model, model); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.numElements * sizeof(Vertex2D), &vertices.data[0]); + + glBindVertexArray(vao); + glDrawArrays(GL_TRIANGLES, 0, vertices.numElements); + glBindVertexArray(0); +} + +void SnowflakeParticleRenderer::unload() { + glDeleteVertexArrays(1, &vao); + glDeleteBuffers(1, &vbo); + vao = 0; + vbo = 0; + vertices.deallocate(); + delete [] updateData; +} diff --git a/themes/src/winter/snowflake.h b/themes/src/winter/snowflake.h new file mode 100644 index 0000000..11a1438 --- /dev/null +++ b/themes/src/winter/snowflake.h @@ -0,0 +1,48 @@ +#ifndef SNOWFLAKE_H +#define SNOWFLAKE_H + +#include "../types.h" +#include "../mathlib.h" +#include "../list.h" +#include "windfield.hpp" + +struct Renderer2d; +struct Vertex2D; + +struct SnowflakeLoadParameters { + i32 numSnowflakes = 480; + f32 windIntervalSeconds = 1.5f; +}; + +struct SnowflakeUpdateData { + Vector2 velocity; + Vector2 position; + f32 rotateVelocity = 0.f; + f32 rotation = 0; + f32 radius; + + i32 vtxIdx = 0; + i32 numVertices = 0; +}; + +struct SnowflakeParticleRenderer { + f32 xMax = 0; + f32 yMax = 0; + f32 windIntervalSeconds = 1.5; + i32 numSnowflakes = 0; + f32 timeUntilNextWindSeconds = 0; + WindField<100, 100, 10> wind; + SnowflakeUpdateData* updateData; + + u32 vao; + u32 vbo; + Mat4x4 model; + matte::List vertices; + + void load(SnowflakeLoadParameters params, Renderer2d* renderer); + void update(f32 dtSeconds); + void render(Renderer2d* renderer); + void unload(); +}; + +#endif diff --git a/themes/src/winter/windfield.cpp b/themes/src/winter/windfield.cpp new file mode 100644 index 0000000..f6c3be3 --- /dev/null +++ b/themes/src/winter/windfield.cpp @@ -0,0 +1,28 @@ +#include "windfield.hpp" + + +template +void WindField::load(f32 cellSizePixels, i32 fieldWithCells, i32 fieldHeightCells, f32 ttl, Vector2 origin) { + this->ttl = ttl; + this->origin = origin; + this->end = this->origin + Vector2(Width * CellDimension, Height * CellDimension); +} + +template +bool WindField::addVector(i32 x, i32 y, Vector2& v) { + field[x][y] = v; + return false; +} + +template +Vector2 WindField::getWindFactor(Vector2& v) { + if (v.x >= origin.x && v.x <= end.x + && v.y >= origin.y && v.y <= end.y) { + Vector2 positionInField = v - this->origin; + i32 cellX = static_cast(Width / positionInField.x); + i32 cellY = static_cast(Height / positionInField.y); + return field[cellX][cellY]; + } + + return Vector2(); +} diff --git a/themes/src/winter/windfield.hpp b/themes/src/winter/windfield.hpp new file mode 100644 index 0000000..5bf0c38 --- /dev/null +++ b/themes/src/winter/windfield.hpp @@ -0,0 +1,39 @@ +#ifndef WIND_FIELD_HPP +#define WIND_FIELD_HPP +#include "../types.h" +#include "../mathlib.h" + +/** + A Windfield represents a field of vectors in a rectangular region. + The Width and Height are given in units of CellDimenions. The CellDimension + is given in pixels. + */ +template +struct WindField { + f32 ttl = 0.f; + Vector2 origin; + Vector2 end; + + /* + Granularity of each cell in pixels. + */ + const f32 cellDimension = CellDimension; + + /* + Width of the vector field in CellDimensions. + */ + const f32 width = Width; + + /* + Height of the vector vield in CellDimensions. + */ + const f32 height = Height; + + Vector2** field; + + void load(f32 cellSizePixels, i32 fieldWithCells, i32 fieldHeightCells, f32 ttl, Vector2 origin); + bool addVector(i32 x, i32 y, Vector2& v); + Vector2 getWindFactor(Vector2& v); +}; + +#endif diff --git a/themes/src/winter/winter_theme.cpp b/themes/src/winter/winter_theme.cpp new file mode 100644 index 0000000..a628f18 --- /dev/null +++ b/themes/src/winter/winter_theme.cpp @@ -0,0 +1,32 @@ +#include "winter_theme.hpp" +#include "../renderer_2d.h" + +WinterTheme::WinterTheme(WebglContext* context) +{ + renderer.load(context); + load(); +} + +WinterTheme::~WinterTheme() +{ + unload(); +} + +void WinterTheme::load() { + renderer.clearColor = Vector4(200, 229, 239, 255).toNormalizedColor(); + SnowflakeLoadParameters lp; + spr.load(lp, &renderer); +} + +void WinterTheme::update(f32 dtSeconds) { + spr.update(dtSeconds); +} + +void WinterTheme::render() { + renderer.render(); + spr.render(&renderer); +} + +void WinterTheme::unload() { + spr.unload(); +} diff --git a/themes/src/winter/winter_theme.hpp b/themes/src/winter/winter_theme.hpp new file mode 100644 index 0000000..d1c3e05 --- /dev/null +++ b/themes/src/winter/winter_theme.hpp @@ -0,0 +1,25 @@ +#ifndef WINTER_THEME_HPP +#define WINTER_THEME_HPP + +#include "snowflake.h" +#include "../types.h" +#include "../theme.h" +#include "../renderer_2d.h" + +struct WebglContext; + +struct WinterTheme : public Theme { +public: + WinterTheme(WebglContext*); + ~WinterTheme(); + SnowflakeParticleRenderer spr; + + void load(); + void update(f32 dtSeconds); + void render(); + void unload(); +private: + Renderer2d renderer; +}; + +#endif -- cgit v1.2.1