diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..b35845c
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "game/lib/Concord"]
+ path = game/lib/Concord
+ url = https://github.com/Keyslam-Group/Concord
+[submodule "game/lib/classic"]
+ path = game/lib/classic
+ url = https://github.com/rxi/classic/
diff --git a/game/asset/audio/bgm/Ensemble.mp3 b/game/asset/audio/bgm/Ensemble.mp3
new file mode 100644
index 0000000..8fd8ec0
Binary files /dev/null and b/game/asset/audio/bgm/Ensemble.mp3 differ
diff --git a/game/asset/audio/sfx/book_flip.1.ogg b/game/asset/audio/sfx/book_flip.1.ogg
new file mode 100644
index 0000000..110051e
Binary files /dev/null and b/game/asset/audio/sfx/book_flip.1.ogg differ
diff --git a/game/asset/audio/sfx/book_flip.10.ogg b/game/asset/audio/sfx/book_flip.10.ogg
new file mode 100644
index 0000000..d7136f7
Binary files /dev/null and b/game/asset/audio/sfx/book_flip.10.ogg differ
diff --git a/game/asset/audio/sfx/book_flip.2.ogg b/game/asset/audio/sfx/book_flip.2.ogg
new file mode 100644
index 0000000..7a9b833
Binary files /dev/null and b/game/asset/audio/sfx/book_flip.2.ogg differ
diff --git a/game/asset/audio/sfx/book_flip.3.ogg b/game/asset/audio/sfx/book_flip.3.ogg
new file mode 100644
index 0000000..4b8a9b4
Binary files /dev/null and b/game/asset/audio/sfx/book_flip.3.ogg differ
diff --git a/game/asset/audio/sfx/book_flip.4.ogg b/game/asset/audio/sfx/book_flip.4.ogg
new file mode 100644
index 0000000..a2adc60
Binary files /dev/null and b/game/asset/audio/sfx/book_flip.4.ogg differ
diff --git a/game/asset/audio/sfx/book_flip.5.ogg b/game/asset/audio/sfx/book_flip.5.ogg
new file mode 100644
index 0000000..eba65d9
Binary files /dev/null and b/game/asset/audio/sfx/book_flip.5.ogg differ
diff --git a/game/asset/audio/sfx/book_flip.6.ogg b/game/asset/audio/sfx/book_flip.6.ogg
new file mode 100644
index 0000000..2ab2487
Binary files /dev/null and b/game/asset/audio/sfx/book_flip.6.ogg differ
diff --git a/game/asset/audio/sfx/book_flip.7.ogg b/game/asset/audio/sfx/book_flip.7.ogg
new file mode 100644
index 0000000..7f30bb9
Binary files /dev/null and b/game/asset/audio/sfx/book_flip.7.ogg differ
diff --git a/game/asset/audio/sfx/book_flip.8.ogg b/game/asset/audio/sfx/book_flip.8.ogg
new file mode 100644
index 0000000..88ff902
Binary files /dev/null and b/game/asset/audio/sfx/book_flip.8.ogg differ
diff --git a/game/asset/audio/sfx/book_flip.9.ogg b/game/asset/audio/sfx/book_flip.9.ogg
new file mode 100644
index 0000000..1052594
Binary files /dev/null and b/game/asset/audio/sfx/book_flip.9.ogg differ
diff --git a/game/asset/image/book/StoryMode.png b/game/asset/image/book/StoryMode.png
new file mode 100644
index 0000000..34c42f4
Binary files /dev/null and b/game/asset/image/book/StoryMode.png differ
diff --git a/game/asset/image/book/StoryModeBook.png b/game/asset/image/book/StoryModeBook.png
new file mode 100644
index 0000000..ab27944
Binary files /dev/null and b/game/asset/image/book/StoryModeBook.png differ
diff --git a/game/asset/image/book/StoryModeBook11.png b/game/asset/image/book/StoryModeBook11.png
new file mode 100644
index 0000000..544d671
Binary files /dev/null and b/game/asset/image/book/StoryModeBook11.png differ
diff --git a/game/asset/image/book/StoryModeBook16.png b/game/asset/image/book/StoryModeBook16.png
new file mode 100644
index 0000000..749c209
Binary files /dev/null and b/game/asset/image/book/StoryModeBook16.png differ
diff --git a/game/asset/image/book/StoryModeBook2.png b/game/asset/image/book/StoryModeBook2.png
new file mode 100644
index 0000000..bd0dfab
Binary files /dev/null and b/game/asset/image/book/StoryModeBook2.png differ
diff --git a/game/asset/image/book/StoryModeBook3.png b/game/asset/image/book/StoryModeBook3.png
new file mode 100644
index 0000000..d36aaad
Binary files /dev/null and b/game/asset/image/book/StoryModeBook3.png differ
diff --git a/game/asset/image/book/StoryModeBook4.png b/game/asset/image/book/StoryModeBook4.png
new file mode 100644
index 0000000..749c209
Binary files /dev/null and b/game/asset/image/book/StoryModeBook4.png differ
diff --git a/game/asset/image/book/StoryModeBook5.png b/game/asset/image/book/StoryModeBook5.png
new file mode 100644
index 0000000..c66f873
Binary files /dev/null and b/game/asset/image/book/StoryModeBook5.png differ
diff --git a/game/asset/image/book/StoryModeBook6.png b/game/asset/image/book/StoryModeBook6.png
new file mode 100644
index 0000000..2a1a397
Binary files /dev/null and b/game/asset/image/book/StoryModeBook6.png differ
diff --git a/game/asset/image/book/StoryModeBook7.png b/game/asset/image/book/StoryModeBook7.png
new file mode 100644
index 0000000..140c11a
Binary files /dev/null and b/game/asset/image/book/StoryModeBook7.png differ
diff --git a/game/asset/image/book/StoryModeBook8.png b/game/asset/image/book/StoryModeBook8.png
new file mode 100644
index 0000000..d3f5bdb
Binary files /dev/null and b/game/asset/image/book/StoryModeBook8.png differ
diff --git a/game/asset/image/book/StoryModeBook9.png b/game/asset/image/book/StoryModeBook9.png
new file mode 100644
index 0000000..544d671
Binary files /dev/null and b/game/asset/image/book/StoryModeBook9.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/LICENSE b/game/asset/image/sample/javascript-racer-master/LICENSE
new file mode 100644
index 0000000..efec39d
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2012, 2013, 2014, 2015, 2016 Jake Gordon and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+===============================================================================
+
diff --git a/game/asset/image/sample/javascript-racer-master/README.md b/game/asset/image/sample/javascript-racer-master/README.md
new file mode 100644
index 0000000..b765bff
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/README.md
@@ -0,0 +1,111 @@
+Javascript Pseudo 3D Racer
+==========================
+
+An Outrun-style pseudo-3d racing game in HTML5 and Javascript
+
+ * [play the game](https://jakesgordon.com/games/racer/)
+ * view the [source](https://github.com/jakesgordon/javascript-racer)
+ * read about [how it works](https://jakesgordon.com/writing/javascript-racer/)
+
+Incrementally built up in 4 parts:
+
+ * play the [straight road demo](https://jakesgordon.com/games/racer/v1-straight/)
+ * play the [curves demo](https://jakesgordon.com/games/racer/v2-curves/)
+ * play the [hills demo](https://jakesgordon.com/games/racer/v3-hills/)
+ * play the [final version](https://jakesgordon.com/games/racer/)
+
+With detailed descriptions of how each part works:
+
+ * read more about [v1 - straight roads](https://jakesgordon.com/writing/javascript-racer-v1-straight/)
+ * read more about [v2 - curves](https://jakesgordon.com/writing/javascript-racer-v2-curves/)
+ * read more about [v3 - hills](https://jakesgordon.com/writing/javascript-racer-v3-hills/)
+ * read more about v4 - final (coming soon)
+
+A note on performance
+=====================
+
+The performance of this game is **very** machine/browser dependent. It works quite well in modern
+browsers, especially those with GPU canvas acceleration, but a bad graphics driver can kill it stone
+dead. So your mileage may vary. There are controls provided to change the rendering resolution
+and the draw distance to scale to fit your machine.
+
+Currently supported browsers include:
+
+ * Firefox (v12+) works great, 60fps at high res - Nice!
+ * Chrome (v19+) works great, 60fps at high res... provided you dont have a bad GPU driver
+ * IE9 - ok, 30fps at medium res... not great, but at least it works
+
+The current state of mobile browser performance is pretty dismal. Dont expect this to be playable on
+any mobile device.
+
+>> _NOTE: I havent actually spent anytime optimizing for performance yet. So it might be possible to
+ make it play well on older browsers, but that's not really what this project is about._
+
+A note on code structure
+========================
+
+This project happens to be implemented in javascript (because its easy for prototyping) but
+is not intended to demonstrate javascript techniques or best practices. In fact, in order to
+keep it simple to understand it embeds the javascript for each example directly in the HTML
+page (horror!) and, even worse, uses global variables and functions (OMG!).
+
+If I was building a real game I would have much more structure and organization to the
+code, but since its just a racing game tech demo, I have elected to [KISS](http://en.wikipedia.org/wiki/KISS_principle).
+
+FUTURE
+======
+
+It's quite astounding what it takes to actually [finish](https://jakesgordon.com/writing/defining-finished/)
+a game, even a simple one. And this is not a project that I plan on polishing into a finished state. It should
+really be considered just how to get started with a pseudo-3d racing game.
+
+If we were to try to turn it into a real game we would have to consider:
+
+ * car sound fx
+ * better synchronized music
+ * full screen mode
+ * HUD fx (flash on fastest lap, confetti, color coded speedometer, etc)
+ * more accurate sprite collision
+ * better car AI (steering, braking etc)
+ * an actual crash when colliding at high speed
+ * more bounce when car is off road
+ * screen shake when off-road or collision
+ * throw up dirt particles when off road
+ * more dynamic camera (lower at faster speed, swoop over hills etc)
+ * automatic resolution & drawDistance detection
+ * projection based curves ? x,y rotation
+ * sub-pixel aliasing artifacts on curves
+ * smarter fog to cover sprites (blue against sky, cover sprites)
+ * multiple stages, different maps
+ * a lap map, with current position indicator
+ * road splits and joins
+ * day/night cycle
+ * weather effects
+ * tunnels, bridges, clouds, walls, buildings
+ * city, desert, ocean
+ * add city of seattle and space needle to background
+ * 'bad guys' - add some competetor drivers to race against as well as the 'traffic'
+ * different game modes - fastest lap, 1-on-1 racing, collect coins ? shoot bad guys ?
+ * a whole lot of gameplay tuning
+ * ...
+ * ...
+
+Related Links
+=============
+
+ * [Lou's Pseudo-3d Page](http://www.extentofthejam.com/pseudo/) - high level how-to guide
+ * [Racer 10k](https://github.com/onaluf/RacerJS) - another javascript racing game
+
+License
+=======
+
+[MIT](http://en.wikipedia.org/wiki/MIT_License) license.
+
+>> NOTE: the music tracks included in this project are royalty free resources paid for and licensed
+from [Lucky Lion Studios](http://luckylionstudios.com/). They are licensed ONLY for use in this
+project and should not be reproduced.
+
+>> NOTE: the sprite graphics are placeholder graphics [borrowed](http://pixel.garoux.net/game/44) from the old
+genesis version of outrun and used here as teaching examples. If there are any pixel artists out there who want to
+provide original art to turn this into a real game please get in touch!
+
diff --git a/game/asset/image/sample/javascript-racer-master/Rakefile b/game/asset/image/sample/javascript-racer-master/Rakefile
new file mode 100644
index 0000000..c02a1df
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/Rakefile
@@ -0,0 +1,36 @@
+
+desc 'recreate sprite sheets'
+task 'resprite' do
+ require 'sprite_factory'
+
+ SpriteFactory.run!('images/sprites', :layout => :packed, :output_style => 'images/sprites.js', :margin => 5, :nocomments => true) do |images|
+ SpriteHelper.javascript_style("SPRITES", images)
+ end
+
+ SpriteFactory.run!('images/background', :layout => :vertical, :output_style => 'images/background.js', :margin => 5, :nocomments => true) do |images|
+ SpriteHelper.javascript_style("BACKGROUND", images)
+ end
+
+end
+
+#------------------------------------------------------------------------------
+
+module SpriteHelper
+
+ # slightly unusual use of sprite-factory to generate a javascript object structure instead of CSS attributes...
+ def self.javascript_style(variable, images)
+ maxname = images.keys.inject(0) {|n,key| [n,key.length].max }
+ rules = []
+ images.each do |name, i|
+ name = name.upcase
+ whitespace = ' '*(maxname-name.length)
+ x = '%4d' % i[:cssx]
+ y = '%4d' % i[:cssy]
+ w = '%4d' % i[:cssw]
+ h = '%4d' % i[:cssh]
+ rules << " #{name}: #{whitespace}{ x: #{x}, y: #{y}, w: #{w}, h: #{h} }"
+ end
+ "var #{variable} = {\n#{rules.join(",\n")}\n};"
+ end
+
+end
diff --git a/game/asset/image/sample/javascript-racer-master/common.css b/game/asset/image/sample/javascript-racer-master/common.css
new file mode 100644
index 0000000..5bb2d2b
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/common.css
@@ -0,0 +1,28 @@
+
+/****************************************/
+/* common styles used for v1 through v4 */
+/****************************************/
+
+body { font-family: Arial, Helvetica, sans-serif; }
+#stats { border: 2px solid black; }
+#controls { width: 28em; float: left; padding: 1em; font-size: 0.7em; }
+#controls th { text-align: right; vertical-align: middle; }
+#instructions { clear: left; float: left; width: 17em; padding: 1em; border: 1px solid black; box-shadow: 0 0 5px black; }
+#racer { position: relative; z-index: 0; width: 640px; height: 480px; margin-left: 20em; border: 2px solid black; }
+#canvas { position: absolute; z-index: 0; width: 640px; height: 480px; z-index: 0; background-color: #72D7EE; }
+#mute { background-position: 0px 0px; width: 32px; height: 32px; background: url(images/mute.png); display: inline-block; cursor: pointer; position: absolute; margin-left: 20em; }
+#mute.on { background-position: -32px 0px; }
+
+/**************************************************/
+/* rudimentary heads up display (only used in v4) */
+/**************************************************/
+
+#hud { position: absolute; z-index: 1; width: 640px; padding: 5px 0; font-family: Verdana, Geneva, sans-serif; font-size: 0.8em; background-color: rgba(255,0,0,0.4); color: black; border-bottom: 2px solid black; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; }
+#hud .hud { background-color: rgba(255,255,255,0.6); padding: 5px; border: 1px solid black; margin: 0 5px; transition-property: background-color; transition-duration: 2s; -webkit-transition-property: background-color; -webkit-transition-duration: 2s; }
+#hud #speed { float: right; }
+#hud #current_lap_time { float: left; }
+#hud #last_lap_time { float: left; display: none; }
+#hud #fast_lap_time { display: block; width: 12em; margin: 0 auto; text-align: center; transition-property: background-color; transition-duration: 2s; -webkit-transition-property: background-color; -webkit-transition-duration: 2s; }
+#hud .value { color: black; font-weight: bold; }
+#hud .fastest { background-color: rgba(255,215,0,0.5); }
+
diff --git a/game/asset/image/sample/javascript-racer-master/common.js b/game/asset/image/sample/javascript-racer-master/common.js
new file mode 100644
index 0000000..a0eb697
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/common.js
@@ -0,0 +1,414 @@
+//=========================================================================
+// minimalist DOM helpers
+//=========================================================================
+
+var Dom = {
+
+ get: function(id) { return ((id instanceof HTMLElement) || (id === document)) ? id : document.getElementById(id); },
+ set: function(id, html) { Dom.get(id).innerHTML = html; },
+ on: function(ele, type, fn, capture) { Dom.get(ele).addEventListener(type, fn, capture); },
+ un: function(ele, type, fn, capture) { Dom.get(ele).removeEventListener(type, fn, capture); },
+ show: function(ele, type) { Dom.get(ele).style.display = (type || 'block'); },
+ blur: function(ev) { ev.target.blur(); },
+
+ addClassName: function(ele, name) { Dom.toggleClassName(ele, name, true); },
+ removeClassName: function(ele, name) { Dom.toggleClassName(ele, name, false); },
+ toggleClassName: function(ele, name, on) {
+ ele = Dom.get(ele);
+ var classes = ele.className.split(' ');
+ var n = classes.indexOf(name);
+ on = (typeof on == 'undefined') ? (n < 0) : on;
+ if (on && (n < 0))
+ classes.push(name);
+ else if (!on && (n >= 0))
+ classes.splice(n, 1);
+ ele.className = classes.join(' ');
+ },
+
+ storage: window.localStorage || {}
+
+}
+
+//=========================================================================
+// general purpose helpers (mostly math)
+//=========================================================================
+
+var Util = {
+
+ timestamp: function() { return new Date().getTime(); },
+ toInt: function(obj, def) { if (obj !== null) { var x = parseInt(obj, 10); if (!isNaN(x)) return x; } return Util.toInt(def, 0); },
+ toFloat: function(obj, def) { if (obj !== null) { var x = parseFloat(obj); if (!isNaN(x)) return x; } return Util.toFloat(def, 0.0); },
+ limit: function(value, min, max) { return Math.max(min, Math.min(value, max)); },
+ randomInt: function(min, max) { return Math.round(Util.interpolate(min, max, Math.random())); },
+ randomChoice: function(options) { return options[Util.randomInt(0, options.length-1)]; },
+ percentRemaining: function(n, total) { return (n%total)/total; },
+ accelerate: function(v, accel, dt) { return v + (accel * dt); },
+ interpolate: function(a,b,percent) { return a + (b-a)*percent },
+ easeIn: function(a,b,percent) { return a + (b-a)*Math.pow(percent,2); },
+ easeOut: function(a,b,percent) { return a + (b-a)*(1-Math.pow(1-percent,2)); },
+ easeInOut: function(a,b,percent) { return a + (b-a)*((-Math.cos(percent*Math.PI)/2) + 0.5); },
+ exponentialFog: function(distance, density) { return 1 / (Math.pow(Math.E, (distance * distance * density))); },
+
+ increase: function(start, increment, max) { // with looping
+ var result = start + increment;
+ while (result >= max)
+ result -= max;
+ while (result < 0)
+ result += max;
+ return result;
+ },
+
+ project: function(p, cameraX, cameraY, cameraZ, cameraDepth, width, height, roadWidth) {
+ p.camera.x = (p.world.x || 0) - cameraX;
+ p.camera.y = (p.world.y || 0) - cameraY;
+ p.camera.z = (p.world.z || 0) - cameraZ;
+ p.screen.scale = cameraDepth/p.camera.z;
+ p.screen.x = Math.round((width/2) + (p.screen.scale * p.camera.x * width/2));
+ p.screen.y = Math.round((height/2) - (p.screen.scale * p.camera.y * height/2));
+ p.screen.w = Math.round( (p.screen.scale * roadWidth * width/2));
+ },
+
+ overlap: function(x1, w1, x2, w2, percent) {
+ var half = (percent || 1)/2;
+ var min1 = x1 - (w1*half);
+ var max1 = x1 + (w1*half);
+ var min2 = x2 - (w2*half);
+ var max2 = x2 + (w2*half);
+ return ! ((max1 < min2) || (min1 > max2));
+ }
+
+}
+
+//=========================================================================
+// POLYFILL for requestAnimationFrame
+//=========================================================================
+
+if (!window.requestAnimationFrame) { // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ window.requestAnimationFrame = window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(callback, element) {
+ window.setTimeout(callback, 1000 / 60);
+ }
+}
+
+//=========================================================================
+// GAME LOOP helpers
+//=========================================================================
+
+var Game = { // a modified version of the game loop from my previous boulderdash game - see https://jakesgordon.com/writing/javascript-boulderdash/#gameloop
+
+ run: function(options) {
+
+ Game.loadImages(options.images, function(images) {
+
+ options.ready(images); // tell caller to initialize itself because images are loaded and we're ready to rumble
+
+ Game.setKeyListener(options.keys);
+
+ var canvas = options.canvas, // canvas render target is provided by caller
+ update = options.update, // method to update game logic is provided by caller
+ render = options.render, // method to render the game is provided by caller
+ step = options.step, // fixed frame step (1/fps) is specified by caller
+ stats = options.stats, // stats instance is provided by caller
+ now = null,
+ last = Util.timestamp(),
+ dt = 0,
+ gdt = 0;
+
+ function frame() {
+ now = Util.timestamp();
+ dt = Math.min(1, (now - last) / 1000); // using requestAnimationFrame have to be able to handle large delta's caused when it 'hibernates' in a background or non-visible tab
+ gdt = gdt + dt;
+ while (gdt > step) {
+ gdt = gdt - step;
+ update(step);
+ }
+ render();
+ stats.update();
+ last = now;
+ requestAnimationFrame(frame, canvas);
+ }
+ frame(); // lets get this party started
+ Game.playMusic();
+ });
+ },
+
+ //---------------------------------------------------------------------------
+
+ loadImages: function(names, callback) { // load multiple images and callback when ALL images have loaded
+ var result = [];
+ var count = names.length;
+
+ var onload = function() {
+ if (--count == 0)
+ callback(result);
+ };
+
+ for(var n = 0 ; n < names.length ; n++) {
+ var name = names[n];
+ result[n] = document.createElement('img');
+ Dom.on(result[n], 'load', onload);
+ result[n].src = "images/" + name + ".png";
+ }
+ },
+
+ //---------------------------------------------------------------------------
+
+ setKeyListener: function(keys) {
+ var onkey = function(keyCode, mode) {
+ var n, k;
+ for(n = 0 ; n < keys.length ; n++) {
+ k = keys[n];
+ k.mode = k.mode || 'up';
+ if ((k.key == keyCode) || (k.keys && (k.keys.indexOf(keyCode) >= 0))) {
+ if (k.mode == mode) {
+ k.action.call();
+ }
+ }
+ }
+ };
+ Dom.on(document, 'keydown', function(ev) { onkey(ev.keyCode, 'down'); } );
+ Dom.on(document, 'keyup', function(ev) { onkey(ev.keyCode, 'up'); } );
+ },
+
+ //---------------------------------------------------------------------------
+
+ stats: function(parentId, id) { // construct mr.doobs FPS counter - along with friendly good/bad/ok message box
+
+ var result = new Stats();
+ result.domElement.id = id || 'stats';
+ Dom.get(parentId).appendChild(result.domElement);
+
+ var msg = document.createElement('div');
+ msg.style.cssText = "border: 2px solid gray; padding: 5px; margin-top: 5px; text-align: left; font-size: 1.15em; text-align: right;";
+ msg.innerHTML = "Your canvas performance is ";
+ Dom.get(parentId).appendChild(msg);
+
+ var value = document.createElement('span');
+ value.innerHTML = "...";
+ msg.appendChild(value);
+
+ setInterval(function() {
+ var fps = result.current();
+ var ok = (fps > 50) ? 'good' : (fps < 30) ? 'bad' : 'ok';
+ var color = (fps > 50) ? 'green' : (fps < 30) ? 'red' : 'gray';
+ value.innerHTML = ok;
+ value.style.color = color;
+ msg.style.borderColor = color;
+ }, 5000);
+ return result;
+ },
+
+ //---------------------------------------------------------------------------
+
+ playMusic: function() {
+ var music = Dom.get('music');
+ music.loop = true;
+ music.volume = 0.05; // shhhh! annoying music!
+ music.muted = (Dom.storage.muted === "true");
+ music.play();
+ Dom.toggleClassName('mute', 'on', music.muted);
+ Dom.on('mute', 'click', function() {
+ Dom.storage.muted = music.muted = !music.muted;
+ Dom.toggleClassName('mute', 'on', music.muted);
+ });
+ }
+
+}
+
+//=========================================================================
+// canvas rendering helpers
+//=========================================================================
+
+var Render = {
+
+ polygon: function(ctx, x1, y1, x2, y2, x3, y3, x4, y4, color) {
+ ctx.fillStyle = color;
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ ctx.lineTo(x3, y3);
+ ctx.lineTo(x4, y4);
+ ctx.closePath();
+ ctx.fill();
+ },
+
+ //---------------------------------------------------------------------------
+
+ segment: function(ctx, width, lanes, x1, y1, w1, x2, y2, w2, fog, color) {
+
+ var r1 = Render.rumbleWidth(w1, lanes),
+ r2 = Render.rumbleWidth(w2, lanes),
+ l1 = Render.laneMarkerWidth(w1, lanes),
+ l2 = Render.laneMarkerWidth(w2, lanes),
+ lanew1, lanew2, lanex1, lanex2, lane;
+
+ ctx.fillStyle = color.grass;
+ ctx.fillRect(0, y2, width, y1 - y2);
+
+ Render.polygon(ctx, x1-w1-r1, y1, x1-w1, y1, x2-w2, y2, x2-w2-r2, y2, color.rumble);
+ Render.polygon(ctx, x1+w1+r1, y1, x1+w1, y1, x2+w2, y2, x2+w2+r2, y2, color.rumble);
+ Render.polygon(ctx, x1-w1, y1, x1+w1, y1, x2+w2, y2, x2-w2, y2, color.road);
+
+ if (color.lane) {
+ lanew1 = w1*2/lanes;
+ lanew2 = w2*2/lanes;
+ lanex1 = x1 - w1 + lanew1;
+ lanex2 = x2 - w2 + lanew2;
+ for(lane = 1 ; lane < lanes ; lanex1 += lanew1, lanex2 += lanew2, lane++)
+ Render.polygon(ctx, lanex1 - l1/2, y1, lanex1 + l1/2, y1, lanex2 + l2/2, y2, lanex2 - l2/2, y2, color.lane);
+ }
+
+ Render.fog(ctx, 0, y1, width, y2-y1, fog);
+ },
+
+ //---------------------------------------------------------------------------
+
+ background: function(ctx, background, width, height, layer, rotation, offset) {
+
+ rotation = rotation || 0;
+ offset = offset || 0;
+
+ var imageW = layer.w/2;
+ var imageH = layer.h;
+
+ var sourceX = layer.x + Math.floor(layer.w * rotation);
+ var sourceY = layer.y
+ var sourceW = Math.min(imageW, layer.x+layer.w-sourceX);
+ var sourceH = imageH;
+
+ var destX = 0;
+ var destY = offset;
+ var destW = Math.floor(width * (sourceW/imageW));
+ var destH = height;
+
+ ctx.drawImage(background, sourceX, sourceY, sourceW, sourceH, destX, destY, destW, destH);
+ if (sourceW < imageW)
+ ctx.drawImage(background, layer.x, sourceY, imageW-sourceW, sourceH, destW-1, destY, width-destW, destH);
+ },
+
+ //---------------------------------------------------------------------------
+
+ sprite: function(ctx, width, height, resolution, roadWidth, sprites, sprite, scale, destX, destY, offsetX, offsetY, clipY) {
+
+ // scale for projection AND relative to roadWidth (for tweakUI)
+ var destW = (sprite.w * scale * width/2) * (SPRITES.SCALE * roadWidth);
+ var destH = (sprite.h * scale * width/2) * (SPRITES.SCALE * roadWidth);
+
+ destX = destX + (destW * (offsetX || 0));
+ destY = destY + (destH * (offsetY || 0));
+
+ var clipH = clipY ? Math.max(0, destY+destH-clipY) : 0;
+ if (clipH < destH)
+ ctx.drawImage(sprites, sprite.x, sprite.y, sprite.w, sprite.h - (sprite.h*clipH/destH), destX, destY, destW, destH - clipH);
+
+ },
+
+ //---------------------------------------------------------------------------
+
+ player: function(ctx, width, height, resolution, roadWidth, sprites, speedPercent, scale, destX, destY, steer, updown) {
+
+ var bounce = (1.5 * Math.random() * speedPercent * resolution) * Util.randomChoice([-1,1]);
+ var sprite;
+ if (steer < 0)
+ sprite = (updown > 0) ? SPRITES.PLAYER_UPHILL_LEFT : SPRITES.PLAYER_LEFT;
+ else if (steer > 0)
+ sprite = (updown > 0) ? SPRITES.PLAYER_UPHILL_RIGHT : SPRITES.PLAYER_RIGHT;
+ else
+ sprite = (updown > 0) ? SPRITES.PLAYER_UPHILL_STRAIGHT : SPRITES.PLAYER_STRAIGHT;
+
+ Render.sprite(ctx, width, height, resolution, roadWidth, sprites, sprite, scale, destX, destY + bounce, -0.5, -1);
+ },
+
+ //---------------------------------------------------------------------------
+
+ fog: function(ctx, x, y, width, height, fog) {
+ if (fog < 1) {
+ ctx.globalAlpha = (1-fog)
+ ctx.fillStyle = COLORS.FOG;
+ ctx.fillRect(x, y, width, height);
+ ctx.globalAlpha = 1;
+ }
+ },
+
+ rumbleWidth: function(projectedRoadWidth, lanes) { return projectedRoadWidth/Math.max(6, 2*lanes); },
+ laneMarkerWidth: function(projectedRoadWidth, lanes) { return projectedRoadWidth/Math.max(32, 8*lanes); }
+
+}
+
+//=============================================================================
+// RACING GAME CONSTANTS
+//=============================================================================
+
+var KEY = {
+ LEFT: 37,
+ UP: 38,
+ RIGHT: 39,
+ DOWN: 40,
+ A: 65,
+ D: 68,
+ S: 83,
+ W: 87
+};
+
+var COLORS = {
+ SKY: '#72D7EE',
+ TREE: '#005108',
+ FOG: '#005108',
+ LIGHT: { road: '#6B6B6B', grass: '#10AA10', rumble: '#555555', lane: '#CCCCCC' },
+ DARK: { road: '#696969', grass: '#009A00', rumble: '#BBBBBB' },
+ START: { road: 'white', grass: 'white', rumble: 'white' },
+ FINISH: { road: 'black', grass: 'black', rumble: 'black' }
+};
+
+var BACKGROUND = {
+ HILLS: { x: 5, y: 5, w: 1280, h: 480 },
+ SKY: { x: 5, y: 495, w: 1280, h: 480 },
+ TREES: { x: 5, y: 985, w: 1280, h: 480 }
+};
+
+var SPRITES = {
+ PALM_TREE: { x: 5, y: 5, w: 215, h: 540 },
+ BILLBOARD08: { x: 230, y: 5, w: 385, h: 265 },
+ TREE1: { x: 625, y: 5, w: 360, h: 360 },
+ DEAD_TREE1: { x: 5, y: 555, w: 135, h: 332 },
+ BILLBOARD09: { x: 150, y: 555, w: 328, h: 282 },
+ BOULDER3: { x: 230, y: 280, w: 320, h: 220 },
+ COLUMN: { x: 995, y: 5, w: 200, h: 315 },
+ BILLBOARD01: { x: 625, y: 375, w: 300, h: 170 },
+ BILLBOARD06: { x: 488, y: 555, w: 298, h: 190 },
+ BILLBOARD05: { x: 5, y: 897, w: 298, h: 190 },
+ BILLBOARD07: { x: 313, y: 897, w: 298, h: 190 },
+ BOULDER2: { x: 621, y: 897, w: 298, h: 140 },
+ TREE2: { x: 1205, y: 5, w: 282, h: 295 },
+ BILLBOARD04: { x: 1205, y: 310, w: 268, h: 170 },
+ DEAD_TREE2: { x: 1205, y: 490, w: 150, h: 260 },
+ BOULDER1: { x: 1205, y: 760, w: 168, h: 248 },
+ BUSH1: { x: 5, y: 1097, w: 240, h: 155 },
+ CACTUS: { x: 929, y: 897, w: 235, h: 118 },
+ BUSH2: { x: 255, y: 1097, w: 232, h: 152 },
+ BILLBOARD03: { x: 5, y: 1262, w: 230, h: 220 },
+ BILLBOARD02: { x: 245, y: 1262, w: 215, h: 220 },
+ STUMP: { x: 995, y: 330, w: 195, h: 140 },
+ SEMI: { x: 1365, y: 490, w: 122, h: 144 },
+ TRUCK: { x: 1365, y: 644, w: 100, h: 78 },
+ CAR03: { x: 1383, y: 760, w: 88, h: 55 },
+ CAR02: { x: 1383, y: 825, w: 80, h: 59 },
+ CAR04: { x: 1383, y: 894, w: 80, h: 57 },
+ CAR01: { x: 1205, y: 1018, w: 80, h: 56 },
+ PLAYER_UPHILL_LEFT: { x: 1383, y: 961, w: 80, h: 45 },
+ PLAYER_UPHILL_STRAIGHT: { x: 1295, y: 1018, w: 80, h: 45 },
+ PLAYER_UPHILL_RIGHT: { x: 1385, y: 1018, w: 80, h: 45 },
+ PLAYER_LEFT: { x: 995, y: 480, w: 80, h: 41 },
+ PLAYER_STRAIGHT: { x: 1085, y: 480, w: 80, h: 41 },
+ PLAYER_RIGHT: { x: 995, y: 531, w: 80, h: 41 }
+};
+
+SPRITES.SCALE = 0.3 * (1/SPRITES.PLAYER_STRAIGHT.w) // the reference sprite width should be 1/3rd the (half-)roadWidth
+
+SPRITES.BILLBOARDS = [SPRITES.BILLBOARD01, SPRITES.BILLBOARD02, SPRITES.BILLBOARD03, SPRITES.BILLBOARD04, SPRITES.BILLBOARD05, SPRITES.BILLBOARD06, SPRITES.BILLBOARD07, SPRITES.BILLBOARD08, SPRITES.BILLBOARD09];
+SPRITES.PLANTS = [SPRITES.TREE1, SPRITES.TREE2, SPRITES.DEAD_TREE1, SPRITES.DEAD_TREE2, SPRITES.PALM_TREE, SPRITES.BUSH1, SPRITES.BUSH2, SPRITES.CACTUS, SPRITES.STUMP, SPRITES.BOULDER1, SPRITES.BOULDER2, SPRITES.BOULDER3];
+SPRITES.CARS = [SPRITES.CAR01, SPRITES.CAR02, SPRITES.CAR03, SPRITES.CAR04, SPRITES.SEMI, SPRITES.TRUCK];
+
diff --git a/game/asset/image/sample/javascript-racer-master/images/background.js b/game/asset/image/sample/javascript-racer-master/images/background.js
new file mode 100644
index 0000000..cae8fa6
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/images/background.js
@@ -0,0 +1,5 @@
+var BACKGROUND = {
+ HILLS: { x: 5, y: 5, w: 1280, h: 480 },
+ SKY: { x: 5, y: 495, w: 1280, h: 480 },
+ TREES: { x: 5, y: 985, w: 1280, h: 480 }
+};
diff --git a/game/asset/image/sample/javascript-racer-master/images/background.png b/game/asset/image/sample/javascript-racer-master/images/background.png
new file mode 100644
index 0000000..6590b02
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/background.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/background/background.svg b/game/asset/image/sample/javascript-racer-master/images/background/background.svg
new file mode 100644
index 0000000..0bbcd8d
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/images/background/background.svg
@@ -0,0 +1,1921 @@
+
+
+
+
diff --git a/game/asset/image/sample/javascript-racer-master/images/background/hills.png b/game/asset/image/sample/javascript-racer-master/images/background/hills.png
new file mode 100644
index 0000000..f1d2be5
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/background/hills.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/background/sky.png b/game/asset/image/sample/javascript-racer-master/images/background/sky.png
new file mode 100644
index 0000000..26c332d
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/background/sky.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/background/trees.png b/game/asset/image/sample/javascript-racer-master/images/background/trees.png
new file mode 100644
index 0000000..57fb422
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/background/trees.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/mute.png b/game/asset/image/sample/javascript-racer-master/images/mute.png
new file mode 100644
index 0000000..5ee7f91
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/mute.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites.js b/game/asset/image/sample/javascript-racer-master/images/sprites.js
new file mode 100644
index 0000000..2cd9324
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/images/sprites.js
@@ -0,0 +1,36 @@
+var SPRITES = {
+ PALM_TREE: { x: 5, y: 5, w: 215, h: 540 },
+ BILLBOARD08: { x: 230, y: 5, w: 385, h: 265 },
+ TREE1: { x: 625, y: 5, w: 360, h: 360 },
+ DEAD_TREE1: { x: 5, y: 555, w: 135, h: 332 },
+ BILLBOARD09: { x: 150, y: 555, w: 328, h: 282 },
+ BOULDER3: { x: 230, y: 280, w: 320, h: 220 },
+ COLUMN: { x: 995, y: 5, w: 200, h: 315 },
+ BILLBOARD01: { x: 625, y: 375, w: 300, h: 170 },
+ BILLBOARD06: { x: 488, y: 555, w: 298, h: 190 },
+ BILLBOARD05: { x: 5, y: 897, w: 298, h: 190 },
+ BILLBOARD07: { x: 313, y: 897, w: 298, h: 190 },
+ BOULDER2: { x: 621, y: 897, w: 298, h: 140 },
+ TREE2: { x: 1205, y: 5, w: 282, h: 295 },
+ BILLBOARD04: { x: 1205, y: 310, w: 268, h: 170 },
+ DEAD_TREE2: { x: 1205, y: 490, w: 150, h: 260 },
+ BOULDER1: { x: 1205, y: 760, w: 168, h: 248 },
+ BUSH1: { x: 5, y: 1097, w: 240, h: 155 },
+ CACTUS: { x: 929, y: 897, w: 235, h: 118 },
+ BUSH2: { x: 255, y: 1097, w: 232, h: 152 },
+ BILLBOARD03: { x: 5, y: 1262, w: 230, h: 220 },
+ BILLBOARD02: { x: 245, y: 1262, w: 215, h: 220 },
+ STUMP: { x: 995, y: 330, w: 195, h: 140 },
+ SEMI: { x: 1365, y: 490, w: 122, h: 144 },
+ TRUCK: { x: 1365, y: 644, w: 100, h: 78 },
+ CAR03: { x: 1383, y: 760, w: 88, h: 55 },
+ CAR02: { x: 1383, y: 825, w: 80, h: 59 },
+ CAR04: { x: 1383, y: 894, w: 80, h: 57 },
+ CAR01: { x: 1205, y: 1018, w: 80, h: 56 },
+ PLAYER_UPHILL_LEFT: { x: 1383, y: 961, w: 80, h: 45 },
+ PLAYER_UPHILL_STRAIGHT: { x: 1295, y: 1018, w: 80, h: 45 },
+ PLAYER_UPHILL_RIGHT: { x: 1385, y: 1018, w: 80, h: 45 },
+ PLAYER_LEFT: { x: 995, y: 480, w: 80, h: 41 },
+ PLAYER_STRAIGHT: { x: 1085, y: 480, w: 80, h: 41 },
+ PLAYER_RIGHT: { x: 995, y: 531, w: 80, h: 41 }
+};
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites.png b/game/asset/image/sample/javascript-racer-master/images/sprites.png
new file mode 100644
index 0000000..0b302ae
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/billboard01.png b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard01.png
new file mode 100644
index 0000000..9c100cd
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard01.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/billboard02.png b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard02.png
new file mode 100644
index 0000000..1db594e
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard02.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/billboard03.png b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard03.png
new file mode 100644
index 0000000..9a43480
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard03.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/billboard04.png b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard04.png
new file mode 100644
index 0000000..abfd4b1
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard04.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/billboard05.png b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard05.png
new file mode 100644
index 0000000..c470e0d
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard05.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/billboard06.png b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard06.png
new file mode 100644
index 0000000..0ba9efb
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard06.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/billboard07.png b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard07.png
new file mode 100644
index 0000000..646c95b
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard07.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/billboard08.png b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard08.png
new file mode 100644
index 0000000..50f59eb
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard08.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/billboard09.png b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard09.png
new file mode 100644
index 0000000..976f6e5
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/billboard09.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/boulder1.png b/game/asset/image/sample/javascript-racer-master/images/sprites/boulder1.png
new file mode 100644
index 0000000..fe98901
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/boulder1.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/boulder2.png b/game/asset/image/sample/javascript-racer-master/images/sprites/boulder2.png
new file mode 100644
index 0000000..67b62f6
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/boulder2.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/boulder3.png b/game/asset/image/sample/javascript-racer-master/images/sprites/boulder3.png
new file mode 100644
index 0000000..637d69a
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/boulder3.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/bush1.png b/game/asset/image/sample/javascript-racer-master/images/sprites/bush1.png
new file mode 100644
index 0000000..b22d74d
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/bush1.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/bush2.png b/game/asset/image/sample/javascript-racer-master/images/sprites/bush2.png
new file mode 100644
index 0000000..61d4b3c
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/bush2.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/cactus.png b/game/asset/image/sample/javascript-racer-master/images/sprites/cactus.png
new file mode 100644
index 0000000..a52df7e
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/cactus.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/car01.png b/game/asset/image/sample/javascript-racer-master/images/sprites/car01.png
new file mode 100644
index 0000000..8efdb84
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/car01.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/car02.png b/game/asset/image/sample/javascript-racer-master/images/sprites/car02.png
new file mode 100644
index 0000000..54ab5a1
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/car02.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/car03.png b/game/asset/image/sample/javascript-racer-master/images/sprites/car03.png
new file mode 100644
index 0000000..dbc629e
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/car03.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/car04.png b/game/asset/image/sample/javascript-racer-master/images/sprites/car04.png
new file mode 100644
index 0000000..611b7ab
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/car04.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/column.png b/game/asset/image/sample/javascript-racer-master/images/sprites/column.png
new file mode 100644
index 0000000..6b1e058
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/column.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/dead_tree1.png b/game/asset/image/sample/javascript-racer-master/images/sprites/dead_tree1.png
new file mode 100644
index 0000000..399c9f6
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/dead_tree1.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/dead_tree2.png b/game/asset/image/sample/javascript-racer-master/images/sprites/dead_tree2.png
new file mode 100644
index 0000000..ad7e52e
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/dead_tree2.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/palm_tree.png b/game/asset/image/sample/javascript-racer-master/images/sprites/palm_tree.png
new file mode 100644
index 0000000..8044b5c
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/palm_tree.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/player_left.png b/game/asset/image/sample/javascript-racer-master/images/sprites/player_left.png
new file mode 100644
index 0000000..457d943
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/player_left.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/player_right.png b/game/asset/image/sample/javascript-racer-master/images/sprites/player_right.png
new file mode 100644
index 0000000..ca26b0a
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/player_right.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/player_straight.png b/game/asset/image/sample/javascript-racer-master/images/sprites/player_straight.png
new file mode 100644
index 0000000..ef1c8cd
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/player_straight.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/player_uphill_left.png b/game/asset/image/sample/javascript-racer-master/images/sprites/player_uphill_left.png
new file mode 100644
index 0000000..8aaa6cf
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/player_uphill_left.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/player_uphill_right.png b/game/asset/image/sample/javascript-racer-master/images/sprites/player_uphill_right.png
new file mode 100644
index 0000000..3e3dfdb
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/player_uphill_right.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/player_uphill_straight.png b/game/asset/image/sample/javascript-racer-master/images/sprites/player_uphill_straight.png
new file mode 100644
index 0000000..634d7a0
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/player_uphill_straight.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/semi.png b/game/asset/image/sample/javascript-racer-master/images/sprites/semi.png
new file mode 100644
index 0000000..f679c1b
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/semi.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/stump.png b/game/asset/image/sample/javascript-racer-master/images/sprites/stump.png
new file mode 100644
index 0000000..6319df2
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/stump.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/tree1.png b/game/asset/image/sample/javascript-racer-master/images/sprites/tree1.png
new file mode 100644
index 0000000..9544094
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/tree1.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/tree2.png b/game/asset/image/sample/javascript-racer-master/images/sprites/tree2.png
new file mode 100644
index 0000000..d3c8a12
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/tree2.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/images/sprites/truck.png b/game/asset/image/sample/javascript-racer-master/images/sprites/truck.png
new file mode 100644
index 0000000..d72b520
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/images/sprites/truck.png differ
diff --git a/game/asset/image/sample/javascript-racer-master/index.html b/game/asset/image/sample/javascript-racer-master/index.html
new file mode 100644
index 0000000..e61ae58
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/index.html
@@ -0,0 +1,18 @@
+
+
+
+ Javascript Racer
+
+
+
+
+
+
+
+
+
diff --git a/game/asset/image/sample/javascript-racer-master/music/racer.mp3 b/game/asset/image/sample/javascript-racer-master/music/racer.mp3
new file mode 100644
index 0000000..15a5c83
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/music/racer.mp3 differ
diff --git a/game/asset/image/sample/javascript-racer-master/music/racer.ogg b/game/asset/image/sample/javascript-racer-master/music/racer.ogg
new file mode 100644
index 0000000..d85ddfe
Binary files /dev/null and b/game/asset/image/sample/javascript-racer-master/music/racer.ogg differ
diff --git a/game/asset/image/sample/javascript-racer-master/stats.js b/game/asset/image/sample/javascript-racer-master/stats.js
new file mode 100644
index 0000000..e9fa9c6
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/stats.js
@@ -0,0 +1,143 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+var Stats = function () {
+
+ var startTime = Date.now(), prevTime = startTime;
+ var ms = 0, msMin = 1000, msMax = 0;
+ var fps = 0, fpsMin = 1000, fpsMax = 0;
+ var frames = 0, mode = 0;mode
+ var container = document.createElement( 'div' );
+ container.id = 'stats';
+ container.addEventListener( 'mousedown', function ( event ) { event.preventDefault(); setMode( ++ mode % 2 ) }, false );
+ container.style.cssText = 'width:80px;opacity:0.9;cursor:pointer';
+
+ var fpsDiv = document.createElement( 'div' );
+ fpsDiv.id = 'fps';
+ fpsDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#002';
+ container.appendChild( fpsDiv );
+
+ var fpsText = document.createElement( 'div' );
+ fpsText.id = 'fpsText';
+ fpsText.style.cssText = 'color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px';
+ fpsText.innerHTML = 'FPS';
+ fpsDiv.appendChild( fpsText );
+
+ var fpsGraph = document.createElement( 'div' );
+ fpsGraph.id = 'fpsGraph';
+ fpsGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0ff';
+ fpsDiv.appendChild( fpsGraph );
+
+ while ( fpsGraph.children.length < 74 ) {
+
+ var bar = document.createElement( 'span' );
+ bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#113';
+ fpsGraph.appendChild( bar );
+
+ }
+
+ var msDiv = document.createElement( 'div' );
+ msDiv.id = 'ms';
+ msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;display:none';
+ container.appendChild( msDiv );
+
+ var msText = document.createElement( 'div' );
+ msText.id = 'msText';
+ msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px';
+ msText.innerHTML = 'MS';
+ msDiv.appendChild( msText );
+
+ var msGraph = document.createElement( 'div' );
+ msGraph.id = 'msGraph';
+ msGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0f0';
+ msDiv.appendChild( msGraph );
+
+ while ( msGraph.children.length < 74 ) {
+
+ var bar = document.createElement( 'span' );
+ bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#131';
+ msGraph.appendChild( bar );
+
+ }
+
+ var setMode = function ( value ) {
+
+ mode = value;
+
+ switch ( mode ) {
+
+ case 0:
+ fpsDiv.style.display = 'block';
+ msDiv.style.display = 'none';
+ break;
+ case 1:
+ fpsDiv.style.display = 'none';
+ msDiv.style.display = 'block';
+ break;
+ }
+
+ }
+
+ var updateGraph = function ( dom, value ) {
+
+ var child = dom.appendChild( dom.firstChild );
+ child.style.height = value + 'px';
+
+ }
+
+ return {
+
+ domElement: container,
+
+ setMode: setMode,
+
+ current: function() { return fps; },
+
+ begin: function () {
+
+ startTime = Date.now();
+
+ },
+
+ end: function () {
+
+ var time = Date.now();
+
+ ms = time - startTime;
+ msMin = Math.min( msMin, ms );
+ msMax = Math.max( msMax, ms );
+
+ msText.textContent = ms + ' MS (' + msMin + '-' + msMax + ')';
+ updateGraph( msGraph, Math.min( 30, 30 - ( ms / 200 ) * 30 ) );
+
+ frames ++;
+
+ if ( time > prevTime + 1000 ) {
+
+ fps = Math.round( ( frames * 1000 ) / ( time - prevTime ) );
+ fpsMin = Math.min( fpsMin, fps );
+ fpsMax = Math.max( fpsMax, fps );
+
+ fpsText.textContent = fps + ' FPS (' + fpsMin + '-' + fpsMax + ')';
+ updateGraph( fpsGraph, Math.min( 30, 30 - ( fps / 100 ) * 30 ) );
+
+ prevTime = time;
+ frames = 0;
+
+ }
+
+ return time;
+
+ },
+
+ update: function () {
+
+ startTime = this.end();
+
+ }
+
+ }
+
+};
+
diff --git a/game/asset/image/sample/javascript-racer-master/v1.straight.html b/game/asset/image/sample/javascript-racer-master/v1.straight.html
new file mode 100644
index 0000000..112a7b0
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/v1.straight.html
@@ -0,0 +1,314 @@
+
+
+
+
+ Javascript Racer - v1 (straight)
+
+
+
+
+
+
+
+
+
+
Use the arrow keys to drive the car.
+
+
+
+
+ Loading...
+
+
+
+
+
+
+
+
+
+
+
diff --git a/game/asset/image/sample/javascript-racer-master/v2.curves.html b/game/asset/image/sample/javascript-racer-master/v2.curves.html
new file mode 100644
index 0000000..0ef0915
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/v2.curves.html
@@ -0,0 +1,387 @@
+
+
+
+
+ Javascript Racer - v2 (curves)
+
+
+
+
+
+
+
+
+
+
Use the arrow keys to drive the car.
+
+
+
+
+ Loading...
+
+
+
+
+
+
+
+
+
+
+
diff --git a/game/asset/image/sample/javascript-racer-master/v3.hills.html b/game/asset/image/sample/javascript-racer-master/v3.hills.html
new file mode 100644
index 0000000..1881035
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/v3.hills.html
@@ -0,0 +1,419 @@
+
+
+
+
+ Javascript Racer - v3 (hills)
+
+
+
+
+
+
+
+
+
+
Use the arrow keys to drive the car.
+
+
+
+
+ Loading...
+
+
+
+
+
+
+
+
+
+
diff --git a/game/asset/image/sample/javascript-racer-master/v4.final.html b/game/asset/image/sample/javascript-racer-master/v4.final.html
new file mode 100644
index 0000000..81d6935
--- /dev/null
+++ b/game/asset/image/sample/javascript-racer-master/v4.final.html
@@ -0,0 +1,688 @@
+
+
+
+
+ Javascript Racer - v4 (final)
+
+
+
+
+
+
+
+
+
+
Use the arrow keys to drive the car.
+
+
+
+
+ 0 mph
+ Time: 0.0
+ Last Lap: 0.0
+ Fastest Lap: 0.0
+
+
+ Loading...
+
+
+
+
+
+
+
+
+
+
+
diff --git a/game/asset/video/1_intro.ogv b/game/asset/video/1_intro.ogv
new file mode 100644
index 0000000..f79380d
Binary files /dev/null and b/game/asset/video/1_intro.ogv differ
diff --git a/game/data/map/segment.json b/game/data/map/segment.json
new file mode 100644
index 0000000..b34da57
--- /dev/null
+++ b/game/data/map/segment.json
@@ -0,0 +1,82 @@
+[
+ {
+ "enter": 1,
+ "hold" : 1,
+ "leave": 90,
+ "curve": 9,
+ "height": 80,
+ "width" : 3000
+ },
+ {
+ "enter": 25,
+ "hold" : 25,
+ "leave": 25,
+ "curve": 0,
+ "height": 0,
+ "width" : 1000
+ },
+ {
+ "enter": 50,
+ "hold" : 50,
+ "leave": 50,
+ "curve": 2,
+ "height": 0,
+ "width" : 2000
+ },
+ {
+ "enter": 50,
+ "hold" : 50,
+ "leave": 50,
+ "curve": 2,
+ "height": 20,
+ "width" : 2000
+ },
+ {
+ "enter": 100,
+ "hold" : 100,
+ "leave": 100,
+ "curve": 4,
+ "height": 40,
+ "width" : 4000
+ },
+ {
+ "enter": 50,
+ "hold" : 50,
+ "leave": 50,
+ "curve": 0,
+ "height": 0,
+ "width" : 1000
+ },
+ {
+ "enter": 50,
+ "hold" : 50,
+ "leave": 50,
+ "curve": 6,
+ "height": 60,
+ "width" : 3000
+ },
+ {
+ "enter": 50,
+ "hold" : 50,
+ "leave": 50,
+ "curve": 0,
+ "height": 60,
+ "width" : 2000
+ },
+ {
+ "enter": 50,
+ "hold" : 50,
+ "leave": 50,
+ "curve": 0,
+ "height": 20,
+ "width" : 3000
+ },
+ {
+ "enter": 100,
+ "hold" : 100,
+ "leave": 100,
+ "curve": 0,
+ "height": -280,
+ "width" : 4000
+ }
+]
diff --git a/game/data/map/sprites.json b/game/data/map/sprites.json
new file mode 100644
index 0000000..a41d6d7
--- /dev/null
+++ b/game/data/map/sprites.json
@@ -0,0 +1,17 @@
+[
+ {
+ "index" : 200,
+ "texture" : "asset/image/sample/javascript-racer-master/images/sprites/column.png",
+ "offset" : -0.1
+ },
+ {
+ "index" : 200,
+ "texture" : "asset/image/sample/javascript-racer-master/images/sprites/cactus.png",
+ "offset" : 4
+ },
+ {
+ "index" : 150,
+ "texture" : "asset/image/sample/javascript-racer-master/images/sprites/stump.png",
+ "offset" : 0
+ }
+]
diff --git a/game/lib/Concord b/game/lib/Concord
new file mode 160000
index 0000000..848652f
--- /dev/null
+++ b/game/lib/Concord
@@ -0,0 +1 @@
+Subproject commit 848652f68887db0c4261efe499facbec88959d03
diff --git a/game/lib/choro/projection.lua b/game/lib/choro/projection.lua
new file mode 100644
index 0000000..5fd3abe
--- /dev/null
+++ b/game/lib/choro/projection.lua
@@ -0,0 +1,78 @@
+local vm = require("lib.vornmath")
+
+-- https://jakesgordon.com/writing/javascript-racer-v1-straight/
+---@class lib.choro.projection
+local projection = {}
+
+--- World {x, y, z} - Camera {x, y, z} (Relative to Camera Pos)
+---@param world table {x, y, z}
+---@param cam table {x, y, z}
+---@return table {x, y, z}
+function projection.translateToRelativeCamera(world, cam)
+ return vm.vec3(world) - vm.vec3(cam)
+end
+
+--- project relative camera {x, y, z} to z render distance on screen {x, y}
+---@param camPos table {x, y, z}
+---@param distance number distance to render point
+---@return table {x, y}
+function projection.projectRelativeCamera(camPos, distance)
+ return vm.vec2({
+ camPos[1] * distance / camPos[3],
+ camPos[2] * distance / camPos[3]
+ })
+end
+
+--- scale things from camera projection {x, y}
+---@param camProj table camera projection {x, y, z}
+---@param screenScale number projection scale / distance
+---@param resolution table {screen width, screen height}
+---@return table {x, y}
+function projection.scalePosToRelativeProjectCamera(camProj, screenScale, resolution)
+ local w2 = resolution[1]/2
+ local h2 = resolution[2]/2
+ return vm.vec2({
+ vm.round(w2 + ( screenScale * camProj[1] * w2)),
+ vm.round(h2 - ( screenScale * camProj[2] * h2)),
+ })
+end
+
+--- scale things from camera projection {w, h}
+---@param size table world size {w, h}
+---@param screenScale number projection scale / distance
+---@param resolution table {screen width, screen height}
+---@return table {w, h}
+function projection.scaleSizeToRelativeProjectCamera(size, screenScale, resolution)
+ local w2 = resolution[1]/2
+ local h2 = resolution[2]/2
+ return vm.vec2({
+ vm.round( screenScale * size[1] * w2),
+ vm.round( screenScale * size[2] * h2)
+ })
+end
+
+--- calculate distance from field of view
+---@param fieldOfView number field of view
+---@return number distance distance from camera to projection plane
+function projection.distanceCamToProjection(fieldOfView)
+ local d = 1 / math.tan((fieldOfView/2) * math.pi/180);
+ return d
+end
+
+--- apply world to camera projection
+---@param world table world position {x, y, z}
+---@param cameraPos table camera position {x, y, z}
+---@param cameraDepth number distance from camera to projection plane
+---@param resolution table render resolution {width, height}
+---@param size table object {width, height}
+---@return table,table,table {x, y, z}, {x, y}, {w, h}
+function projection.projectWorldToCam(world, cameraPos, cameraDepth, resolution, size)
+ local relativetocamera = projection.translateToRelativeCamera(world, cameraPos)
+ local scale = cameraDepth / relativetocamera[3];
+ return
+ relativetocamera,
+ projection.scalePosToRelativeProjectCamera(relativetocamera, scale, resolution),
+ projection.scaleSizeToRelativeProjectCamera(size, scale, resolution)
+end
+
+return projection
diff --git a/game/lib/classic b/game/lib/classic
new file mode 160000
index 0000000..e561075
--- /dev/null
+++ b/game/lib/classic
@@ -0,0 +1 @@
+Subproject commit e5610756c98ac2f8facd7ab90c94e1a097ecd2c6
diff --git a/game/lib/json.lua b/game/lib/json.lua
new file mode 100644
index 0000000..711ef78
--- /dev/null
+++ b/game/lib/json.lua
@@ -0,0 +1,388 @@
+--
+-- json.lua
+--
+-- Copyright (c) 2020 rxi
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+-- of the Software, and to permit persons to whom the Software is furnished to do
+-- so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all
+-- copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-- SOFTWARE.
+--
+
+local json = { _version = "0.1.2" }
+
+-------------------------------------------------------------------------------
+-- Encode
+-------------------------------------------------------------------------------
+
+local encode
+
+local escape_char_map = {
+ [ "\\" ] = "\\",
+ [ "\"" ] = "\"",
+ [ "\b" ] = "b",
+ [ "\f" ] = "f",
+ [ "\n" ] = "n",
+ [ "\r" ] = "r",
+ [ "\t" ] = "t",
+}
+
+local escape_char_map_inv = { [ "/" ] = "/" }
+for k, v in pairs(escape_char_map) do
+ escape_char_map_inv[v] = k
+end
+
+
+local function escape_char(c)
+ return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
+end
+
+
+local function encode_nil(val)
+ return "null"
+end
+
+
+local function encode_table(val, stack)
+ local res = {}
+ stack = stack or {}
+
+ -- Circular reference?
+ if stack[val] then error("circular reference") end
+
+ stack[val] = true
+
+ if rawget(val, 1) ~= nil or next(val) == nil then
+ -- Treat as array -- check keys are valid and it is not sparse
+ local n = 0
+ for k in pairs(val) do
+ if type(k) ~= "number" then
+ error("invalid table: mixed or invalid key types")
+ end
+ n = n + 1
+ end
+ if n ~= #val then
+ error("invalid table: sparse array")
+ end
+ -- Encode
+ for i, v in ipairs(val) do
+ table.insert(res, encode(v, stack))
+ end
+ stack[val] = nil
+ return "[" .. table.concat(res, ",") .. "]"
+
+ else
+ -- Treat as an object
+ for k, v in pairs(val) do
+ if type(k) ~= "string" then
+ error("invalid table: mixed or invalid key types")
+ end
+ table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
+ end
+ stack[val] = nil
+ return "{" .. table.concat(res, ",") .. "}"
+ end
+end
+
+
+local function encode_string(val)
+ return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
+end
+
+
+local function encode_number(val)
+ -- Check for NaN, -inf and inf
+ if val ~= val or val <= -math.huge or val >= math.huge then
+ error("unexpected number value '" .. tostring(val) .. "'")
+ end
+ return string.format("%.14g", val)
+end
+
+
+local type_func_map = {
+ [ "nil" ] = encode_nil,
+ [ "table" ] = encode_table,
+ [ "string" ] = encode_string,
+ [ "number" ] = encode_number,
+ [ "boolean" ] = tostring,
+}
+
+
+encode = function(val, stack)
+ local t = type(val)
+ local f = type_func_map[t]
+ if f then
+ return f(val, stack)
+ end
+ error("unexpected type '" .. t .. "'")
+end
+
+
+function json.encode(val)
+ return ( encode(val) )
+end
+
+
+-------------------------------------------------------------------------------
+-- Decode
+-------------------------------------------------------------------------------
+
+local parse
+
+local function create_set(...)
+ local res = {}
+ for i = 1, select("#", ...) do
+ res[ select(i, ...) ] = true
+ end
+ return res
+end
+
+local space_chars = create_set(" ", "\t", "\r", "\n")
+local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
+local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
+local literals = create_set("true", "false", "null")
+
+local literal_map = {
+ [ "true" ] = true,
+ [ "false" ] = false,
+ [ "null" ] = nil,
+}
+
+
+local function next_char(str, idx, set, negate)
+ for i = idx, #str do
+ if set[str:sub(i, i)] ~= negate then
+ return i
+ end
+ end
+ return #str + 1
+end
+
+
+local function decode_error(str, idx, msg)
+ local line_count = 1
+ local col_count = 1
+ for i = 1, idx - 1 do
+ col_count = col_count + 1
+ if str:sub(i, i) == "\n" then
+ line_count = line_count + 1
+ col_count = 1
+ end
+ end
+ error( string.format("%s at line %d col %d", msg, line_count, col_count) )
+end
+
+
+local function codepoint_to_utf8(n)
+ -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
+ local f = math.floor
+ if n <= 0x7f then
+ return string.char(n)
+ elseif n <= 0x7ff then
+ return string.char(f(n / 64) + 192, n % 64 + 128)
+ elseif n <= 0xffff then
+ return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
+ elseif n <= 0x10ffff then
+ return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
+ f(n % 4096 / 64) + 128, n % 64 + 128)
+ end
+ error( string.format("invalid unicode codepoint '%x'", n) )
+end
+
+
+local function parse_unicode_escape(s)
+ local n1 = tonumber( s:sub(1, 4), 16 )
+ local n2 = tonumber( s:sub(7, 10), 16 )
+ -- Surrogate pair?
+ if n2 then
+ return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
+ else
+ return codepoint_to_utf8(n1)
+ end
+end
+
+
+local function parse_string(str, i)
+ local res = ""
+ local j = i + 1
+ local k = j
+
+ while j <= #str do
+ local x = str:byte(j)
+
+ if x < 32 then
+ decode_error(str, j, "control character in string")
+
+ elseif x == 92 then -- `\`: Escape
+ res = res .. str:sub(k, j - 1)
+ j = j + 1
+ local c = str:sub(j, j)
+ if c == "u" then
+ local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
+ or str:match("^%x%x%x%x", j + 1)
+ or decode_error(str, j - 1, "invalid unicode escape in string")
+ res = res .. parse_unicode_escape(hex)
+ j = j + #hex
+ else
+ if not escape_chars[c] then
+ decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
+ end
+ res = res .. escape_char_map_inv[c]
+ end
+ k = j + 1
+
+ elseif x == 34 then -- `"`: End of string
+ res = res .. str:sub(k, j - 1)
+ return res, j + 1
+ end
+
+ j = j + 1
+ end
+
+ decode_error(str, i, "expected closing quote for string")
+end
+
+
+local function parse_number(str, i)
+ local x = next_char(str, i, delim_chars)
+ local s = str:sub(i, x - 1)
+ local n = tonumber(s)
+ if not n then
+ decode_error(str, i, "invalid number '" .. s .. "'")
+ end
+ return n, x
+end
+
+
+local function parse_literal(str, i)
+ local x = next_char(str, i, delim_chars)
+ local word = str:sub(i, x - 1)
+ if not literals[word] then
+ decode_error(str, i, "invalid literal '" .. word .. "'")
+ end
+ return literal_map[word], x
+end
+
+
+local function parse_array(str, i)
+ local res = {}
+ local n = 1
+ i = i + 1
+ while 1 do
+ local x
+ i = next_char(str, i, space_chars, true)
+ -- Empty / end of array?
+ if str:sub(i, i) == "]" then
+ i = i + 1
+ break
+ end
+ -- Read token
+ x, i = parse(str, i)
+ res[n] = x
+ n = n + 1
+ -- Next token
+ i = next_char(str, i, space_chars, true)
+ local chr = str:sub(i, i)
+ i = i + 1
+ if chr == "]" then break end
+ if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
+ end
+ return res, i
+end
+
+
+local function parse_object(str, i)
+ local res = {}
+ i = i + 1
+ while 1 do
+ local key, val
+ i = next_char(str, i, space_chars, true)
+ -- Empty / end of object?
+ if str:sub(i, i) == "}" then
+ i = i + 1
+ break
+ end
+ -- Read key
+ if str:sub(i, i) ~= '"' then
+ decode_error(str, i, "expected string for key")
+ end
+ key, i = parse(str, i)
+ -- Read ':' delimiter
+ i = next_char(str, i, space_chars, true)
+ if str:sub(i, i) ~= ":" then
+ decode_error(str, i, "expected ':' after key")
+ end
+ i = next_char(str, i + 1, space_chars, true)
+ -- Read value
+ val, i = parse(str, i)
+ -- Set
+ res[key] = val
+ -- Next token
+ i = next_char(str, i, space_chars, true)
+ local chr = str:sub(i, i)
+ i = i + 1
+ if chr == "}" then break end
+ if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
+ end
+ return res, i
+end
+
+
+local char_func_map = {
+ [ '"' ] = parse_string,
+ [ "0" ] = parse_number,
+ [ "1" ] = parse_number,
+ [ "2" ] = parse_number,
+ [ "3" ] = parse_number,
+ [ "4" ] = parse_number,
+ [ "5" ] = parse_number,
+ [ "6" ] = parse_number,
+ [ "7" ] = parse_number,
+ [ "8" ] = parse_number,
+ [ "9" ] = parse_number,
+ [ "-" ] = parse_number,
+ [ "t" ] = parse_literal,
+ [ "f" ] = parse_literal,
+ [ "n" ] = parse_literal,
+ [ "[" ] = parse_array,
+ [ "{" ] = parse_object,
+}
+
+
+parse = function(str, idx)
+ local chr = str:sub(idx, idx)
+ local f = char_func_map[chr]
+ if f then
+ return f(str, idx)
+ end
+ decode_error(str, idx, "unexpected character '" .. chr .. "'")
+end
+
+
+function json.decode(str)
+ if type(str) ~= "string" then
+ error("expected argument of type string, got " .. type(str))
+ end
+ local res, idx = parse(str, next_char(str, 1, space_chars, true))
+ idx = next_char(str, idx, space_chars, true)
+ if idx <= #str then
+ decode_error(str, idx, "trailing garbage")
+ end
+ return res
+end
+
+
+return json
diff --git a/game/lib/reap/init.lua b/game/lib/reap/init.lua
new file mode 100644
index 0000000..74ed535
--- /dev/null
+++ b/game/lib/reap/init.lua
@@ -0,0 +1,33 @@
+local reap = {}
+
+--- given lua require file path, return the base in lua path notation
+--- usage example :
+--- ``local BASE_PATH = reap.base_path(...)``
+---@param path string ex: lib.reap.test
+---@return string base_path ex: lib.reap
+---@return number replacement_count ex: 1
+function reap.base_path(path)
+ return path:gsub('%.[^%.]+$', '')
+end
+
+--- given lua require file path, return the base in directory notation
+--- usage example :
+--- ``local BASE_DIR = reap.base_dir(...)``
+---@param path string ex: lib.reap.test
+---@return string base_dir ex: lib/reap
+---@return number replacement_count ex: 1
+function reap.base_dir(path)
+ return reap.base_path(path):gsub("[.]", "/")
+end
+
+--- given lua require file path, return in directory notation
+--- usage example :
+--- ``local DIR = reap.dir(...)``
+---@param path string ex: lib.reap.test
+---@return string lua_dir ex: lib/reap/test
+---@return number replacement_count ex: 1
+function reap.dir(path)
+ return path:gsub("[.]", "/")
+end
+
+return reap
diff --git a/game/lib/reoof/cache.lua b/game/lib/reoof/cache.lua
new file mode 100644
index 0000000..880bc3d
--- /dev/null
+++ b/game/lib/reoof/cache.lua
@@ -0,0 +1,112 @@
+local format = string.format
+
+local classic = require("lib.classic.classic")
+
+---@class Cache : lib.classic.class
+---@field private fn function
+---@field cache table
+---@field count integer
+---@field name string
+local cache = classic:extend()
+cache._VERSION = "0.0.0-alpha"
+cache._DESCRIPTION = "A simple and straightforward cache made for LÖVE."
+cache._URL = "https://github.com/FNicon/reoof"
+cache._LICENSE = [[
+ MIT License
+
+ Copyright (c) 2025 FNicon
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+]]
+
+local function default_empty()
+end
+
+--- create entity
+---@param fn? function lambda to load resource
+---@param name? string for debug purposes
+-- ---@return Cache self
+function cache:new(fn, name)
+ -- local self = setmetatable({}, cache)
+ self.fn = fn or default_empty
+ self.cache = {}
+ self.count = 0
+ self.name = name or "cache"
+end
+
+--- load resource to cache
+---@param self Cache
+---@param key string assigned_key
+---@param ... any
+---@return any resource
+function cache:load_to(key, ...)
+ if (self.cache[key] == nil) then
+ self.cache[key] = self.fn(...)
+ self.count = self.count + 1
+ return self.cache[key]
+ else
+ return self:load_from(key)
+ end
+end
+
+
+--- load resource from cache
+---@param self Cache
+---@param key string assigned_key
+---@return any resource
+function cache:load_from(key)
+ if (self.cache[key] == nil) then
+ print(format("WARNING : key %s not found"), key)
+ else
+ return self.cache[key]
+ end
+end
+
+--- release object and set it up to nil
+---@param self Cache
+function cache:release()
+ for k, _ in pairs(self.cache) do
+ self:release(k)
+ end
+ self.cache = nil
+ self.count = nil
+ self.name = nil
+ self.fn = nil
+ setmetatable(self, nil)
+ self = nil
+end
+
+--- release object and set it up to nil
+---@param self Cache
+---@param key string
+function cache:release_from(key)
+ if (self.cache[key]) then
+ if (self.cache[key].release) then
+ self.cache[key]:release()
+ self.cache[key] = nil
+ self.count = self.count - 1
+ else
+ print(format("WARNING : during release, %s don't have release function"), key)
+ end
+ else
+ error(format("ERROR : during release, %s not found", key))
+ end
+end
+
+return cache
diff --git a/game/lib/reoof/pool.lua b/game/lib/reoof/pool.lua
new file mode 100644
index 0000000..a01423d
--- /dev/null
+++ b/game/lib/reoof/pool.lua
@@ -0,0 +1,193 @@
+local insert = table.insert
+local remove = table.remove
+
+local classic = require("lib.classic.classic")
+
+
+---@class Pool : lib.classic.class
+---@field private fn function
+---@field private rfn function
+---@field private efn function
+---@field active any[]
+---@field hidden any[]
+---@field max number | nil
+---@field name string
+local pool = classic:extend()
+pool._VERSION = "0.0.0-alpha"
+pool._DESCRIPTION = "A simple and straightforward pool made for LÖVE."
+pool._URL = "https://github.com/FNicon/reoof"
+pool._LICENSE = [[
+ MIT License
+
+ Copyright (c) 2025 FNicon
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+]]
+
+--- default equal function
+---@param v1 any
+---@param v2 any
+local function defaultEqual(v1, v2)
+ return v1 == v2
+end
+
+--- Return the first index with the given value (or nil if not found).
+---@param array any[]
+---@param value any
+---@param efn fun(...):boolean
+---@return number?
+local function indexOf(array, value, efn)
+ for i, v in pairs(array) do
+ if efn(v, value) then
+ return i
+ end
+ end
+ return nil
+end
+
+--- initialize pool
+---@param fn fun(...):any generate Function
+---@param rfn fun(...):any reset Function
+---@param name? string for debug purposes
+---@param max? number pool max size
+---@param efn? fun(...):boolean equal Function
+-- ---@return Pool self
+function pool:new(fn, rfn, name, max, efn)
+ -- local self = setmetatable({}, pool)
+ self.active = {}
+ self.hidden = {}
+ self.fn = fn
+ self.rfn = rfn
+ self.efn = efn or defaultEqual
+ self.max = max > 0 and max or nil
+ self.name = name or "pool"
+end
+
+--- put entity to pool
+---@param self Pool
+---@param entity any
+---@return Pool
+---@return number hidden_idx index from hidden pool
+function pool:put(entity)
+ local idx = indexOf(self.active, entity, self.efn)
+ if (idx) then
+ self:put_to(entity, idx)
+ return self, #self.hidden
+ else
+ print("WARNING : trying to insert entity not from generate function")
+ return self, -1
+ end
+end
+
+--- put entity to pool
+---@param self Pool
+---@param entity any
+---@param idx number
+---@return Pool
+---@return number hidden_idx index from hidden pool
+function pool:put_to(entity, idx)
+ if (self.max) then
+ if (#self.hidden < self.max) then
+ insert(self.hidden, entity)
+ else
+ self:release(entity)
+ end
+ else
+ insert(self.hidden, entity)
+ end
+ remove(self.active, idx)
+ return self, #self.hidden
+end
+
+--- get entity from pool
+---@param self Pool
+---@param ... any
+---@return any entity from pool
+---@return number active_idx
+function pool:get(...)
+ local entity = self.hidden[#self.hidden]
+ if (entity) then
+ insert(self.active, entity)
+ remove(self.hidden, #self.hidden)
+ self.rfn(entity, ...)
+ return entity, #self.active
+ else
+ entity = self.fn(...)
+ insert(self.active, entity)
+ return entity, #self.active
+ end
+end
+
+--- release function
+---@param self Pool
+---@param entity? any
+function pool:release(entity)
+ if (entity == nil) then
+ for _, v in pairs(self.active) do
+ self:release(v)
+ end
+ for _, v in pairs(self.hidden) do
+ self:release(v)
+ end
+ self.active = nil
+ self.hidden = nil
+ self.fn = nil
+ self.rfn = nil
+ self.efn = nil
+ self.name = nil
+ setmetatable(self, nil)
+ self = nil
+ else
+ local idx = indexOf(self.active, entity, self.efn)
+ if (idx) then
+ self:release_from("active", idx)
+ else
+ idx = indexOf(self.hidden, entity, self.efn)
+ if (idx) then
+ self:release_from("hidden", idx)
+ else
+ print("WARNING : trying to release object not from generate function")
+ end
+ end
+ end
+end
+
+--- release function
+---@param self Pool
+---@param from_pool "hidden" | "active"
+---@param idx number
+function pool:release_from(from_pool, idx)
+ if (from_pool == "hidden") then
+ if (self.hidden[idx].release) then
+ self.hidden[idx]:release()
+ remove(self.hidden, idx)
+ else
+ print("WARNING : during release hidden, %d don't have release function", idx)
+ end
+ else
+ if (self.active[idx].release) then
+ self.active[idx]:release()
+ remove(self.active, idx)
+ else
+ print("WARNING : during release active, %d don't have release function", idx)
+ end
+ end
+end
+
+return pool
diff --git a/game/lib/vornmath.lua b/game/lib/vornmath.lua
new file mode 100644
index 0000000..94b044e
--- /dev/null
+++ b/game/lib/vornmath.lua
@@ -0,0 +1,6072 @@
+--[[
+MIT License
+
+Copyright (c) 2022-2025 Dan Uznanski
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+]]
+
+local vornmath = {}
+
+vornmath.settings = {
+ setColorspace = function(new_space)
+ vornmath.settings._colorspace = new_space
+ vornmath.utils.prepareColorConverters()
+ end,
+ getColorspace = function()
+ return vornmath.settings._colorspace
+ end
+}
+
+-- loadstring gets folded into load in later versions (5.2+)
+---@diagnostic disable-next-line: deprecated
+local load = loadstring or load
+
+-- these are used to name generic parameters for functions that accept a different
+-- number of arguments based on type
+
+local LETTERS = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q'}
+
+-- prefixes for types that use different storage types
+
+local SCALAR_PREFIXES = {
+ boolean = 'b',
+ number = '',
+ complex = 'c',
+ quat = 'q'
+}
+
+vornmath.utils = {}
+vornmath.bakeries = {}
+vornmath.metabakeries = {}
+vornmath.metatables = {}
+vornmath.walks = {}
+
+vornmath.utils.bakerymeta = {
+ __index = function(bakeries, name)
+ for _,metabakery in pairs(vornmath.metabakeries) do
+ local bakery = metabakery(name)
+ if bakery then
+ bakeries[name] = bakery
+ return bakery
+ end
+ end
+ end
+}
+
+setmetatable(vornmath.bakeries, vornmath.utils.bakerymeta)
+
+function vornmath.utils.hasBakery(function_name, types)
+ local available_bakeries = vornmath.bakeries[function_name]
+ if not available_bakeries then return nil end
+ for _, bakery in ipairs(available_bakeries) do
+ if bakery.signature_check(types) then return bakery end
+ end
+ return false
+end
+
+local function walk(name, ...)
+ local t = vornmath.walks[name]
+ if not t then return vornmath.utils.bakeByCall(name, ...)(...) end
+ local i = 1
+ while true do -- I can't use select('#', ...) here because some signatures have trailing nils
+ local target_type = vornmath.utils.type(select(i, ...))
+ local thing = t[target_type]
+ if not thing then
+ return vornmath.utils.bakeByCall(name, ...)(...)
+ elseif type(thing) == 'table' then
+ t = thing
+ else -- type(thing) == function
+ return thing(...)
+ end
+ i = i + 1
+ end
+end
+
+local function buildWalk(function_name, types, f)
+ local target = vornmath.walks
+ if not target[function_name] then target[function_name] = {} end
+ target = target[function_name]
+ for i,t in ipairs(types) do
+ if i == #types then
+ target[t] = f
+ else
+ if not target[t] then
+ target[t] = {}
+ end
+ target = target[t]
+ end
+ end
+ if not vornmath[function_name] then vornmath[function_name] = function(...) return walk(function_name, ...) end end
+end
+
+function vornmath.utils.bake(function_name, types)
+ local bakery = vornmath.utils.hasBakery(function_name, types)
+ if bakery == nil then error("unknown vornmath function `" .. function_name .. "`.") end
+ if bakery == false then error('vornmath function `' .. function_name .. '` does not accept types `' .. table.concat(types, ', ') .. '`.') end
+ local name = function_name .. '_' .. table.concat(types, '_')
+ if rawget(vornmath, name) then return vornmath[name] end
+ ---@diagnostic disable-next-line: need-check-nil, undefined-field
+ local result = bakery.create(types)
+ vornmath[name] = result
+ buildWalk(function_name, types, result)
+ return result
+end
+
+function vornmath.utils.bakeByCall(name, ...)
+ local types = {}
+ for i = 1, select('#', ...) do
+ types[i] = vornmath.utils.type(select(i, ...))
+ end
+ return vornmath.utils.bake(name, types)
+end
+
+function vornmath.utils.findTypeByData(shape, dim, storage)
+ if shape == 'scalar' then dim = 1 end
+ for typename, meta in pairs(vornmath.metatables) do
+ if meta.vm_storage == storage and meta.vm_shape == shape then
+ if type(dim) == 'number' then
+ if meta.vm_dim == dim then
+ return typename
+ end
+ else -- type(dim) == 'table'
+ local matches = true
+ for i,k in ipairs(dim) do
+ if meta.vm_dim[i] ~= k then matches = false end
+ end
+ if matches then return typename end
+ end
+ end
+ end
+end
+
+function vornmath.utils.returnType(function_name, types)
+ local bakery = vornmath.utils.hasBakery(function_name, types)
+ if bakery == nil then error("unknown vornmath function `" .. function_name .. "`.") end
+ if bakery == false then error('vornmath function `' .. function_name .. '` does not accept types `' .. table.concat(types, ', ') .. '`.') end
+ ---@diagnostic disable-next-line: need-check-nil, undefined-field
+ return bakery.return_type(types)
+end
+
+function vornmath.utils.componentWiseReturnOnlys(function_name, arity, force_output_storage)
+ return {
+ signature_check = function(types)
+ if #types > arity then
+ -- since we're targeting a specific arity only nils after that
+ for i, typename in ipairs(types) do
+ if i > arity and typename ~= 'nil' then return false end
+ end
+ end
+ local big_type = vornmath.utils.componentWiseConsensusType(types, force_output_storage)
+ if not big_type then return false end
+ local full_types = {}
+ for i, typename in ipairs(types) do
+ full_types[i] = typename
+ end
+ full_types[arity + 1] = big_type
+ if vornmath.utils.hasBakery(function_name, full_types) then
+ for i = #types + 1, arity + 1 do -- fill out all the rest of the types thing with nil until I get there
+ types[i] = 'nil'
+ end
+ return true
+ end
+ end,
+ create = function(types)
+ local big_type = vornmath.utils.componentWiseConsensusType(types, force_output_storage)
+ local full_types = {}
+ local letters = {}
+ for i = 1,arity do
+ full_types[i] = types[i]
+ letters[i] = LETTERS[i]
+ end
+ full_types[arity + 1] = big_type
+ local f = vornmath.utils.bake(function_name, full_types)
+ local construct = vornmath.utils.bake(big_type, {})
+ local letter_glom = table.concat(letters, ', ')
+ local code = [[
+ local f = select(1, ...)
+ local construct = select(2, ...)
+ return function(]] .. letter_glom .. [[)
+ return f(]] .. letter_glom .. [[, construct())
+ end
+ ]]
+ return load(code)(f, construct)
+ end,
+ return_type = function(types)
+ return vornmath.utils.componentWiseConsensusType(types, force_output_storage)
+ end
+ }
+end
+
+function vornmath.utils.componentWiseConsensusType(types, force_output_storage)
+ local shape, dim, storage
+ for _, typename in ipairs(types) do
+ local meta = vornmath.metatables[typename]
+ if meta.vm_shape == 'vector' then
+ if shape and (shape ~= 'vector' and shape ~= 'scalar') then return nil end
+ if dim and dim ~= meta.vm_dim then return nil end
+ shape = meta.vm_shape
+ dim = meta.vm_dim
+ elseif meta.vm_shape == 'matrix' then
+ if shape and (shape ~= 'matrix' and shape ~= 'scalar') then return nil end
+ if dim and (dim[1] ~= meta.vm_dim[1] or dim[2] ~= meta.vm_dim[2]) then return nil end
+ shape = meta.vm_shape
+ dim = meta.vm_dim
+ elseif meta.vm_shape == 'scalar' then
+ if not shape then shape = 'scalar' end
+ elseif meta.vm_shape == 'string' then -- strings aren't allowed
+ return nil
+ end
+ end
+ if force_output_storage then
+ storage = force_output_storage
+ else
+ storage = vornmath.utils.consensusStorage(types)
+ end
+ return vornmath.utils.findTypeByData(shape, dim, storage)
+end
+
+vornmath.utils.vm_meta = {
+ __index = function(vm, index)
+ -- is this supposed to be a base proxy or a fully qualified function?
+ local is_fully_qualified = string.find(index, '_')
+ if is_fully_qualified then
+ -- do the stuff needed to bake; this is a split...
+ local types = {}
+ for match in string.gmatch(index, '[^_]+') do
+ table.insert(types,match)
+ end
+ local name = table.remove(types, 1)
+ -- safety: make sure the name I'm about to make in the bakery is the same as the one I just passed in.
+ local rebuilt_index = name .. '_' .. table.concat(types, '_')
+ if rebuilt_index ~= index then error("invalid vornmath function signature `" .. index .. "` (should probably be `" .. rebuilt_index .."`).") end
+ -- return the baked object even if it doesn't land in the expected name.
+ return vm.utils.bake(name, types)
+ else
+ -- instead, bake *only* the proxy.
+ if not vm.bakeries[index] then error("unknown vornmath function `" .. index .. "`.") end
+ vm[index] = function(...) return walk(index, ...) end
+ return vm[index]
+ end
+ end
+}
+
+setmetatable(vornmath, vornmath.utils.vm_meta)
+
+function vornmath.utils.type(obj)
+ local mt = getmetatable(obj)
+ return (mt and mt.vm_type) or type(obj)
+end
+
+function vornmath.utils.getmetatable(obj)
+ local mt = getmetatable(obj)
+ if mt and mt.vm_type then
+ return mt
+ else
+ return vornmath.metatables[type(obj)]
+ end
+end
+
+vornmath.utils.CONSENSUS_TABLE = { -- this hierarchy is in alphabetical order:
+-- it will try checking CT['complex']['number'], but not CT['number']['complex']
+ complex = {
+ number = 'complex',
+ quat = 'quat'
+ },
+ number = {
+ quat = 'quat'
+ }
+
+}
+
+do
+ local function consensusStoragePair(a, b)
+ if a == b then return a end
+ if a > b then a,b = b,a end
+ local sub_table = vornmath.utils.CONSENSUS_TABLE[a]
+ if not sub_table then return nil end -- I don't have one
+ return sub_table[b]
+ end
+
+ function vornmath.utils.consensusStorage(types)
+ local consensus_size = 0
+ local consensus_type
+ for _,typename in ipairs(types) do
+ if typename ~= 'nil' then
+ local storage = vornmath.metatables[typename].vm_storage
+ if not consensus_type then
+ consensus_type = storage
+ else
+ consensus_type = consensusStoragePair(consensus_type, storage)
+ end
+ end
+ end
+ return consensus_type
+ end
+end
+
+-- Swizzle
+
+local swizzle_characters_to_indices = {x = 1, y = 2, z = 3, w = 4}
+
+local swizzle_alternate_spellings = {
+ {x = 'x', y = 'y', z = 'z', w = 'w'},
+ {r = 'x', g = 'y', b = 'z', a = 'w'},
+ {s = 'x', t = 'y', p = 'z', q = 'w'}
+}
+
+function vornmath.utils.swizzleRespell(swizzle)
+ local exemplar = swizzle:sub(1,1)
+ for _,alphabet in ipairs(swizzle_alternate_spellings) do
+ if alphabet[exemplar] then
+ local letters = {}
+ for i = 1,#swizzle do
+ letters[i] = alphabet[swizzle:sub(i,i)]
+ if not letters[i] then error("Invalid swizzle string: " .. swizzle) end
+ end
+ return table.concat(letters, '')
+ end
+ end
+ error("Invalid swizzle string: " .. swizzle)
+end
+
+function vornmath.utils.swizzleReadBakery(function_name)
+ if function_name:sub(1,11) ~= 'swizzleRead' then return false end -- not a swizzle read
+ local swizzle_string = function_name:sub(12)
+ local target_dimension = #swizzle_string
+ if target_dimension < 1 or target_dimension > 4 then return false end -- can't make vectors this big
+ local min_dimension = 2
+ local swizzle_indices = {}
+ for k = 1,#swizzle_string do
+ local letter = swizzle_string:sub(k,k)
+ local index = swizzle_characters_to_indices[letter]
+ if not index then return false end -- not a legal index
+ table.insert(swizzle_indices, index)
+ min_dimension = math.max(min_dimension, index)
+ end
+ if target_dimension == 1 then
+ -- this is a single index swizzle, I don't have to do anything fancy to get tables out of my upvalues
+ local source_index = swizzle_indices[1]
+ return {
+ {
+ signature_check = function(types)
+ if #types < 2 then return false end
+ local source = vornmath.metatables[types[1]]
+ local target = vornmath.metatables[types[2]]
+ if source.vm_shape ~= 'vector' or source.vm_dim < min_dimension or
+ target.vm_shape ~= 'scalar' or source.vm_storage ~= target.vm_storage
+ then
+ return false
+ end
+ types[3] = nil
+ return true
+ end,
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {types[2], types[2]})
+ return function(source, target)
+ return fill(target, source[source_index])
+ end
+ end,
+ return_type = function(types) return types[2] end
+ },
+ {
+ signature_check = function(types)
+ if #types < 1 then return false end
+ local source = vornmath.metatables[types[1]]
+ if source.vm_shape ~= 'vector' or source.vm_dim < min_dimension then return false end
+ if not types[2] then types[2] = 'nil' end
+ return types[2] == 'nil'
+ end,
+ create = function(types)
+ local source_metatable = vornmath.metatables[types[1]]
+ local construct = vornmath.utils.bake(source_metatable.vm_storage, {})
+ local read = vornmath.utils.bake(function_name, {types[1], source_metatable.vm_storage})
+ return function(source)
+ return read(source, construct())
+ end
+ end,
+ return_type = function(types) return vornmath.metatables[types[1]].vm_source end
+ }
+ }
+ else
+ return {
+ {
+ signature_check = function(types)
+ if #types < 2 then return false end
+ local source = vornmath.metatables[types[1]]
+ local target = vornmath.metatables[types[2]]
+ if source.vm_shape ~= 'vector' or source.vm_dim < min_dimension or
+ target.vm_shape ~= 'vector' or target.vm_dim ~= target_dimension or
+ source.vm_storage ~= target.vm_storage
+ then
+ return false
+ end
+ types[3] = nil
+ return true
+ end,
+ create = function(types)
+ local storage_type = vornmath.metatables[types[1]].vm_storage
+ local big_fill = vornmath.utils.bake('fill', {types[2], types[2]})
+ local little_fill = vornmath.utils.bake('fill', {storage_type, storage_type})
+ local make_scratch = vornmath.utils.bake(types[2], {})
+ local scratch = make_scratch()
+ local fill_targets = {}
+ for s,t in ipairs(swizzle_indices) do
+ local fill_command = "scratch[" .. s .. "] = little_fill(scratch[" .. s .. "], source[" .. t .. "])"
+ table.insert(fill_targets, fill_command)
+ end
+ local function_text = [[
+ local big_fill = select(1, ...)
+ local little_fill = select(2, ...)
+ local scratch = select(3, ...)
+ return function(source, target)
+ ]] .. table.concat(fill_targets, '\n') .. [[
+ return big_fill(target, scratch)
+ end]]
+ return load(function_text)(big_fill, little_fill, scratch)
+ end,
+ return_type = function(types) return types[2] end
+ },
+ {
+ signature_check = function(types)
+ if #types < 1 then return false end
+ local source = vornmath.metatables[types[1]]
+ if source.vm_shape ~= 'vector' or source.vm_dim < min_dimension then return false end
+ if not types[2] then types[2] = 'nil' end
+ return types[2] == 'nil'
+ end,
+ create = function(types)
+ local source_metatable = vornmath.metatables[types[1]]
+ local target_type = vornmath.utils.findTypeByData('vector', target_dimension, source_metatable.vm_storage)
+ local construct = vornmath.utils.bake(target_type, {})
+ local read = vornmath.utils.bake(function_name, {types[1], target_type})
+ return function(source)
+ return read(source, construct())
+ end
+ end,
+ return_type = function(types) return vornmath.metatables[types[1]].vm_storage end
+ }
+ }
+ end
+end
+
+table.insert(vornmath.metabakeries, vornmath.utils.swizzleReadBakery)
+
+function vornmath.utils.swizzleGetter(t, k)
+ if type(k) ~= 'string' then return nil end
+ local mt = getmetatable(t)
+ if not mt.getters[k] then
+ local real_k = vornmath.utils.swizzleRespell(k)
+ mt.getters[k] = vornmath.utils.bake('swizzleRead' .. real_k, {mt.vm_type})
+ end
+ return mt.getters[k](t)
+end
+
+function vornmath.utils.swizzleWriteBakery(function_name)
+ if function_name:sub(1,12) ~= 'swizzleWrite' then return false end -- not a swizzle read
+ local swizzle_string = function_name:sub(13)
+ local target_dimension = #swizzle_string
+ if target_dimension < 1 or target_dimension > 4 then return false end -- can't do this much
+ local min_dimension = 2
+ local swizzle_indices = {}
+ for k = 1,#swizzle_string do
+ local letter = swizzle_string:sub(k,k)
+ local index = swizzle_characters_to_indices[letter]
+ if not index then return false end -- not a legal index
+ for _,i in ipairs(swizzle_indices) do
+ if i == index then return false end -- can't duplicate indices
+ end
+ table.insert(swizzle_indices, index)
+ min_dimension = math.max(min_dimension, index)
+ end
+ if target_dimension == 1 then
+ -- this is a single index swizzle, I don't have to do anything fancy to get tables out of my upvalues
+ local source_index = swizzle_indices[1]
+ return {
+ {
+ signature_check = function(types)
+ if #types < 2 then return false end
+ local lvalue_meta = vornmath.metatables[types[1]]
+ local rvalue_meta = vornmath.metatables[types[2]]
+ if lvalue_meta.vm_shape ~= 'vector' or lvalue_meta.vm_dim < min_dimension or
+ rvalue_meta.vm_shape ~= 'scalar' or
+ not vornmath.utils.hasBakery('fill', {lvalue_meta.vm_storage, rvalue_meta.vm_storage})
+ then
+ return false
+ end
+ types[3] = nil
+ return true
+ end,
+ create = function(types)
+ local lvalue_meta = vornmath.metatables[types[1]]
+ local rvalue_meta = vornmath.metatables[types[2]]
+ local fill = vornmath.utils.bake('fill', {lvalue_meta.vm_storage, rvalue_meta.vm_storage})
+ return function(lvalue, rvalue)
+ lvalue[source_index] = fill(lvalue[source_index], rvalue)
+ end
+ end,
+ return_type = function(types) return 'nil' end
+ },
+ }
+ else
+ return {
+ {
+ signature_check = function(types)
+ if #types < 2 then return false end
+ local lvalue_meta = vornmath.metatables[types[1]]
+ local rvalue_meta = vornmath.metatables[types[2]]
+ if lvalue_meta.vm_shape ~= 'vector' or lvalue_meta.vm_dim < min_dimension or
+ rvalue_meta.vm_shape ~= 'vector' or rvalue_meta.vm_dim ~= target_dimension or
+ not vornmath.utils.hasBakery('fill', {lvalue_meta.vm_storage, rvalue_meta.vm_storage})
+ then
+ return false
+ end
+ types[3] = nil
+ return true
+ end,
+ create = function(types)
+ local lvalue_meta = vornmath.metatables[types[1]]
+ local rvalue_meta = vornmath.metatables[types[2]]
+ local big_fill = vornmath.utils.bake('fill', {types[2], types[2]})
+ local little_fill = vornmath.utils.bake('fill', {lvalue_meta.vm_storage, rvalue_meta.vm_storage})
+ local make_scratch = vornmath.utils.bake(types[2], {})
+ local scratch = make_scratch()
+ local fill_targets = {}
+ for s,t in ipairs(swizzle_indices) do
+ local fill_command = "lvalue[" .. t .. "] = little_fill(lvalue[" .. t .. "], scratch[" .. s .. "])"
+ table.insert(fill_targets, fill_command)
+ end
+ local function_text = [[
+ local big_fill = select(1, ...)
+ local little_fill = select(2, ...)
+ local scratch = select(3, ...)
+ return function(lvalue, rvalue)
+ scratch = big_fill(scratch, rvalue)
+ ]] .. table.concat(fill_targets, '\n') .. [[
+ end]]
+ return load(function_text)(big_fill, little_fill, scratch)
+ end,
+ return_type = function(types) return 'nil' end
+ },
+ }
+ end
+end
+
+table.insert(vornmath.metabakeries, vornmath.utils.swizzleWriteBakery)
+
+function vornmath.utils.swizzleSetter(t, k, v)
+ local mt = vornmath.utils.getmetatable(t)
+ local vmt = vornmath.utils.getmetatable(v)
+ if not mt.setters[k] then
+ local real_k = vornmath.utils.swizzleRespell(k)
+ mt.setters[k] = vornmath.utils.bake('swizzleWrite' .. real_k, {mt.vm_type, vmt.vm_type})
+ end
+ return mt.setters[k](t, v)
+end
+
+-- simple type checkers
+
+function vornmath.utils.justNilTypeCheck(types)
+ if not types[1] then
+ types[1] = 'nil' -- I have to edit type lists that need to include a nil.
+ end
+ return types[1] == 'nil'
+end
+
+function vornmath.utils.clearingExactTypeCheck(correct_types)
+ return function(types)
+ for i,t in ipairs(correct_types) do
+ if not types[i] then types[i] = 'nil' end
+ if types[i] ~= t then return false end
+ end
+ types[#correct_types + 1] = nil
+ return true
+ end
+end
+
+function vornmath.utils.nilFollowingExactTypeCheck(correct_types)
+ return function(types)
+ for i,t in ipairs(correct_types) do
+ if types[i] ~= t then return false end
+ end
+ if not types[#correct_types + 1] then
+ types[#correct_types + 1] = 'nil'
+ end
+ return types[#correct_types + 1] == 'nil'
+ end
+end
+
+-- common bakeries
+
+function vornmath.utils.quatOperatorFromComplex(funcname)
+ return {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'quat'}),
+ create = function(types)
+ local decompose = vornmath.utils.bake('axisDecompose',{'quat', 'complex', 'vec3'})
+ local complex_function = vornmath.utils.bake(funcname, {'complex', 'complex'})
+ local fill = vornmath.utils.bake('fill',{'quat', 'complex', 'vec3'})
+ local cpx = vornmath.complex()
+ local axis = vornmath.vec3()
+ return function(z, result)
+ cpx, axis = decompose(z, cpx, axis)
+ cpx = complex_function(cpx, cpx)
+ return fill(result, cpx, axis)
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ }
+end
+
+function vornmath.utils.genericConstructor(typename)
+ return {
+ signature_check = function(types)
+ local extended_types = {typename}
+ for _,t in ipairs(types) do
+ table.insert(extended_types, t)
+ end
+ if vornmath.utils.hasBakery('fill', extended_types) then
+ -- copy any edits to extended_types back
+ for i = 2,#extended_types + 1 do -- grab up the nil too
+ types[i - 1] = extended_types[i]
+ end
+ return true
+ else
+ return false
+ end
+ end,
+ create = function(types)
+ local constructor = vornmath.utils.bake(typename, {})
+ local fill_types = {typename}
+ for _,t in ipairs(types) do table.insert(fill_types, t) end
+ local fill = vornmath.utils.bake('fill', fill_types)
+ return function(...)
+ local result = constructor()
+ return fill(result, ...)
+ end
+ end,
+ return_type = function(types) return typename end
+ }
+end
+
+function vornmath.utils.vectorNilConstructor(storage,d)
+ local typename = SCALAR_PREFIXES[storage] .. 'vec' .. d
+ return { -- vecd()
+ signature_check = vornmath.utils.justNilTypeCheck,
+ create = function(types)
+ local mt = vornmath.metatables[typename]
+ local constructor = vornmath.utils.bake(storage, {})
+ return function()
+ local result = {}
+ for k = 1,d do
+ result[k] = constructor()
+ end
+ return setmetatable(result, mt)
+ end
+ end,
+ return_type = function(types) return typename end
+ }
+end
+
+function vornmath.utils.matrixNilConstructor(storage,w,h)
+ local prefix = SCALAR_PREFIXES[storage]
+ local typename = prefix .. 'mat' .. w .. 'x' .. h
+ return {
+ signature_check = vornmath.utils.justNilTypeCheck,
+ create = function(types)
+ local mt = vornmath.metatables[typename]
+ local vectype = prefix .. 'vec' .. h
+ local vec = vornmath.utils.bake(vectype, {})
+ local identity_diagonal_length = math.min(w, h)
+ local fill = vornmath.utils.bake('fill', {storage, 'number'})
+ return function()
+ local result = setmetatable({}, mt)
+ for i = 1,w do
+ result[i] = vec()
+ end
+ for i = 1,identity_diagonal_length do
+ result[i][i] = fill(result[i][i], 1)
+ end
+ return result
+ end
+ end,
+ return_type = function(types) return typename end
+ }
+end
+
+local COMPONENT_EXPANSION_SHAPES = {
+ scalar = '',
+ vector = '[col]',
+ matrix = '[col][row]',
+ ['nil'] = ''
+}
+
+local COMPONENT_LOOP_PARTS = {
+ vector = {'for col = 1,width do', 'end'},
+ matrix = {'for col = 1,width do for row = 1,height do', 'end end'}
+}
+
+function vornmath.utils.componentWiseExpander(function_name, pattern, force_output_storage)
+ return {
+ signature_check = function(types)
+ if #types < #pattern + 1 then return false end
+ local shortened_types = {}
+ local scalar_types = {}
+ for i,shape in ipairs(pattern) do
+ local meta = vornmath.metatables[types[i]]
+ if meta.vm_shape ~= shape then return false end
+ table.insert(shortened_types, types[i])
+ table.insert(scalar_types, meta.vm_storage)
+ end
+ local return_type = vornmath.utils.componentWiseConsensusType(shortened_types, force_output_storage)
+ if return_type ~= types[#pattern + 1] then return false end
+ table.insert(scalar_types, vornmath.metatables[return_type].vm_storage)
+ if vornmath.utils.hasBakery(function_name, scalar_types) then
+ types[#pattern + 2] = nil
+ return true
+ end
+ return false
+ end,
+ create = function(types)
+ local scalar_types = {}
+ local arguments = {}
+ local argument_uses = {}
+ local last_shape, last_dim
+ for i,typename in ipairs(types) do
+ local meta = vornmath.metatables[typename]
+ table.insert(scalar_types,meta.vm_storage)
+ table.insert(arguments, LETTERS[i])
+ table.insert(argument_uses, LETTERS[i] .. COMPONENT_EXPANSION_SHAPES[meta.vm_shape])
+ last_shape = meta.vm_shape
+ last_dim = meta.vm_dim
+ end
+ local width, height
+ if last_shape == 'matrix' then
+ width, height = last_dim[1], last_dim[2]
+ else -- last_shape == 'vector'
+ width = last_dim
+ end
+ local func = vornmath.utils.bake(function_name, scalar_types)
+ local code = [[
+ local func, width, height = ...
+ return function(]] .. table.concat(arguments, ', ') ..[[)
+ ]] .. COMPONENT_LOOP_PARTS[last_shape][1] .. [[
+ ]] .. argument_uses[#argument_uses] .. [[ = func(]] .. table.concat(argument_uses, ', ') .. [[)
+ ]] .. COMPONENT_LOOP_PARTS[last_shape][2] .. [[
+ return ]] .. arguments[#arguments] .. [[
+ end
+ ]]
+ return load(code)(func,width,height)
+ end,
+ return_type = function(types) return types[#types] end
+ }
+end
+
+function vornmath.utils.twoMixedScalars(function_name)
+ return { -- add(mixed scalars)
+ signature_check = function(types)
+ local left_meta = vornmath.metatables[types[1]]
+ local right_meta = vornmath.metatables[types[2]]
+ if left_meta.vm_shape ~= 'scalar' then return false end
+ if right_meta.vm_shape ~= 'scalar' then return false end
+ local joint_type = vornmath.utils.consensusStorage({types[1], types[2]})
+ if types[3] ~= joint_type then return false end
+ if not vornmath.utils.hasBakery(function_name, {types[3], types[3], types[3]}) then return false end
+ types[4] = nil
+ return true
+ end,
+ create = function(types)
+ local final_function = vornmath.utils.bake(function_name, {types[3], types[3], types[3]})
+ if types[1] ~= types[3] then
+ local left_cast = vornmath.utils.bake(types[3], {types[1]})
+ if types[2] ~= types[3] then
+ local right_cast = vornmath.utils.bake(types[3], {types[2]})
+ return function(a,b,result) return final_function(left_cast(a), right_cast(b), result) end
+ else
+ return function(a,b,result) return final_function(left_cast(a), b, result) end
+ end
+ end
+ local right_cast = vornmath.utils.bake(types[3], {types[2]})
+ return function(a,b,result) return final_function(a, right_cast(b), result) end
+ end,
+ return_type = function(types)
+ return types[3]
+ end
+
+ }
+end
+
+function vornmath.utils.consensusType(types)
+ local storage, shape, dim
+ for _,typename in ipairs(types) do
+ local meta = vornmath.metatables[typename]
+
+ end
+end
+
+-- types
+
+vornmath.bakeries.boolean = {
+ { -- boolean()
+ signature_check = vornmath.utils.justNilTypeCheck,
+ create = function(types)
+ return function() return false end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ vornmath.utils.genericConstructor('boolean')
+}
+
+vornmath.bakeries.number = {
+ { -- number()
+ signature_check = vornmath.utils.justNilTypeCheck,
+ create = function(types)
+ return function() return 0 end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.genericConstructor('number')
+}
+
+vornmath.bakeries.complex = {
+ { -- complex()
+ signature_check = vornmath.utils.justNilTypeCheck,
+ create = function(types)
+ local complex_meta = vornmath.metatables.complex
+ return function()
+ return setmetatable({a = 0, b = 0}, complex_meta)
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ vornmath.utils.genericConstructor('complex')
+}
+
+vornmath.bakeries.quat = {
+ { -- quat()
+ signature_check = vornmath.utils.justNilTypeCheck,
+ create = function(types)
+ local quat_meta = vornmath.metatables.quat
+ return function()
+ return setmetatable({a = 0, b = 0, c = 0, d = 0}, quat_meta)
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ vornmath.utils.genericConstructor('quat')
+}
+
+for _,storage in ipairs({'boolean', 'number', 'complex'}) do
+ for d = 2,4 do
+ vornmath.bakeries[SCALAR_PREFIXES[storage] .. 'vec' .. d] = {
+ vornmath.utils.vectorNilConstructor(storage, d),
+ vornmath.utils.genericConstructor(SCALAR_PREFIXES[storage] .. 'vec' .. d)
+ }
+ end
+end
+
+for _,storage in ipairs({'number', 'complex'}) do
+ for w = 2,4 do
+ for h = 2,4 do
+ local typename = SCALAR_PREFIXES[storage] .. 'mat' .. w .. 'x' .. h
+ vornmath.bakeries[typename] = {
+ vornmath.utils.matrixNilConstructor(storage, w, h),
+ vornmath.utils.genericConstructor(typename)
+ }
+ end
+ end
+end
+
+-- fills (also constructors)
+
+vornmath.bakeries.fill = {
+ { -- fill(boolean)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'boolean'}),
+ create = function(types)
+ return function(target) return false end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ { -- fill(boolean, boolean)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'boolean', 'boolean'}),
+ create = function(types)
+ return function(target, x) return x end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ { -- fill(number)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types)
+ return function(target) return 0 end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- fill(number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number','number'}),
+ create = function(types)
+ return function(target, x) return x end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- fill(number, string[, number])
+ signature_check = function(types)
+ return types[1] == 'number' and types[2] == 'string'
+ end,
+ create = function(types)
+ return function(target, s, base) return tonumber(s, base) or error("Couldn't convert `" .. s .. "` to number.") end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- fill(complex)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'complex'}),
+ create = function(types)
+ return function(z)
+ z.a = 0
+ z.b = 0
+ return z
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- fill(complex, number[, nil])
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'complex','number'}),
+ create = function(types)
+ return function(z, a)
+ z.a = a
+ z.b = 0
+ return z
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- fill(complex, number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex','number','number'}),
+ create = function(types)
+ return function(z, a, b)
+ z.a = a
+ z.b = b
+ return z
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- fill(complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex','complex'}),
+ create = function(types)
+ return function(target, z)
+ target.a = z.a
+ target.b = z.b
+ return target
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- fill(quat)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'quat'}),
+ create = function(types)
+ return function(z)
+ z.a = 0
+ z.b = 0
+ z.c = 0
+ z.d = 0
+ return z
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ { -- fill(quat, number)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'quat','number'}),
+ create = function(types)
+ return function(z,x)
+ z.a = x
+ z.b = 0
+ z.c = 0
+ z.d = 0
+ return z
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ { -- fill(quat, number, number, number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat','number','number','number','number'}),
+ create = function(types)
+ return function(z,a,b,c,d)
+ z.a = a
+ z.b = b
+ z.c = c
+ z.d = d
+ return z
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ { -- fill(quat, complex, nil)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'quat','complex'}),
+ create = function(types)
+ return function(z,x)
+ z.a = x.a
+ z.b = x.b
+ z.c = 0
+ z.d = 0
+ return z
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ { -- fill(quat, complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat','complex','complex'}),
+ create = function(types)
+ return function(z,x,y)
+ z.a = x.a
+ z.b = x.b
+ z.c = y.a
+ z.d = y.b
+ return z
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ { -- fill(quat, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat','quat'}),
+ create = function(types)
+ return function(z,x)
+ z.a = x.a
+ z.b = x.b
+ z.c = x.c
+ z.d = x.d
+ return z
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ { -- fill(quat, axis, angle)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat','vec3','number'}),
+ create = function(types)
+ local sin, cos = math.sin, math.cos
+ return function(z, axis, angle)
+ local halfangle = angle / 2
+ local c = cos(halfangle)
+ local s = sin(halfangle)
+ z.a, z.b, z.c, z.d = c, s * axis[1], s * axis[2], s * axis[3]
+ return z
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ { -- fill(quat, complex, axis)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat','complex','vec3'}),
+ create = function(types)
+ return function(z, cpx, axis)
+ z.a, z.b, z.c, z.d = cpx.a, cpx.b * axis[1], cpx.b * axis[2], cpx.b * axis[3]
+ return z
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ { -- fill(quat, vec3, vec3)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat','vec3','vec3'}),
+ create = function(types)
+ local dot = vornmath.utils.bake('dot', {'vec3', 'vec3'})
+ local cross = vornmath.utils.bake('cross', {'vec3', 'vec3', 'vec3'})
+ local eq = vornmath.utils.bake('eq', {'vec3', 'vec3'})
+ local fill = vornmath.utils.bake('fill', {'quat', 'number', 'number', 'number', 'number'})
+ local sqrt = vornmath.utils.bake('sqrt', {'quat', 'quat'})
+ local normalize = vornmath.utils.bake('normalize', {'vec3', 'vec3'})
+ local c = vornmath.vec3()
+ local zero = vornmath.vec3()
+ return function(q, from, to)
+ local d = dot(from, to)
+ c = cross(from, to, c)
+ if eq(c, zero) and d < 0 then
+ -- we need to pick an axis at all.
+ if from[1] == 0 and from[2] == 0 then
+ return fill(q,0,1,0,0)
+ else
+ c = fill(c,from[2], -from[1],0)
+ c = normalize(c,c)
+ return fill(q,0,c[1],c[2],0)
+ end
+ end
+ q = fill(q, d, c[1], c[2], c[3])
+ return sqrt(q, q)
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ { -- fill(vec)
+ signature_check = function(types)
+ local first = vornmath.metatables[types[1]]
+ if first.vm_shape ~= 'vector' then
+ return false
+ end
+ if not types[2] then types[2] = 'nil' end
+ return types[2] == 'nil'
+ end,
+ create = function(types)
+ local d = vornmath.metatables[types[1]].vm_dim
+ local storage = vornmath.metatables[types[1]].vm_storage
+ local fill = vornmath.utils.bake('fill', {storage, 'nil'})
+ return function(v)
+ for i = 1,d do
+ v[i] = fill(v[i])
+ end
+ return v
+ end
+ end,
+ return_type = function(types) return types[1] end
+ },
+ { -- fill(vec, scalar)
+ signature_check = function(types)
+ local first = vornmath.metatables[types[1]]
+ local second = vornmath.metatables[types[2]]
+ if first.vm_shape ~= 'vector' then return false end
+ if second.vm_shape ~= 'scalar' then return false end
+ if not vornmath.utils.hasBakery('fill', {first.vm_storage, second.vm_storage}) then return false end
+ if not types[3] then types[3] = 'nil' end
+ return types[3] == 'nil'
+ end,
+ create = function(types)
+ local first = vornmath.metatables[types[1]]
+ local second = vornmath.metatables[types[2]]
+ local fill = vornmath.utils.bake('fill', {first.vm_storage, second.vm_storage})
+ local d = vornmath.metatables[types[1]].vm_dim
+ return function(v,x)
+ for i = 1,d do
+ v[i] = fill(v[i],x)
+ end
+ return v
+ end
+ end,
+ return_type = function(types) return types[1] end
+ },
+ { -- fill(vec, mixed)
+ signature_check = function(types)
+ if #types < 2 then return false end
+ local first = vornmath.metatables[types[1]]
+ if first.vm_shape ~= 'vector' then
+ return false
+ end
+ local d = first.vm_dim
+ local input_count = 0
+ if vornmath.utils.consensusStorage(types) ~= first.vm_storage then return false end
+ for i = 2,#types do
+ local t = types[i]
+ if input_count >= d then return false end -- I have a pattern that's too long
+ local mt = vornmath.metatables[t]
+ if mt.vm_shape == 'scalar' then
+ input_count = input_count + 1
+ elseif mt.vm_shape == 'vector' then
+ input_count = input_count + mt.vm_dim
+ elseif mt.vm_shape == 'matrix' then
+ input_count = input_count + mt.vm_dim[1] * mt.vm_dim[2]
+ else
+ return false
+ end
+ end
+ return input_count >= d
+ end,
+ create = function(types)
+ -- we'll have to do this the hard way: string loading.
+ local arguments = {}
+ local full_inputs = {}
+ local first = vornmath.metatables[types[1]]
+ local d = first.vm_dim
+ local casts = {}
+ local cast_assigns = {}
+ local idx = 1
+ for i = 2,#types do
+ local t = types[i]
+ local letter = LETTERS[i]
+ table.insert(arguments, letter)
+ local tmt = vornmath.metatables[t]
+ if not casts[tmt.vm_storage] then
+ casts[tmt.vm_storage] = vornmath.utils.bake('fill', {first.vm_storage, tmt.vm_storage})
+ end
+ if tmt.vm_shape == 'scalar' then
+ -- a scalar goes in directly.
+ table.insert(full_inputs, 'target[' .. idx .. '] = fill_' .. tmt.vm_storage .. '(target[' .. idx .. '], ' .. letter .. ')')
+ idx = idx + 1
+ elseif tmt.vm_shape == 'vector' then
+ -- a vector goes in by element
+ for j = 1,tmt.vm_dim do
+ if #full_inputs == d then break end
+ table.insert(full_inputs, 'target[' .. idx .. '] = fill_' .. tmt.vm_storage .. '(target[' .. idx .. '], ' .. letter .. '[' .. j .. '])')
+ idx = idx + 1
+ end
+ else -- tmt.vm_shape == 'matrix'
+ for j = 1,tmt.vm_dim[1] do
+ for k = 1,tmt.vm_dim[2] do
+ if #full_inputs == d then break end
+ table.insert(full_inputs, 'target[' .. idx .. '] = fill_' .. tmt.vm_storage .. '(target[' .. idx .. '], ' .. letter .. '[' .. j .. '][' .. k .. '])')
+ idx = idx + 1
+ end
+ end
+ end
+ end
+ for name,_ in pairs(casts) do
+ table.insert(cast_assigns, 'local fill_'.. name .. ' = casts.' .. name)
+ end
+ local cast_glom = table.concat(cast_assigns, '\n')
+ local letter_glom = table.concat(arguments, ', ')
+ local inputs_glom = table.concat(full_inputs, '\n')
+
+ local code = [[
+ local casts = select(1, ...)
+ ]] .. cast_glom .. [[
+ return function(target, ]] .. letter_glom .. [[)
+ ]] .. inputs_glom .. [[
+ return target
+ end
+ ]]
+ return load(code)(casts)
+ end,
+ return_type = function(types) return types[1] end
+ },
+ { -- fill(vec, table)
+ signature_check = function(types)
+ local first = vornmath.metatables[types[1]]
+ if first.vm_shape ~= 'vector' then return false end
+ if types[2] == 'table' then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local typename = types[1]
+ local first = vornmath.metatables[typename]
+ local dim = first.vm_dim
+ local storage = first.vm_storage
+ local write = vornmath.utils.bake('fill', {typename, typename})
+ local fill = vornmath.fill
+ local scratch = vornmath[types[1]]()
+ return function(target, t)
+ for i = 1,dim do
+ if t[i] == nil then error("Filling " .. typename .. " from table failed: need at least " .. dim .. "elements, got " .. string(i-1) .. ".") end
+ local worked, result = pcall(fill, scratch[i], t[i])
+ if not worked then error("Filling " .. typename .. " from table failed: element " .. i .. " (" .. string(t[i]) .. ") could not be converted to " .. storage .. ".") end
+ scratch[i] = result
+ end
+ return write(target, scratch)
+ end
+ end,
+ return_type = function(types) return types[1] end
+ },
+ { -- fill(matrix)
+ signature_check = function(types)
+ local first = vornmath.metatables[types[1]]
+ if first.vm_shape ~= 'matrix' then
+ return false
+ end
+ if not types[2] then types[2] = 'nil' end
+ return types[2] == 'nil'
+ end,
+ create = function(types)
+ local first = vornmath.metatables[types[1]]
+ local storage = first.vm_storage
+ local dim = first.vm_dim
+ local w, h = dim[1], dim[2]
+ local nilfill = vornmath.utils.bake('fill', {storage, 'nil'})
+ local numfill = vornmath.utils.bake('fill', {storage, 'number'})
+ return function(m)
+ for x = 1,w do
+ for y = 1,h do
+ if x == y then
+ m[x][y] = numfill(m[x][y], 1)
+ else
+ m[x][y] = nilfill(m[x][y])
+ end
+ end
+ end
+ return m
+ end
+ end,
+ return_type = function(types) return types[1] end
+ },
+ { -- fill(matrix, scalar)
+ signature_check = function(types)
+ local first = vornmath.metatables[types[1]]
+ local second = vornmath.metatables[types[2]]
+ if first.vm_shape ~= 'matrix' then return false end
+ if second.vm_shape ~= 'scalar' then return false end
+ if not vornmath.utils.hasBakery('fill', {first.vm_storage, second.vm_storage}) then return false end
+ if not types[3] then types[3] = 'nil' end
+ return types[3] == 'nil'
+ end,
+ create = function(types)
+ local first = vornmath.metatables[types[1]]
+ local second = vornmath.metatables[types[2]]
+ local nilfill = vornmath.utils.bake('fill', {first.vm_storage})
+ local valfill = vornmath.utils.bake('fill', {first.vm_storage, second.vm_storage})
+ local dim = first.vm_dim
+ local w, h = dim[1], dim[2]
+ return function(m,val)
+ for x = 1,w do
+ for y = 1,h do
+ if x == y then
+ m[x][y] = valfill(m[x][y], val)
+ else
+ m[x][y] = nilfill(m[x][y])
+ end
+ end
+ end
+ return m
+ end
+ end,
+ return_type = function(types) return types[1] end
+ },
+ { -- fill(matrix, matrix)
+ signature_check = function(types)
+ local first = vornmath.metatables[types[1]]
+ local second = vornmath.metatables[types[2]]
+ if first.vm_shape ~= 'matrix' then return false end
+ if second.vm_shape ~= 'matrix' then return false end
+ if vornmath.utils.consensusStorage(types) ~= first.vm_storage then return false end
+ if not types[3] then types[3] = 'nil' end
+ return types[3] == 'nil'
+ end,
+ create = function(types)
+ local first = vornmath.metatables[types[1]]
+ local second = vornmath.metatables[types[2]]
+ local nilfill = vornmath.utils.bake('fill', {first.vm_storage})
+ local valfill = vornmath.utils.bake('fill', {first.vm_storage, second.vm_storage})
+ local numfill = vornmath.utils.bake('fill', {first.vm_storage, 'number'})
+ local dest_dim = first.vm_dim
+ local src_dim = second.vm_dim
+ local dest_w, dest_h = dest_dim[1], dest_dim[2]
+ local src_w, src_h = src_dim[1], src_dim[2]
+ return function(dest, src)
+ for x = 1,dest_w do
+ for y = 1, dest_h do
+ if x <= src_w and y <= src_h then
+ dest[x][y] = valfill(dest[x][y], src[x][y])
+ elseif x == y then
+ dest[x][y] = numfill(dest[x][y], 1)
+ else
+ dest[x][y] = nilfill(dest[x][y])
+ end
+ end
+ end
+ return dest
+ end
+ end,
+ return_type = function(types) return types[1] end
+ },
+ { -- fill(matrix, mixed)
+ signature_check = function(types)
+ if #types < 2 then return false end
+ local first = vornmath.metatables[types[1]]
+ if first.vm_shape ~= 'matrix' then
+ return false
+ end
+
+ if vornmath.utils.consensusStorage(types) ~= first.vm_storage then return false end
+ local d = first.vm_dim[1] * first.vm_dim[2]
+ local input_count = 0
+ for i = 2,#types do
+ local t = types[i]
+ if input_count >= d then return false end -- I have a pattern that's too long
+ local mt = vornmath.metatables[t]
+ if mt.vm_shape == 'scalar' then
+ input_count = input_count + 1
+ elseif mt.vm_shape == 'vector' then
+ input_count = input_count + mt.vm_dim
+ else
+ return false
+ end
+ end
+ return input_count >= d
+ end,
+ create = function(types)
+ -- we'll have to do this the hard way: string loading.
+ local arguments = {}
+ local full_inputs = {}
+ local first = vornmath.metatables[types[1]]
+ local w,h = first.vm_dim[1], first.vm_dim[2]
+ local d = w * h
+ local casts = {}
+ local cast_assigns = {}
+ local x,y = 1,1
+ for i = 2,#types do
+ local t = types[i]
+ local letter = LETTERS[i]
+ table.insert(arguments, letter)
+ local tmt = vornmath.metatables[t]
+ if not casts[tmt.vm_storage] then
+ casts[tmt.vm_storage] = vornmath.utils.bake('fill', {first.vm_storage, tmt.vm_storage})
+ end
+ if tmt.vm_shape == 'scalar' then
+ -- a scalar goes in directly.
+ table.insert(full_inputs, 'target[' .. x .. '][' .. y .. '] = fill_' .. tmt.vm_storage .. '(target[' .. x .. '][' .. y .. '], ' .. letter .. ')')
+ y = y + 1
+ if y > h then
+ x = x + 1
+ y = 1
+ end
+ elseif tmt.vm_shape == 'vector' then
+ -- a vector goes in by element
+ for j = 1,tmt.vm_dim do
+ if #full_inputs == d then break end
+ table.insert(full_inputs, 'target[' .. x .. '][' .. y .. '] = fill_' .. tmt.vm_storage .. '(target[' .. x .. '][' .. y .. '], ' .. letter .. '[' .. j .. '])')
+ y = y + 1
+ if y > h then
+ x = x + 1
+ y = 1
+ end
+ end
+ end
+ end
+ for name,_ in pairs(casts) do
+ table.insert(cast_assigns, 'local fill_'.. name .. ' = casts.' .. name)
+ end
+ local cast_glom = table.concat(cast_assigns, '\n')
+ local letter_glom = table.concat(arguments, ', ')
+ local inputs_glom = table.concat(full_inputs, '\n')
+
+ local code = [[
+ local casts = select(1, ...)
+ ]] .. cast_glom .. [[
+ return function(target, ]] .. letter_glom .. [[)
+ ]] .. inputs_glom .. [[
+ return target
+ end
+ ]]
+ return load(code)(casts)
+ end,
+ return_type = function(types) return types[1] end
+ },
+ { -- fill(matrix, table)
+ signature_check = function(types)
+ local first = vornmath.metatables[types[1]]
+ if first.vm_shape ~= 'matrix' then return false end
+ if types[2] == 'table' then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local typename = types[1]
+ local first = vornmath.metatables[typename]
+ local width = first.vm_dim[1]
+ local height = first.vm_dim[2]
+ local dim = width * height
+ local storage = first.vm_storage
+ local write = vornmath.utils.bake('fill', {typename, typename})
+ local fill = vornmath.fill
+ local scratch = vornmath[types[1]]()
+ return function(target, t)
+ for x = 1,width do
+ for y = 1,height do
+ local i = (x-1) * height + y
+ if t[i] == nil then error("Filling " .. typename .. " from table failed: need at least " .. dim .. "elements, got " .. i-1 .. ".") end
+ local worked, result = pcall(fill, scratch[x][y], t[i])
+ if not worked then error("Filling " .. typename .. " from table failed: element " .. i .. " (" .. tostring(t[i]) .. ") could not be converted to " .. storage .. ".") end
+ scratch[x][y] = result
+ end
+ end
+ return write(target, scratch)
+ end
+ end,
+ return_type = function(types) return types[1] end
+ },
+ { -- fill(mat3, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'mat3x3', 'quat'}),
+ create = function(types)
+ local sqabs = vornmath.utils.bake('sqabs', {'quat'})
+ local fill = vornmath.utils.bake('fill', {'mat3x3', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'})
+ return function(m, q)
+ local s = 2 / sqabs(q)
+ local bs, cs, ds = q.b * s, q.c * s, q.d * s
+ local ab, ac, ad = q.a * bs, q.a * cs, q.a * ds
+ local bb, cc, dd = q.b * bs, q.c * cs, q.d * ds
+ local bc, cd, bd = q.b * cs, q.c * ds, q.b * ds
+ return fill(m, 1 - cc - dd, bc + ad, bd - ac, bc - ad, 1 - bb - dd, cd + ab, bd + ac, cd - ab, 1 - bb - cc)
+ end
+ end,
+ return_type = function(types) return 'mat3x3' end
+ },
+ { -- fill(mat4, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'mat4x4', 'quat'}),
+ create = function(types)
+ local make_3 = vornmath.utils.bake('fill', {'mat3x3', 'quat'})
+ local fill = vornmath.utils.bake('fill', {'mat4x4', 'mat3x3'})
+ local scratch = vornmath.mat3()
+ return function(m, q)
+ return fill(m, make_3(scratch, q))
+ end
+ end,
+ return_type = function(types) return 'mat4x4' end
+ }
+}
+
+-- arithmetic operators
+
+vornmath.bakeries.add = {
+ { -- add(number, number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number', 'number'}),
+ create = function(types)
+ return function(x, y) return x + y end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- add(complex, complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'complex', 'complex'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ return function(x, y, result)
+ return fill(result, x.a + y.a, x.b + y.b)
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- add(quat, quat, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'quat', 'quat'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'quat', 'number', 'number', 'number', 'number'})
+ return function(x, y, result)
+ return fill(result, x.a + y.a, x.b + y.b, x.c + y.c, x.d + y.d)
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ vornmath.utils.componentWiseExpander('add', {'vector', 'scalar'}),
+ vornmath.utils.componentWiseExpander('add', {'scalar', 'vector'}),
+ vornmath.utils.componentWiseExpander('add', {'vector', 'vector'}),
+ vornmath.utils.componentWiseExpander('add', {'matrix', 'scalar'}),
+ vornmath.utils.componentWiseExpander('add', {'scalar', 'matrix'}),
+ vornmath.utils.componentWiseExpander('add', {'matrix', 'matrix'}),
+ vornmath.utils.componentWiseReturnOnlys('add', 2),
+ vornmath.utils.twoMixedScalars('add')
+}
+
+vornmath.bakeries.unm = {
+ { -- unm(number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ return function(x) return -x end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- unm(complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'complex'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ return function(x, result)
+ return fill(result, -x.a, -x.b)
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- unm(quat, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'quat'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'quat', 'number', 'number', 'number', 'number'})
+ return function(x, result)
+ return fill(result, -x.a, -x.b, -x.c, -x.d)
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ vornmath.utils.componentWiseExpander('unm', {'vector'}),
+ vornmath.utils.componentWiseExpander('unm', {'matrix'}),
+ vornmath.utils.componentWiseReturnOnlys('unm', 1),
+}
+
+vornmath.bakeries.sub = {
+ { -- sub(number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number', 'number'}),
+ create = function(types)
+ return function(x, y) return x - y end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- sub(complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'complex', 'complex'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ return function(x, y, result)
+ return fill(result, x.a - y.a, x.b - y.b)
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- sub(quat, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'quat', 'quat'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'quat', 'number', 'number', 'number', 'number'})
+ return function(x, y, result)
+ return fill(result, x.a - y.a, x.b - y.b, x.c - y.c, x.d - y.d)
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ vornmath.utils.componentWiseExpander('sub', {'vector', 'scalar'}),
+ vornmath.utils.componentWiseExpander('sub', {'scalar', 'vector'}),
+ vornmath.utils.componentWiseExpander('sub', {'vector', 'vector'}),
+ vornmath.utils.componentWiseExpander('sub', {'matrix', 'scalar'}),
+ vornmath.utils.componentWiseExpander('sub', {'scalar', 'matrix'}),
+ vornmath.utils.componentWiseExpander('sub', {'matrix', 'matrix'}),
+ vornmath.utils.componentWiseReturnOnlys('sub', 2),
+ vornmath.utils.twoMixedScalars('sub')
+}
+
+vornmath.bakeries.mul = {
+ { -- mul(number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number', 'number'}),
+ create = function(types)
+ return function(x, y) return x * y end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- mul(complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'complex', 'complex'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ return function(x, y, result)
+ return fill(result, x.a * y.a - x.b * y.b, x.a * y.b + x.b * y.a)
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- mul(quat, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'quat', 'quat'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'quat', 'number', 'number', 'number', 'number'})
+ return function(x, y, result)
+ return fill(result, x.a * y.a - x.b * y.b - x.c * y.c - x.d * y.d,
+ x.a * y.b + x.b * y.a + x.c * y.d - x.d * y.c,
+ x.a * y.c - x.b * y.d + x.c * y.a + x.d * y.b,
+ x.a * y.d + x.b * y.c - x.c * y.b + x.d * y.a)
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ { -- mul(matrix, matrix, matrix)}
+ signature_check = function(types)
+ if #types < 3 then return false end
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ local result = vornmath.metatables[types[3]]
+ if (left.vm_shape ~= 'matrix' or
+ right.vm_shape ~= 'matrix' or
+ result.vm_shape ~= 'matrix' or
+ left.vm_dim[1] ~= right.vm_dim[2] or
+ left.vm_dim[2] ~= result.vm_dim[2] or
+ result.vm_dim[1] ~= right.vm_dim[1] or
+ not vornmath.utils.hasBakery('mul', {left.vm_storage, right.vm_storage, result.vm_storage})
+ or not vornmath.utils.hasBakery('add', {result.vm_storage, result.vm_storage, result.vm_storage})
+ )
+ then
+ return false
+ end
+ types[4] = nil
+ return true
+ end,
+ create = function(types)
+ local left_type = vornmath.metatables[types[1]]
+ local right_type = vornmath.metatables[types[2]]
+ local result_type = vornmath.metatables[types[3]]
+ local width = result_type.vm_dim[1]
+ local height = result_type.vm_dim[2]
+ local depth = left_type.vm_dim[1]
+ local mul = vornmath.utils.bake('mul', {left_type.vm_storage, right_type.vm_storage, result_type.vm_storage})
+ local add = vornmath.utils.bake('add', {result_type.vm_storage, result_type.vm_storage, result_type.vm_storage})
+ local empty_temp = vornmath.utils.bake('fill', {types[3], 'number'})
+ local fill = vornmath.utils.bake('fill', {types[3], types[3]})
+ local temp = vornmath[types[3]]()
+ local value_scratch = vornmath[types[3]]
+ return function(left, right, result)
+ temp = empty_temp(temp, 0)
+ for x = 1, width do
+ for y = 1, height do
+ for z = 1, depth do
+ value_scratch = mul(left[z][y], right[x][z], value_scratch)
+ temp[x][y] = add(temp[x][y], value_scratch, temp[x][y])
+ end
+ end
+ end
+ fill(result, temp)
+ return result
+ end
+ end,
+ return_type = function(types) return types[3] end
+ },
+ { -- mul(vector, matrix, vector)}
+ signature_check = function(types)
+ if #types < 3 then return false end
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ local result = vornmath.metatables[types[3]]
+ if (left.vm_shape ~= 'vector' or
+ right.vm_shape ~= 'matrix' or
+ result.vm_shape ~= 'vector' or
+ left.vm_dim ~= right.vm_dim[2] or
+ result.vm_dim ~= right.vm_dim[1] or
+ not vornmath.utils.hasBakery('mul', {left.vm_storage, right.vm_storage, result.vm_storage})
+ or not vornmath.utils.hasBakery('add', {result.vm_storage, result.vm_storage, result.vm_storage})
+ )
+ then
+ return false
+ end
+ types[4] = nil
+ return true
+ end,
+ create = function(types)
+ local left_type = vornmath.metatables[types[1]]
+ local right_type = vornmath.metatables[types[2]]
+ local result_type = vornmath.metatables[types[3]]
+ local width = result_type.vm_dim
+ local depth = right_type.vm_dim[2]
+ local mul = vornmath.utils.bake('mul', {left_type.vm_storage, right_type.vm_storage, result_type.vm_storage})
+ local add = vornmath.utils.bake('add', {result_type.vm_storage, result_type.vm_storage, result_type.vm_storage})
+ local empty_temp = vornmath.utils.bake('fill', {types[3], 'number'})
+ local fill = vornmath.utils.bake('fill', {types[3], types[3]})
+ local temp = vornmath[types[3]]()
+ local value_scratch = vornmath[types[3]]
+ return function(left, right, result)
+ temp = empty_temp(temp, 0)
+ for x = 1, width do
+ for z = 1, depth do
+ value_scratch = mul(left[z], right[x][z], value_scratch)
+ temp[x] = add(temp[x], value_scratch, temp[x])
+ end
+ end
+ fill(result, temp)
+ return result
+ end
+ end,
+ return_type = function(types) return types[3] end
+ },
+ { -- mul(matrix, vector, vector)}
+ signature_check = function(types)
+ if #types < 3 then return false end
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ local result = vornmath.metatables[types[3]]
+ if (left.vm_shape ~= 'matrix' or
+ right.vm_shape ~= 'vector' or
+ result.vm_shape ~= 'vector' or
+ left.vm_dim[1] ~= right.vm_dim or
+ left.vm_dim[2] ~= result.vm_dim or
+ not vornmath.utils.hasBakery('mul', {left.vm_storage, right.vm_storage, result.vm_storage})
+ or not vornmath.utils.hasBakery('add', {result.vm_storage, result.vm_storage, result.vm_storage})
+ )
+ then
+ return false
+ end
+ types[4] = nil
+ return true
+ end,
+ create = function(types)
+ local left_type = vornmath.metatables[types[1]]
+ local right_type = vornmath.metatables[types[2]]
+ local result_type = vornmath.metatables[types[3]]
+ local height = result_type.vm_dim
+ local depth = left_type.vm_dim[1]
+ local mul = vornmath.utils.bake('mul', {left_type.vm_storage, right_type.vm_storage, result_type.vm_storage})
+ local add = vornmath.utils.bake('add', {result_type.vm_storage, result_type.vm_storage, result_type.vm_storage})
+ local empty_temp = vornmath.utils.bake('fill', {types[3], 'number'})
+ local fill = vornmath.utils.bake('fill', {types[3], types[3]})
+ local temp = vornmath[types[3]]()
+ local value_scratch = vornmath[types[3]]
+ return function(left, right, result)
+ temp = empty_temp(temp, 0)
+ for y = 1, height do
+ for z = 1, depth do
+ value_scratch = mul(left[z][y], right[z], value_scratch)
+ temp[y] = add(temp[y], value_scratch, temp[y])
+ end
+ end
+ fill(result, temp)
+ return result
+ end
+ end,
+ return_type = function(types) return types[3] end
+ },
+ { -- mul(matrix, matrix)
+ signature_check = function(types)
+ if #types < 2 then return false end
+ if #types > 2 then
+ -- only nils after
+ for i, typename in ipairs(types) do
+ if i > 2 and typename ~= 'nil' then return false end
+ end
+ end
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ if left.vm_shape ~= 'matrix' or right.vm_shape ~= 'matrix' then
+ return false
+ end
+ local consensus_storage = vornmath.utils.consensusStorage({left.vm_storage, right.vm_storage})
+ local result_type = vornmath.utils.findTypeByData('matrix', {right.vm_dim[1], left.vm_dim[2]}, consensus_storage)
+ if vornmath.utils.hasBakery('mul', {types[1], types[2], result_type}) then
+ types[3] = 'nil'
+ types[4] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ local consensus_storage = vornmath.utils.consensusStorage({left.vm_storage, right.vm_storage})
+ local result_type = vornmath.utils.findTypeByData('matrix', {right.vm_dim[1], left.vm_dim[2]}, consensus_storage)
+ local make = vornmath.utils.bake(result_type, {})
+ local action = vornmath.utils.bake('mul', {types[1], types[2], result_type})
+ return function(a, b)
+ local result = make()
+ return action(a, b, result)
+ end
+ end,
+ return_type = function(types)
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ local consensus_storage = vornmath.utils.consensusStorage({left.vm_storage, right.vm_storage})
+ return vornmath.utils.findTypeByData('matrix', {right.vm_dim[1], left.vm_dim[2]}, consensus_storage)
+ end
+ },
+ { -- mul(vector, matrix)
+ signature_check = function(types)
+ if #types < 2 then return false end
+ if #types > 2 then
+ -- only nils after
+ for i, typename in ipairs(types) do
+ if i > 2 and typename ~= 'nil' then return false end
+ end
+ end
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ if left.vm_shape ~= 'vector' or right.vm_shape ~= 'matrix' then
+ return false
+ end
+ local consensus_storage = vornmath.utils.consensusStorage({left.vm_storage, right.vm_storage})
+ local result_type = vornmath.utils.findTypeByData('vector', right.vm_dim[1], consensus_storage)
+ if vornmath.utils.hasBakery('mul', {types[1], types[2], result_type}) then
+ types[3] = 'nil'
+ types[4] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ local consensus_storage = vornmath.utils.consensusStorage({left.vm_storage, right.vm_storage})
+ local result_type = vornmath.utils.findTypeByData('vector', right.vm_dim[1], consensus_storage)
+ local make = vornmath.utils.bake(result_type, {})
+ local action = vornmath.utils.bake('mul', {types[1], types[2], result_type})
+ return function(a, b)
+ local result = make()
+ return action(a, b, result)
+ end
+ end,
+ return_type = function(types)
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ local consensus_storage = vornmath.utils.consensusStorage({left.vm_storage, right.vm_storage})
+ return vornmath.utils.findTypeByData('vector', right.vm_dim[1], consensus_storage)
+ end
+ },
+ { -- mul(matrix, vector)
+ signature_check = function(types)
+ if #types < 2 then return false end
+ if #types > 2 then
+ -- only nils after
+ for i, typename in ipairs(types) do
+ if i > 2 and typename ~= 'nil' then return false end
+ end
+ end
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ if left.vm_shape ~= 'matrix' or right.vm_shape ~= 'vector' then
+ return false
+ end
+ local consensus_storage = vornmath.utils.consensusStorage({left.vm_storage, right.vm_storage})
+ local result_type = vornmath.utils.findTypeByData('vector', left.vm_dim[2], consensus_storage)
+ if vornmath.utils.hasBakery('mul', {types[1], types[2], result_type}) then
+ types[3] = 'nil'
+ types[4] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ local consensus_storage = vornmath.utils.consensusStorage({left.vm_storage, right.vm_storage})
+ local result_type = vornmath.utils.findTypeByData('vector', left.vm_dim[2], consensus_storage)
+ local make = vornmath.utils.bake(result_type, {})
+ local action = vornmath.utils.bake('mul', {types[1], types[2], result_type})
+ return function(a, b)
+ local result = make()
+ return action(a, b, result)
+ end
+ end,
+ return_type = function(types)
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ local consensus_storage = vornmath.utils.consensusStorage({left.vm_storage, right.vm_storage})
+ return vornmath.utils.findTypeByData('matrix', {right.vm_dim[1], left.vm_dim[2]}, consensus_storage)
+ end
+ },
+ { -- mul(quat, vec3, vec3)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'vec3', 'vec3'}),
+ create = function(types)
+ local mul = vornmath.utils.bake('mul', {'quat', 'quat', 'quat'})
+ local conj = vornmath.utils.bake('conj', {'quat', 'quat'})
+ local qfill = vornmath.utils.bake('fill', {'quat', 'number', 'number', 'number', 'number'})
+ local vfill = vornmath.utils.bake('fill', {'vec3', 'number', 'number', 'number'})
+ local c = vornmath.quat()
+ local p = vornmath.quat()
+ return function(q, v, result)
+ c = conj(q, c)
+ p = qfill(p, 0, v[1], v[2], v[3])
+ p = mul(q, p, p)
+ p = mul(p, c, p)
+ return vfill(result, p.b, p.c, p.d)
+ end
+ end,
+ return_type = function(types) return 'vec3' end
+ },
+ { -- mul(quat, vec3)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'vec3'}),
+ create = function(types)
+ local create = vornmath.utils.bake('vec3', {})
+ local f = vornmath.utils.bake('mul', {'quat', 'vec3', 'vec3'})
+ return function(q, v)
+ local result = create()
+ return f(q, v, result)
+ end
+ end,
+ return_type = function(types) return 'vec3' end
+},
+ vornmath.utils.componentWiseExpander('mul', {'vector', 'scalar'}),
+ vornmath.utils.componentWiseExpander('mul', {'scalar', 'vector'}),
+ vornmath.utils.componentWiseExpander('mul', {'vector', 'vector'}),
+ vornmath.utils.componentWiseExpander('mul', {'matrix', 'scalar'}),
+ vornmath.utils.componentWiseExpander('mul', {'scalar', 'matrix'}),
+ vornmath.utils.componentWiseReturnOnlys('mul', 2),
+ vornmath.utils.twoMixedScalars('mul')
+}
+
+vornmath.bakeries.div = {
+ { -- div(number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number', 'number'}),
+ create = function(types)
+ return function(x, y) return x / y end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- div(complex, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'number', 'complex'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ return function(x, y, result)
+ return fill(result, x.a / y, x.b / y)
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- div(quat, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'number', 'quat'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'quat', 'number', 'number', 'number', 'number'})
+ return function(x, y, result)
+ return fill(result, x.a / y, x.b / y, x.c / y, x.d / y)
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ { -- div(scalar, non-number scalar, combined)
+ -- div I do special because unlike the other things, the straightforward one isn't the matched-types one.
+ signature_check = function(types)
+ local first = vornmath.metatables[types[1]]
+ local second = vornmath.metatables[types[2]]
+ if first.vm_shape ~= 'scalar' then return false end
+ if second.vm_shape ~= 'scalar' then return false end
+ if second.vm_shape == 'number' then return false end
+ local combined_type = vornmath.utils.consensusStorage({types[1], types[2]})
+ if types[3] ~= combined_type then return false end
+ if vornmath.utils.hasBakery('sqabs', {types[2]}) and
+ vornmath.utils.hasBakery('mul', {types[1], types[2], types[3]}) and
+ vornmath.utils.hasBakery('conj', {types[2]}) and
+ vornmath.utils.hasBakery('div', {types[3], 'number', types[3]}) then
+ return true
+ end
+ end,
+ create = function(types)
+ local sqabs = vornmath.utils.bake('sqabs', {types[2]})
+ local mul = vornmath.utils.bake('mul', {types[1], types[2], types[3]})
+ local conj = vornmath.utils.bake('conj', {types[2]})
+ local div = vornmath.utils.bake('div', {types[3], 'number', types[3]})
+ return function(x, y, result)
+ local denominator = sqabs(y)
+ result = mul(x, conj(y), result)
+ result = div(result, denominator, result)
+ return result
+ end
+ end,
+ return_type = function(types) return types[3] end
+ },
+ vornmath.utils.componentWiseExpander('div', {'vector', 'scalar'}),
+ vornmath.utils.componentWiseExpander('div', {'scalar', 'vector'}),
+ vornmath.utils.componentWiseExpander('div', {'vector', 'vector'}),
+ vornmath.utils.componentWiseExpander('div', {'matrix', 'scalar'}),
+ vornmath.utils.componentWiseExpander('div', {'scalar', 'matrix'}),
+ vornmath.utils.componentWiseExpander('div', {'matrix', 'matrix'}),
+ vornmath.utils.componentWiseReturnOnlys('div', 2),
+}
+
+vornmath.bakeries.mod = {
+ { -- mod(number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ return function(x, y) return x % y end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('mod', {'vector', 'scalar'}),
+ vornmath.utils.componentWiseExpander('mod', {'scalar', 'vector'}),
+ vornmath.utils.componentWiseExpander('mod', {'vector', 'vector'}),
+ vornmath.utils.componentWiseExpander('mod', {'matrix', 'scalar'}),
+ vornmath.utils.componentWiseExpander('mod', {'scalar', 'matrix'}),
+ vornmath.utils.componentWiseExpander('mod', {'matrix', 'matrix'}),
+ vornmath.utils.componentWiseReturnOnlys('mod', 2),
+
+}
+
+vornmath.bakeries.pow = {
+ { -- pow(number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+-- pow disappears in later versions (5.2+) because ^ replaces it
+---@diagnostic disable-next-line: deprecated
+ return math.pow or function(x,y) return x^y end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- pow(complex, complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'complex', 'complex'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ local log = vornmath.utils.bake('log', {'complex', 'nil', 'complex'})
+ local exp = math.exp
+ local sin = math.sin
+ local cos = math.cos
+ local mul = vornmath.utils.bake('mul', {'complex', 'complex', 'complex'})
+ local w = vornmath.complex()
+ return function(x, y, result)
+ if y.a == 0 and y.b == 0 then return fill(result, 1, 0) end
+ if x.a == 0 and x.b == 0 then return fill(result, 0, 0) end
+ w = log(x, nil, w)
+ w = mul(w, y, w)
+ local size = exp(w.a)
+ return fill(result, size * cos(w.b), size * sin(w.b))
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- pow(quat, quat, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat','quat','quat'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'quat', 'number', 'number', 'number', 'number'})
+ local decompose = vornmath.utils.bake('axisDecompose', {'quat', 'complex', 'vec3'})
+ local log = vornmath.utils.bake('log', {'complex', 'nil', 'complex'})
+ local exp = vornmath.utils.bake('exp', {'quat', 'quat'})
+ local mul = vornmath.utils.bake('mul', {'quat','quat', 'quat'})
+ local regenerate = vornmath.utils.bake('fill', {'quat', 'complex', 'vec3'})
+ local eq = vornmath.utils.bake('eq', {'quat', 'number'})
+ local cpx = vornmath.complex()
+ local axis = vornmath.vec3()
+ local _ = vornmath.complex()
+ return function(base, exponent, result)
+ if eq(exponent, 0) then return fill(result, 1, 0, 0, 0) end
+ if eq(base, 0) then return fill(result, 0, 0, 0, 0) end
+ cpx, axis = decompose(base, cpx, axis)
+ if cpx.b == 0 then
+ _, axis = decompose(exponent, _, axis)
+ -- since log can come up with complex results for real inputs,
+ -- I want to make sure that it does so in line with the other quaternion.
+ -- this is how we do that.
+ end
+ cpx = log(cpx, nil, cpx)
+ result = regenerate(result, cpx, axis)
+ result = mul(result, exponent, result)
+ result = exp(result, result)
+ return result
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ vornmath.utils.componentWiseExpander('pow', {'vector', 'scalar'}),
+ vornmath.utils.componentWiseExpander('pow', {'scalar', 'vector'}),
+ vornmath.utils.componentWiseExpander('pow', {'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('pow', 2),
+ vornmath.utils.twoMixedScalars('pow'),
+}
+
+vornmath.bakeries.eq = {
+ { -- eq(boolean, boolean)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'boolean', 'boolean'}),
+ create = function(types)
+ return function(x, y) return x == y end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ { -- eq(number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ return function(x, y) return x == y end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ { -- eq(number, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'complex'}),
+ create = function(types)
+ return function(x, y)
+ return x == y.a and 0 == y.b
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ { -- eq(complex, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'number'}),
+ create = function(types)
+ return function(x, y)
+ return x.a == y and x.b == 0
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ { -- eq(complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'complex'}),
+ create = function(types)
+ return function(x, y)
+ return x.a == y.a and x.b == y.b
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ { -- eq(number, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'quat'}),
+ create = function(types)
+ return function(x, y)
+ return x == y.a and y.b == 0 and y.c == 0 and y.d == 0
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ { -- eq(complex, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'quat'}),
+ create = function(types)
+ return function(x, y)
+ return x.a == y.a and x.b == y.b and y.c == 0 and y.d == 0
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ { -- eq(quat, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'quat'}),
+ create = function(types)
+ return function(x, y)
+ return x == y.a and y.b == 0 and x.c == y.c and x.d == y.d
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ { -- eq(quat, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'number'}),
+ create = function(types)
+ return function(x, y)
+ return x.a == y and x.b == 0 and x.c == 0 and x.d == 0
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ { -- eq(quat, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'complex'}),
+ create = function(types)
+ return function(x, y)
+ return x.a == y.a and x.b == y.b and x.c == 0 and x.d == 0
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+
+ { -- eq(vector, vector)
+ signature_check = function(types)
+ if #types < 2 then return false end
+ local first = vornmath.metatables[types[1]]
+ local second = vornmath.metatables[types[2]]
+ if first.vm_shape ~= 'vector' or second.vm_shape ~= 'vector' then return false end
+ if first.vm_dim ~= second.vm_dim then return false end
+ if vornmath.utils.hasBakery('eq', {first.vm_storage, second.vm_storage}) then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local first = vornmath.metatables[types[1]]
+ local second = vornmath.metatables[types[2]]
+ local equals = vornmath.utils.bake('eq', {first.vm_storage, second.vm_storage})
+ local length = first.vm_dim
+ return function(a, b)
+ for i = 1,length do
+ if not equals(a[i], b[i]) then return false end
+ end
+ return true
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ { -- eq(matrix, matrix)
+ signature_check = function(types)
+ if #types < 2 then return false end
+ local first = vornmath.metatables[types[1]]
+ local second = vornmath.metatables[types[2]]
+ if first.vm_shape ~= 'matrix' or second.vm_shape ~= 'matrix' then return false end
+ if first.vm_dim[1] ~= second.vm_dim[1] or first.vm_dim[2] ~= second.vm_dim[2] then return false end
+ if vornmath.utils.hasBakery('eq', {first.vm_storage, second.vm_storage}) then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local first = vornmath.metatables[types[1]]
+ local second = vornmath.metatables[types[2]]
+ local equals = vornmath.utils.bake('eq', {first.vm_storage, second.vm_storage})
+ local width = first.vm_dim[1]
+ local height = first.vm_dim[2]
+ return function(a, b)
+ for x = 1, width do
+ for y = 1, height do
+ if not equals(a[x][y], b[x][y]) then return false end
+ end
+ end
+ return true
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ }
+}
+
+vornmath.bakeries.tostring = {
+ { -- tostring(boolean)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'boolean'}),
+ create = function(types)
+ return tostring
+ end,
+ return_type = function(types) return 'string' end
+ },
+ { -- tostring(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types)
+ return tostring
+ end,
+ return_type = function(types) return 'string' end
+ },
+ { -- tostring(complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex'}),
+ create = function(types)
+ return function(z)
+ return tostring(z.a).. ' + ' .. tostring(z.b) .. 'i'
+ end
+ end,
+ return_type = function(types) return 'string' end
+ },
+ { -- tostring(quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat'}),
+ create = function(types)
+ return function(z)
+ return tostring(z.a).. ' + ' .. tostring(z.b) .. 'i + ' .. tostring(z.c) .. 'j + ' .. tostring(z.d) .. 'k'
+ end
+ end,
+ return_type = function(types) return 'string' end
+ },
+ { -- tostring(vector)
+ signature_check = function(types)
+ local mt = vornmath.metatables[types[1]]
+ return mt.vm_shape == 'vector'
+ end,
+ create = function(types)
+ return function(v)
+ local things = {}
+ for _,x in ipairs(v) do
+ table.insert(things, tostring(x))
+ end
+ return "<" .. table.concat(things, ', ') .. ">"
+ end
+ end,
+ return_type = function(types) return 'string' end
+ },
+ { -- tostring(matrix)
+ signature_check = function(types)
+ local mt = vornmath.metatables[types[1]]
+ return mt.vm_shape == 'matrix'
+ end,
+ create = function(types)
+ local meta = vornmath.metatables[types[1]]
+ local width = meta.vm_dim[1]
+ local height = meta.vm_dim[2]
+ return function(a)
+ local rows = {}
+ for y = 1, height do
+ local row = {}
+ for x = 1, width do
+ row[x] = tostring(a[x][y])
+ end
+ rows[y] = table.concat(row, ', ')
+ end
+ return table.concat(rows, '\n')
+ end
+ end
+ }
+}
+
+-- angle and trig
+
+vornmath.bakeries.rad = {
+ { -- rad(number)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types) return math.rad end,
+ return_type = function(types) return 'number' end
+ },
+ { -- rad(other scalar)
+ signature_check = function(types)
+ if types[1] ~= types[2] or
+ vornmath.metatables[types[1]].vm_shape ~= 'scalar' or
+ not vornmath.utils.hasBakery('mul', {types[1], 'number', types[2]}) then
+ return false
+ end
+ types[3] = nil
+ return true
+ end,
+ create = function(types)
+ local scale = math.pi / 180
+ local mul = vornmath.utils.bake('mul', {types[1], 'number', types[2]})
+ return function(phi, r)
+ return mul(phi, scale, r)
+ end
+ end
+ },
+ vornmath.utils.componentWiseExpander('rad', {'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('rad', 1)
+}
+
+vornmath.bakeries.deg = {
+ { -- deg(number)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types) return math.deg end,
+ return_type = function(types) return 'number' end
+ },
+ { -- deg(other scalar)
+ signature_check = function(types)
+ if types[1] ~= types[2] or
+ vornmath.metatables[types[1]].vm_shape ~= 'scalar' or
+ not vornmath.utils.hasBakery('mul', {types[1], 'number', types[2]}) then
+ return false
+ end
+ types[3] = nil
+ return true
+ end,
+ create = function(types)
+ local scale = 180 / math.pi
+ local mul = vornmath.utils.bake('mul', {types[1], 'number', types[2]})
+ return function(phi, r)
+ return mul(phi, scale, r)
+ end
+ end
+ },
+ vornmath.utils.componentWiseExpander('deg', {'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('deg', 1)
+}
+
+vornmath.bakeries.sin = {
+ { -- sin(number)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types) return math.sin end,
+ return_type = function(types) return 'number' end
+ },
+ { -- sin(complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex','complex'}),
+ create = function(types)
+ local sinh = vornmath.utils.bake('sinh', {'complex', 'complex'})
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ return function(z, r)
+ r = fill(r, -z.b, z.a)
+ r = sinh(r,r)
+ return fill(r, r.b, -r.a)
+ end
+ end
+ },
+ vornmath.utils.componentWiseExpander('sin', {'vector'}),
+ vornmath.utils.quatOperatorFromComplex('sin'),
+ vornmath.utils.componentWiseReturnOnlys('sin', 1)
+}
+
+vornmath.bakeries.cos = {
+ { -- cos(number)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types) return math.cos end,
+ return_type = function(types) return 'number' end
+ },
+ { -- cos(complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex','complex'}),
+ create = function(types)
+ local cosh = vornmath.utils.bake('cosh', {'complex', 'complex'})
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ return function(z, r)
+ r = fill(r, -z.b, z.a)
+ return cosh(r, r)
+ end
+ end
+ },
+ vornmath.utils.componentWiseExpander('cos', {'vector'}),
+ vornmath.utils.quatOperatorFromComplex('cos'),
+ vornmath.utils.componentWiseReturnOnlys('cos', 1)
+}
+
+vornmath.bakeries.cis = {
+ { -- cis(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number','complex'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ local sin, cos = math.sin, math.cos
+ return function(theta, z)
+ return fill(z, cos(theta), sin(theta))
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ vornmath.utils.componentWiseExpander('cis', {'vector'}, 'complex'),
+ vornmath.utils.componentWiseReturnOnlys('cis', 1, 'complex'),
+}
+
+vornmath.bakeries.tan = {
+ { -- tan(number)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types) return math.tan end,
+ return_type = function(types) return 'number' end
+ },
+ { -- tan(complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex','complex'}),
+ create = function(types)
+ local tanh = vornmath.utils.bake('tanh', {'complex', 'complex'})
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ return function(z, r)
+ r = fill(r, -z.b, z.a)
+ r = tanh(r,r)
+ return fill(r, r.b, -r.a)
+ end
+ end
+ },
+
+ vornmath.utils.componentWiseExpander('tan', {'vector'}),
+ vornmath.utils.quatOperatorFromComplex('tan'),
+ vornmath.utils.componentWiseReturnOnlys('tan', 1)
+}
+
+vornmath.bakeries.asin = {
+ { -- asin(number)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types)
+ return math.asin
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- asin(complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex','complex'}),
+ create = function(types)
+ local mul = vornmath.utils.bake('mul', {'complex', 'complex', 'complex'})
+ local asinh = vornmath.utils.bake('asinh', {'complex', 'complex'})
+ local i = vornmath.complex(0, 1)
+ local negi = -i
+ return function(z, r)
+ r = mul(z, i, r)
+ r = asinh(r, r)
+ return mul(r, negi, r)
+ end
+
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ vornmath.utils.componentWiseExpander('asin', {'vector'}),
+ vornmath.utils.quatOperatorFromComplex('asin'),
+ vornmath.utils.componentWiseReturnOnlys('asin', 1)
+}
+
+vornmath.bakeries.acos = {
+ { -- acos(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types)
+ return math.acos
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- acos(complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex','complex'}),
+ create = function(types)
+ -- TODO: large and non-finite results
+ local sqrt = vornmath.utils.bake('sqrt', {'complex', 'complex'})
+ local asinh = vornmath.utils.bake('asinh', {'number'})
+ local atan = vornmath.utils.bake('atan', {'number', 'number'})
+ local sub = vornmath.utils.bake('sub', {'number', 'complex', 'complex'})
+ local add = vornmath.utils.bake('add', {'number', 'complex', 'complex'})
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+
+ local x = vornmath.complex()
+ local y = vornmath.complex()
+ return function(z, result)
+ x = sub(1, z, x)
+ x = sqrt(x, x)
+ y = add(1, z, y)
+ y = sqrt(y, y)
+ return fill(result, 2 * atan(x.a, y.a), asinh(y.a * x.b - y.b * x.a))
+
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ vornmath.utils.componentWiseExpander('acos', {'vector'}),
+ vornmath.utils.quatOperatorFromComplex('acos'),
+ vornmath.utils.componentWiseReturnOnlys('acos', 1)
+}
+
+vornmath.bakeries.atan = {
+ { -- atan(number)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types)
+ return math.atan
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- atan(number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+---@diagnostic disable-next-line: deprecated
+ return math.atan2 or math.atan
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- atan(complex, nil, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex','nil','complex'}),
+ create = function(types)
+ local mul = vornmath.utils.bake('mul', {'complex', 'complex', 'complex'})
+ local atanh = vornmath.utils.bake('atanh', {'complex', 'complex'})
+ local i = vornmath.complex(0, 1)
+ local negi = -i
+ return function(z, _, r)
+ r = mul(z, i, r)
+ r = atanh(r, r)
+ return mul(r, negi, r)
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- atan(complex, complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex','complex','complex'}),
+ create = function(types)
+ local atan1 = vornmath.utils.bake('atan', {'complex', 'nil', 'complex'})
+ local pi = math.pi
+ local pi2 = pi / 2
+ local add = vornmath.utils.bake('add', {'complex', 'number', 'complex'})
+ local div = vornmath.utils.bake('div', {'complex', 'complex', 'complex'})
+ local eq = vornmath.utils.bake('eq', {'complex', 'number'})
+ local fill = vornmath.utils.bake('fill', {'complex', 'number'})
+ return function(n, d, r)
+ if eq(d, 0) then
+ if n.a > 0 or n.a == 0 and n.b > 0 then
+ return fill(r,pi2)
+ elseif n.a < 0 or n.a == 0 and n.b < 0 then
+ return fill(r,-pi2)
+ else -- n == 0
+ return 0
+ end
+ end
+ local correction
+ if d.a >= 0 then
+ correction = 0
+ elseif n.a >= 0 then -- d.a < 0 and...
+ correction = pi
+ else -- d.a < 0 and n.a < 0
+ correction = -pi
+ end
+ r = div(n, d, r)
+ r = atan1(r, nil, r)
+ return add(r, correction, r)
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- atan(quat, nil, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat','nil','quat'}),
+ create = function(types)
+ local axisDecompose = vornmath.utils.bake('axisDecompose', {'quat', 'complex', 'vec3'})
+ local atanComplex = vornmath.utils.bake('atan', {'complex', 'nil', 'complex'})
+ local fill = vornmath.utils.bake('fill', {'quat', 'complex', 'vec3'})
+ local z = vornmath.complex()
+ local v = vornmath.vec3()
+ return function(q, _, r)
+ z, v = axisDecompose(q, z, v)
+ z = atanComplex(z, nil, z)
+ return fill(r, z, v)
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ { -- atan(quat, quat, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat','quat','quat'}),
+ create = function(types)
+ local atan1 = vornmath.utils.bake('atan', {'quat', 'nil', 'quat'})
+ local pi = math.pi
+ local pi2 = pi / 2
+ local add = vornmath.utils.bake('add', {'quat', 'number', 'quat'})
+ local div = vornmath.utils.bake('div', {'quat', 'quat', 'quat'})
+ local eq = vornmath.utils.bake('eq', {'quat', 'number'})
+ local fill = vornmath.utils.bake('fill', {'quat', 'number'})
+ return function(n, d, r)
+ if eq(d, 0) then
+ if (n.a > 0 or
+ n.a == 0 and n.b > 0 or
+ n.a == 0 and n.b == 0 and n.c > 0 or
+ n.a == 0 and n.b == 0 and n.c == 0 and n.d > 0) then
+ return fill(r,pi2)
+ elseif n.a < 0 or n.b < 0 or n.c < 0 or n.d < 0 then
+ return fill(r,-pi2)
+ else -- n == 0
+ return 0
+ end
+ end
+ local correction
+ if d.a >= 0 then
+ correction = 0
+ elseif n.a >= 0 then -- d.a < 0 and...
+ correction = pi
+ else -- d.a < 0 and n.a < 0
+ correction = -pi
+ end
+ r = div(n, d, r)
+ r = atan1(r, nil, r)
+ return add(r, correction, r)
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ vornmath.utils.componentWiseExpander('atan', {'vector', 'vector'}),
+ vornmath.utils.componentWiseExpander('atan', {'vector', 'nil'}),
+ vornmath.utils.componentWiseReturnOnlys('atan', 2),
+ vornmath.utils.twoMixedScalars('atan'),
+}
+
+vornmath.bakeries.sinh = {
+ {
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types)
+---@diagnostic disable-next-line: deprecated
+ if math.sinh then return math.sinh end
+ local exp = math.exp
+ return function(x)
+ return (exp(x) - exp(-x)) / 2
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ {
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'complex', 'complex'}),
+ create = function(types)
+ local cos = math.cos
+ local sin = math.sin
+ local cosh = vornmath.utils.bake('cosh', {'number'})
+ local sinh = vornmath.utils.bake('sinh', {'number'})
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ return function(z, r)
+ return fill(r, cos(z.b) * sinh(z.a), sin(z.b) * cosh(z.a))
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ vornmath.utils.componentWiseExpander('sinh', {'vector'}),
+ vornmath.utils.quatOperatorFromComplex('sinh'),
+ vornmath.utils.componentWiseReturnOnlys('sinh', 1)
+
+}
+
+vornmath.bakeries.cosh = {
+ {
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types)
+---@diagnostic disable-next-line: deprecated
+ if math.cosh then return math.cosh end
+ local exp = math.exp
+ return function(x)
+ return (exp(x) + exp(-x)) / 2
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ {
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'complex', 'complex'}),
+ create = function(types)
+ local cos = math.cos
+ local sin = math.sin
+ local cosh = vornmath.utils.bake('cosh', {'number'})
+ local sinh = vornmath.utils.bake('sinh', {'number'})
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ return function(z, r)
+ return fill(r, cos(z.b) * cosh(z.a), sin(z.b) * sinh(z.a))
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ vornmath.utils.componentWiseExpander('cosh', {'vector'}),
+ vornmath.utils.quatOperatorFromComplex('cosh'),
+ vornmath.utils.componentWiseReturnOnlys('cosh', 1)
+
+}
+
+vornmath.bakeries.tanh = {
+ {
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types)
+---@diagnostic disable-next-line: deprecated
+ if math.tanh then return math.tanh end
+ local exp = math.exp
+ return function(x)
+ local y = exp(2 * x)
+ return (y - 1) / (y + 1)
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ {
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'complex', 'complex'}),
+ create = function(types)
+ local tan = math.tan
+ local cosh = vornmath.utils.bake('cosh', {'number'})
+ local tanh = vornmath.utils.bake('tanh', {'number'})
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ return function(z, r)
+ local tx = tanh(z.a)
+ local ty = tan(z.b)
+ local cx = 1 / cosh(z.a)
+ local txty = tx * ty
+ local denom = 1 + txty * txty
+ return fill(r, tx * (1 + ty * ty) / denom, ((ty / denom) * cx) * cx)
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ vornmath.utils.componentWiseExpander('sinh', {'vector'}),
+ vornmath.utils.quatOperatorFromComplex('sinh'),
+ vornmath.utils.componentWiseReturnOnlys('sinh', 1)
+
+}
+
+vornmath.bakeries.asinh = {
+ { -- asinh(number)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types)
+ return function(x)
+ return math.log(x + math.sqrt(x*x + 1))
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- asinh(complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex','complex'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ local sqrt = vornmath.utils.bake('sqrt', {'complex', 'complex'})
+ local asinh = vornmath.utils.bake('asinh', {'number'})
+ local atan = vornmath.utils.bake('atan', {'number', 'number'})
+ local x, y = vornmath.complex(), vornmath.complex()
+ return function(z, r)
+ x = fill(x, 1 + z.b, -z.a)
+ y = fill(y, 1 - z.b, z.a)
+ x = sqrt(x, x)
+ y = sqrt(y, y)
+ return fill(r, asinh(x.a * y.b - y.a * x.b), atan(z.b, x.a * y.a - x.b * y.b))
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ vornmath.utils.componentWiseExpander('asinh', {'vector'}),
+ vornmath.utils.quatOperatorFromComplex('asinh'),
+ vornmath.utils.componentWiseReturnOnlys('asinh', 1)
+}
+
+vornmath.bakeries.acosh = {
+ { -- acosh(number)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types)
+ return function(x)
+ return math.log(x + math.sqrt(x*x - 1))
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- acosh(complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex','complex'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ local sqrt = vornmath.utils.bake('sqrt', {'complex', 'complex'})
+ local asinh = vornmath.utils.bake('asinh', {'number'})
+ local atan = vornmath.utils.bake('atan', {'number', 'number'})
+ local x, y = vornmath.complex(), vornmath.complex()
+ return function(z, r)
+ x = fill(x, z.a - 1, z.b)
+ y = fill(y, z.a + 1, z.b)
+ x = sqrt(x, x)
+ y = sqrt(y, y)
+ return fill(r, asinh(x.a * y.a + x.b * y.b), 2 * atan(x.b, y.a))
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ vornmath.utils.componentWiseExpander('acosh', {'vector'}),
+ vornmath.utils.quatOperatorFromComplex('acosh'),
+ vornmath.utils.componentWiseReturnOnlys('acosh', 1)
+}
+
+vornmath.bakeries.atanh = {
+ { -- atanh(number)
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types)
+ return function(x)
+ return math.log((1 + x) / (1 - x)) / 2
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- atanh(complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex','complex'}),
+ create = function(types)
+ local huge = math.huge
+ local unm = vornmath.utils.bake('unm', {'complex', 'complex'})
+ local mul = vornmath.utils.bake('mul', {'complex', 'number', 'complex'})
+ local cfill = vornmath.utils.bake('fill', {'complex', 'complex'})
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ local log = vornmath.utils.bake('log', {'number'})
+ local atan = vornmath.utils.bake('atan', {'number', 'number'})
+ local eq = vornmath.utils.bake('eq', {'complex', 'number'})
+ return function(z, r)
+ -- TODO: infinities, large numbers, small imaginaries
+ local sign
+ if z.a < 0 then
+ r = unm(z, r)
+ sign = -1
+ else
+ r = cfill(r, z)
+ sign = 1
+ end
+ if eq(r,1) then
+ r = fill(r, huge, 0)
+ return mul(r,sign,r)
+ end
+ return fill(r,
+ sign * log(1 + 4 * r.a/((1 - r.a)*(1-r.a) + r.b * r.b))/4,
+ -sign * atan(-2 * r.b, (1 - r.a)*(1 + r.a) - r.b * r.b)/2)
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ vornmath.utils.componentWiseExpander('atanh', {'vector'}),
+ vornmath.utils.quatOperatorFromComplex('atanh'),
+ vornmath.utils.componentWiseReturnOnlys('atanh', 1)
+}
+
+vornmath.bakeries.polarComplex = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number','number','complex'}),
+ create = function(types)
+ local cis = vornmath.utils.bake('cis', {'number', 'complex'})
+ local mul = vornmath.utils.bake('mul', {'number', 'complex', 'complex'})
+ return function(r, theta, z)
+ z = cis(theta, z)
+ return mul(r, z, z)
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ vornmath.utils.componentWiseExpander('polarComplex', {'vector', 'vector'}, 'complex'),
+ vornmath.utils.componentWiseReturnOnlys('polarComplex', 2, 'complex'),
+}
+
+vornmath.bakeries.polarVec2 = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number','number','vec2'}),
+ create = function(types)
+ local sin, cos = math.sin, math.cos
+ local fill = vornmath.utils.bake('fill', {'vec2', 'number', 'number'})
+ return function(r, theta, v)
+ return fill(v, r * cos(theta), r * sin(theta))
+ end
+ end,
+ return_type = function(types) return 'vec2' end
+ },
+ { -- return only
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ local construct = vornmath.utils.bake('vec2', {})
+ local f = vornmath.utils.bake('polarVec2', {'number', 'number', 'vec2'})
+ return function(r, theta)
+ local v = construct()
+ return f(r, theta, v)
+ end
+ end,
+ return_type = function(types) return 'vec2' end
+ }
+}
+
+vornmath.bakeries.cylindricalVec3 = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number', 'number', 'vec3'}),
+ create = function(types)
+ local sin, cos = math.sin, math.cos
+ local fill = vornmath.utils.bake('fill', {'vec3', 'number', 'number', 'number'})
+ return function(r, theta, z, v)
+ return fill(v, r * cos(theta), r * sin(theta), z)
+ end
+ end,
+ return_type = function(types) return 'vec3' end
+ },
+ { -- return only
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number', 'number', 'number'}),
+ create = function(types)
+ local construct = vornmath.utils.bake('vec3', {})
+ local f = vornmath.utils.bake('cylindricalVec3', {'number', 'number', 'number', 'vec3'})
+ return function(r, theta, z)
+ local v = construct()
+ return f(r, theta, z, v)
+ end
+ end,
+ return_type = function(types) return 'vec3' end
+ }
+}
+
+vornmath.bakeries.sphericalVec3 = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number', 'number', 'vec3'}),
+ create = function(types)
+ local sin, cos = math.sin, math.cos
+ local fill = vornmath.utils.bake('fill', {'vec3', 'number', 'number', 'number'})
+ return function(r, theta, phi, v)
+ local c = cos(phi)
+ return fill(v, r * cos(theta) * c, r * sin(theta) * c, r * sin(phi))
+ end
+ end,
+ return_type = function(types) return 'vec3' end
+ },
+ { -- return only
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number', 'number', 'number'}),
+ create = function(types)
+ local construct = vornmath.utils.bake('vec3', {})
+ local f = vornmath.utils.bake('sphericalVec3', {'number', 'number', 'number', 'vec3'})
+ return function(r, theta, phi)
+ local v = construct()
+ return f(r, theta, phi, v)
+ end
+ end,
+ return_type = function(types) return 'vec3' end
+ }
+}
+
+-- exponential and logarithmic functions
+
+vornmath.bakeries.exp = {
+ { -- exp(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types)
+ return math.exp
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- exp(complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex','complex'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ local sin = math.sin
+ local cos = math.cos
+ local exp = math.exp
+ return function(z, result)
+ local magnitude = exp(z.a)
+ return fill(result, magnitude * cos(z.b), magnitude * sin(z.b))
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ vornmath.utils.componentWiseReturnOnlys('exp', 1),
+ vornmath.utils.componentWiseExpander('exp', {'vector'}),
+ vornmath.utils.quatOperatorFromComplex('exp')
+}
+
+vornmath.bakeries.exp2 = {
+ {
+ signature_check = function(types)
+ if vornmath.utils.hasBakery('pow', {'number', types[1], types[2]}) then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local pow = vornmath.utils.bake('pow', {'number', types[1], types[2]})
+ return function(x,r)
+ return pow(2, x, r)
+ end
+ end,
+ return_type = function(types) return types[1] end
+ }
+}
+
+vornmath.bakeries.log = {
+ { -- log(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'nil'}),
+ create = function(types)
+ return function(x) return math.log(x) end -- have to do it this way; luajit hates math.log(x, nil)
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- log(number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ if math.log(2,2) ~= math.log(2) then -- does 2 parameter log exist in this version?
+ return math.log
+ else -- no? gotta make it myself
+ local log = math.log
+ return function(x,b)
+ return log(x)/log(b)
+ end
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- log(complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'nil', 'complex'}),
+ create = function(types)
+ local arg = vornmath.utils.bake('arg', {'complex'})
+ local abs = vornmath.utils.bake('abs', {'complex'})
+ local log = math.log
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ return function(z, _, result)
+ return fill(result, log(abs(z)), arg(z))
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- log(complex, complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'complex', 'complex'}),
+ create = function(types)
+ local log = vornmath.utils.bake('log', {'complex', 'nil', 'complex'})
+ local complex = vornmath.utils.bake('complex', {'nil'})
+ local div = vornmath.utils.bake('div', {'complex', 'complex', 'complex'})
+ local loga = complex()
+ local logb = complex()
+ return function(a, b, result)
+ loga, logb = log(a, nil, loga), log(b, nil, logb)
+ result = div(loga, logb, result)
+ return result
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- log(quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'nil', 'quat'}),
+ create = function(types)
+ local log = vornmath.utils.bake('log', {'complex', 'nil', 'complex'})
+ local axisDecompose = vornmath.utils.bake('axisDecompose', {'quat', 'complex', 'vec3'})
+ local fill = vornmath.utils.bake('fill', {'quat', 'complex', 'vec3'})
+ local z = vornmath.complex()
+ local v = vornmath.vec3()
+ return function(q, _, r)
+ z, v = axisDecompose(q, z, v)
+ z = log(z, nil, z)
+ return fill(r, z, v)
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ { -- log(quat, quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'quat', 'quat'}),
+ create = function(types)
+ local log = vornmath.utils.bake('log', {'quat', 'nil', 'quat'})
+ local construct = vornmath.utils.bake('quat', {'nil'})
+ local div = vornmath.utils.bake('div', {'quat', 'quat', 'quat'})
+ local loga = construct()
+ local logb = construct()
+ return function(a, b, result)
+ loga, logb = log(a, nil, loga), log(b, nil, logb)
+ result = div(loga, logb, result)
+ return result
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ vornmath.utils.componentWiseExpander('log', {'vector', 'scalar'}),
+ vornmath.utils.componentWiseExpander('log', {'vector', 'vector'}),
+ vornmath.utils.componentWiseExpander('log', {'vector', 'nil'}),
+ vornmath.utils.componentWiseReturnOnlys('log', 2),
+ vornmath.utils.twoMixedScalars('log'),
+}
+
+vornmath.bakeries.log10 = {
+ {
+ signature_check = function(types)
+ if vornmath.utils.hasBakery('log', {types[1], 'number', types[2]}) then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local log = vornmath.utils.bake('log', {types[1], 'number', types[2]})
+ return function(x,r)
+ return log(x, 10, r)
+ end
+ end,
+ return_type = function(types) return types[1] end
+ }
+}
+
+vornmath.bakeries.log2 = {
+ {
+ signature_check = function(types)
+ if vornmath.utils.hasBakery('log', {types[1], 'number', types[2]}) then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local log = vornmath.utils.bake('log', {types[1], 'number', types[2]})
+ return function(x,r)
+ return log(x, 2, r)
+ end
+ end,
+ return_type = function(types) return types[1] end
+ }
+}
+
+vornmath.bakeries.sqrt = {
+ { -- sqrt(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types)
+ return math.sqrt
+ end
+ },
+ { -- sqrt(complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'complex'}),
+ create = function(types)
+ local eq = vornmath.utils.bake('eq', {'complex', 'number'})
+ local fill = vornmath.utils.bake('fill', {'complex', 'number','number'})
+ local abs = vornmath.utils.bake('abs', {'number'})
+ local sqrt = vornmath.utils.bake('sqrt', {'number'})
+ local hypot = vornmath.utils.bake('hypot', {'number', 'number'})
+ local copysign = vornmath.utils.bake('copysign', {'number', 'number'})
+
+ return function(z, r)
+
+ -- TODO infinities
+
+ local s, d, ax, ay
+
+ if eq(z, 0) then return fill(r, 0, 0) end
+
+ ax = abs(z.a) / 8
+ ay = abs(z.b)
+
+ -- TODO tiny z values
+
+ s = 2 * sqrt(ax + hypot(ax, ay/8))
+ d = ay / (2 * s);
+
+ if z.a >= 0 then
+ return fill(r, s, copysign(d, z.b))
+ else
+ return fill(r, d, copysign(s, z.b))
+ end
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ vornmath.utils.componentWiseReturnOnlys('sqrt', 1),
+ vornmath.utils.componentWiseExpander('sqrt', {'vector'}),
+ vornmath.utils.quatOperatorFromComplex('sqrt')
+
+}
+
+vornmath.bakeries.inversesqrt = {
+ {
+ signature_check = function(types)
+ if vornmath.utils.hasBakery('sqrt', types) then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local sqrt = vornmath.utils.bake('sqrt', types)
+ local div = vornmath.utils.bake('div', {'number', types[1], types[2]})
+ return function(x,r)
+ return div(1, sqrt(x, r), r)
+ end
+ end,
+ return_type = function(types) return types[1] end
+ }
+}
+
+vornmath.bakeries.hypot = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ return function(a,b)
+ -- TODO: big numbers
+ return math.sqrt(a*a + b*b)
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ {
+ signature_check = function(types)
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ if left.vm_shape ~= 'scalar' or right.vm_shape ~= 'scalar' then return false end
+ if not vornmath.utils.hasBakery('sqabs', {types[1]}) or not vornmath.utils.hasBakery('sqabs', {types[2]}) then return false end
+ if types[3] and types[3] ~= 'number' and types[3] ~= 'nil' then return false end
+ types[3] = nil
+ return true
+ end,
+ create = function(types)
+ local lsqabs, rsqabs = vornmath.utils.bake('sqabs', {types[1]}), vornmath.utils.bake('sqabs', {types[2]})
+ return function(a,b)
+ return math.sqrt(lsqabs(a) + rsqabs(b))
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('hypot', {'vector', 'vector'}, 'number'),
+ vornmath.utils.componentWiseReturnOnlys('hypot', 2, 'number'),
+}
+
+-- complex and quaternion functions
+
+vornmath.bakeries.arg = {
+ { -- arg(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types)
+ return function(x) return x >= 0 and 0 or math.pi end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- arg(complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex'}),
+ create = function(types)
+ local atan = vornmath.utils.bake('atan', {'number', 'number'})
+ return function(z)
+ if z.b == 0 and z.a == 0 then return 0 end
+ return atan(z.b, z.a)
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- arg(quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat'}),
+ create = function(types)
+ local carg = vornmath.utils.bake('arg', {'complex'})
+ local decompose = vornmath.utils.bake('axisDecompose', {'quat', 'complex', 'vec3'})
+ local z = vornmath.complex()
+ local _ = vornmath.vec3()
+ return function(q)
+ z, _ = decompose(q,z,_)
+ return carg(z)
+ end
+ end
+ },
+ vornmath.utils.componentWiseExpander('arg', {'vector'}, true)
+}
+
+vornmath.bakeries.axisDecompose = {
+ { -- axisDecompose(quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'complex', 'vec3'}),
+ create = function(types)
+ local fill_complex = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ local fill_vec3 = vornmath.utils.bake('fill', {'vec3', 'number', 'number', 'number'})
+ local length = vornmath.utils.bake('length', {'vec3'})
+ local div = vornmath.utils.bake('div', {'vec3', 'number', 'vec3'})
+ return function(z, cpx, axis)
+ axis = fill_vec3(axis, z.b, z.c, z.d)
+ -- do this instead of normalizing: I need both length and normal
+ local l = length(axis)
+ if l == 0 then
+ axis = fill_vec3(axis, 1, 0, 0) -- make something up.
+ -- some operators - log is the big one - can create complex results
+ -- from "real" inputs. I want to make sure that the quat version
+ -- succeeds too, so I'm going to pretend that there's a "correct"
+ -- direction.
+ else
+ axis = div(axis, l, axis)
+ end
+ cpx = fill_complex(cpx, z.a, l)
+ return cpx, axis
+ end
+ end,
+ return_type = function(types) return 'complex', 'vec3' end
+ },
+ { -- return-only
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'quat'}),
+ create = function(types)
+ local make_complex = vornmath.utils.bake('complex', {})
+ local make_vec3 = vornmath.utils.bake('vec3', {})
+ local axisDecompose = vornmath.utils.bake('axisDecompose', {'quat', 'complex', 'vec3'})
+ return function(z)
+ local cpx = make_complex()
+ local axis = make_vec3()
+ return axisDecompose(z, cpx, axis)
+ end
+ end
+ }
+}
+
+vornmath.bakeries.conj = {
+ { -- conj(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types)
+ return function(x) return x end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- conj(complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'complex'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'complex', 'number', 'number'})
+ return function(x, result)
+ return fill(result, x.a, -x.b)
+ end
+ end,
+ return_type = function(types) return 'complex' end
+ },
+ { -- conj(quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat', 'quat'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'quat', 'number', 'number', 'number', 'number'})
+ return function(x, result)
+ return fill(result, x.a, -x.b, -x.c, -x.d)
+ end
+ end,
+ return_type = function(types) return 'quat' end
+ },
+ vornmath.utils.componentWiseExpander('conj', {'vector'}),
+ vornmath.utils.componentWiseExpander('conj', {'matrix'}),
+ vornmath.utils.componentWiseReturnOnlys('conj', 1),
+}
+
+-- common functions
+
+vornmath.bakeries.abs = {
+ { -- abs(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types)
+ return math.abs
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- abs(complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex'}),
+ create = function(types)
+ local sqrt = math.sqrt
+ return function(x, result)
+ return sqrt(x.a * x.a + x.b * x.b)
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- abs(quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat'}),
+ create = function(types)
+ local sqrt = math.sqrt
+ return function(x, result)
+ return sqrt(x.a * x.a + x.b * x.b + x.c * x.c + x.d * x.d)
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('abs', {'vector'}, 'number'),
+ vornmath.utils.componentWiseReturnOnlys('abs', 1, 'number')
+}
+
+vornmath.bakeries.sqabs = {
+ { -- sqabs(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types)
+ return function(x, result)
+ return x * x
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- sqabs(complex)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex'}),
+ create = function(types)
+ local sqrt = math.sqrt
+ return function(x, result)
+ return x.a * x.a + x.b * x.b
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- sqabs(quat)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat'}),
+ create = function(types)
+ local sqrt = math.sqrt
+ return function(x, result)
+ return x.a * x.a + x.b * x.b + x.c * x.c + x.d * x.d
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('sqabs', {'vector'}, 'number'),
+ vornmath.utils.componentWiseReturnOnlys('sqabs', 1, 'number')
+}
+
+vornmath.bakeries.sign = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types)
+ local abs = math.abs
+ return function(x)
+ if x ~= 0 then return x / abs(x) end
+ return 0
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex', 'complex'}),
+ create = function(types)
+ local eq = vornmath.utils.bake('eq', {'complex', 'complex'})
+ local abs = vornmath.utils.bake('abs', {'complex', 'number'})
+ local div = vornmath.utils.bake('div', {'complex', 'number', 'complex'})
+ local fill = vornmath.utils.bake('fill', {'complex'})
+ local zero = vornmath.complex()
+ return function(x, r)
+ if eq(x, zero) then
+ return fill(r)
+ end
+ local magnitude = abs(x)
+ return div(x, magnitude, r)
+ end
+ end,
+
+ },
+ vornmath.utils.componentWiseExpander('sign', {'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('sign', 1),
+ vornmath.utils.quatOperatorFromComplex('sign')
+}
+
+vornmath.bakeries.copysign = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ return function(magnitude,sign)
+ local result = math.abs(magnitude)
+ if sign >= 0 then
+ return result
+ else
+ return -result
+ end
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('copysign', {'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('copysign', 2)
+}
+
+vornmath.bakeries.floor = {
+ { -- floor(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types) return math.floor end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('floor', {'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('floor', 1),
+}
+
+vornmath.bakeries.ceil = {
+ { -- ceil(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types) return math.ceil end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('ceil', {'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('ceil', 1),
+}
+
+vornmath.bakeries.trunc = {
+ { -- trunc(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types)
+ return function(x)
+ if x < 0 then
+ return math.ceil(x)
+ else
+ return math.floor(x)
+ end
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('trunc', {'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('trunc', 1),
+}
+
+vornmath.bakeries.round = {
+ { -- round(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types)
+ local floor = math.floor
+ return function(x)
+ return floor(x+0.5)
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('round', {'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('round', 1),
+}
+
+vornmath.bakeries.roundEven = {
+ { -- roundEven(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types)
+ local floor = math.floor
+ return function(x)
+ x = x + 0.5
+ local f = floor(x)
+ if f == x and f % 2 == 1 then
+ return f - 1
+ end
+ return f
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('roundEven', {'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('roundEven', 1),
+}
+
+vornmath.bakeries.fract = {
+ {
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types)
+ return function(x)
+ local _,y = math.modf(x)
+ return y
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('fract', {'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('fract', 1),
+}
+
+vornmath.bakeries.modf = {
+ {
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types)
+ return math.modf
+ end,
+ return_type = function(types) return 'number', 'number' end
+ },
+ { -- vector
+ signature_check = function(types)
+ local meta = vornmath.metatables[types[1]]
+ if meta.vm_shape ~= 'vector' or meta.vm_storage ~= 'number' then return false end
+ if types[2] ~= types[1] or types[3] ~= types[1] then return false end
+ types[4] = nil
+ return true
+ end,
+ create = function(types)
+ local d = vornmath.metatables[types[1]].vm_dim
+ local modf = vornmath.utils.bake('modf', {'number'})
+ return function(x, whole, frac)
+ for i = 1,d do
+ whole[i], frac[i] = modf(x[i])
+ end
+ return whole, frac
+ end
+ end,
+ return_type = function(types) return types[1], types[1] end
+ },
+ { -- return-only
+ signature_check = function(types)
+ if types[2] and types[2] ~= 'nil' or types[3] and types[3] ~= 'nil' then return false end
+ if vornmath.utils.hasBakery('modf', {types[1], types[1], types[1]}) then
+ types[2] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local modf = vornmath.utils.bake('modf', {types[1], types[1], types[1]})
+ local construct = vornmath.utils.bake(types[1], {})
+ return function(x)
+ local whole = construct()
+ local frac = construct()
+ return modf(x, whole, frac)
+ end
+ end,
+ return_type = function(types) return types[1], types[1] end
+ }
+}
+
+vornmath.bakeries.fmod = {
+ { -- fmod(number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types) return math.fmod end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('fmod', {'vector', 'scalar'}),
+ vornmath.utils.componentWiseExpander('fmod', {'scalar', 'vector'}),
+ vornmath.utils.componentWiseExpander('fmod', {'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('fmod', 2),
+}
+
+vornmath.bakeries.min = {
+ { -- min(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ return function(a,b)
+ return math.min(a,b) -- gotta do it this way because math.min is variadic
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('min', {'scalar', 'vector'}),
+ vornmath.utils.componentWiseExpander('min', {'vector', 'scalar'}),
+ vornmath.utils.componentWiseExpander('min', {'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('min', 2)
+}
+
+vornmath.bakeries.max = {
+ { -- max(number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ return function(a,b)
+ return math.max(a,b)
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('max', {'scalar', 'vector'}),
+ vornmath.utils.componentWiseExpander('max', {'vector', 'scalar'}),
+ vornmath.utils.componentWiseExpander('max', {'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('max', 2)
+}
+
+vornmath.bakeries.clamp = {
+ { -- clamp(number, number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number', 'number'}),
+ create = function(types)
+ local min = math.min
+ local max = math.max
+ return function(x, lo, hi)
+ return min(max(x, lo), hi)
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('clamp', {'vector', 'vector', 'vector'}),
+ vornmath.utils.componentWiseExpander('clamp', {'vector', 'number', 'scalar'}),
+ vornmath.utils.componentWiseReturnOnlys('clamp', 3),
+}
+
+vornmath.bakeries.mix = {
+ { -- mix(scalar, scalar, scalar)
+ signature_check = function(types)
+ if #types < 4 then return false end
+ for i = 1,4 do
+ if vornmath.metatables[types[i]].vm_shape ~= 'scalar' then return false end
+ end
+ if not (vornmath.utils.hasBakery('mul', {types[1], types[3]}) and
+ vornmath.utils.hasBakery('mul', {types[2], types[3]})) then
+ return false
+ end
+ local at_type = vornmath.utils.returnType('mul', {types[1], types[3]})
+ local bt_type = vornmath.utils.returnType('mul', {types[2], types[3]})
+ if not vornmath.utils.hasBakery('add', {at_type, bt_type, types[4]}) then
+ return false
+ end
+ types[5] = nil
+ return true
+ end,
+ create = function(types)
+ local at_type = vornmath.utils.returnType('mul', {types[1], types[3]})
+ local bt_type = vornmath.utils.returnType('mul', {types[2], types[3]})
+ local leftmul = vornmath.utils.bake('mul', {types[1], types[3], at_type})
+ local rightmul = vornmath.utils.bake('mul', {types[2], types[3], bt_type})
+ local add = vornmath.utils.bake('add', {at_type, bt_type, types[4]})
+ local sub = vornmath.utils.bake('sub', {'number', types[3], types[3]})
+ local at = vornmath[at_type]()
+ local bt = vornmath[bt_type]()
+ local s = vornmath[types[3]]()
+ return function(a,b,t,r)
+ s = sub(1, t, s)
+ at = leftmul(a,s,at)
+ bt = rightmul(b,t,bt)
+ return add(at, bt, r)
+ end
+ end,
+ return_type = function(types) return types[4] end
+ },
+ vornmath.utils.componentWiseExpander('mix', {'vector', 'vector', 'vector'}),
+ vornmath.utils.componentWiseExpander('mix', {'vector', 'vector', 'scalar'}),
+ vornmath.utils.componentWiseReturnOnlys('mix', 3),
+ { -- boolean vector version
+ signature_check = function(types)
+ if #types < 4 then return false end
+ if types[4] ~= vornmath.utils.componentWiseConsensusType({types[1], types[2]}) then return false end
+ local mixes = vornmath.metatables[types[3]]
+ local out = vornmath.metatables[types[4]]
+ if mixes.vm_shape ~= 'vector' or mixes.vm_storage ~= 'boolean' or mixes.vm_dim ~= out.vm_dim then return false end
+ types[5] = nil
+ return true
+ end,
+ create = function(types)
+ local out_type = vornmath.metatables[types[4]]
+ local left_storage = vornmath.metatables[types[1]].vm_storage
+ local right_storage = vornmath.metatables[types[2]].vm_storage
+ local size = out_type.vm_dim
+ local out_storage = out_type.vm_storage
+ local left_fill = vornmath.utils.bake('fill', {out_storage, left_storage})
+ local right_fill = vornmath.utils.bake('fill', {out_storage, right_storage})
+ return function(left, right, mixer, out)
+ for i = 1,size do
+ if mixer[i] then
+ out[i] = right_fill(out[i], right[i])
+ else
+ out[i] = left_fill(out[i], left[i])
+ end
+ end
+ return out
+ end
+ end
+ },
+ {
+ signature_check = function(types)
+ if #types < 3 then return false end
+ if types[4] and types[4] ~= 'nil' then return false end
+ if vornmath.metatables[types[3]].vm_storage ~= 'boolean' then return false end
+ local out_type = vornmath.utils.componentWiseConsensusType({types[1], types[2]})
+ if not out_type then return false end
+ if vornmath.utils.hasBakery('mix', {types[1], types[2], types[3], out_type}) then
+ types[4] = 'nil'
+ types[5] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local out_type = vornmath.utils.componentWiseConsensusType({types[1], types[2]})
+ local construct = vornmath.utils.bake(out_type, {})
+ local f = vornmath.utils.bake('mix', {types[1], types[2], types[3], out_type})
+ return function(a,b,t)
+ local r = construct()
+ return f(a,b,t,r)
+ end
+ end,
+ return_type = function(types)
+ return vornmath.utils.componentWiseConsensusType({types[1], types[2]})
+ end
+ }
+}
+
+vornmath.bakeries.unmix = {
+ { -- unmix(scalar, scalar, scalar)
+ signature_check = function(types)
+ if #types < 4 then return false end
+ for i = 1,4 do
+ if vornmath.metatables[types[i]].vm_shape ~= 'scalar' then return false end
+ end
+ if not (vornmath.utils.hasBakery('sub', {types[3], types[1]}) and
+ vornmath.utils.hasBakery('sub', {types[2], types[1]})) then
+ return false
+ end
+ local at_type = vornmath.utils.returnType('sub', {types[3], types[1]})
+ local bt_type = vornmath.utils.returnType('sub', {types[2], types[1]})
+ if not vornmath.utils.hasBakery('div', {at_type, bt_type, types[4]}) then
+ return false
+ end
+ types[5] = nil
+ return true
+ end,
+ create = function(types)
+ local at_type = vornmath.utils.returnType('sub', {types[3], types[1]})
+ local bt_type = vornmath.utils.returnType('sub', {types[2], types[1]})
+ local topsub = vornmath.utils.bake('sub', {types[3], types[1], at_type})
+ local bottomsub = vornmath.utils.bake('sub', {types[2], types[1], bt_type})
+ local div = vornmath.utils.bake('div', {at_type, bt_type, types[4]})
+ local at = vornmath[at_type]()
+ local bt = vornmath[bt_type]()
+ return function(a,b,x,r)
+ at = topsub(x,a,at)
+ bt = bottomsub(b,a,bt)
+ return div(at, bt, r)
+ end
+ end,
+ return_type = function(types) return types[4] end
+ },
+ vornmath.utils.componentWiseExpander('unmix', {'vector', 'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('unmix', 3),
+}
+
+vornmath.bakeries.geometricMix = {
+ { -- mix(scalar, scalar, scalar)
+ signature_check = function(types)
+ if #types < 4 then return false end
+ for i = 1,4 do
+ if vornmath.metatables[types[i]].vm_shape ~= 'scalar' then return false end
+ end
+ if not (vornmath.utils.hasBakery('pow', {types[1], types[3]}) and
+ vornmath.utils.hasBakery('pow', {types[2], types[3]})) then
+ return false
+ end
+ local at_type = vornmath.utils.returnType('pow', {types[1], types[3]})
+ local bt_type = vornmath.utils.returnType('pow', {types[2], types[3]})
+ if not vornmath.utils.hasBakery('mul', {at_type, bt_type, types[4]}) then
+ return false
+ end
+ types[5] = nil
+ return true
+ end,
+ create = function(types)
+ local at_type = vornmath.utils.returnType('pow', {types[1], types[3]})
+ local bt_type = vornmath.utils.returnType('pow', {types[2], types[3]})
+ local leftpow = vornmath.utils.bake('pow', {types[1], types[3], at_type})
+ local rightpow = vornmath.utils.bake('pow', {types[2], types[3], bt_type})
+ local mul = vornmath.utils.bake('mul', {at_type, bt_type, types[4]})
+ local sub = vornmath.utils.bake('sub', {'number', types[3], types[3]})
+ local at = vornmath[at_type]()
+ local bt = vornmath[bt_type]()
+ local s = vornmath[types[3]]()
+ return function(a,b,t,r)
+ s = sub(1, t, s)
+ at = leftpow(a,s,at)
+ bt = rightpow(b,t,bt)
+ return mul(at, bt, r)
+ end
+ end,
+ return_type = function(types) return types[4] end
+ },
+ vornmath.utils.componentWiseExpander('geometricMix', {'vector', 'vector', 'vector'}),
+ vornmath.utils.componentWiseExpander('geometricMix', {'vector', 'vector', 'scalar'}),
+ vornmath.utils.componentWiseReturnOnlys('geometricMix', 3)
+}
+
+vornmath.bakeries.geometricUnmix = {
+ { -- unmix(scalar, scalar, scalar)
+ signature_check = function(types)
+ if #types < 4 then return false end
+ for i = 1,4 do
+ if vornmath.metatables[types[i]].vm_shape ~= 'scalar' then return false end
+ end
+ if not (vornmath.utils.hasBakery('log', {types[1]}) and
+ vornmath.utils.hasBakery('log', {types[2]}) and
+ vornmath.utils.hasBakery('log', {types[3]}) and
+ vornmath.utils.hasBakery('sub', {types[3], types[1]}) and
+ vornmath.utils.hasBakery('sub', {types[2], types[1]})) then
+ return false
+ end
+ local at_type = vornmath.utils.returnType('sub', {types[3], types[1]})
+ local bt_type = vornmath.utils.returnType('sub', {types[2], types[1]})
+ if not vornmath.utils.hasBakery('div', {at_type, bt_type, types[4]}) then
+ return false
+ end
+ types[5] = nil
+ return true
+ end,
+ create = function(types)
+ local at_type = vornmath.utils.returnType('sub', {types[3], types[1]})
+ local bt_type = vornmath.utils.returnType('sub', {types[2], types[1]})
+ local loga = vornmath.utils.bake('log', {types[1], 'nil', types[1]})
+ local logb = vornmath.utils.bake('log', {types[2], 'nil', types[2]})
+ local logc = vornmath.utils.bake('log', {types[3], 'nil', types[3]})
+ local topsub = vornmath.utils.bake('sub', {types[3], types[1], at_type})
+ local bottomsub = vornmath.utils.bake('sub', {types[2], types[1], bt_type})
+ local div = vornmath.utils.bake('div', {at_type, bt_type, types[4]})
+ local la = vornmath[types[1]]()
+ local lb = vornmath[types[2]]()
+ local lx = vornmath[types[3]]()
+ local at = vornmath[at_type]()
+ local bt = vornmath[bt_type]()
+ return function(a,b,x,r)
+ la = loga(a, nil, la)
+ lb = logb(b, nil, lb)
+ lx = logc(x, nil, lx)
+ at = topsub(lx,la,at)
+ bt = bottomsub(lb,la,bt)
+ return div(at, bt, r)
+ end
+ end,
+ return_type = function(types) return types[4] end
+ },
+ vornmath.utils.componentWiseExpander('geometricUnmix', {'vector', 'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('geometricUnmix', 3),
+}
+
+vornmath.bakeries.decay = {
+ {
+ signature_check = function(types)
+ if #types < 4 then return false end
+ for i = 1,4 do
+ if vornmath.metatables[types[i]].vm_shape ~= 'scalar' then return false end
+ end
+ if not vornmath.utils.hasBakery('exp2', {types[3]}) then return false end
+ if vornmath.utils.hasBakery('mix', {types[2], types[1], types[3], types[4]}) then
+ types[5] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local negate = vornmath.utils.bake('unm', {types[3], types[3]})
+ local exp2 = vornmath.utils.bake('exp2', {types[3], types[3]})
+ local mix = vornmath.utils.bake('mix', {types[2], types[1], types[3], types[4]})
+ local scratch = vornmath[types[3]]()
+ return function(a, b, t, r)
+ scratch = exp2(negate(t, scratch), scratch)
+ return mix(b, a, scratch, r)
+ end
+ end,
+ return_type = function(types) return types[4] end
+ },
+ vornmath.utils.componentWiseExpander('decay', {'vector', 'vector', 'vector'}),
+ vornmath.utils.componentWiseExpander('decay', {'vector', 'vector', 'scalar'}),
+ vornmath.utils.componentWiseReturnOnlys('decay', 3)
+}
+
+vornmath.bakeries.step = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ return function(edge,x)
+ if x < edge then
+ return 0
+ else
+ return 1
+ end
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('step', {'scalar', 'vector'}),
+ vornmath.utils.componentWiseExpander('step', {'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('step', 2)
+}
+
+vornmath.bakeries.linearStep = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number', 'number'}),
+ create = function(types)
+ local clamp = vornmath.utils.bake('clamp', {'number', 'number', 'number'})
+ local unmix = vornmath.utils.bake('unmix', {'number', 'number', 'number'})
+ return function(lo, hi, x)
+ return clamp(unmix(lo, hi, x),0,1)
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('linearStep', {'scalar', 'scalar', 'vector'}),
+ vornmath.utils.componentWiseExpander('linearStep', {'vector', 'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('linearStep', 3)
+}
+
+vornmath.bakeries.smoothStep = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number', 'number'}),
+ create = function(types)
+ local linearStep = vornmath.utils.bake('linearStep', {'number', 'number', 'number'})
+ return function(lo, hi, x)
+ local t = linearStep(lo, hi, x)
+ return t * t * (3 - 2 * t)
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('smoothStep', {'scalar', 'scalar', 'vector'}),
+ vornmath.utils.componentWiseExpander('smoothStep', {'vector', 'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('smoothStep', 3)
+}
+
+vornmath.bakeries.isnan = {
+ {
+ signature_check = function(types)
+ return vornmath.metatables[types[1]].vm_shape == 'scalar'
+ end,
+ create = function(types)
+ return function(n)
+ return n ~= n
+ end
+ end
+ },
+ vornmath.utils.componentWiseExpander('isnan', {'vector'}, 'boolean'),
+ vornmath.utils.componentWiseReturnOnlys('isnan', 1, 'boolean')
+}
+
+vornmath.bakeries.isinf = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number'}),
+ create = function(types)
+ local inf = math.huge
+ return function(n)
+ return n == inf or n == -inf
+ end
+ end
+ },
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'complex'}),
+ create = function(types)
+ local inf = math.huge
+ return function(n)
+ return n.a == inf or n.a == -inf or n.b == inf or n.b == -inf
+ end
+ end
+ },
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'quat'}),
+ create = function(types)
+ local inf = math.huge
+ return function(n)
+ return (n.a == inf or n.a == -inf or
+ n.b == inf or n.b == -inf or
+ n.c == inf or n.c == -inf or
+ n.d == inf or n.d == -inf)
+ end
+ end
+ },
+ vornmath.utils.componentWiseExpander('isinf', {'vector'}, 'boolean'),
+ vornmath.utils.componentWiseReturnOnlys('isinf', 1, 'boolean')
+
+}
+
+vornmath.bakeries.fma = {
+ { -- fma(scalar, scalar, scalar)
+ signature_check = function(types)
+ if #types < 4 then return false end
+ for i = 1,4 do
+ if vornmath.metatables[types[i]].vm_shape ~= 'scalar' then return false end
+ end
+ if not vornmath.utils.hasBakery('mul', {types[1], types[2]}) then return false end
+ local mul_type = vornmath.utils.returnType('mul', {types[1], types[2]})
+ if not vornmath.utils.hasBakery('add', {mul_type, types[3], types[4]}) then return false end
+ types[5] = nil
+ return true
+ end,
+ create = function(types)
+ local scratch_type = vornmath.utils.returnType('mul', {types[1], types[2]})
+ local mul = vornmath.utils.bake('mul', {types[1], types[2], scratch_type})
+ local add = vornmath.utils.bake('add', {scratch_type, types[3], types[4]})
+ local scratch = vornmath[scratch_type]()
+ return function(a,b,c,r)
+ scratch = mul(a,b,scratch)
+ return add(scratch,c,r)
+ end
+ end,
+ return_type = function(types) return types[4] end
+ },
+ vornmath.utils.componentWiseReturnOnlys('fma', 3),
+ vornmath.utils.componentWiseExpander('fma', {'vector', 'vector', 'vector'})
+}
+
+vornmath.bakeries.frexp = {
+ {
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'number'}),
+ create = function(types)
+---@diagnostic disable-next-line: deprecated
+ if math.frexp then return math.frexp end
+ -- this implementation of frexp stolen from ToxicFrog's vstruct under MIT license.
+ local abs,floor,log = math.abs,math.floor,math.log
+ local log2 = log(2)
+ return function(x)
+ if x == 0 then return 0,0 end
+ local e = floor(log(abs(x)) / log2)
+ if e > 0 then
+ -- Why not x / 2^e? Because for large-but-still-legal values of e this
+ -- ends up rounding to inf and the wheels come off.
+ x = x * 2^-e
+ else
+ x = x / 2^e
+ end
+ -- Normalize to the range [0.5,1)
+ if abs(x) >= 1.0 then
+ x,e = x/2,e+1
+ end
+ return x,e
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ { -- vector
+ signature_check = function(types)
+ local meta = vornmath.metatables[types[1]]
+ if meta.vm_shape ~= 'vector' or meta.vm_storage ~= 'number' then return false end
+ if types[2] ~= types[1] or types[3] ~= types[1] then return false end
+ types[4] = nil
+ return true
+ end,
+ create = function(types)
+ local d = vornmath.metatables[types[1]].vm_dim
+ local frexp = vornmath.utils.bake('frexp', {'number'})
+ return function(x, mantissa, exponent)
+ for i = 1,d do
+ mantissa[i], exponent[i] = frexp(x[i])
+ end
+ return mantissa, exponent
+ end
+ end,
+ return_type = function(types) return types[1], types[1] end
+ },
+ { -- return-only
+ signature_check = function(types)
+ if types[2] and types[2] ~= 'nil' or types[3] and types[3] ~= 'nil' then return false end
+ if vornmath.utils.hasBakery('frexp', {types[1], types[1], types[1]}) then
+ types[2] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local frexp = vornmath.utils.bake('frexp', {types[1], types[1], types[1]})
+ local construct = vornmath.utils.bake(types[1], {})
+ return function(x)
+ local mantissa = construct()
+ local exponent = construct()
+ return frexp(x, mantissa, exponent)
+ end
+ end,
+ return_type = function(types) return types[1], types[1] end
+ }
+}
+
+vornmath.bakeries.ldexp = {
+ { -- ldexp(number, number)
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+---@diagnostic disable-next-line: deprecated
+ if math.ldexp then return math.ldexp end
+ return function(mantissa, exponent)
+ return mantissa * 2 ^ exponent
+ end
+ end,
+ return_type = function(types) return 'number' end
+ },
+ vornmath.utils.componentWiseExpander('ldexp', {'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('ldexp', 2),
+}
+
+-- vector functions
+
+vornmath.bakeries.length = {
+ {
+ signature_check = function(types)
+ local meta = vornmath.metatables[types[1]]
+ if meta.vm_shape == 'vector' and meta.vm_storage ~= 'boolean' then
+ types[2] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local meta = vornmath.metatables[types[1]]
+ local sqabs = vornmath.utils.bake('sqabs', {meta.vm_storage})
+ local dim = meta.vm_dim
+ local sqrt = math.sqrt
+ return function(x)
+ local result = 0
+ for i = 1,dim do
+ result = result + sqabs(x[i])
+ end
+ return sqrt(result)
+ end
+ end,
+ return_type = function(types) return 'number' end
+ }
+}
+
+vornmath.bakeries.distance = {
+ {
+ signature_check = function(types)
+ if vornmath.utils.hasBakery('sub', {types[1], types[2]}) and
+ vornmath.utils.hasBakery('length', {vornmath.utils.componentWiseConsensusType(types)}) then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local composite_type = vornmath.utils.returnType('sub', types)
+ local sub = vornmath.utils.bake('sub', {types[1], types[2], composite_type})
+ local length = vornmath.utils.bake('length', {composite_type})
+ local scratch = vornmath[composite_type]()
+ return function(a, b)
+ scratch = sub(a, b, scratch)
+ return length(scratch)
+ end
+ end
+ }
+}
+
+vornmath.bakeries.dot = {
+ { -- dot(vec, vec, out_type)
+ signature_check = function(types)
+ if #types < 3 then return false end
+ -- can I multiply these vectors together?
+ if not vornmath.utils.hasBakery('mul', {types[1], types[2]}) then return false end
+ local mul_result = vornmath.utils.returnType('mul', {types[1], types[2]})
+ local mul_meta = vornmath.metatables[mul_result]
+ if mul_meta.vm_storage ~= types[3] then return false end
+ types[4] = nil
+ return true
+ end,
+ create = function(types)
+ local mul_result = vornmath.utils.returnType('mul', {types[1], types[2]})
+ local mul = vornmath.utils.bake('mul', {types[1], types[2], mul_result})
+ local add = vornmath.utils.bake('add', {types[3], types[3], types[3]})
+ local d = vornmath.metatables[types[1]].vm_dim
+ local fill = vornmath.utils.bake('fill', {types[3]})
+ local conjscratch = vornmath[types[2]]()
+ local scratch = vornmath[mul_result]()
+ return function(a, b, r)
+ r = fill(r)
+ conjscratch = vornmath.conj(b, conjscratch)
+ scratch = mul(a, conjscratch, scratch)
+ for i = 1,d do
+ r = add(r, scratch[i], r)
+ end
+ return r
+ end
+ end,
+ return_type = function(types) return types[3] end
+ },
+ { -- return-onlys
+ signature_check = function(types)
+ if #types < 2 then return false end
+ for i = 3,#types do
+ if types[i] ~= 'nil' and types[i] ~= nil then return false end
+ end
+ if not vornmath.utils.hasBakery('mul', {types[1], types[2]}) then return false end
+ local mul_result = vornmath.utils.returnType('mul', {types[1], types[2]})
+ local mul_meta = vornmath.metatables[mul_result]
+ if not vornmath.utils.hasBakery('dot', {types[1], types[2], mul_meta.vm_storage}) then return false end
+ types[3] = 'nil'
+ types[4] = nil
+ return true
+ end,
+ create = function(types)
+ local mul_result = vornmath.utils.returnType('mul', {types[1], types[2]})
+ local out_type = vornmath.metatables[mul_result].vm_storage
+ local dot = vornmath.utils.bake('dot', {types[1], types[2], out_type})
+ local construct = vornmath.utils.bake(out_type, {})
+ return function(a,b)
+ local r = construct()
+ return dot(a,b,r)
+ end
+ end,
+ return_type = function(types)
+ local mul_result = vornmath.utils.returnType('mul', {types[1], types[2]})
+ return vornmath.metatables[mul_result].vm_storage
+ end
+ }
+}
+
+vornmath.bakeries.cross = {
+ {
+ signature_check = function(types)
+ if #types < 3 then return false end
+ local first_meta = vornmath.metatables[types[1]]
+ local second_meta = vornmath.metatables[types[2]]
+ local third_meta = vornmath.metatables[types[3]]
+ if first_meta.vm_shape ~= 'vector' or first_meta.vm_dim ~= 3 or
+ second_meta.vm_shape ~= 'vector' or second_meta.vm_dim ~= 3 or
+ not vornmath.utils.hasBakery('mul', types) then -- cross product is really a sum of multiplications of swizzles
+ return false
+ end
+ types[4] = nil
+ return true
+ end,
+ create = function(types)
+ local first_meta = vornmath.metatables[types[1]]
+ local second_meta = vornmath.metatables[types[2]]
+ local third_meta = vornmath.metatables[types[3]]
+ local final_type = third_meta.vm_storage
+ local mul = vornmath.utils.bake('mul', {first_meta.vm_storage, second_meta.vm_storage, final_type})
+ local sub = vornmath.utils.bake('sub', {final_type, final_type, final_type})
+ local fill = vornmath.utils.bake('fill', {types[3], types[3]})
+ local scratch_vec = vornmath[types[3]]()
+ local scratch_thing = vornmath[final_type]()
+ return function(a, b, r)
+ scratch_vec[1] = mul(a[2], b[3], scratch_vec[1])
+ scratch_thing = mul(a[3], b[2], scratch_thing)
+ scratch_vec[1] = sub(scratch_vec[1], scratch_thing, scratch_vec[1])
+ scratch_vec[2] = mul(a[3], b[1], scratch_vec[2])
+ scratch_thing = mul(a[1], b[3], scratch_thing)
+ scratch_vec[2] = sub(scratch_vec[2], scratch_thing, scratch_vec[2])
+ scratch_vec[3] = mul(a[1], b[2], scratch_vec[3])
+ scratch_thing = mul(a[2], b[1], scratch_thing)
+ scratch_vec[3] = sub(scratch_vec[3], scratch_thing, scratch_vec[3])
+ return fill(r, scratch_vec)
+ end
+ end
+ },
+ vornmath.utils.componentWiseReturnOnlys('cross',2)
+}
+
+vornmath.bakeries.minComponent = {
+ {
+ signature_check = function(types)
+ local first = vornmath.metatables[types[1]]
+ if first.vm_shape == 'vector' and first.vm_storage == 'number' then
+ types[2] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local l = vornmath.metatables[types[1]].vm_dim
+ return function(v)
+ local x = v[1]
+ for k = 2,l do
+ x = math.min(x, v[k])
+ end
+ return x
+ end
+ end,
+ return_type = function(types) return 'number' end
+ }
+}
+
+vornmath.bakeries.maxComponent = {
+ {
+ signature_check = function(types)
+ local first = vornmath.metatables[types[1]]
+ if first.vm_shape == 'vector' and first.vm_storage == 'number' then
+ types[2] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local l = vornmath.metatables[types[1]].vm_dim
+ return function(v)
+ local x = v[1]
+ for k = 2,l do
+ x = math.max(x, v[k])
+ end
+ return x
+ end
+ end,
+ return_type = function(types) return 'number' end
+ }
+}
+
+vornmath.bakeries.normalize = {
+ {
+ signature_check = function(types)
+ if #types < 2 then return false end
+ if types[1] ~= types[2] then return false end
+ local first_meta = vornmath.metatables[types[1]]
+ if first_meta.vm_shape ~= 'vector' then return false end
+ if vornmath.utils.hasBakery('length', {types[1]}) then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local len = vornmath.utils.bake('length', {types[1]})
+ local div = vornmath.utils.bake('div', {types[1], 'number', types[2]})
+ return function(a, r)
+ local l = len(a)
+ return div(a, l, r)
+ end
+ end
+ },
+ vornmath.utils.componentWiseReturnOnlys('normalize', 1)
+}
+
+vornmath.bakeries.homogeneousNormalize = {
+ {
+ signature_check = function(types)
+ if #types < 2 then return false end
+ if types[1] ~= types[2] then return false end
+ local first_meta = vornmath.metatables[types[1]]
+ if first_meta.vm_shape ~= 'vector' then return false end
+ if vornmath.utils.hasBakery('length', {types[1]}) then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local first_meta = vornmath.metatables[types[1]]
+ local normalize = vornmath.utils.bake('normalize', {types[1], types[1]})
+ local div = vornmath.utils.bake('div', {types[1], first_meta.vm_storage, types[2]})
+ local eq = vornmath.utils.bake('eq', {first_meta.vm_storage, 'number'})
+ local l = first_meta.vm_dim
+ return function(a, r)
+ if not eq(a[l],0) then
+ return div(a, a[l], r)
+ else
+ return normalize(a, r)
+ end
+ end
+ end
+ },
+ vornmath.utils.componentWiseReturnOnlys('homogeneousNormalize', 1)
+}
+
+vornmath.bakeries.hesseNormalize = {
+ {
+ signature_check = function(types)
+ if #types < 2 then return false end
+ if types[1] ~= types[2] then return false end
+ local first_meta = vornmath.metatables[types[1]]
+ if first_meta.vm_shape ~= 'vector' or first_meta.vm_dim < 3 then return false end
+ if vornmath.utils.hasBakery('length', {types[1]}) then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local first_meta = vornmath.metatables[types[1]]
+ local short_type = vornmath.utils.findTypeByData('vector', first_meta.vm_dim - 1, first_meta.vm_storage)
+ local demote = vornmath.utils.bake('fill', {short_type, types[1]})
+ local length = vornmath.utils.bake('length', {short_type})
+ local div = vornmath.utils.bake('div', {types[1], 'number'})
+ local shortened = vornmath[short_type]()
+ return function(a, r)
+ return div(a, length(demote(shortened, a)), r)
+ end
+ end
+ },
+ vornmath.utils.componentWiseReturnOnlys('hesseNormalize', 1)
+}
+
+vornmath.bakeries.cubeNormalize = {
+ {
+ signature_check = function(types)
+ if #types < 2 then return false end
+ if types[1] ~= types[2] then return false end
+ local first = vornmath.metatables[types[1]]
+ if first.vm_shape ~= 'vector' or first.vm_dim < 3 then return false end
+ if vornmath.utils.hasBakery('div', {types[1], 'number'}) then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local first = vornmath.metatables[types[1]]
+ local nvec = vornmath.utils.findTypeByData(first.vm_shape, first.vm_dim, 'number')
+ local abs = vornmath.utils.bake('abs', {types[1], nvec})
+ local cm = vornmath.utils.bake('maxComponent', {nvec})
+ local div = vornmath.utils.bake('div', {types[1], 'number', types[1]})
+ local scratch = vornmath[nvec]()
+ return function(v, r)
+ return div(v, cm(abs(v,scratch)), r)
+ end
+ end,
+ return_type = function(types) return types[1] end
+ },
+ vornmath.utils.componentWiseReturnOnlys('cubeNormalize', 1)
+
+}
+
+vornmath.bakeries.faceForward = {
+ {
+ signature_check = function(types)
+ if #types < 4 then return false end
+ if types[1] ~= types[2] or types[1] ~= types[3] or types[1] ~= types[4] then return false end
+ local first_meta = vornmath.metatables[types[1]]
+ if first_meta.vm_shape ~= 'vector' or first_meta.vm_storage ~= 'number' then
+ return false
+ end
+ types[5] = nil
+ return true
+ end,
+ create = function(types)
+ local dot = vornmath.utils.bake('dot', {types[1], types[1]})
+ local unm = vornmath.utils.bake('unm', {types[1], types[1]})
+ local fill = vornmath.utils.bake('fill', {types[1], types[1]})
+ return function(n, i, nref, r)
+ if dot(nref, i) >= 0 then
+ return unm(n,r)
+ else
+ return fill(r,n)
+ end
+ end
+ end,
+ return_type = function(types) return types[4] end
+ },
+ vornmath.utils.componentWiseReturnOnlys('faceForward', 3)
+}
+
+vornmath.bakeries.reflect = {
+ {
+ signature_check = function(types)
+ if #types < 3 then return false end
+ if vornmath.utils.hasBakery('dot', {types[2], types[1]}) and
+ vornmath.utils.hasBakery('add', types) then
+ return true
+ end
+ end,
+ create = function(types)
+ local dot_result_type = vornmath.utils.returnType('dot', {types[2], types[1]})
+ local dot = vornmath.utils.bake('dot', {types[2], types[1], dot_result_type})
+ local mul_result_type = vornmath.utils.returnType('mul', {dot_result_type, types[2]})
+ local mul = vornmath.utils.bake('mul', {dot_result_type, types[2], mul_result_type})
+ local scalar_mul = vornmath.utils.bake('mul', {'number', dot_result_type, dot_result_type})
+ local sub = vornmath.utils.bake('sub', {types[1], mul_result_type, types[3]})
+ local scratch = vornmath[dot_result_type]()
+ local scratch_vec = vornmath[mul_result_type]()
+ return function(i, n, r)
+ scratch = dot(n, i, scratch)
+ scratch = scalar_mul(2, scratch, scratch)
+ scratch_vec = mul(scratch, n, scratch_vec)
+ return sub(i, scratch_vec, r)
+ end
+ end,
+ return_type = function(types) return types[3] end
+ },
+ vornmath.utils.componentWiseReturnOnlys('reflect', 2)
+}
+
+vornmath.bakeries.refract = {
+ {
+ signature_check = function(types)
+ if types[1] ~= types[2] or types[1] ~= types[4] then return false end
+ local meta = vornmath.metatables[types[1]]
+ if meta.vm_shape == 'vector' and meta.vm_storage == 'number' and types[3] == 'number' then
+ types[5] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ -- okay so I don't actually care about non-number ones
+ -- so I can be a little bit less picky about targeting complexes
+ -- so standard builtin arithmetic ops will do nicely for all that work
+ local dot = vornmath.utils.bake('dot', {types[1], types[2]})
+ local sqrt = math.sqrt
+ local emptyfill = vornmath.utils.bake('fill', {types[4]})
+ local mul = vornmath.utils.bake('mul', {types[1], 'number', types[1]})
+ local sub = vornmath.utils.bake('sub', {types[1], types[1], types[1]})
+ local scratch = vornmath[types[4]]()
+ return function(i, n, eta, r)
+ local cosine = dot(n, i)
+ local k = 1 - eta * eta * (1 - cosine * cosine)
+ if k < 0 then return emptyfill(r) end
+ local n_influence = eta * cosine + sqrt(k)
+ scratch = mul(i, eta, scratch)
+ r = mul(n, n_influence, r)
+ return sub(scratch, r, r)
+ end
+ end,
+ return_type = function(types) return types[4] end
+ },
+ vornmath.utils.componentWiseReturnOnlys('refract', 3)
+}
+
+vornmath.bakeries.slerp = {
+ {
+ signature_check = function(types)
+ if types[1] ~= types[2] or types[1] ~= types[4] then return false end
+ local meta = vornmath.metatables[types[1]]
+ if meta.vm_shape == 'vector' and meta.vm_storage == 'number' and types[3] == 'number' then
+ types[5] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local dot = vornmath.utils.bake('dot', {types[1], types[2]})
+ local length = vornmath.utils.bake('length', {types[1]})
+ local mul = vornmath.utils.bake('mul', {types[1], 'number', types[1]})
+ local add = vornmath.utils.bake('add', {types[1], types[1], types[1]})
+ local scratch = vornmath[types[1]]()
+ return function(a,b,t,r)
+ local theta = math.acos(dot(a,b)/(length(a) * length(b)))
+ local denominator = math.sin(theta)
+ local u = math.sin((1-t) * theta) / denominator
+ local v = math.sin(t * theta) / denominator
+ scratch = mul(a, u, scratch)
+ r = mul(b, v, r)
+ return add(scratch, r, r)
+ end
+ end,
+ return_type = function(types) return types[4] end
+ },
+ vornmath.utils.componentWiseReturnOnlys('slerp', 3)
+}
+
+-- matrix functions
+
+vornmath.bakeries.matrixCompMult = {
+ vornmath.utils.componentWiseExpander('mul', {'matrix', 'matrix'}),
+ vornmath.utils.componentWiseReturnOnlys('matrixCompMult', 2)
+}
+
+vornmath.bakeries.outerProduct = {
+ { -- outerProduct(vector, vector, matrix)}
+ signature_check = function(types)
+ if #types < 3 then return false end
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ local result = vornmath.metatables[types[3]]
+ if (left.vm_shape ~= 'vector' or
+ right.vm_shape ~= 'vector' or
+ result.vm_shape ~= 'matrix' or
+ left.vm_dim ~= result.vm_dim[2] or
+ result.vm_dim[1] ~= right.vm_dim or
+ not vornmath.utils.hasBakery('mul', {left.vm_storage, right.vm_storage, result.vm_storage})
+ or not vornmath.utils.hasBakery('add', {result.vm_storage, result.vm_storage, result.vm_storage})
+ )
+ then
+ return false
+ end
+ types[4] = nil
+ return true
+ end,
+ create = function(types)
+ local left_type = vornmath.metatables[types[1]]
+ local right_type = vornmath.metatables[types[2]]
+ local result_type = vornmath.metatables[types[3]]
+ local width = result_type.vm_dim[1]
+ local height = result_type.vm_dim[2]
+ local mul = vornmath.utils.bake('mul', {left_type.vm_storage, right_type.vm_storage, result_type.vm_storage})
+ return function(left, right, result)
+ for x = 1, width do
+ for y = 1, height do
+ result[x][y] = mul(left[y], right[x], result[x][y])
+ end
+ end
+ return result
+ end
+ end,
+ return_type = function(types) return types[3] end
+ },
+ { -- outerProduct(vector, vector)
+ signature_check = function(types)
+ if #types < 2 then return false end
+ if #types > 2 then
+ -- only nils after
+ for i, typename in ipairs(types) do
+ if i > 2 and typename ~= 'nil' then return false end
+ end
+ end
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ if left.vm_shape ~= 'vector' or right.vm_shape ~= 'vector' then
+ return false
+ end
+ local consensus_storage = vornmath.utils.consensusStorage({left.vm_storage, right.vm_storage})
+ local result_type = vornmath.utils.findTypeByData('matrix', {right.vm_dim, left.vm_dim}, consensus_storage)
+ if vornmath.utils.hasBakery('outerProduct', {types[1], types[2], result_type}) then
+ types[3] = 'nil'
+ types[4] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ local consensus_storage = vornmath.utils.consensusStorage({left.vm_storage, right.vm_storage})
+ local result_type = vornmath.utils.findTypeByData('matrix', {right.vm_dim, left.vm_dim}, consensus_storage)
+ local make = vornmath.utils.bake(result_type, {})
+ local action = vornmath.utils.bake('outerProduct', {types[1], types[2], result_type})
+ return function(a, b)
+ local result = make()
+ return action(a, b, result)
+ end
+ end,
+ return_type = function(types)
+ local left = vornmath.metatables[types[1]]
+ local right = vornmath.metatables[types[2]]
+ local consensus_storage = vornmath.utils.consensusStorage({left.vm_storage, right.vm_storage})
+ return vornmath.utils.findTypeByData('matrix', {right.vm_dim[1], left.vm_dim[2]}, consensus_storage)
+ end
+ },
+
+}
+
+vornmath.bakeries.transpose = {
+ { -- transpose(mataxb, matbxa)
+ signature_check = function(types)
+ if #types < 2 then return false end
+ local first_meta = vornmath.metatables[types[1]]
+ local second_meta = vornmath.metatables[types[2]]
+ if first_meta.vm_shape == 'matrix' and
+ second_meta.vm_shape == 'matrix' and
+ first_meta.vm_storage == second_meta.vm_storage and
+ first_meta.vm_dim[1] == second_meta.vm_dim[2] and
+ first_meta.vm_dim[2] == second_meta.vm_dim[1] then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local meta = vornmath.metatables[types[1]]
+ local fill = vornmath.utils.bake('fill', {meta.vm_storage, meta.vm_storage})
+ local clone = vornmath.utils.bake('fill', {types[1], types[1]})
+ local cols = meta.vm_dim[1]
+ local rows = meta.vm_dim[2]
+ local scratch = vornmath[types[1]]()
+ return function(m, target)
+ scratch = clone(scratch, m) -- do thiscloning so transposing a square matrix onto itself is safe
+ for c = 1,cols do
+ for r = 1,rows do
+ target[r][c] = fill(target[r][c], scratch[c][r])
+ end
+ end
+ return target
+ end
+ end,
+ return_type = function(types) return types[2] end
+ },
+ { -- return onlys
+ signature_check = function(types)
+ if #types < 1 then return false end
+ if types[2] and types[2] ~= 'nil' then return false end
+ local meta = vornmath.metatables[types[1]]
+ if meta.vm_shape ~= 'matrix' then return false end
+ local second_dim = {meta.vm_dim[2], meta.vm_dim[1]}
+ local second_type = vornmath.utils.findTypeByData('matrix', second_dim, meta.vm_storage)
+ if vornmath.utils.hasBakery('transpose', {types[1], second_type}) then
+ types[2] = 'nil'
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local meta = vornmath.metatables[types[1]]
+ local second_dim = {meta.vm_dim[2], meta.vm_dim[1]}
+ local second_type = vornmath.utils.findTypeByData('matrix', second_dim, meta.vm_storage)
+ local f = vornmath.utils.bake('transpose', {types[1], second_type})
+ local construct = vornmath.utils.bake(second_type, {})
+ return function(m)
+ local result = construct()
+ return f(m, result)
+ end
+ end,
+ return_type = function(types)
+ local meta = vornmath.metatables[types[1]]
+ local second_dim = {meta.vm_dim[2], meta.vm_dim[1]}
+ return vornmath.utils.findTypeByData('matrix', second_dim, meta.vm_storage)
+ end
+ }
+}
+
+vornmath.bakeries.determinant = {
+ { -- determinant(mat2x2, scalar)
+ signature_check = function(types)
+ if #types < 2 then return false end
+ local meta = vornmath.metatables[types[1]]
+ if meta.vm_shape == 'matrix' and
+ meta.vm_dim[1] == 2 and
+ meta.vm_dim[2] == 2 and
+ meta.vm_storage == types[2] then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local mul = vornmath.utils.bake('mul', {types[2], types[2], types[2]})
+ local sub = vornmath.utils.bake('sub', {types[2], types[2], types[2]})
+ local scratch = vornmath[types[2]]()
+ return function(m, r)
+ r = mul(m[1][1], m[2][2], r)
+ scratch = mul(m[1][2], m[2][1], scratch)
+ return sub(r, scratch, r)
+ end
+ end,
+ return_type = function(types) return types[2] end
+ },
+ { -- determinant(mat3x3, scalar)
+ signature_check = function(types)
+ if #types < 2 then return false end
+ local meta = vornmath.metatables[types[1]]
+ if meta.vm_shape == 'matrix' and
+ meta.vm_dim[1] == 3 and
+ meta.vm_dim[2] == 3 and
+ meta.vm_storage == types[2] then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {types[2]})
+ local mul = vornmath.utils.bake('mul', {types[2], types[2], types[2]})
+ local add = vornmath.utils.bake('add', {types[2], types[2], types[2]})
+ local sub = vornmath.utils.bake('sub', {types[2], types[2], types[2]})
+ local scratch = vornmath[types[2]]()
+ return function(m, r)
+ r = fill(r)
+ scratch = mul(m[1][1], m[2][2], scratch)
+ scratch = mul(scratch, m[3][3], scratch)
+ r = add(r, scratch, r)
+ scratch = mul(m[1][2], m[2][3], scratch)
+ scratch = mul(scratch, m[3][1], scratch)
+ r = add(r, scratch, r)
+ scratch = mul(m[1][3], m[2][1], scratch)
+ scratch = mul(scratch, m[3][2], scratch)
+ r = add(r, scratch, r)
+ scratch = mul(m[1][1], m[2][3], scratch)
+ scratch = mul(scratch, m[3][2], scratch)
+ r = sub(r, scratch, r)
+ scratch = mul(m[1][2], m[2][1], scratch)
+ scratch = mul(scratch, m[3][3], scratch)
+ r = sub(r, scratch, r)
+ scratch = mul(m[1][3], m[2][2], scratch)
+ scratch = mul(scratch, m[3][1], scratch)
+ r = sub(r, scratch, r)
+ return r
+ end
+ end,
+ return_type = function(types) return types[2] end
+ },
+ { -- determinant(mat4x4, scalar)
+ signature_check = function(types)
+ if #types < 2 then return false end
+ local meta = vornmath.metatables[types[1]]
+ if meta.vm_shape == 'matrix' and
+ meta.vm_dim[1] == 4 and
+ meta.vm_dim[2] == 4 and
+ meta.vm_storage == types[2] then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {types[2], types[2]})
+ local mul = vornmath.utils.bake('mul', {types[2], types[2], types[2]})
+ local add = vornmath.utils.bake('add', {types[2], types[2], types[2]})
+ local sub = vornmath.utils.bake('sub', {types[2], types[2], types[2]})
+ local scratch = vornmath[types[2]]()
+ local scratch_2 = vornmath[types[2]]()
+ local scratch_3 = vornmath[types[2]]()
+ local scratch_4 = vornmath[types[2]]()
+ local scratch_5 = vornmath[types[2]]()
+ local scratch_6 = vornmath[types[2]]()
+
+ local low_12 = vornmath[types[2]]()
+ local low_13 = vornmath[types[2]]()
+ local low_14 = vornmath[types[2]]()
+ local low_23 = vornmath[types[2]]()
+ local low_24 = vornmath[types[2]]()
+ local low_34 = vornmath[types[2]]()
+ return function(m, det)
+ -- cover the bottom rows with pairs
+ low_34 = sub(mul(m[3][3], m[4][4], scratch), mul(m[4][3], m[3][4], scratch_2), low_34)
+ low_24 = sub(mul(m[2][3], m[4][4], scratch), mul(m[4][3], m[2][4], scratch_2), low_24)
+ low_14 = sub(mul(m[1][3], m[4][4], scratch), mul(m[4][3], m[1][4], scratch_2), low_14)
+ low_23 = sub(mul(m[2][3], m[3][4], scratch), mul(m[3][3], m[2][4], scratch_2), low_23)
+ low_13 = sub(mul(m[1][3], m[3][4], scratch), mul(m[3][3], m[1][4], scratch_2), low_13)
+ low_12 = sub(mul(m[1][3], m[2][4], scratch), mul(m[2][3], m[1][4], scratch_2), low_12)
+ scratch = mul(m[1][1], add(sub(mul(m[2][2], low_34, scratch_2), mul(m[3][2], low_24, scratch_3), scratch_4), mul(m[4][2], low_23, scratch_5), scratch_6), scratch)
+ det = fill(det, scratch)
+ scratch = mul(m[2][1], add(sub(mul(m[1][2], low_34, scratch_2), mul(m[3][2], low_14, scratch_3), scratch_4), mul(m[4][2], low_13, scratch_5), scratch_6), scratch)
+ det = sub(det, scratch, det)
+
+ scratch = mul(m[3][1], add(sub(mul(m[1][2], low_24, scratch_2), mul(m[2][2], low_14, scratch_3), scratch_4), mul(m[4][2], low_12, scratch_5), scratch_6), scratch)
+ det = add(det, scratch, det)
+
+ scratch = mul(m[4][1], add(sub(mul(m[1][2], low_23, scratch_2), mul(m[2][2], low_13, scratch_3), scratch_4), mul(m[3][2], low_12, scratch_5), scratch_6), scratch)
+ return sub(det, scratch, det)
+ end
+ end,
+ return_type = function(types) return types[2] end
+ },
+ { -- return-onlys
+ signature_check = function(types)
+ if #types < 1 or types[2] and types[2] ~= 'nil' then return false end
+ local meta = vornmath.metatables[types[1]]
+ if vornmath.utils.hasBakery('determinant', {types[1], meta.vm_storage}) then
+ types[2] = 'nil'
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local meta = vornmath.metatables[types[1]]
+ local f = vornmath.utils.bake('determinant', {types[1], meta.vm_storage})
+ local construct = vornmath.utils.bake(meta.vm_storage, {})
+ return function(m)
+ local r = construct()
+ return f(m, r)
+ end
+ end
+ }
+}
+
+vornmath.bakeries.inverse = {
+ { -- inverse(mat2x2, mat2x2)
+ signature_check = function(types)
+ if #types < 2 or types[1] ~= types[2] then return false end
+ local meta = vornmath.metatables[types[1]]
+ if meta.vm_shape == 'matrix' and meta.vm_dim[1] == 2 and meta.vm_dim[2] == 2 then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local meta = vornmath.metatables[types[1]]
+ local det = vornmath.utils.bake('determinant', {types[1], meta.vm_storage})
+ local div = vornmath.utils.bake('div', {types[1], meta.vm_storage, types[1]})
+ local unm = vornmath.utils.bake('unm', {meta.vm_storage, meta.vm_storage})
+ local fill = vornmath.utils.bake('fill', {meta.vm_storage, meta.vm_storage})
+ local scratch = vornmath[types[1]]()
+ local d = vornmath[meta.vm_storage]()
+ return function(m, r)
+ d = det(m, d)
+ scratch[1][1] = fill(scratch[1][1], m[2][2])
+ scratch[1][2] = unm(m[1][2], scratch[1][2])
+ scratch[2][1] = unm(m[2][1], scratch[2][1])
+ scratch[2][2] = fill(scratch[2][2], m[1][1])
+ return div(scratch, d, r)
+ end
+ end
+ },
+ { -- inverse(mat3x3, mat3x3)
+ signature_check = function(types)
+ if #types < 2 or types[1] ~= types[2] then return false end
+ local meta = vornmath.metatables[types[1]]
+ if meta.vm_shape == 'matrix' and meta.vm_dim[1] == 3 and meta.vm_dim[2] == 3 then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local meta = vornmath.metatables[types[1]]
+ local det = vornmath.utils.bake('determinant', {types[1], meta.vm_storage})
+ local div = vornmath.utils.bake('div', {types[1], meta.vm_storage, types[1]})
+ local mul = vornmath.utils.bake('mul', {meta.vm_storage, meta.vm_storage, meta.vm_storage})
+ local sub = vornmath.utils.bake('sub', {meta.vm_storage, meta.vm_storage, meta.vm_storage})
+ local scratch = vornmath[types[1]]()
+ local scratch_num = vornmath[meta.vm_storage]()
+ local scratch_num_2 = vornmath[meta.vm_storage]()
+ local d = vornmath[meta.vm_storage]()
+ return function(m, r)
+ d = det(m, d)
+ scratch[1][1] = sub(mul(m[2][2], m[3][3], scratch_num), mul(m[2][3], m[3][2], scratch_num_2), scratch[1][1])
+ scratch[1][2] = sub(mul(m[3][2], m[1][3], scratch_num), mul(m[3][3], m[1][2], scratch_num_2), scratch[1][2])
+ scratch[1][3] = sub(mul(m[1][2], m[2][3], scratch_num), mul(m[1][3], m[2][2], scratch_num_2), scratch[1][3])
+ scratch[2][1] = sub(mul(m[2][3], m[3][1], scratch_num), mul(m[2][1], m[3][3], scratch_num_2), scratch[2][1])
+ scratch[2][2] = sub(mul(m[3][3], m[1][1], scratch_num), mul(m[3][1], m[1][3], scratch_num_2), scratch[2][2])
+ scratch[2][3] = sub(mul(m[1][3], m[2][1], scratch_num), mul(m[1][1], m[2][3], scratch_num_2), scratch[2][3])
+ scratch[3][1] = sub(mul(m[2][1], m[3][2], scratch_num), mul(m[2][2], m[3][1], scratch_num_2), scratch[3][1])
+ scratch[3][2] = sub(mul(m[3][1], m[1][2], scratch_num), mul(m[3][2], m[1][1], scratch_num_2), scratch[3][2])
+ scratch[3][3] = sub(mul(m[1][1], m[2][2], scratch_num), mul(m[1][2], m[2][1], scratch_num_2), scratch[3][3])
+ return div(scratch, d, r)
+ end
+ end
+ },
+ { -- inverse(mat4x4, mat4x4)
+ signature_check = function(types)
+ if #types < 2 or types[1] ~= types[2] then return false end
+ local meta = vornmath.metatables[types[1]]
+ if meta.vm_shape == 'matrix' and meta.vm_dim[1] == 4 and meta.vm_dim[2] == 4 then
+ types[3] = nil
+ return true
+ end
+ end,
+ create = function(types)
+ local meta = vornmath.metatables[types[1]]
+ local det = vornmath.utils.bake('determinant', {types[1], meta.vm_storage})
+ local div = vornmath.utils.bake('div', {types[1], meta.vm_storage, types[1]})
+ local mul = vornmath.utils.bake('mul', {meta.vm_storage, meta.vm_storage, meta.vm_storage})
+ local sub = vornmath.utils.bake('sub', {meta.vm_storage, meta.vm_storage, meta.vm_storage})
+ local add = vornmath.utils.bake('add', {meta.vm_storage, meta.vm_storage, meta.vm_storage})
+ local s = vornmath[types[1]]()
+ local make = vornmath[meta.vm_storage]
+ local s1, s2, s3, s4 = make(), make(), make(), make()
+ local lo12, lo13, lo14 = make(), make(), make()
+ local lo23, lo24, lo34 = make(), make(), make()
+ local hi12, hi13, hi14 = make(), make(), make()
+ local hi23, hi24, hi34 = make(), make(), make()
+ local d = make()
+ return function(m, r)
+ d = det(m, d)
+ lo12 = sub(mul(m[1][3], m[2][4], s1), mul(m[2][3], m[1][4], s2), lo12)
+ lo13 = sub(mul(m[1][3], m[3][4], s1), mul(m[3][3], m[1][4], s2), lo13)
+ lo14 = sub(mul(m[1][3], m[4][4], s1), mul(m[4][3], m[1][4], s2), lo14)
+
+ lo23 = sub(mul(m[2][3], m[3][4], s1), mul(m[3][3], m[2][4], s2), lo23)
+ lo24 = sub(mul(m[2][3], m[4][4], s1), mul(m[4][3], m[2][4], s2), lo24)
+ lo34 = sub(mul(m[3][3], m[4][4], s1), mul(m[4][3], m[3][4], s2), lo34)
+
+ hi12 = sub(mul(m[1][1], m[2][2], s1), mul(m[2][1], m[1][2], s2), hi12)
+ hi13 = sub(mul(m[1][1], m[3][2], s1), mul(m[3][1], m[1][2], s2), hi13)
+ hi14 = sub(mul(m[1][1], m[4][2], s1), mul(m[4][1], m[1][2], s2), hi14)
+
+ hi23 = sub(mul(m[2][1], m[3][2], s1), mul(m[3][1], m[2][2], s2), hi23)
+ hi24 = sub(mul(m[2][1], m[4][2], s1), mul(m[4][1], m[2][2], s2), hi24)
+ hi34 = sub(mul(m[3][1], m[4][2], s1), mul(m[4][1], m[3][2], s2), hi34)
+
+ s[1][1] = add(sub(mul(m[4][2], lo23, s1), mul(m[3][2], lo24, s2), s4), mul(m[2][2], lo34, s3), s[1][1])
+ s[1][2] = sub(sub(mul(m[3][2], lo14, s1), mul(m[4][2], lo13, s2), s4), mul(m[1][2], lo34, s3), s[1][2])
+ s[1][3] = add(sub(mul(m[1][2], lo24, s1), mul(m[2][2], lo14, s2), s4), mul(m[4][2], lo12, s3), s[1][3])
+ s[1][4] = sub(sub(mul(m[2][2], lo13, s1), mul(m[1][2], lo23, s2), s4), mul(m[3][2], lo12, s3), s[1][4])
+
+ s[2][1] = sub(sub(mul(m[3][1], lo24, s1), mul(m[4][1], lo23, s2), s4), mul(m[2][1], lo34, s3), s[2][1])
+ s[2][2] = add(sub(mul(m[4][1], lo13, s1), mul(m[3][1], lo14, s2), s4), mul(m[1][1], lo34, s3), s[2][2])
+ s[2][3] = sub(sub(mul(m[2][1], lo14, s1), mul(m[1][1], lo24, s2), s4), mul(m[4][1], lo12, s3), s[2][3])
+ s[2][4] = add(sub(mul(m[1][1], lo23, s1), mul(m[2][1], lo13, s2), s4), mul(m[3][1], lo12, s3), s[2][4])
+
+ s[3][1] = add(sub(mul(m[4][4], hi23, s1), mul(m[3][4], hi24, s2), s4), mul(m[2][4], hi34, s3), s[3][1])
+ s[3][2] = sub(sub(mul(m[3][4], hi14, s1), mul(m[4][4], hi13, s2), s4), mul(m[1][4], hi34, s3), s[3][2])
+ s[3][3] = add(sub(mul(m[1][4], hi24, s1), mul(m[2][4], hi14, s2), s4), mul(m[4][4], hi12, s3), s[3][3])
+ s[3][4] = sub(sub(mul(m[2][4], hi13, s1), mul(m[1][4], hi23, s2), s4), mul(m[3][4], hi12, s3), s[3][4])
+
+ s[4][1] = sub(sub(mul(m[3][3], hi24, s1), mul(m[4][3], hi23, s2), s4), mul(m[2][3], hi34, s3), s[4][1])
+ s[4][2] = add(sub(mul(m[4][3], hi13, s1), mul(m[3][3], hi14, s2), s4), mul(m[1][3], hi34, s3), s[4][2])
+ s[4][3] = sub(sub(mul(m[2][3], hi14, s1), mul(m[1][3], hi24, s2), s4), mul(m[4][3], hi12, s3), s[4][3])
+ s[4][4] = add(sub(mul(m[1][3], hi23, s1), mul(m[2][3], hi13, s2), s4), mul(m[3][3], hi12, s3), s[4][4])
+
+ return div(s, d, r)
+ end
+ end
+ },
+
+ vornmath.utils.componentWiseReturnOnlys('inverse', 1)
+
+}
+
+-- vector relational functions
+
+vornmath.bakeries.equal = {
+ vornmath.utils.componentWiseExpander('eq', {'vector', 'vector'}, 'boolean'),
+ vornmath.utils.componentWiseReturnOnlys('equal', 2, 'boolean')
+}
+
+vornmath.bakeries.notEqual = {
+ {
+ signature_check = function(types)
+ if #types < 2 then return false end
+ for i = 1,2 do
+ if vornmath.metatables[types[i]].vm_shape ~= 'scalar' then return false end
+ end
+ return vornmath.utils.hasBakery('eq', types)
+ end,
+ create = function(types)
+ local eq = vornmath.utils.bake('eq', types)
+ return function(a,b) return not eq(a,b) end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ vornmath.utils.componentWiseExpander('notEqual', {'vector', 'vector'}, 'boolean'),
+ vornmath.utils.componentWiseReturnOnlys('notEqual', 2, 'boolean')
+}
+
+vornmath.bakeries.greaterThan = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ return function(a,b) return a > b end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ vornmath.utils.componentWiseExpander('greaterThan', {'vector', 'vector'}, 'boolean'),
+ vornmath.utils.componentWiseReturnOnlys('greaterThan', 2, 'boolean')
+}
+
+vornmath.bakeries.greaterThanEqual = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ return function(a,b) return a >= b end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ vornmath.utils.componentWiseExpander('greaterThanEqual', {'vector', 'vector'}, 'boolean'),
+ vornmath.utils.componentWiseReturnOnlys('greaterThanEqual', 2, 'boolean')
+}
+
+vornmath.bakeries.lessThan = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ return function(a,b) return a < b end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ vornmath.utils.componentWiseExpander('lessThan', {'vector', 'vector'}, 'boolean'),
+ vornmath.utils.componentWiseReturnOnlys('lessThan', 2, 'boolean')
+}
+
+vornmath.bakeries.lessThanEqual = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'number', 'number'}),
+ create = function(types)
+ return function(a,b) return a <= b end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ vornmath.utils.componentWiseExpander('lessThanEqual', {'vector', 'vector'}, 'boolean'),
+ vornmath.utils.componentWiseReturnOnlys('lessThanEqual', 2, 'boolean')
+}
+
+vornmath.bakeries.all = {
+ {
+ signature_check = function(types)
+ if #types < 1 then return false end
+ local first = vornmath.metatables[types[1]]
+ if first.vm_storage ~= 'boolean' or first.vm_shape ~= 'vector' then return false end
+ types[2] = nil
+ return true
+ end,
+ create = function(types)
+ local n = vornmath.metatables[types[1]].vm_dim
+ return function(v)
+ for i = 1,n do
+ if not v[i] then return false end
+ end
+ return true
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ }
+}
+
+vornmath.bakeries.any = {
+ {
+ signature_check = function(types)
+ if #types < 1 then return false end
+ local first = vornmath.metatables[types[1]]
+ if first.vm_storage ~= 'boolean' or first.vm_shape ~= 'vector' then return false end
+ types[2] = nil
+ return true
+ end,
+ create = function(types)
+ local n = vornmath.metatables[types[1]].vm_dim
+ return function(v)
+ for i = 1,n do
+ if v[i] then return true end
+ end
+ return false
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ }
+}
+
+vornmath.bakeries.logicalAnd = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'boolean', 'boolean'}),
+ create = function(types)
+ return function(a,b)
+ return a and b
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ vornmath.utils.componentWiseExpander('logicalAnd', {'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('logicalAnd', 2)
+}
+
+vornmath.bakeries.logicalOr = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'boolean', 'boolean'}),
+ create = function(types)
+ return function(a,b)
+ return a or b
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ vornmath.utils.componentWiseExpander('logicalOr', {'vector', 'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('logicalOr', 2)
+}
+
+vornmath.bakeries.logicalNot = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'boolean'}),
+ create = function(types)
+ return function(a)
+ return not a
+ end
+ end,
+ return_type = function(types) return 'boolean' end
+ },
+ vornmath.utils.componentWiseExpander('logicalNot', {'vector'}),
+ vornmath.utils.componentWiseReturnOnlys('logicalNot', 1)
+}
+
+-- pseudometatables for non-numerics
+
+vornmath.metatables['nil'] = {
+ vm_type = 'nil',
+ vm_shape = 'nil',
+ vm_dim = 0,
+ vm_storage = 'nil'
+}
+
+vornmath.metatables['string'] = {
+ vm_type = 'string',
+ vm_shape = 'string',
+ vm_dim = 1,
+ vm_storage = 'string'
+}
+
+vornmath.metatables['table'] = {
+ vm_type = 'table',
+ vm_shape = 'table',
+ vm_dim = 1,
+ vm_storage = 'table'
+}
+
+-- so it turns out that __unm is called with two copies of the thing to negate
+-- This breaks outvar detection and causes `-a` to actually mutate a when possible.
+-- I have to work around this by only accepting one thing!
+
+do
+ local unm = vornmath.unm
+ vornmath.utils.unmProxy = function(a) return unm(a) end
+end
+
+-- metatable setup
+
+for _, scalar_name in ipairs({'boolean', 'number', 'complex', 'quat'}) do
+ vornmath.metatables[scalar_name] = {
+ vm_type = scalar_name,
+ vm_shape = 'scalar',
+ vm_dim = 1,
+ vm_storage = scalar_name,
+ __eq = vornmath.eq,
+ __add = vornmath.add,
+ __sub = vornmath.sub,
+ __mul = vornmath.mul,
+ __div = vornmath.div,
+ __mod = vornmath.mod,
+ __unm = vornmath.utils.unmProxy,
+ __pow = vornmath.pow,
+ __tostring = vornmath.tostring,
+ }
+end
+
+for _, scalar_name in ipairs({'boolean', 'number', 'complex'}) do
+ for vector_size = 2,4 do
+ local typename = SCALAR_PREFIXES[scalar_name] .. 'vec' .. tostring(vector_size)
+ vornmath.metatables[typename] = {
+ vm_type = typename,
+ vm_shape = 'vector',
+ vm_dim = vector_size,
+ vm_storage = scalar_name,
+ __eq = vornmath.eq,
+ __add = vornmath.add,
+ __sub = vornmath.sub,
+ __mul = vornmath.mul,
+ __div = vornmath.div,
+ __mod = vornmath.mod,
+ __unm = vornmath.utils.unmProxy,
+ __pow = vornmath.pow,
+ __tostring = vornmath.tostring,
+ __index = vornmath.utils.swizzleGetter,
+ __newindex = vornmath.utils.swizzleSetter,
+ getters = {},
+ setters = {}
+ }
+ end
+end
+
+for _, scalar_name in ipairs({'number', 'complex'}) do
+ for width = 2,4 do
+ for height = 2,4 do
+ local typename = SCALAR_PREFIXES[scalar_name] .. 'mat' .. tostring(width) .. 'x' .. tostring(height)
+ vornmath.metatables[typename] = {
+ vm_type = typename,
+ vm_shape = 'matrix',
+ vm_dim = {width, height},
+ vm_storage = scalar_name,
+ __eq = vornmath.eq,
+ __add = vornmath.add,
+ __sub = vornmath.sub,
+ __mul = vornmath.mul,
+ __div = vornmath.div,
+ __mod = vornmath.mod,
+ __unm = vornmath.utils.unmProxy,
+ __pow = vornmath.pow,
+ __tostring = vornmath.tostring,
+ }
+ end
+ vornmath[SCALAR_PREFIXES[scalar_name] .. 'mat' .. tostring(width)] = vornmath[SCALAR_PREFIXES[scalar_name] .. 'mat' .. tostring(width) .. 'x' .. tostring(width)]
+ end
+end
+
+-- color
+
+local named_colors = {
+ aliceblue = "#f0f8ffff", antiquewhite = "#faebd7ff", aqua = "#00ffffff", aquamarine = "#7fffd4ff",
+ azure = "#f0ffffff", beige = "#f5f5dcff", bisque = "#ffe4c4ff", black = "#000000ff",
+ blanchedalmond = "#ffebcdff", blue = "#0000ffff", blueviolet = "#8a2be2ff", brown = "#a52a2aff",
+ burlywood = "#deb887ff", cadetblue = "#5f9ea0ff", chartreuse = "#7fff00ff",
+ chocolate = "#d2691eff", coral = "#ff7f50ff", cornflowerblue = "#6495edff",
+ cornsilk = "#fff8dcff", crimson = "#dc143cff", cyan = "#00ffffff", darkblue = "#00008bff",
+ darkcyan = "#008b8bff", darkgoldenrod = "#b8860bff", darkgray = "#a9a9a9ff",
+ darkgreen = "#006400ff", darkgrey = "#a9a9a9ff", darkkhaki = "#bdb76bff",
+ darkmagenta = "#8b008bff", darkolivegreen = "#556b2fff", darkorange = "#ff8c00ff",
+ darkorchid = "#9932ccff", darkred = "#8b0000ff", darksalmon = "#e9967aff",
+ darkseagreen = "#8fbc8fff", darkslateblue = "#483d8bff", darkslategray = "#2f4f4fff",
+ darkslategrey = "#2f4f4fff", darkturquoise = "#00ced1ff", darkviolet = "#9400d3ff",
+ deeppink = "#ff1493ff", deepskyblue = "#00bfffff", dimgray = "#696969ff", dimgrey = "#696969ff",
+ dodgerblue = "#1e90ffff", firebrick = "#b22222ff", floralwhite = "#fffaf0ff",
+ forestgreen = "#228b22ff", fuchsia = "#ff00ffff", gainsboro = "#dcdcdcff",
+ ghostwhite = "#f8f8ffff", gold = "#ffd700ff", goldenrod = "#daa520ff", gray = "#808080ff",
+ green = "#008000ff", greenyellow = "#adff2fff", grey = "#808080ff", honeydew = "#f0fff0ff",
+ hotpink = "#ff69b4ff", indianred = "#cd5c5cff", indigo = "#4b0082ff", ivory = "#fffff0ff",
+ khaki = "#f0e68cff", lavender = "#e6e6faff", lavenderblush = "#fff0f5ff", lawngreen = "#7cfc00ff",
+ lemonchiffon = "#fffacdff", lightblue = "#add8e6ff", lightcoral = "#f08080ff",
+ lightcyan = "#e0ffffff", lightgoldenrodyellow = "#fafad2ff", lightgray = "#d3d3d3ff",
+ lightgreen = "#90ee90ff", lightgrey = "#d3d3d3ff", lightpink = "#ffb6c1ff",
+ lightsalmon = "#ffa07aff", lightseagreen = "#20b2aaff", lightskyblue = "#87cefaff",
+ lightslategray = "#778899ff", lightslategrey = "#778899ff", lightsteelblue = "#b0c4deff",
+ lightyellow = "#ffffe0ff", lime = "#00ff00ff", limegreen = "#32cd32ff", linen = "#faf0e6ff",
+ magenta = "#ff00ffff", maroon = "#800000ff", mediumaquamarine = "#66cdaaff",
+ mediumblue = "#0000cdff", mediumorchid = "#ba55d3ff", mediumpurple = "#9370dbff",
+ mediumseagreen = "#3cb371ff", mediumslateblue = "#7b68eeff", mediumspringgreen = "#00fa9aff",
+ mediumturquoise = "#48d1ccff", mediumvioletred = "#c71585ff", midnightblue = "#191970ff",
+ mintcream = "#f5fffaff", mistyrose = "#ffe4e1ff", moccasin = "#ffe4b5ff",
+ navajowhite = "#ffdeadff", navy = "#000080ff", oldlace = "#fdf5e6ff", olive = "#808000ff",
+ olivedrab = "#6b8e23ff", orange = "#ffa500ff", orangered = "#ff4500ff", orchid = "#da70d6ff",
+ palegoldenrod = "#eee8aaff", palegreen = "#98fb98ff", paleturquoise = "#afeeeeff",
+ palevioletred = "#db7093ff", papayawhip = "#ffefd5ff", peachpuff = "#ffdab9ff",
+ peru = "#cd853fff", pink = "#ffc0cbff", plum = "#dda0ddff", powderblue = "#b0e0e6ff",
+ purple = "#800080ff", rebeccapurple = "#663399ff", red = "#ff0000ff", rosybrown = "#bc8f8fff",
+ royalblue = "#4169e1ff", saddlebrown = "#8b4513ff", salmon = "#fa8072ff",
+ sandybrown = "#f4a460ff", seagreen = "#2e8b57ff", seashell = "#fff5eeff", sienna = "#a0522dff",
+ silver = "#c0c0c0ff", skyblue = "#87ceebff", slateblue = "#6a5acdff", slategray = "#708090ff",
+ slategrey = "#708090ff", snow = "#fffafaff", springgreen = "#00ff7fff", steelblue = "#4682b4ff",
+ tan = "#d2b48cff", teal = "#008080ff", thistle = "#d8bfd8ff", tomato = "#ff6347ff",
+ turquoise = "#40e0d0ff", violet = "#ee82eeff", wheat = "#f5deb3ff", white = "#ffffffff",
+ whitesmoke = "#f5f5f5ff", yellow = "#ffff00ff", yellowgreen = "#9acd32ff"
+}
+
+local function dehex(x)
+ return tonumber(x, 16)
+end
+
+vornmath.bakeries.colorParse = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'string', 'vec4'}),
+ create = function(types)
+ local fill = vornmath.utils.bake('fill', {'vec4', 'number', 'number', 'number', 'number'})
+ local div = vornmath.utils.bake('div', {'vec4', 'number', 'vec4'})
+ return function(s, result)
+ local rs,gs,bs,as,r,g,b,a,size
+ if named_colors[s] then s = named_colors[s] end
+ -- hashnumbers
+ rs,gs,bs = string.match(s, '^#(%x)(%x)(%x)$')
+ if rs then
+ r, g, b, a, size = dehex(rs), dehex(gs), dehex(bs), 15, 15
+ end
+ rs,gs,bs,as = string.match(s, '^#(%x)(%x)(%x)(%x)$')
+ if rs then
+ r, g, b, a, size = dehex(rs), dehex(gs), dehex(bs), dehex(as), 15
+ end
+ rs,gs,bs = string.match(s, '^#(%x%x)(%x%x)(%x%x)$')
+ if rs then
+ r, g, b, a, size = dehex(rs), dehex(gs), dehex(bs), 255, 255
+ end
+ rs,gs,bs,as = string.match(s, '^#(%x%x)(%x%x)(%x%x)(%x%x)$')
+ if rs then
+ r, g, b, a, size = dehex(rs), dehex(gs), dehex(bs), dehex(as), 255
+ end
+ if not r then error("Unknown color code: " .. s) end
+ result = fill(result, r,g,b,a)
+ return div(result, size, result)
+ end
+ end,
+ return_type = function(types) return 'vec4' end
+ },
+ {
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'string'}),
+ create = function(types)
+ local make = vornmath.utils.bake('vec4', {})
+ local act = vornmath.utils.bake('colorParse', {'string', 'vec4'})
+ return function(s)
+ local result = make()
+ return act(s, result)
+ end
+ end,
+ return_type = function(types) return 'vec4' end
+ }
+}
+
+-- conversion formulas
+-- color_conversions[from][to]
+-- you only need to and from srgb for each thing; eventually it will try that one.
+-- more specific things can be helpful though!
+
+do
+ local fill = vornmath.utils.bake('fill', {'vec4', 'number', 'number', 'number', 'number'})
+ local clone = vornmath.utils.bake('fill', {'vec4', 'vec4'})
+ local mmul = vornmath.utils.bake('mul', {'mat4x4', 'vec4', 'vec4'})
+
+ local cc = {}
+
+ vornmath.color_conversions = cc
+
+ local cc_meta = {
+ __index = function(self, i)
+ self[i] = {}
+ return self[i]
+ end
+ }
+
+ setmetatable(cc, cc_meta)
+
+ -- hsl: hue/saturation/lightness, derived from hsv
+
+ cc.hsv.hsl = function(from, to)
+ local lightness = from[3] * (1 - from[2] / 2)
+ local saturation
+ if lightness <= 0 or lightness >= 1 then
+ saturation = 0
+ else
+ saturation = (from[3] - lightness) / math.min(lightness, 1 - lightness)
+ end
+ return fill(to, from[1], saturation, lightness, from[4])
+ end
+
+ cc.hsl.hsv = function(from, to)
+ local value = from[3] + from[2] * math.min(from[3], 1 - from[3])
+ local saturation
+ if value <= 0 then
+ saturation = 0
+ else
+ saturation = 2 * (1 - from[3] / value)
+ end
+ return fill(to, from[1], saturation, value, from[4])
+ end
+
+ -- hwb: hue/whiteness/blackness, a
+
+ cc.hsv.hwb = function(from, to)
+ to[1] = from[1]
+ to[2] = (1 - from[2]) * from[3]
+ to[3] = 1 - from[3]
+ to[4] = from[4]
+ return to
+ end
+
+ cc.hwb.hsv = function(from, to)
+ local value = 1 - from[3]
+ to[1] = from[1]
+ if value == 0 then
+ to[2] = 0
+ else
+ to[2] = 1 - from[2] / value
+ end
+ to[3] = value
+ to[4] = from[4]
+ return to
+ end
+
+ local hsv_indices = {
+ {1,2,3},
+ {2,1,3},
+ {3,1,2},
+ {3,2,1},
+ {2,3,1},
+ {1,3,2}
+ }
+
+ cc.hsv.srgb = function(from, to)
+ local small_hue = from[1] / 60
+ local segment = math.floor(small_hue) + 1
+ local is = hsv_indices[segment]
+ local hue_strength = 1 - math.abs(small_hue % 2 - 1)
+ local chroma = from[2] * from[3]
+ local secondary = chroma * hue_strength
+ local bottom = from[3] - chroma
+ local results = {chroma + bottom, secondary + bottom, bottom}
+ return fill(to, results[is[1]], results[is[2]], results[is[3]], from[4])
+ end
+
+ cc.srgb.hsv = function(from, to)
+ local value = math.max(from[1], from[2], from[3])
+ local bottom = math.min(from[1], from[2], from[3])
+ local chroma = value - bottom
+ local small_hue
+ if chroma == 0 then
+ small_hue = 0
+ else
+ if from[1] == value then
+ small_hue = (from[2] - from[3]) / chroma
+ elseif from[2] == value then
+ small_hue = (from[3] - from[1]) / chroma + 2
+ else -- from[3] == value
+ small_hue = (from[1] - from[2]) / chroma + 4
+ end
+ end
+ local hue = (small_hue % 6) * 60
+ local saturation = chroma / value
+ return fill(to, hue, saturation, value, from[4])
+ end
+
+ cc.srgb.linearrgb = function(from, to)
+ for i = 1, 3 do
+ local x = from[i]
+ if x > 0.041 then
+ to[i] = ((x+0.055)/1.055)^2.4
+ elseif x < 0.04 then
+ to[i] = x / 12.92
+ else -- between 0.04 and 0.041, the area where the two meet
+ to[i] = math.max(x / 12.92, ((x + 0.055)/1.055)^2.4)
+ end
+ end
+ to[4] = from[4]
+ return to
+ end
+
+ cc.linearrgb.srgb = function(from, to)
+ for i = 1,3 do
+ local x = from[i]
+ if x > 0.0032 then
+ to[i] = 1.055 * x^(1/2.4) - 0.055
+ elseif x < 0.003 then
+ to[i] = x * 12.92
+ else
+ to[i] = math.min(x * 12.92, 1.055 * x^(1/2.4) - 0.055)
+ end
+ end
+ to[4] = from[4]
+ return to
+ end
+
+ local xyz_from_rgb = vornmath.mat4(
+ 0.4124, 0.2126, 0.0193, 0,
+ 0.3576, 0.7152, 0.1192, 0,
+ 0.1805, 0.0722, 0.9505, 0,
+ 0 , 0 , 0 , 1
+ )
+
+ local rgb_from_xyz = vornmath.inverse(xyz_from_rgb)
+
+ cc.linearrgb.xyz = function(from, to)
+ return mmul(xyz_from_rgb, from, to)
+ end
+
+ cc.xyz.linearrgb = function(from, to)
+ return mmul(rgb_from_xyz, from, to)
+ end
+
+ local whitepoint = vornmath.vec4(0.95489,1,1.088840,1)
+
+
+ do
+ local lab_from_modded_xyz = vornmath.mat4(
+ 116, -500, -200, 0,
+ 0, 500, 0, 0,
+ 0, 0, 200, 0,
+ 0, 0, 0, 1
+ )
+
+ local modded_xyz_from_lab = vornmath.inverse(lab_from_modded_xyz)
+
+ local div = vornmath.utils.bake('div', {'vec4', 'vec4', 'vec4'})
+ local pow = vornmath.utils.bake('pow', {'vec4', 'number', 'vec4'})
+ local add = vornmath.utils.bake('add', {'vec4', 'number', 'vec4'})
+ local mul = vornmath.utils.bake('mul', {'vec4', 'number', 'vec4'})
+ local vmul = vornmath.utils.bake('mul', {'vec4', 'vec4', 'vec4'})
+ local pick = vornmath.utils.bake('mix', {'vec4', 'vec4', 'bvec4', 'vec4'})
+ local lt = vornmath.utils.bake('lessThan', {'vec4', 'vec4', 'bvec4'})
+ local cubic = vornmath.vec4()
+ local linear = vornmath.vec4()
+ local flags = vornmath.bvec4()
+
+ local lab_threshold = vornmath.vec4(216/24389)
+ local xyz_threshold = vornmath.vec4(6/29)
+
+ cc.xyz.lab = function(from, to)
+ local alpha = from[4]
+ to = div(from, whitepoint, to)
+ cubic = pow(to, 1/3, cubic)
+ linear = add(mul(to, 12/841, linear), 4/29, linear)
+ flags = lt(to, lab_threshold, flags)
+ to = pick(cubic, linear, flags, to)
+ to = mmul(lab_from_modded_xyz, to, to)
+ to[4] = alpha
+ to[1] = to[1] - 16
+ return to
+ end
+
+ cc.lab.xyz = function(from, to)
+ local alpha = from[4]
+ to = clone(to, from)
+ to[1] = to[1] + 16
+ to = mmul(modded_xyz_from_lab, to, to)
+ linear = add(mul(from, 841/12, linear), -4/29, linear)
+ cubic = pow(to, 3, cubic)
+ flags = lt(to, xyz_threshold, flags)
+ to = pick(cubic, linear, flags, to)
+ to = vmul(to, whitepoint, to)
+ to[4] = alpha
+ return to
+ end
+ end
+
+ local atan = math.atan2 or math.atan
+ local hypot = vornmath.utils.bake('hypot', {'number', 'number'})
+ cc.lab.lch = function(from, to)
+ to[1] = from[1]
+ to[4] = from[4]
+ local chroma = hypot(from[2], from[3])
+ local hue = math.deg(atan(from[3], from[2])) % 360
+ to[2], to[3] = chroma, hue
+ return to
+ end
+
+
+ cc.lch.lab = function(from, to)
+ to[1] = from[1]
+ to[4] = from[4]
+ local chroma = from[2]
+ local hue_radians = math.rad(from[3])
+ to[2] = chroma * math.cos(hue_radians)
+ to[3] = chroma * math.sin(hue_radians)
+ return to
+ end
+ local duplicate = vornmath.utils.bake('fill', {'vec4', 'vec4'})
+
+ do
+ local pow = vornmath.utils.bake('pow', {'vec4', 'vec4', 'vec4'})
+
+ local responses_from_xyz = vornmath.mat4(
+ 0.8189330101, 0.0329845436, 0.0482003018, 0,
+ 0.3618667424, 0.9293118715, 0.2643662691, 0,
+ -0.1288597137, 0.0361456387, 0.6338517070, 0,
+ 0 , 0 , 0 , 1
+ )
+
+ local oklab_from_responses = vornmath.mat4(
+ 0.2104542553, 1.9779984951, 0.0259040371, 0,
+ 0.7936177850, -2.4285922050, 0.7827717662, 0,
+ -0.0040720468, 0.4505937099, -0.8086757660, 0,
+ 0 , 0 , 0 , 1
+ )
+
+ local forward_powers = vornmath.vec4(1/3, 1/3, 1/3, 1)
+ local backward_powers = vornmath.vec4(3, 3, 3, 1)
+
+ local xyz_from_responses = vornmath.inverse(responses_from_xyz)
+ local responses_from_oklab = vornmath.inverse(oklab_from_responses)
+
+ cc.xyz.oklab = function(from, to)
+ to = mmul(responses_from_xyz, from, to)
+ to = pow(to, forward_powers, to)
+ return mmul(oklab_from_responses, to, to)
+ end
+
+ cc.oklab.xyz = function(from, to)
+ to = mmul(responses_from_oklab, from, to)
+ to = pow(to, backward_powers, to)
+ return mmul(xyz_from_responses, to, to)
+ end
+ end
+
+ cc.oklab.oklch = cc.lab.lch
+ cc.oklch.oklab = cc.lch.lab
+
+
+ local function reverseFill(a,b)
+ return duplicate(b,a)
+ end
+
+ local function generateColorConverter(steps)
+ if #steps == 1 then
+ return reverseFill
+ elseif #steps == 2 then
+ return vornmath.color_conversions[steps[1]][steps[2]]
+ else
+ -- this one's big, so I have to compose the functions.
+ local functions = {}
+ local conversion_lines = {}
+ local loader_lines = {}
+ for i = 1,#steps - 1 do
+ local from = steps[i]
+ local to = steps[i+1]
+ local f = vornmath.color_conversions[from][to]
+ local name = from .. '_to_' .. to
+ local loader_line = "local " .. name .. " = converters[" .. i .. "]"
+ local conversion_opener, conversion_send
+ if i == #steps - 1 then
+ conversion_opener = 'return '
+ else
+ conversion_opener = 'to = '
+ end
+ if i == 1 then
+ conversion_send = 'from'
+ else
+ conversion_send = 'to'
+ end
+ local conversion_line = conversion_opener .. name .. '(' .. conversion_send .. ', to)'
+ table.insert(functions, f)
+ table.insert(loader_lines, loader_line)
+ table.insert(conversion_lines, conversion_line)
+ end
+ local code = 'local converters = select(1, ...)\n' .. table.concat(loader_lines,'\n') .. '\nreturn function(from, to)\n ' .. table.concat(conversion_lines, '\n ') .. '\nend'
+ return load(code)(functions)
+ end
+ end
+
+ local colorFroms = {}
+ local colorTos = {}
+
+ function vornmath.utils.prepareColorConverters()
+ local itinerary = {{vornmath.settings._colorspace, {vornmath.settings._colorspace}}}
+ local visited_to = {}
+ while #itinerary > 0 do
+ local current = table.remove(itinerary, 1)
+ if not visited_to[current[1]] then
+ visited_to[current[1]] = current[2]
+ for nearby,_ in pairs(vornmath.color_conversions[current[1]]) do
+ if not visited_to[nearby] then
+ local new_route = {}
+ for _,step in ipairs(current[2]) do
+ table.insert(new_route, step)
+ end
+ table.insert(new_route, nearby)
+ table.insert(itinerary, {nearby, new_route})
+ end
+ end
+ end
+ end
+ for target, steps in pairs(visited_to) do
+ colorTos[target] = generateColorConverter(steps)
+ end
+ -- now do the froms
+ itinerary = {{vornmath.settings._colorspace, {vornmath.settings._colorspace}}}
+ local visited_from = {}
+ while #itinerary > 0 do
+ local current = table.remove(itinerary, 1)
+ if not visited_from[current[1]] then
+ visited_from[current[1]] = current[2]
+ -- we're filling it out backwards: what can I get *to* this thing from?
+ for source,targets in pairs(vornmath.color_conversions) do
+ if targets[current[1]] then
+ if not visited_from[source] then
+ local new_route = {source}
+ for _,step in ipairs(current[2]) do
+ table.insert(new_route, step)
+ end
+ table.insert(itinerary, {source, new_route})
+ end
+ end
+ end
+ end
+ end
+ for target, steps in pairs(visited_from) do
+ colorFroms[target] = generateColorConverter(steps)
+ end
+ end
+
+ vornmath.bakeries.colorFrom = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'vec4', 'string', 'vec4'}),
+ create = function(types)
+ return function(from, space, to)
+ assert(colorFroms[space], "unknown color space '" .. space .."'.")
+ return colorFroms[space](from, to)
+ end
+ end,
+ return_type = function(types) return 'vec4' end
+ },
+ {
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'vec4', 'string'}),
+ create = function(types)
+ local construct = vornmath.utils.bake('vec4', {})
+ local f = vornmath.utils.bake('colorFrom', {'vec4', 'string', 'vec4'})
+ return function(from, space)
+ return f(from, space, construct())
+ end
+ end,
+ return_type = function(types) return 'vec4' end
+ }
+ }
+
+ vornmath.bakeries.colorTo = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'vec4', 'string', 'vec4'}),
+ create = function(types)
+ return function(from, space, to)
+ assert(colorTos[space], "unknown color space '" .. space .. "'.")
+ return colorTos[space](from, to)
+ end
+ end,
+ return_type = function(types) return 'vec4' end
+ },
+ {
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'vec4', 'string'}),
+ create = function(types)
+ local construct = vornmath.utils.bake('vec4', {})
+ local f = vornmath.utils.bake('colorTo', {'vec4', 'string', 'vec4'})
+ return function(from, space)
+ return f(from, space, construct())
+ end
+ end,
+ return_type = function(types) return 'vec4' end
+ }
+ }
+
+ local hue_indices = {
+ hsl = 1,
+ hsv = 1,
+ hwb = 1,
+ lch = 3,
+ oklch = 3
+ }
+
+ vornmath.color_hue_indices = hue_indices
+
+ local missing_channels = {
+ hsl = function(c,v)
+ if c[3] >= 1 or c[3] <= 0 then
+ v[1] = true
+ v[2] = true
+ end
+ if c[2] == 0 then
+ v[1] = true
+ end
+ return v
+ end,
+ hsv = function(c,v)
+ if c[3] <= 0 then
+ v[1] = true
+ v[2] = true
+ end
+ if c[2] == 0 then
+ v[1] = true
+ end
+ return v
+ end,
+ hwb = function(c,v)
+ if c[2] + c[3] >= 1 then
+ v[1] = true
+ end
+ return v
+ end,
+ lch = function(c,v)
+ if c[1] <= 0 then
+ v[2] = true
+ v[3] = true
+ end
+ if c[2] == 0 then
+ v[3] = true
+ end
+ return v
+ end,
+ oklch = function(c,v)
+ if c[1] <= 0 then
+ v[2] = true
+ v[3] = true
+ end
+ if c[2] == 0 then
+ v[3] = true
+ end
+ return v
+ end
+ }
+
+ vornmath.color_channel_fixes = missing_channels
+
+ vornmath.bakeries.colorMix = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'vec4', 'vec4', 'number', 'string', 'vec4'}),
+ create = function(types)
+ local colorTo = vornmath.utils.bake('colorTo', {'vec4', 'string', 'vec4'})
+ local colorFrom = vornmath.utils.bake('colorFrom', {'vec4', 'string', 'vec4'})
+ local clear_flags = vornmath.utils.bake('fill', {'bvec4'})
+ local mix = vornmath.utils.bake('mix', {'vec4', 'vec4', 'number', 'vec4'})
+ local a_spaced = vornmath.vec4()
+ local b_spaced = vornmath.vec4()
+ local a_missing = vornmath.bvec4()
+ local b_missing = vornmath.bvec4()
+ return function(a, b, t, space, result)
+ -- convert colors into the interpolating space
+ a_spaced = colorTo(a, space, a_spaced)
+ b_spaced = colorTo(b, space, b_spaced)
+ -- look for "missing" components, where the data is ambiguous
+ a_missing = clear_flags(a_missing)
+ b_missing = clear_flags(b_missing)
+ local check_missing = missing_channels[space]
+ if check_missing then
+ a_missing = check_missing(a_spaced, a_missing)
+ b_missing = check_missing(b_spaced, b_missing)
+ end
+ for i = 1,4 do
+ -- fill in missing components with set data; use the other input if available
+ if a_missing[i] and b_missing[i] then
+ a_spaced[i] = 0
+ b_spaced[i] = 0
+ elseif a_missing[i] then
+ a_spaced[i] = b_spaced[i]
+ elseif b_missing[i] then
+ b_spaced[i] = a_spaced[i]
+ end
+ -- alpha premultiply
+ if i ~= hue_indices[space] then
+ if i ~= 4 then
+ a_spaced[i] = a_spaced[i] * a_spaced[4]
+ b_spaced[i] = b_spaced[i] * b_spaced[4]
+ end
+ -- rewrap hue angle for calculation
+ else
+ if math.abs(a_spaced[i] - b_spaced[i]) > 180 then
+ if a_spaced[i] < b_spaced[i] then
+ a_spaced[i] = a_spaced[i] + 360
+ else
+ b_spaced[i] = b_spaced[i] + 360
+ end
+ end
+ end
+ end
+ result = mix(a_spaced, b_spaced, t, result)
+ -- check if we're transparent
+ if result[4] == 0 then
+ result = fill(result, 0,0,0,0)
+ return colorFrom(result, 'srgb', result)
+ else
+ for i = 1,3 do
+ -- unmultiply alpha
+ if i ~= hue_indices[space] then
+ result[i] = result[i] / result[4]
+ else
+ -- rewrap hue angle for result
+ result[i] = result[i] % 360
+ end
+ end
+ return colorFrom(result, space, result)
+ end
+ end
+ end,
+ return_type = function(types) return 'vec4' end
+ },
+ {
+ signature_check = vornmath.utils.nilFollowingExactTypeCheck({'vec4', 'vec4', 'number', 'string'}),
+ create = function(types)
+ local construct = vornmath.utils.bake('vec4', {})
+ local f = vornmath.utils.bake('colorMix', {'vec4', 'vec4', 'number', 'string', 'vec4'})
+ return function(a, b, t, space)
+ return f(a, b, t, space, construct())
+ end
+ end,
+ return_type = function(types) return 'vec4' end
+ }
+ }
+
+ vornmath.bakeries.colorFallback = {
+ {
+ signature_check = vornmath.utils.clearingExactTypeCheck({'vec4', 'vec4'}),
+ create = function(types)
+ local colorTo = vornmath.utils.bake('colorTo', {'vec4', 'string', 'vec4'})
+ local colorFrom = vornmath.utils.bake('colorFrom', {'vec4', 'string', 'vec4'})
+ local toLinearRGB = generateColorConverter({'oklch', 'oklab', 'xyz', 'linearrgb'})
+ local fromLinearRGB = generateColorConverter({'linearrgb', 'xyz', 'oklab', 'oklch'})
+ local toOkLab = cc.oklch.oklab
+ local clamp = vornmath.utils.bake('clamp', {'vec4', 'vec4', 'vec4', 'vec4'})
+ local minComponent = vornmath.utils.bake('minComponent', {'vec4'})
+ local maxComponent = vornmath.utils.bake('maxComponent', {'vec4'})
+ local distance = vornmath.utils.bake('distance', {'vec4', 'vec4'})
+ local gamut_target = vornmath.vec4()
+ local a_oklab = vornmath.vec4()
+ local b_oklab = vornmath.vec4()
+ local clipped = vornmath.vec4()
+ local clamp_bottom = vornmath.vec4(0,0,0,0)
+ local clamp_top = vornmath.vec4(1,1,1,1)
+ local function in_gamut(c)
+ gamut_target = toLinearRGB(c, gamut_target)
+ local bottom = minComponent(gamut_target)
+ local top = maxComponent(gamut_target)
+ return bottom >= 0 and top <= 1
+ end
+ local function clip(c, result)
+ result = toLinearRGB(c, result)
+ result = clamp(result, clamp_bottom, clamp_top, result)
+ return fromLinearRGB(result, result)
+ end
+ local function delta(a, b)
+ a_oklab = toOkLab(a, a_oklab)
+ b_oklab = toOkLab(b, b_oklab)
+ return distance(a_oklab, b_oklab)
+ end
+ local current = vornmath.vec4()
+ local transparent = vornmath.vec4(0,0,0,0)
+ local white = vornmath.vec4(1,0,0,1)
+ local black = vornmath.vec4(0,0,0,1)
+ local close_enough = 0.02
+ local resolution = 0.0001
+ return function(c,result)
+ current = colorTo(c, 'oklch', current)
+ -- if origin has alpha greater than 1, just set it to 1 as fully opaque
+ if current[4] > 1 then
+ current[4] = 1
+ -- if origin has an alpha of 0 or less, return fully black transparent
+ elseif current[4] <= 0 then
+ return colorFrom(transparent, 'oklch', result)
+ end
+ -- if origin is fully white or brighter, return white.
+ if current[1] >= 1 then
+ result = colorFrom(white, 'oklch', result)
+ result[4] = current[4]
+ return result
+ end
+ -- if it's fully black or blacker, return black.
+ if current[1] <= 0 then
+ result = colorFrom(black, 'oklch', result)
+ result[4] = current[4]
+ return result
+ end
+ -- if it's in gamut completely, we're done already.
+ if in_gamut(current) then
+ return colorFrom(current, 'oklch', result)
+ end
+ -- try clipping, is that close enough?
+ clipped = clip(current, clipped)
+ if delta(clipped, current) < close_enough then
+ return colorFrom(clipped, 'oklch', result)
+ end
+ -- okay, all the short circuits done, let's try reducing the chroma.
+ local low_chroma = 0
+ local high_chroma = current[2]
+ local low_in_gamut = true
+ while high_chroma - low_chroma > resolution do
+ local chroma = (low_chroma + high_chroma) / 2
+ current[2] = chroma
+ -- move low up till it's as close to the gamut edge as I can get
+ if in_gamut(current) and low_in_gamut then
+ low_chroma = chroma
+ else
+ clipped = clip(current, clipped)
+ local gamut_distance = delta(clipped, current)
+ -- are we close to the gamut edge?
+ if gamut_distance < close_enough then
+ -- if we're really close to the with-clip edge, then we're done
+ if close_enough - gamut_distance < resolution then
+ return colorFrom(clipped, 'oklch', result)
+ else
+ -- otherwise, step the low chroma out of the gamut
+ low_in_gamut = false
+ low_chroma = chroma
+ end
+ else
+ -- step the high chroma down
+ high_chroma = chroma
+ end
+ end
+ end
+ return colorFrom(clipped, 'oklch', result)
+ end
+ end,
+ return_type = function(types) return 'vec4' end
+ },
+ vornmath.utils.componentWiseReturnOnlys('colorFallback', 1)
+ }
+end
+
+vornmath.settings.setColorspace('srgb')
+
+return vornmath
diff --git a/game/main.lua b/game/main.lua
index a1a5d59..9f34db6 100644
--- a/game/main.lua
+++ b/game/main.lua
@@ -1,40 +1,40 @@
-local mode = {
- require("src.modes.racing"),
- require("src.modes.raising_sim")
+local wm = require("world_map")
+
+world = {
+ ["main_menu"] = require("src.world.main_menu")(),
+ ["1_intro"] = require("src.world.1_intro")(),
+ ["2_town_square"] = require("src.world.2_town_square")(),
+ ["race"] = require("src.world.race")(),
+ ["train"] = require("src.world.train")(),
};
-local actor = require("src.entities.shared.actor")
-local player = actor.load("player")
+current = wm["2_town_square"]
-local mode_i = 1
+function load_world(world_to_load)
+ current = world_to_load
+ world[current]:reload()
+end
function love.load()
- mode[mode_i].load(player)
+ world[current]:load()
end
function love.update(dt)
- mode[mode_i].update(dt)
+ world[current]:update(dt)
end
function love.draw()
- mode[mode_i].draw()
+ world[current]:draw()
end
function love.keyreleased(key, scancode)
- if (key == "right") then
- mode_i = mode_i + 1
- if (mode_i > 2) then
- mode_i = 1
- end
- mode[mode_i].load(player)
- end
- mode[mode_i].keyreleased(key, scancode)
+ world[current]:keyreleased(key, scancode)
end
function love.keypressed(key, scancode, isrepeat)
- mode[mode_i].keypressed(key, scancode, isrepeat)
+ world[current]:keypressed(key, scancode, isrepeat)
end
function love.mousereleased(x, y, button, istouch, presses)
- mode[mode_i].mousereleased(x, y, button, istouch, presses)
+ world[current]:mousereleased(x, y, button, istouch, presses)
end
diff --git a/game/src/world/1_intro/init.lua b/game/src/world/1_intro/init.lua
new file mode 100644
index 0000000..3dbd806
--- /dev/null
+++ b/game/src/world/1_intro/init.lua
@@ -0,0 +1,68 @@
+local reap = require("lib.reap")
+
+local BASE = reap.base_path(...)
+
+local world = require("wrapper.Concord.world")
+
+local debug_entity = require("src.world.common.template.debug_entity")
+local button = require("src.world.common.template.button")
+local video = require("src.world.common.template.video")
+local c_video = require("src.world.common.component.video")
+local wm = require("world_map")
+
+local wrapper = world:extend()
+
+local function button_func()
+ load_world(wm["2_town_square"])
+end
+
+function wrapper:new()
+ wrapper.super.new(self, BASE, ".1_intro")
+end
+
+function wrapper:load(_args)
+ wrapper.super.load(self, {
+ "src/world/common/system/"
+ }, {
+ {
+ assemblage = debug_entity.assembleDebug,
+ data = {
+ position = {0, 0},
+ label = "1_intro"
+ }
+ },
+ {
+ assemblage = button.assemble,
+ data = {
+ collider = {
+ x = 20,
+ y = 20,
+ w = 20,
+ h = 20
+ },
+ func = button_func,
+ label = "skip"
+ }
+ },
+ {
+ assemblage = video.assemble,
+ data = {
+ path = "asset/video/1_intro.ogv"
+ }
+ }
+ })
+end
+
+function wrapper:update(dt)
+ wrapper.super.update(self, dt)
+ for k, v in pairs(self.entities) do
+ local c_v = v[c_video.dict.video]
+ if c_v ~= nil then
+ if (not c_v.data.video:isPlaying()) then
+ load_world(wm["2_town_square"])
+ end
+ end
+ end
+end
+
+return wrapper
diff --git a/game/src/world/2_town_square/component/perspective.lua b/game/src/world/2_town_square/component/perspective.lua
new file mode 100644
index 0000000..822bf08
--- /dev/null
+++ b/game/src/world/2_town_square/component/perspective.lua
@@ -0,0 +1,69 @@
+local components = {}
+
+components.dict = {
+ resolution = "perspective.resolution",
+ lanes = "perspective.lanes",
+ draw_distance = "perspective.draw_distance",
+ road_width = "perspective.road_width",
+ fog_density = "perspective.fog_density",
+
+ field_of_view = "perspective.field_of_view",
+ camera_height = "perspective.camera_height",
+
+ segment_length = "perspective.segment_length",
+ segment_count = "perspective.segment_count",
+ rumble_length = "perspective.rumble_length",
+
+ segment_path = "perspective.segment_path",
+ segment_sprite_map_path = "perspective.segment_sprite_map_path"
+}
+
+function components.resolution (c, x, y)
+ c.data = {love.graphics.getDimensions()}
+end
+
+function components.lanes (c, x)
+ c.data = x
+end
+
+function components.draw_distance (c, x)
+ c.data = x
+end
+
+function components.road_width (c, x)
+ c.data = x
+end
+
+function components.fog_density (c, x)
+ c.data = x
+end
+
+function components.field_of_view (c, x)
+ c.data = x
+end
+
+function components.camera_height (c, x)
+ c.data = x
+end
+
+function components.segment_length (c, x)
+ c.data = x
+end
+
+function components.segment_count (c, x)
+ c.data = x
+end
+
+function components.rumble_length (c, x)
+ c.data = x
+end
+
+function components.segment_path (c, x)
+ c.data = x
+end
+
+function components.segment_sprite_map_path (c, x)
+ c.data = x
+end
+
+return components
diff --git a/game/src/world/2_town_square/component/pos3d.lua b/game/src/world/2_town_square/component/pos3d.lua
new file mode 100644
index 0000000..4ea4347
--- /dev/null
+++ b/game/src/world/2_town_square/component/pos3d.lua
@@ -0,0 +1,13 @@
+local vm = require("lib.vornmath")
+
+local components = {}
+
+components.dict = {
+ pos = "pos3d.pos"
+}
+
+function components.pos (c, x, y, z)
+ c.data = vm.vec3({x, y, z})
+end
+
+return components
diff --git a/game/src/world/2_town_square/init.lua b/game/src/world/2_town_square/init.lua
new file mode 100644
index 0000000..4fb747e
--- /dev/null
+++ b/game/src/world/2_town_square/init.lua
@@ -0,0 +1,37 @@
+local reap = require("lib.reap")
+
+local BASE = reap.base_path(...)
+
+local world = require("wrapper.Concord.world")
+
+local debug_entity = require("src.world.common.template.debug_entity")
+local road = require("src.world.2_town_square.template.road")
+
+local wm = require("world_map")
+
+local wrapper = world:extend()
+
+function wrapper:new()
+ wrapper.super.new(self, BASE, ".2_town_square")
+end
+
+function wrapper:load(_args)
+ wrapper.super.load(self, {
+ "src/world/common/system/",
+ "src/world/2_town_square/system"
+ }, {
+ {
+ assemblage = debug_entity.assembleDebug,
+ data = {
+ position = {0, 0},
+ label = "2_town_square"
+ }
+ },
+ {
+ assemblage = road.assemble,
+ data = road.default_data
+ },
+ })
+end
+
+return wrapper
diff --git a/game/src/world/2_town_square/pseudo3d/collide.lua b/game/src/world/2_town_square/pseudo3d/collide.lua
new file mode 100644
index 0000000..adf8b3d
--- /dev/null
+++ b/game/src/world/2_town_square/pseudo3d/collide.lua
@@ -0,0 +1,12 @@
+local utils = {}
+
+function utils.overlap(x1, w1, x2, w2, percent)
+ local half = (percent or 1)/2;
+ local min1 = x1 - (w1*half);
+ local max1 = x1 + (w1*half);
+ local min2 = x2 - (w2*half);
+ local max2 = x2 + (w2*half);
+ return not ((max1 < min2) or (min1 > max2));
+end
+
+return utils
diff --git a/game/src/world/2_town_square/pseudo3d/ease.lua b/game/src/world/2_town_square/pseudo3d/ease.lua
new file mode 100644
index 0000000..60c2943
--- /dev/null
+++ b/game/src/world/2_town_square/pseudo3d/ease.lua
@@ -0,0 +1,55 @@
+local vm = require("lib.vornmath")
+
+local utils = {}
+
+function utils.easeIn(a,b,percent)
+ return a + (b-a)*math.pow(percent,2);
+end
+
+function utils.easeOut(a,b,percent)
+ return a + (b-a)*(1-math.pow(1-percent,2));
+end
+
+function utils.easeInOut(a,b,percent)
+ return a + (b-a)*((-math.cos(percent*math.pi)/2) + 0.5);
+end
+
+function utils.percentRemaining(n, total)
+ return (n%total)/total;
+end
+
+function utils.interpolate(a, b, percent)
+ return a + (b-a)*percent
+end
+
+function utils.randomInt(min, max)
+ return vm.round(utils.interpolate(min, max, math.random()));
+end
+
+function utils.randomChoice(options)
+ return options[utils.randomInt(1, #options)]
+end
+
+function utils.limit(value, min, max)
+ return math.max(min, math.min(value, max))
+end
+
+function utils.accelerate(v, accel, dt)
+ return v + (accel * dt)
+end
+
+function utils.exponentialFog(distance, density)
+ return 1 / (math.exp(distance * distance * density))
+end
+
+function utils.increase(start, increment, max, is_loop) -- with looping
+ local result = start + increment
+ if (result > max) and is_loop then
+ result = 1
+ elseif result <= 0 and is_loop then
+ result = max - 1
+ end
+ return result
+end
+
+return utils
diff --git a/game/src/world/2_town_square/pseudo3d/render/road.lua b/game/src/world/2_town_square/pseudo3d/render/road.lua
new file mode 100644
index 0000000..983524d
--- /dev/null
+++ b/game/src/world/2_town_square/pseudo3d/render/road.lua
@@ -0,0 +1,89 @@
+local ease = require("src.world.2_town_square.pseudo3d.ease")
+
+local utils = {}
+
+local function drawQuad(slice, image, quads, x1, y1, x2, y2, w1, w2, h1, h2, sw, sh)
+ for i = 1, #quads do
+ local percent = ease.percentRemaining(i, #quads)
+ local destY = ease.interpolate(y1, y2, percent)
+ local destX = ease.interpolate(x1, x2, percent)
+ local destW = ease.interpolate(w1, w2, percent)
+ local destH = ease.interpolate(h1, h2, percent)
+
+ if (slice == "vertical") then
+ love.graphics.draw(image,quads[i],
+ destX, destY, 0, destW / sw, 1
+ )
+ elseif (slice == "horizontal") then
+ love.graphics.draw(image,quads[i],
+ destX, destY, 0, 1, destH / sh
+ )
+ end
+ end
+end
+
+local function drawSection(texture, key, x1, y1, x2, y2, w1, w2, h1, h2, resolution)
+ local image = texture[key].image
+ local quads = texture[key].quads
+
+ local sw, sh = image:getDimensions()
+
+ if (key == "floor") then
+ drawQuad("vertical", image, quads,
+ x1, y1,
+ x2, y2,
+ w1, w2,
+ h1, h2,
+ sw, sh
+ )
+ elseif (key == "ceil") then
+ drawQuad("vertical", image, quads,
+ x1, -y1 + resolution[2]/2,
+ x2, -y2 + resolution[2]/2,
+ w1, w2,
+ h1, h2,
+ sw, sh
+ )
+ elseif (key == "wallL") then
+ drawQuad("horizontal", image, quads,
+ x1, -y1 + h1,
+ x2, -y2 + h2,
+ w1, w2,
+ h1, h2,
+ sw, sh
+ )
+ elseif (key == "wallR") then
+ drawQuad("horizontal", image, quads,
+ x1 + w1, -y1,
+ x2 + w2, -y2,
+ w1, w2,
+ h1 + y1,
+ h2 + y2,
+ sw, sh
+ )
+ end
+end
+
+function utils.draw(p1screenpos, p2screenpos, p1screensize, p2screensize, texture, resolution, maxy)
+ local x1 = p1screenpos[1]
+ local y1 = p1screenpos[2]
+ local x2 = p2screenpos[1]
+ local y2 = p2screenpos[2]
+
+ local w1 = p1screensize[1]
+ local w2 = p2screensize[1]
+
+ local h1 = (y1)
+ local h2 = (y2)
+
+ local x1e = x1 + w1
+ local x2e = x2 + w2
+
+ drawSection(texture, "wallL", x1 , y1, x2, y2, w1, w2, h1, h2, resolution)
+ drawSection(texture, "wallR", x1 , y1, x2, y2, w1, w2, h1, h2, resolution)
+ drawSection(texture, "floor", x1 , y1, x2, y2, w1, w2, h1, h2, resolution)
+ drawSection(texture, "ceil", x1 , y1, x2, y2, w1, w2, h1, h2, resolution)
+
+end
+
+return utils
diff --git a/game/src/world/2_town_square/pseudo3d/render/sprite.lua b/game/src/world/2_town_square/pseudo3d/render/sprite.lua
new file mode 100644
index 0000000..43409dc
--- /dev/null
+++ b/game/src/world/2_town_square/pseudo3d/render/sprite.lua
@@ -0,0 +1,72 @@
+local projection = require("lib.choro.projection")
+
+local UV = require("engine.utils.obj3d.texture.uv")
+local quadToUV = UV.quadToUV
+local imageToUV = UV.imageToUV
+
+local QuadCache = require("engine.cache.loveapi.quadcache").obj
+
+local utils = {}
+
+function utils.drawstatic(segment, roadWidth, resolution)
+ -- render roadside sprites
+ local w2 = resolution[1]/2
+ for i = 1, #segment.sprites do
+ local sprite = segment.sprites[i].source;
+ local spriteoffset = segment.sprites[i].offset;
+
+ local spritePath = segment.sprites[i].data.path;
+ local spriteType = segment.sprites[i].data.type;
+ local spriteState = segment.sprites[i].data.state;
+
+ local quad
+ local img
+ if (spriteType == "aseprite") then
+ img = sprite.image
+ quad = sprite.frame.quad
+ else
+ img = sprite
+ quad = QuadCache:load(spritePath, 0, 0, 1, 1, img:getWidth(), img:getHeight(), spriteType)
+ end
+
+ local u, v, nu, nv = quadToUV(quad)
+
+ local spriteScale = segment.p1.screen.scale;
+ local spriteX = segment.p1.screen.pos.x + (spriteScale * spriteoffset * roadWidth * w2);
+ local spriteY = segment.p1.screen.pos.y;
+
+ local offsetX
+ if (spriteoffset < 0) then
+ offsetX = -1
+ else
+ offsetX = 0
+ end
+ local offsetY = -1
+
+ -- scale for projection AND relative to roadWidth (for tweakUI)
+ local destW = (img:getWidth() * spriteScale * w2) * (projection.getSpriteScale(resolution[1]/3) * roadWidth);
+ local destH = (img:getHeight() * spriteScale * w2) * (projection.getSpriteScale(resolution[1]/3) * roadWidth);
+
+ local destX = spriteX + (destW * (offsetX or 0));
+ local destY = spriteY + (destH * (offsetY or 0));
+
+ local clipH = 0;
+ if (segment.clip) then
+ clipH = math.max(0, destY+destH-segment.clip)
+ end
+
+ if (clipH < destH) then
+ local _nv = nv - (nv * clipH/destH)
+
+ local _destH = destH - clipH
+
+ local _quad = quad
+ if (_nv ~= nv) then
+ _quad = QuadCache:load(spritePath, u, v, nu, _nv, img:getWidth(), img:getHeight(), spriteType, spriteState)
+ end
+ love.graphics.draw(img, _quad, destX, destY, 0, destW, _destH)
+ end
+ end
+end
+
+return utils
diff --git a/game/src/world/2_town_square/pseudo3d/render/uv.lua b/game/src/world/2_town_square/pseudo3d/render/uv.lua
new file mode 100644
index 0000000..70546b9
--- /dev/null
+++ b/game/src/world/2_town_square/pseudo3d/render/uv.lua
@@ -0,0 +1,37 @@
+--- get texture UV
+---@param x number y
+---@param y number x
+---@param frameSizeX number size Frame X
+---@param frameSizeY number size Frame Y
+---@param sw number size Width
+---@param sh number size Height
+---@return number, number, number, number
+local function getUV(x, y, frameSizeX, frameSizeY, sw, sh)
+ return
+ x / sw,
+ y / sh,
+ (x + 1 * frameSizeX) / sw,
+ (y + 1 * frameSizeY) / sh
+end
+
+--- get uv from quad
+---@param quad love.Quad
+local function quadToUV(quad)
+ local sw, sh = quad:getTextureDimensions()
+ local x, y, w, h = quad:getViewport()
+ local u, v, nu, nv = getUV(x, y, w, h, sw, sh)
+ return u, v, nu, nv
+end
+
+--- get uv from image
+---@param image love.Image
+local function imageToUV(image)
+ local u, v, nu, nv = getUV(0, 0, image:getWidth(), image:getHeight(),image:getWidth(), image:getHeight())
+ return u, v, nu, nv
+end
+
+return {
+ getUV = getUV,
+ imageToUV = imageToUV,
+ quadToUV = quadToUV
+}
diff --git a/game/src/world/2_town_square/pseudo3d/roadsegment.lua b/game/src/world/2_town_square/pseudo3d/roadsegment.lua
new file mode 100644
index 0000000..a3a2795
--- /dev/null
+++ b/game/src/world/2_town_square/pseudo3d/roadsegment.lua
@@ -0,0 +1,332 @@
+local reap = require("lib.reap")
+
+local BASE = reap.base_path(...)
+
+local easeFN = require(BASE .. ".ease")
+local json = require("lib.json")
+local QuadCache = require("wrapper.lappy.new.quad").obj
+local ImageCache = require("wrapper.lappy.new.image").obj
+
+local utils = {}
+
+local function loadJSON(path)
+ local data = json.decode(love.filesystem.read(path))
+ return data
+end
+
+local ROAD = {
+ LENGTH= { NONE= 0, SHORT= 25, MEDIUM= 50, LONG= 100 }, -- num segments
+ HILL= { NONE= 0, LOW= 20, MEDIUM= 40, HIGH= 60 },
+ CURVE= { NONE= 0, EASY= 2, MEDIUM= 4, HARD= 6 }
+};
+
+-- function utils.getRumbleColor(index, rumbleLength)
+-- local colorDark = hex2color("#1a1d33")
+-- local colorBright = hex2color("#c8d0fa")
+-- if (math.floor(index/rumbleLength)%2 == 0) then
+-- return colorBright
+-- else
+-- return colorDark
+-- end
+-- end
+
+-- function utils.getFinishStartColor()
+-- local colorDark = hex2color("#CCFFCC")
+-- local colorBright = hex2color("#FFCCFF")
+
+-- return colorDark, colorBright
+-- end
+
+local function lastY(segments)
+ if (#segments == 0) then
+ return 0
+ else
+ return segments[#segments].p2.world.y
+ end
+end
+
+local function lastWidth(segments, fallback)
+ if (#segments == 0) then
+ return fallback
+ else
+ return segments[#segments].p2.world.w
+ end
+end
+
+function utils.findSegment(zPosition, segments, segmentLength)
+ local index = (math.floor(zPosition/segmentLength) % #segments) + 1
+ if (index >= #segments) then
+ index = #segments
+ end
+ return segments[index];
+end
+
+--- add quad and image
+---@param path string
+---@param slice "horizontal" | "vertical"
+---@param w1 number
+---@param w2 number
+function utils.addQuads(path, slice, w1, w2)
+ local image = ImageCache:load_to(path, path)
+
+ local sw, sh = image:getDimensions()
+
+ local quads = {}
+ if (slice == "vertical") then
+ for i = 0, sh - 1 do
+ local percent = easeFN.percentRemaining(i, sh - 1)
+ local destW = easeFN.interpolate(w1, w2, percent)
+ table.insert(quads,
+ QuadCache:load_to(string.format("%s_%s_%s_%s_%s_%s_%s",
+ path, 0, i, sw, 1, sw, sh),
+ 0, i, sw, 1, sw, sh)
+ )
+ end
+ elseif (slice == "horizontal") then
+ for i = 0, sw - 1 do
+ local percent = easeFN.percentRemaining(i, sw - 1)
+ local destH = easeFN.interpolate(w1, w2, percent)
+ table.insert(quads,
+ QuadCache:load_to(
+ string.format("%s_%s_%s_%s_%s_%s_%s",
+ path, i, 0, 1, sh, sw, sh),
+ i, 0, 1, sh, sw, sh)
+ )
+ end
+ end
+ return quads, image
+end
+
+--- add new segment to segments
+---@param segments any
+---@param segmentLength number z coord length
+---@param rumbleLength number for debug purpose only
+---@param curve number curve degree
+---@param y number height
+---@param width number
+---@return table segment it's basically part that are drawn
+function utils.addSegment(segments, segmentLength, rumbleLength, curve, y, width)
+ local index = #segments
+
+ local w1 = lastWidth(segments, width)
+ local w2 = width
+
+ local y1 = lastY(segments)
+ local y2 = y
+
+ local fQuads, fImage = utils.addQuads("asset/image/sample/javascript-racer-master/images/sprites/column.png", "vertical", w1, w2)
+ local cQuads, cImage = utils.addQuads("asset/image/sample/javascript-racer-master/images/sprites/column.png", "vertical", w1, w2)
+
+ local lQuads, lImage = utils.addQuads("asset/image/sample/javascript-racer-master/images/sprites/column.png", "horizontal", y1, y2)
+ local rQuads, rImage = utils.addQuads("asset/image/sample/javascript-racer-master/images/sprites/column.png", "horizontal", y1, y2)
+
+ return {
+ --- index for seeking
+ index= index + 1,
+ --- part 1 start
+ p1 = {
+ world =
+ {
+ w = w1,
+ y = y1,
+ z = index * segmentLength,
+ },
+ camera = {},
+ screen = {}
+ },
+ --- part 2 end
+ p2 = {
+ world = {
+ w = w2,
+ y = y2,
+ z = (index+1)* segmentLength,
+ },
+ camera = {},
+ screen = {}
+ },
+ --- curve to follow along
+ curve = curve,
+ sprites= {},
+ ---p1 -> p2 quads
+ texture = {
+ floor = {
+ image = fImage,
+ quads = fQuads
+ },
+ ceil = {
+ image = cImage,
+ quads = cQuads
+ },
+ wallL = {
+ image = lImage,
+ quads = lQuads
+ },
+ wallR = {
+ image = rImage,
+ quads = rQuads
+ },
+ }
+ }
+end
+
+function utils.addRoad(segments, segmentLength, rumbleLength, enter, hold, leave, curve, y, roadWidth)
+ local startY = lastY(segments);
+ local endY = startY + ((y or 0) * segmentLength);
+
+ local startWidth = lastWidth(segments, (roadWidth or 0));
+ local endWidth = (roadWidth or 0)
+
+ local total = enter + hold + leave;
+ for n = 1, enter do
+ table.insert(
+ segments,
+ utils.addSegment(
+ segments,
+ segmentLength,
+ rumbleLength,
+ easeFN.easeIn(0, curve, n/enter),
+ easeFN.easeInOut(startY, endY, n/total),
+ easeFN.easeInOut(startWidth, endWidth, n/total)
+ )
+ )
+ end
+ for n = 1, hold do
+ table.insert(
+ segments,
+ utils.addSegment(
+ segments,
+ segmentLength,
+ rumbleLength,
+ curve,
+ easeFN.easeInOut(startY, endY, (enter + n)/total),
+ easeFN.easeInOut(startWidth, endWidth, (enter + n)/total)
+ )
+ )
+ end
+ for n = 1, leave do
+ table.insert(
+ segments,
+ utils.addSegment(
+ segments,
+ segmentLength,
+ rumbleLength,
+ easeFN.easeInOut(curve, 0, n/leave),
+ easeFN.easeInOut(startY, endY, (enter+hold+n)/total),
+ easeFN.easeInOut(startWidth, endWidth, (enter + hold + n)/total)
+ )
+ )
+ end
+ return segments
+end
+
+function utils.addStraight(segments, segmentLength, rumbleLength, num)
+ num = num or ROAD.LENGTH.MEDIUM;
+ return utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, 2000);
+end
+
+function utils.addHill(segments, segmentLength, rumbleLength, num, height)
+ num = num or ROAD.LENGTH.MEDIUM;
+ height = height or ROAD.HILL.MEDIUM;
+ return utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, height, 2000);
+end
+
+function utils.addCurve(segments, segmentLength, rumbleLength, num, curve, height)
+ num = num or ROAD.LENGTH.MEDIUM;
+ curve = curve or ROAD.CURVE.MEDIUM;
+ height = height or ROAD.HILL.NONE;
+ return utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, curve, height, 2000);
+end
+
+function utils.addLowRollingHills(segments, segmentLength, rumbleLength, num, height)
+ num = num or ROAD.LENGTH.SHORT;
+ height = height or ROAD.HILL.LOW;
+ utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, height/2, 2000);
+ utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, -height, 2000);
+ utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, height, 2000);
+ utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, 0, 2000);
+ utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, height/2, 2000);
+ utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, 0, 2000);
+ return segments
+end
+
+function utils.addSCurves(segments, segmentLength, rumbleLength)
+ utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, 2000);
+ utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM, 2000);
+ utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.EASY, 2000);
+ utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, 2000);
+ utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.MEDIUM, 2000);
+ return segments
+end
+
+function utils.addDownhillToEnd(segments, segmentLength, rumbleLength, num)
+ num = num or 200;
+ utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, -ROAD.CURVE.EASY, -lastY(segments)/segmentLength, 2000);
+ return segments
+end
+
+function utils.resetCars(cars, segments, segmentLength, totalCars, maxSpeed)
+ local n, car, segment, offset, z, sprite, speed;
+ for n = 0, totalCars do
+ offset = math.random() * easeFN.randomChoice({-0.8, 0.8});
+ z = math.floor(math.random() * segments.length) * segmentLength;
+ sprite = easeFN.randomChoice({
+ ImageCache:load_to("asset/image/sample/javascript-racer-master/images/sprites/bush1.png",
+ "asset/image/sample/javascript-racer-master/images/sprites/bush1.png")
+ });
+
+ speed = maxSpeed/4 + math.random() * maxSpeed;
+ car = { offset = offset, z = z, sprite = sprite, speed = speed };
+ segment = utils.findSegment(car.z, segments, segmentLength);
+ table.insert(segment.cars, car);
+ table.insert(cars, car);
+ end
+end
+
+function utils.loadRoad(segments, segmentLength, rumbleLength, path)
+ local segmentData = loadJSON(path)
+ for _, v in ipairs(segmentData) do
+ local enter = v.enter
+ local hold = v.hold
+ local leave = v.leave
+ local curve = v.curve
+ local height = v.height
+ local width = v.width
+ utils.addRoad(segments, segmentLength, rumbleLength, enter, hold, leave, curve, height, width);
+ end
+end
+
+function utils.resetRoad(path, pathSprite, segmentLength, rumbleLength, playerZ)
+ local segments = {}
+ utils.loadRoad(segments, segmentLength, rumbleLength, path)
+ if (pathSprite) then
+ utils.loadSpriteMapData(segments, pathSprite);
+ end
+ local trackLength = #segments * segmentLength;
+ return segments, trackLength
+end
+
+function utils.loadSpriteMapData(segments, path)
+ local segmentData = loadJSON(path)
+ for _, v in ipairs(segmentData) do
+ local index = v.index
+ local texture = v.texture
+ local offset = v.offset
+ utils.addSprite(segments, index, ImageCache:load_to(texture, texture), offset, texture);
+ end
+end
+
+function utils.addSprite(segments, index, sprite, offset, key)
+ table.insert(segments[index].sprites, {
+ source= sprite,
+ offset= offset,
+ data = {}
+ })
+ local _sIndex = #segments[index].sprites
+ segments[index].sprites[_sIndex].data = {
+ path = key,
+ type = "static"
+ }
+end
+
+
+return utils
diff --git a/game/src/world/2_town_square/system/perspective.lua b/game/src/world/2_town_square/system/perspective.lua
new file mode 100644
index 0000000..da9d8b5
--- /dev/null
+++ b/game/src/world/2_town_square/system/perspective.lua
@@ -0,0 +1,252 @@
+local system_constructor = require("wrapper.Concord.system")
+
+local roadsegment = require("src.world.2_town_square.pseudo3d.roadsegment")
+
+local perspective = require("src.world.2_town_square.component.perspective")
+local pos3d = require("src.world.2_town_square.component.pos3d")
+
+local projection = require("lib.choro.projection")
+local ease = require("src.world.2_town_square.pseudo3d.ease")
+local vm = require("lib.vornmath")
+local roaddraw = require("src.world.2_town_square.pseudo3d.render.road")
+
+local system = {}
+
+system.__index = system
+
+system.pool = {
+ pool = {
+ perspective.dict.camera_height,
+ perspective.dict.draw_distance,
+ perspective.dict.field_of_view,
+ perspective.dict.fog_density,
+ perspective.dict.lanes,
+ perspective.dict.resolution,
+ perspective.dict.road_width,
+ perspective.dict.rumble_length,
+ perspective.dict.segment_count,
+ perspective.dict.segment_length,
+ perspective.dict.segment_path,
+ perspective.dict.segment_sprite_map_path,
+
+ pos3d.dict.pos,
+ }
+}
+
+system.components = {
+ [perspective.dict.camera_height] = perspective.camera_height,
+ [perspective.dict.draw_distance] = perspective.draw_distance,
+ [perspective.dict.field_of_view] = perspective.field_of_view,
+ [perspective.dict.fog_density] = perspective.fog_density,
+ [perspective.dict.lanes] = perspective.lanes,
+ [perspective.dict.resolution] = perspective.resolution,
+ [perspective.dict.road_width] = perspective.road_width,
+ [perspective.dict.rumble_length] = perspective.rumble_length,
+ [perspective.dict.segment_count] = perspective.segment_count,
+ [perspective.dict.segment_length] = perspective.segment_length,
+ [perspective.dict.segment_path] = perspective.segment_path,
+ [perspective.dict.segment_sprite_map_path] = perspective.segment_sprite_map_path,
+
+ [pos3d.dict.pos] = pos3d.pos,
+}
+function system.new()
+ local new_system = system_constructor.new("perspective", system.pool)
+ if (new_system) then
+ for k, v in pairs(system) do
+ new_system[k] = v
+ end
+ return new_system
+ else
+ return nil
+ end
+end
+
+function system:load()
+ for _, e in ipairs(self.pool) do
+ local segment_path = e[perspective.dict.segment_path].data
+ local segment_sprite_map_path = e[perspective.dict.segment_sprite_map_path].data
+ local segment_length = e[perspective.dict.segment_length].data
+ local rumble_length = e[perspective.dict.rumble_length].data
+
+ local player_y = e[pos3d.dict.pos].data
+
+ local segments, track_length = roadsegment.resetRoad(segment_path, segment_sprite_map_path, segment_length, rumble_length, scroll)
+ -- entityBuilder.setComponent(e, "segments", segments)
+ -- entityBuilder.setComponent(e, "track_length", track_length)
+
+ local field_of_view = e[perspective.dict.field_of_view].data
+ local camera_height = e[perspective.dict.camera_height].data
+ local camera_depth = projection.distanceCamToProjection(field_of_view)
+ local player_z = camera_depth * camera_height
+
+ e[pos3d.dict.pos].data[3] = player_z
+
+ e.segments = segments
+ e.track_length = track_length
+
+ e.camera_depth = camera_depth
+ end
+end
+
+function system:update(dt)
+ for _, e in ipairs(self.pool) do
+ if love.keyboard.isDown("up") then
+ e[pos3d.dict.pos].data[3] = e[pos3d.dict.pos].data[3] + 1000 * dt
+ elseif love.keyboard.isDown("down") then
+ e[pos3d.dict.pos].data[3] = e[pos3d.dict.pos].data[3] - 1000 * dt
+ end
+ if love.keyboard.isDown("left") then
+ e[pos3d.dict.pos].data[1] = e[pos3d.dict.pos].data[1] - 1 * dt
+ elseif love.keyboard.isDown("right") then
+ e[pos3d.dict.pos].data[1] = e[pos3d.dict.pos].data[1] + 1 * dt
+ end
+ if love.keyboard.isDown("w") then
+ e[pos3d.dict.pos].data[2] = e[pos3d.dict.pos].data[2] + 1000 * dt
+ elseif love.keyboard.isDown("s") then
+ e[pos3d.dict.pos].data[2] = e[pos3d.dict.pos].data[2] - 1000 * dt
+
+ end
+ end
+end
+
+local function drawRoad(
+ zPosition, drawDistance,
+ segments,
+ segment_length, track_length,
+ playerX, cameraHeight, cameraDepth,
+ resolution, lanes,
+ playerZ, fogDensity
+)
+ if (zPosition == 0) then
+ zPosition = 1
+ end
+ local baseSegment = roadsegment.findSegment(zPosition, segments, segment_length);
+ local basePercent = ease.percentRemaining(zPosition, segment_length);
+
+ local playerSegment = roadsegment.findSegment(zPosition + playerZ, segments, segment_length);
+ local playerPercent = ease.percentRemaining(zPosition+playerZ, segment_length);
+ local playerY = ease.interpolate(playerSegment.p1.world.y, playerSegment.p2.world.y, playerPercent);
+ local maxy = resolution[2];
+
+ local x = 0;
+ local dx = - ((baseSegment.curve or 0) * basePercent);
+
+ local n, segment;
+ for n = 0, drawDistance do
+ local index = (baseSegment.index + n) % #segments
+
+ if (index >= #segments) then
+ index = n + 1
+ elseif (index <= 0) then
+ index = #segments - (n + 1)
+ end
+
+ segment = segments[index];
+ local loopPos = 0
+ if (segment.index < baseSegment.index) then
+ loopPos = track_length
+ end
+
+ local p1Width = segment.p1.world.w
+ local p2Width = segment.p2.world.w
+
+ local camera1 = vm.vec3({
+ (playerX * p1Width) - x, --x
+ playerY + cameraHeight, --y
+ zPosition - loopPos --z
+ })
+
+ local camera2 = vm.vec3({
+ (playerX * p2Width) - x - dx, --x
+ playerY + cameraHeight, --y
+ zPosition - loopPos --z
+ })
+
+ segment.fog = ease.exponentialFog(n/drawDistance, fogDensity);
+ segment.clip = maxy
+
+ local p1cam, p1screenpos, p1screensize = projection.projectWorldToCam({
+ segment.p1.world.w,
+ segment.p1.world.y,
+ segment.p1.world.z
+ }, camera1, cameraDepth, resolution, {
+ p1Width,
+ 1
+ });
+ local p2cam, p2screenpos, p2screensize = projection.projectWorldToCam({
+ segment.p2.world.w,
+ segment.p2.world.y,
+ segment.p2.world.z
+ }, camera2, cameraDepth, resolution, {
+ p2Width,
+ 1
+ });
+
+ x = x + dx;
+ dx = dx + (segment.curve or 0);
+
+ if ((p1cam[3] <= cameraDepth) or -- behind us
+ (p2screenpos[2] >= p1screenpos[2]) or -- back face cull
+ (p2screenpos[2] >= maxy)) -- clip by (already rendered) segment
+ then
+ else
+ -- segmentdebugdraw.draw(segment, resolution, maxy)
+ roaddraw.draw(p1screenpos, p2screenpos, p1screensize, p2screensize, segment.texture, resolution, maxy)
+
+ maxy = p1screenpos[2];
+ end
+ end
+
+ for n = (drawDistance), 0, -1 do
+ local index = (baseSegment.index + n) % #segments
+
+ if (index >= #segments) then
+ index = n + 1
+ elseif (index <= 0) then
+ index = #segments - (n + 1)
+ end
+
+ segment = segments[index];
+
+ local p1Width = segment.p1.world.w
+
+ -- for i = 1, #segment.sprites do
+ -- drawstatic.drawstatic(
+ -- segment, p1Width, resolution)
+ -- end
+
+ end
+end
+
+function system:draw()
+ for _, e in ipairs(self.pool) do
+ local player_y = e[pos3d.dict.pos].data[2] -- pos[2]
+ local draw_distance = e[perspective.dict.draw_distance].data
+ local camera_height = e[perspective.dict.camera_height].data
+ local resolution = e[perspective.dict.resolution].data
+ local lanes = e[perspective.dict.lanes].data
+ local segment_length = e[perspective.dict.segment_length].data
+ local fogDensity = e[perspective.dict.fog_density].data
+
+ local segments = e.segments
+ local player_x = e[pos3d.dict.pos].data[1] -- pos[1]
+ local camera_depth = e.camera_depth
+ local track_length = e.track_length
+ local player_z = e[pos3d.dict.pos].data[3] -- pos[3]
+
+ love.graphics.push()
+ drawRoad(
+ player_y, draw_distance,
+ segments,
+ segment_length, track_length,
+ player_x, camera_height, camera_depth,
+ resolution, lanes,
+ player_z,
+ fogDensity
+ )
+ love.graphics.pop()
+ love.graphics.print(string.format("%s,%s,%s",player_x, player_y, player_z), 20, 20)
+ end
+end
+
+return system
diff --git a/game/src/world/2_town_square/template/road.lua b/game/src/world/2_town_square/template/road.lua
new file mode 100644
index 0000000..0779080
--- /dev/null
+++ b/game/src/world/2_town_square/template/road.lua
@@ -0,0 +1,53 @@
+local perspective = require("src.world.2_town_square.component.perspective")
+local pos3d = require("src.world.2_town_square.component.pos3d")
+
+local template = {}
+
+template.default_data = {
+ resolution = {love.graphics.getDimensions()}, -- resolution x, y
+ lanes = 3,
+ draw_distance = 300,
+ road_width = 2000,
+
+ fog_density = 0,
+
+ -- player
+ pos = {0, 1, 0},
+ player_width = 50,
+ centrifugal = 0.3,
+ player_speed = 0,
+ player_max_speed = 5000,
+ player_accel = 100,
+
+ -- camera
+ field_of_view = 140, -- field of view
+ camera_height = 1000,
+
+ -- road segment
+ segment_length = 200,
+ segment_count = 500,
+ rumble_length = 3,
+
+
+ segment_path = "data/map/segment.json",
+ segment_sprite_map_path = "data/map/sprites.json",
+}
+
+function template.assemble(e, data)
+ e:give(perspective.dict.camera_height, data.camera_height)
+ e:give(perspective.dict.draw_distance, data.draw_distance)
+ e:give(perspective.dict.field_of_view, data.field_of_view)
+ e:give(perspective.dict.fog_density, data.fog_density)
+ e:give(perspective.dict.lanes, data.lanes)
+ e:give(perspective.dict.resolution, data.resolution.x, data.resolution.y)
+ e:give(perspective.dict.road_width, data.road_width)
+ e:give(perspective.dict.rumble_length, data.rumble_length)
+ e:give(perspective.dict.segment_count, data.segment_count)
+ e:give(perspective.dict.segment_length, data.segment_length)
+ e:give(perspective.dict.segment_path, data.segment_path)
+ e:give(perspective.dict.segment_sprite_map_path, data.segment_sprite_map_path)
+
+ e:give(pos3d.dict.pos, data.pos[1], data.pos[2], data.pos[3])
+end
+
+return template
diff --git a/game/src/world/common/component/animation.lua b/game/src/world/common/component/animation.lua
new file mode 100644
index 0000000..f3cc6c8
--- /dev/null
+++ b/game/src/world/common/component/animation.lua
@@ -0,0 +1,49 @@
+local image = require("wrapper.lappy.new.image").obj
+
+local components = {}
+
+components.dict = {
+ image_frame = "image_frame",
+ trigger_frame = "trigger_frame",
+ frame_i = "frame_i",
+ run_time = "run_time",
+ anim_direction = "anim_direction",
+ next_trigger_frame = "next_trigger_frame",
+ fps = "fps"
+}
+
+function components.image_frame (c, paths)
+ c.data = {
+ images = {},
+ path = paths or {}
+ }
+ for k, v in ipairs(paths) do
+ table.insert(c.data.images, image:load_to(v, v))
+ end
+end
+
+function components.trigger_frame (c, array)
+ c.data = array or {}
+end
+
+function components.frame_i (c, index)
+ c.data = index or 1
+end
+
+function components.run_time (c, float)
+ c.data = float or 0
+end
+
+function components.anim_direction (c, one)
+ c.data = one or 1
+end
+
+function components.next_trigger_frame (c, index)
+ c.data = index or 1
+end
+
+function components.fps (c, frame_per_second)
+ c.data = frame_per_second or 12
+end
+
+return components
diff --git a/game/src/world/common/component/audio.lua b/game/src/world/common/component/audio.lua
new file mode 100644
index 0000000..6f30d83
--- /dev/null
+++ b/game/src/world/common/component/audio.lua
@@ -0,0 +1,24 @@
+local source = require("wrapper.lappy.new.source").obj
+
+local components = {}
+
+components.dict = {
+ bgm = "bgm",
+ sfx = "sfx",
+}
+
+function components.bgm (c, path)
+ c.data = {
+ source = source:load_to(path, path, "stream"),
+ path = path
+ }
+end
+
+function components.sfx (c, path)
+ c.data = {
+ source = source:load_to(path, path, "static"),
+ path = path
+ }
+end
+
+return components
diff --git a/game/src/world/common/component/button.lua b/game/src/world/common/component/button.lua
new file mode 100644
index 0000000..e299f6d
--- /dev/null
+++ b/game/src/world/common/component/button.lua
@@ -0,0 +1,30 @@
+local components = {}
+
+components.dict = {
+ collider = "button.collider",
+ func = "button.func",
+ label = "button.label"
+}
+
+function components.collider (c, x, y, w, h)
+ c.data = {
+ x = x or 0,
+ y = y or 0,
+ w = w or 0,
+ h = h or 0
+ }
+end
+
+function default()
+ print("click")
+end
+
+function components.func (c, func)
+ c.data = func or default
+end
+
+function components.label (c, label)
+ c.data = label or ""
+end
+
+return components
diff --git a/game/src/world/common/component/debug_label.lua b/game/src/world/common/component/debug_label.lua
new file mode 100644
index 0000000..6b1e39c
--- /dev/null
+++ b/game/src/world/common/component/debug_label.lua
@@ -0,0 +1,11 @@
+local components = {}
+
+components.dict = {
+ debug_label = "debug_label",
+}
+
+function components.debug_label (c, label)
+ c.data = label
+end
+
+return components
diff --git a/game/src/world/common/component/transform.lua b/game/src/world/common/component/transform.lua
new file mode 100644
index 0000000..a5c4e1c
--- /dev/null
+++ b/game/src/world/common/component/transform.lua
@@ -0,0 +1,23 @@
+local vm = require("lib.vornmath")
+
+local components = {}
+
+components.dict = {
+ position = "position",
+ scale = "scale",
+ rotation = "rotation"
+}
+
+function components.position (c, x, y, z)
+ c.data = vm.vec3(x or 0, y or 0, z or 0)
+end
+
+function components.scale (c, sx, sy, sz)
+ c.data = vm.vec3(sx or 1, sy or 1, sz or 1)
+end
+
+function components.rotation (c, rx, ry, rz)
+ c.data = vm.vec3(rx or 0, ry or 0, rz or 0)
+end
+
+return components
diff --git a/game/src/world/common/component/video.lua b/game/src/world/common/component/video.lua
new file mode 100644
index 0000000..40d2a1a
--- /dev/null
+++ b/game/src/world/common/component/video.lua
@@ -0,0 +1,16 @@
+local video = require("wrapper.lappy.new.video").obj
+
+local components = {}
+
+components.dict = {
+ video = "video",
+}
+
+function components.video (c, path)
+ c.data = {
+ video = video:load_to(path, path),
+ path = path
+ }
+end
+
+return components
diff --git a/game/src/world/common/system/debug_world_draw.lua b/game/src/world/common/system/debug_world_draw.lua
new file mode 100644
index 0000000..943cb5c
--- /dev/null
+++ b/game/src/world/common/system/debug_world_draw.lua
@@ -0,0 +1,48 @@
+local system_constructor = require("wrapper.Concord.system")
+local debug_label = require("src.world.common.component.debug_label")
+local transform = require("src.world.common.component.transform")
+
+local system = {}
+
+system.__index = system
+
+system.pool = {
+ pool = {
+ debug_label.dict.debug_label,
+ transform.dict.position
+ }
+}
+
+system.components = {
+ [debug_label.dict.debug_label] = debug_label.debug_label,
+ [transform.dict.position] = transform.position
+}
+
+function system.new()
+ local new_system = system_constructor.new("debug_world_draw", system.pool)
+ if (new_system) then
+ for k, v in pairs(system) do
+ new_system[k] = v
+ end
+ return new_system
+ else
+ return nil
+ end
+end
+
+local function draw(text, x, y)
+ love.graphics.push()
+ love.graphics.print(text, x, y)
+ love.graphics.pop()
+end
+
+function system:draw()
+ for _, e in ipairs(self.pool) do
+ local text = e[debug_label.dict.debug_label].data
+ local x = e[transform.dict.position].data[1]
+ local y = e[transform.dict.position].data[2]
+ draw(text, x, y)
+ end
+end
+
+return system
diff --git a/game/src/world/common/system/flip.lua b/game/src/world/common/system/flip.lua
new file mode 100644
index 0000000..b493a17
--- /dev/null
+++ b/game/src/world/common/system/flip.lua
@@ -0,0 +1,97 @@
+local floor = math.floor
+
+local system_constructor = require("wrapper.Concord.system")
+local audio = require("src.world.common.component.audio")
+local animation = require("src.world.common.component.animation")
+
+local system = {}
+
+system.__index = system
+
+system.pool = {
+ pool = {
+ audio.dict.sfx,
+ animation.dict.anim_direction,
+ animation.dict.fps,
+ animation.dict.frame_i,
+ animation.dict.image_frame,
+ animation.dict.next_trigger_frame,
+ animation.dict.run_time,
+ animation.dict.trigger_frame,
+ }
+}
+
+system.components = {
+ [audio.dict.sfx] = audio.sfx,
+ [animation.dict.anim_direction] = animation.anim_direction,
+ [animation.dict.fps] = animation.fps,
+ [animation.dict.frame_i] = animation.frame_i,
+ [animation.dict.image_frame] = animation.image_frame,
+ [animation.dict.next_trigger_frame] = animation.next_trigger_frame,
+ [animation.dict.run_time] = animation.run_time,
+ [animation.dict.trigger_frame] = animation.trigger_frame,
+}
+
+function system.new()
+ local new_system = system_constructor.new("flip", system.pool)
+ if (new_system) then
+ for k, v in pairs(system) do
+ new_system[k] = v
+ end
+ return new_system
+ else
+ return nil
+ end
+end
+
+--- system frame
+local function update_time(time, delta, indicator)
+ return time + (delta * indicator)
+end
+
+local function get_loop(index, _max)
+ local md = index % _max
+ if (md == 0) then
+ return _max
+ else
+ return index % _max
+ end
+end
+
+local function update_fps_time(time, fps)
+ -- floor time to 1. check x fps per second
+ return floor(time / (1 / fps))
+end
+
+-- system trigger frame
+local function is_trigger(trigger_frames, trigger_id, frame_id)
+ return (trigger_frames[trigger_id] == frame_id)
+end
+
+function system:update(dt)
+ for _, e in ipairs(self.pool) do
+ e[animation.dict.run_time].data = update_time(
+ e[animation.dict.run_time].data, dt, e[animation.dict.anim_direction].data
+ )
+ local new_index = update_fps_time(
+ e[animation.dict.run_time].data, e[animation.dict.fps].data
+ )
+ e[animation.dict.frame_i].data = get_loop(
+ new_index,
+ #e[animation.dict.image_frame].data.images
+ )
+
+ if (is_trigger(
+ e[animation.dict.trigger_frame].data,
+ e[animation.dict.next_trigger_frame].data,
+ e[animation.dict.frame_i].data
+ )) then
+ e[animation.dict.next_trigger_frame].data = get_loop(
+ e[animation.dict.next_trigger_frame].data + 1, #e[animation.dict.trigger_frame].data
+ )
+ e[audio.dict.sfx].data.source:play()
+ end
+ end
+end
+
+return system
diff --git a/game/src/world/common/system/trigger_button.lua b/game/src/world/common/system/trigger_button.lua
new file mode 100644
index 0000000..a90ccd0
--- /dev/null
+++ b/game/src/world/common/system/trigger_button.lua
@@ -0,0 +1,81 @@
+local system_constructor = require("wrapper.Concord.system")
+local c_button = require("src.world.common.component.button")
+
+local system = {}
+
+system.__index = system
+
+system.pool = {
+ pool = {
+ c_button.dict.collider,
+ c_button.dict.func,
+ c_button.dict.label
+ }
+}
+
+system.components = {
+ [c_button.dict.collider] = c_button.collider,
+ [c_button.dict.func] = c_button.func,
+ [c_button.dict.label] = c_button.label
+}
+
+function system.new()
+ local new_system = system_constructor.new("trigger_button", system.pool)
+ if (new_system) then
+ for k, v in pairs(system) do
+ new_system[k] = v
+ end
+ return new_system
+ else
+ return nil
+ end
+end
+
+-- x1 y1 -- (x1 + w1) y1
+-- x2 y2
+-- x1 (y1 + h1) -- (x1 + w1) (y1 + h1)
+local function is_inside(x1,y1,w1,h1, x2,y2)
+ return x1 < x2 and
+ x2 < x1+w1 and
+ y1 < y2 and
+ y2 < y1+h1
+end
+
+function system:mousereleased(x, y, button, istouch, presses)
+ for _, e in ipairs(self.pool) do
+ local x1 = e[c_button.dict.collider].data.x
+ local y1 = e[c_button.dict.collider].data.y
+ local w1 = e[c_button.dict.collider].data.w
+ local h1 = e[c_button.dict.collider].data.h
+ local func = e[c_button.dict.func].data
+ if (is_inside(x1, y1, w1, h1, x, y) and button == 1) then
+ func()
+ end
+ end
+end
+
+local function draw(text, x, y, w, h)
+ love.graphics.push()
+ love.graphics.line(
+ x, y,
+ x + w, y,
+ x + w, y + h,
+ x, y + h,
+ x, y
+ )
+ love.graphics.print(text, x, y)
+ love.graphics.pop()
+end
+
+function system:draw()
+ for _, e in ipairs(self.pool) do
+ local x = e[c_button.dict.collider].data.x
+ local y = e[c_button.dict.collider].data.y
+ local w = e[c_button.dict.collider].data.w
+ local h = e[c_button.dict.collider].data.h
+ local label = e[c_button.dict.label].data
+ draw(label, x, y, w, h)
+ end
+end
+
+return system
diff --git a/game/src/world/common/system/video_render.lua b/game/src/world/common/system/video_render.lua
new file mode 100644
index 0000000..123e204
--- /dev/null
+++ b/game/src/world/common/system/video_render.lua
@@ -0,0 +1,50 @@
+local system_constructor = require("wrapper.Concord.system")
+local c_video = require("src.world.common.component.video")
+
+local system = {}
+
+system.__index = system
+
+system.pool = {
+ pool = {
+ c_video.dict.video,
+ }
+}
+
+system.components = {
+ [c_video.dict.video] = c_video.video,
+}
+
+function system.new()
+ local new_system = system_constructor.new("video_render", system.pool)
+ if (new_system) then
+ for k, v in pairs(system) do
+ new_system[k] = v
+ end
+ return new_system
+ else
+ return nil
+ end
+end
+
+function system:load()
+ for _, e in ipairs(self.pool) do
+ video = e[c_video.dict.video].data.video
+ video:play()
+ end
+end
+
+function system:update(dt)
+ for _, e in ipairs(self.pool) do
+ video = e[c_video.dict.video].data.video
+ end
+end
+
+function system:draw()
+ for _, e in ipairs(self.pool) do
+ video = e[c_video.dict.video].data.video
+ love.graphics.draw(video, 0, 0)
+ end
+end
+
+return system
diff --git a/game/src/world/common/template/book.lua b/game/src/world/common/template/book.lua
new file mode 100644
index 0000000..4003813
--- /dev/null
+++ b/game/src/world/common/template/book.lua
@@ -0,0 +1,42 @@
+local flip = require("src.world.common.system.flip")
+local transform = require("src.world.common.component.transform")
+
+local template = {}
+
+template.default_data = {
+ sfx = "asset/audio/sfx/book_flip.1.ogg",
+ anim_direction = 1,
+ fps = 12,
+ frame_i = 1,
+ image_frame = {
+ "asset/image/book/StoryMode.png",
+ "asset/image/book/StoryModeBook.png",
+ "asset/image/book/StoryModeBook2.png",
+ "asset/image/book/StoryModeBook3.png",
+ "asset/image/book/StoryModeBook4.png",
+ "asset/image/book/StoryModeBook5.png",
+ "asset/image/book/StoryModeBook6.png",
+ "asset/image/book/StoryModeBook7.png",
+ "asset/image/book/StoryModeBook8.png",
+ "asset/image/book/StoryModeBook9.png",
+ "asset/image/book/StoryModeBook11.png",
+ "asset/image/book/StoryModeBook16.png",
+ },
+ next_trigger_frame = 1,
+ run_time = 0,
+ trigger_frame = {1},
+ position = {0, 0, 0}
+}
+function template.assemble(e, data)
+ e:give(flip.pool.pool[1], data.sfx)
+ e:give(flip.pool.pool[2], data.anim_direction)
+ e:give(flip.pool.pool[3], data.fps)
+ e:give(flip.pool.pool[4], data.frame_i)
+ e:give(flip.pool.pool[5], data.image_frame)
+ e:give(flip.pool.pool[6], data.next_trigger_frame)
+ e:give(flip.pool.pool[7], data.run_time)
+ e:give(flip.pool.pool[8], data.trigger_frame)
+ e:give(transform.dict.position, data.position)
+end
+
+return template
diff --git a/game/src/world/common/template/button.lua b/game/src/world/common/template/button.lua
new file mode 100644
index 0000000..8c19399
--- /dev/null
+++ b/game/src/world/common/template/button.lua
@@ -0,0 +1,24 @@
+local button = require("src.world.common.component.button")
+
+local template = {}
+
+local function default()
+end
+
+template.default_data = {
+ collider = {
+ x = 0,
+ y = 0,
+ w = 20,
+ h = 20
+ },
+ func = default,
+ label = "debug"
+}
+function template.assemble(e, data)
+ e:give(button.dict.collider, data.collider.x, data.collider.y, data.collider.w, data.collider.h)
+ e:give(button.dict.func, data.func)
+ e:give(button.dict.label, data.label)
+end
+
+return template
diff --git a/game/src/world/common/template/debug_entity.lua b/game/src/world/common/template/debug_entity.lua
new file mode 100644
index 0000000..6375cac
--- /dev/null
+++ b/game/src/world/common/template/debug_entity.lua
@@ -0,0 +1,14 @@
+local debug_world_draw = require("src.world.common.system.debug_world_draw")
+
+local template = {}
+
+template.default_data = {
+ position = {0, 0},
+ label = "debug"
+}
+function template.assembleDebug(e, data)
+ e:give(debug_world_draw.pool.pool[1], data.label)
+ e:give(debug_world_draw.pool.pool[2], data.position[1], data.position[2])
+end
+
+return template
diff --git a/game/src/world/common/template/video.lua b/game/src/world/common/template/video.lua
new file mode 100644
index 0000000..0e7767e
--- /dev/null
+++ b/game/src/world/common/template/video.lua
@@ -0,0 +1,12 @@
+local video = require("src.world.common.component.video")
+
+local template = {}
+
+template.default_data = {
+ path = ""
+}
+function template.assemble(e, data)
+ e:give(video.dict.video, data.path)
+end
+
+return template
diff --git a/game/src/world/main_menu/init.lua b/game/src/world/main_menu/init.lua
new file mode 100644
index 0000000..4d4c5ac
--- /dev/null
+++ b/game/src/world/main_menu/init.lua
@@ -0,0 +1,48 @@
+local reap = require("lib.reap")
+
+local BASE = reap.base_path(...)
+
+local world = require("wrapper.Concord.world")
+
+local debug_entity = require("src.world.common.template.debug_entity")
+local button = require("src.world.common.template.button")
+local wm = require("world_map")
+
+local wrapper = world:extend()
+
+local function button_func()
+ load_world(wm["1_intro"])
+end
+
+function wrapper:new()
+ wrapper.super.new(self, BASE, ".main_menu")
+end
+
+function wrapper:load(_args)
+ wrapper.super.load(self, {
+ "src/world/common/system/"
+ }, {
+ {
+ assemblage = debug_entity.assembleDebug,
+ data = {
+ position = {0, 0},
+ label = "main_menu"
+ }
+ },
+ {
+ assemblage = button.assemble,
+ data = {
+ collider = {
+ x = 20,
+ y = 20,
+ w = 20,
+ h = 20
+ },
+ func = button_func,
+ label = "play"
+ }
+ }
+ })
+end
+
+return wrapper
diff --git a/game/src/world/race/init.lua b/game/src/world/race/init.lua
new file mode 100644
index 0000000..d4b86b9
--- /dev/null
+++ b/game/src/world/race/init.lua
@@ -0,0 +1,52 @@
+local reap = require("lib.reap")
+
+local BASE = reap.base_path(...)
+local image = require("wrapper.lappy.new.image").obj
+
+local world = require("wrapper.Concord.world")
+
+local debug_entity = require("src.world.common.template.debug_entity")
+local book = require("src.world.common.template.book")
+
+local animation = require("src.world.common.component.animation")
+local transform = require("src.world.common.component.transform")
+
+local wrapper = world:extend()
+
+function wrapper:new()
+ wrapper.super.new(self, BASE, ".race")
+end
+
+function wrapper:load(_args)
+ wrapper.super.load(self, {
+ "src/world/common/system/"
+ }, {
+ {
+ assemblage = debug_entity.assembleDebug,
+ data = {
+ position = {0, 0},
+ label = "race world"
+ }
+ },
+ {
+ assemblage = book.assemble,
+ data = book.default_data
+ }
+ })
+end
+
+function wrapper:draw()
+ wrapper.super.draw(self)
+
+ for k, v in pairs(self.entities) do
+ if v[animation.dict.frame_i] ~= nil then
+ local frame_i = v[animation.dict.frame_i].data
+ local img = v[animation.dict.image_frame].data.images
+ if (img ~= nil) then
+ love.graphics.draw(img[frame_i], v[transform.dict.position].data[1], v[transform.dict.position].data[2])
+ end
+ end
+ end
+end
+
+return wrapper
diff --git a/game/src/world/train/init.lua b/game/src/world/train/init.lua
new file mode 100644
index 0000000..6ba5d80
--- /dev/null
+++ b/game/src/world/train/init.lua
@@ -0,0 +1,29 @@
+local reap = require("lib.reap")
+
+local BASE = reap.base_path(...)
+
+local world = require("wrapper.Concord.world")
+
+local debug_entity = require("src.world.common.template.debug_entity")
+
+local wrapper = world:extend()
+
+function wrapper:new()
+ wrapper.super.new(self, BASE, ".train")
+end
+
+function wrapper:load(_args)
+ wrapper.super.load(self, {
+ "src/world/common/system/"
+ }, {
+ {
+ assemblage = debug_entity.assembleDebug,
+ data = {
+ position = {0, 0},
+ label = "train world"
+ }
+ }
+ })
+end
+
+return wrapper
diff --git a/game/world_map.lua b/game/world_map.lua
new file mode 100644
index 0000000..65f5ccf
--- /dev/null
+++ b/game/world_map.lua
@@ -0,0 +1,7 @@
+return {
+ ["main_menu"] = "main_menu",
+ ["1_intro"] = "1_intro",
+ ["2_town_square"] = "2_town_square",
+ ["race"] = "race",
+ ["train"] = "train"
+}
diff --git a/game/wrapper/Concord/component.lua b/game/wrapper/Concord/component.lua
new file mode 100644
index 0000000..842f728
--- /dev/null
+++ b/game/wrapper/Concord/component.lua
@@ -0,0 +1,37 @@
+local Concord = require("lib.Concord")
+
+-- Modules
+local Entity = Concord.entity
+local Component = Concord.component
+local System = Concord.system
+local World = Concord.world
+
+-- Containers
+local Components = Concord.components
+
+local constructor = {}
+
+function constructor.default_component_lambda(c, data)
+ c.data = data
+end
+
+--- build component ecs component
+---@param name string
+---@param lambda? function(c, data)
+function constructor.component(name, lambda)
+ local ok, value = Components.try(name)
+ if not ok then
+ return Component(name, lambda or constructor.default_component_lambda)
+ end
+end
+
+--- build component ecs state
+---@param name string
+function constructor.state(name)
+ local ok, value = Components.try(name)
+ if not ok then
+ return Component(name)
+ end
+end
+
+return constructor
diff --git a/game/wrapper/Concord/entity.lua b/game/wrapper/Concord/entity.lua
new file mode 100644
index 0000000..3786627
--- /dev/null
+++ b/game/wrapper/Concord/entity.lua
@@ -0,0 +1,91 @@
+local format = string.format
+
+local reap = require("lib.reap")
+
+local BASE = reap.base_path(...)
+local component_builder = require(format("%s.component", BASE))
+
+local Concord = require("lib.Concord")
+
+-- Modules
+local Entity = Concord.entity
+local Component = Concord.component
+local System = Concord.system
+local World = Concord.world
+
+-- Containers
+local Components = Concord.components
+
+local constructor = {}
+
+--- build entity ecs
+function constructor.new(system)
+ if (type(system) == "table") then
+ local e = Entity()
+ for _, s in pairs(system) do
+ for c, f in pairs(s.components) do
+ if (type(c) == "string") then
+ component_builder.component(c, f)
+ else
+ component_builder.state(f)
+ end
+ end
+ end
+ return e
+ elseif (system == nil) then
+ return Entity()
+ else
+ return nil
+ end
+end
+
+--- ensure component
+---@param entity any
+---@param name string
+function constructor.ensure(entity, name, ...)
+ if (#{...} == 0) then
+ component_builder.state(name)
+ entity:ensure(name)
+ else
+ component_builder.component(name)
+ entity:ensure(name, ...)
+ end
+end
+
+--- give component
+---@param entity any
+---@param name string
+function constructor.give(entity, name, ...)
+ if (#{...} == 0) then
+ component_builder.state(name)
+ entity:give(name)
+ else
+ component_builder.component(name)
+ entity:give(name, ...)
+ end
+end
+
+function constructor.remove(entity, name)
+ if Components.has(name) == false then
+ return false, nil
+ else
+ if entity:has(name) then
+ local data = entity[name]
+ entity:remove(name)
+ return true, data
+ else
+ return false, nil
+ end
+ end
+end
+
+function constructor.destroy(entity)
+ local all_c = entity:getComponents()
+ for c_id, c in ipairs(all_c) do
+ entity:remove(c)
+ print("destroy", c_id, c)
+ end
+ entity:destroy()
+end
+
+return constructor
diff --git a/game/wrapper/Concord/system.lua b/game/wrapper/Concord/system.lua
new file mode 100644
index 0000000..beed534
--- /dev/null
+++ b/game/wrapper/Concord/system.lua
@@ -0,0 +1,84 @@
+local insert = table.insert
+
+local Concord = require("lib.Concord")
+
+-- Modules
+local Entity = Concord.entity
+local Component = Concord.component
+local System = Concord.system
+local World = Concord.world
+
+-- Containers
+local Components = Concord.components
+
+
+local constructor = {}
+
+constructor.systems = {}
+
+local function is_component_complete(pool)
+ local is_complete = true
+ for _, v in pairs(pool) do
+ for _, c in ipairs(v) do
+ if (Components.has(c) == false) then
+ is_complete = false
+ break
+ end
+ end
+ if (not is_complete) then
+ break
+ end
+ end
+ return is_complete
+end
+
+function constructor.new(name, pool)
+ if (constructor.systems[name]) then
+ return constructor.systems[name]
+ else
+ if (type(pool) == "table") then
+ if (is_component_complete(pool)) then
+ local self = System(pool)
+ self.name = name
+ constructor.systems[name] = self
+ return self
+ else
+ return nil
+ end
+ else
+ error("new system register error. pool and wrapper should be a table.")
+ return nil
+ end
+ end
+end
+
+function constructor.add(world, path)
+ local namespace = {}
+ if (type(path) == "string") then
+ Concord.utils.loadNamespace(path, namespace)
+ else
+ for k,v in pairs(path) do
+ if type(v) == "string" then
+ local new_namespace = {}
+ Concord.utils.loadNamespace(v, new_namespace)
+ for l, w in pairs(new_namespace) do
+ namespace[l] = w
+ end
+ else
+ insert(namespace, v)
+ end
+ end
+ end
+ for k,v in pairs(namespace) do
+ local new_sys = v.new()
+ if (new_sys == nil) then
+
+ else
+ if not world:hasSystem(new_sys) then
+ world:addSystems(new_sys)
+ end
+ end
+ end
+end
+
+return constructor
diff --git a/game/wrapper/Concord/world.lua b/game/wrapper/Concord/world.lua
new file mode 100644
index 0000000..15053e1
--- /dev/null
+++ b/game/wrapper/Concord/world.lua
@@ -0,0 +1,153 @@
+local format = string.format
+
+local reap = require("lib.reap")
+
+local BASE = reap.base_path(...)
+local component_builder = require(format("%s.component", BASE))
+local entity_builder = require(format("%s.entity", BASE))
+
+local Concord = require("lib.Concord")
+
+-- Modules
+local Entity = Concord.entity
+local Component = Concord.component
+local System = Concord.system
+local World = Concord.world
+
+local main_wrapper = require "wrapper.lappy.world"
+---@class wrappers.Concord.world : lappy.world
+local wrapper = main_wrapper:extend()
+
+function wrapper:new(path, name)
+ wrapper.super.new(self, path, name)
+ self.is_loaded = false
+ self.world = World()
+ self.entities = {}
+end
+
+local function load_systems(world, system_paths)
+ for _, p in pairs(system_paths) do
+ local Systems = {}
+ Concord.utils.loadNamespace(p, Systems)
+ for k, s in pairs(Systems) do
+ for c, l in pairs(s.components) do
+ if (type(c) == "number") then
+ component_builder.state(l)
+ else
+ component_builder.component(c, l)
+ end
+ end
+ local new_s = s.new()
+ if (new_s) then
+ world:addSystem(
+ new_s
+ )
+ end
+ end
+ end
+end
+
+local function load_entities(self, world, data)
+ for _, d in pairs(data) do
+ local entity = entity_builder.new()
+ entity:assemble(d.assemblage, d.data)
+ world:addEntity(entity)
+ table.insert(self.entities, entity)
+ end
+end
+
+function wrapper:load(system_paths, entities_data)
+ load_systems(self.world, system_paths)
+ load_entities(self, self.world, entities_data)
+ self.is_loaded = true
+ self.world:emit("load")
+end
+
+function wrapper:reload(system_paths, entities_data)
+ if not self.is_loaded then
+ self:load(system_paths, entities_data)
+ end
+ self.world:emit("reload")
+end
+
+function wrapper:draw()
+ self.world:emit("draw")
+end
+
+function wrapper:update(dt)
+ self.world:emit("update", dt)
+end
+
+-- mouse
+
+function wrapper:mousemoved( x, y, dx, dy, istouch)
+ self.world:emit("mousemoved", x, y, dx, dy, istouch)
+end
+
+function wrapper:mousepressed(x, y, button, istouch, presses)
+ self.world:emit("mousepressed", x, y, button, istouch, presses)
+end
+
+function wrapper:mousereleased(x, y, button, istouch, presses)
+ self.world:emit("mousereleased", x, y, button, istouch, presses)
+end
+
+function wrapper:wheelmoved(x, y)
+ self.world:emit("wheelmoved", x, y)
+end
+
+-- keyboard
+
+function wrapper:keypressed(key, scancode, isrepeat)
+ self.world:emit("keypressed", key, scancode, isrepeat)
+end
+
+function wrapper:keyreleased(key, scancode)
+ self.world:emit("keyreleased", key, scancode)
+end
+
+-- touchscreen
+
+function wrapper:touchpressed(id, x, y, dx, dy, pressure)
+ self.world:emit("touchpressed", id, x, y, dx, dy, pressure)
+end
+
+function wrapper:textinput(t)
+ self.world:emit("textinput", t)
+end
+
+function wrapper:resize(w,h)
+ self.world:emit("rezise", w, h)
+end
+
+function wrapper:focus(f)
+ self.world:emit("focus", f)
+end
+
+function wrapper:quit()
+ self.world:emit("quit")
+end
+
+-- for gamepad support also add the following:
+
+function wrapper:joystickadded(joystick)
+ self.world:emit("joystickadded", joystick)
+end
+
+function wrapper:joystickremoved(joystick)
+ self.world:emit("joystickremoved", joystick)
+end
+
+function wrapper:gamepadpressed(joystick, button)
+ self.world:emit("gamepadpressed", joystick, button)
+end
+
+function wrapper:gamepadreleased(joystick, button)
+ self.world:emit("gamepadreleased", joystick, button)
+end
+
+function wrapper:gamepadaxis(joystick, axis, value)
+ self.world:emit("gamepadaxis", joystick, axis, value)
+end
+
+return wrapper
diff --git a/game/wrapper/lappy/new/image.lua b/game/wrapper/lappy/new/image.lua
new file mode 100644
index 0000000..ef94c68
--- /dev/null
+++ b/game/wrapper/lappy/new/image.lua
@@ -0,0 +1,17 @@
+local cache = require("lib.reoof.cache")
+---@class lappy.image : Cache
+---@field cache any
+local _cache = cache:extend()
+
+--- new function
+function _cache:new()
+ _cache.super.new(self, love.graphics.newImage, ".image")
+ self.cache = {}
+end
+
+local obj = _cache()
+
+return {
+ class = _cache,
+ obj = obj
+}
diff --git a/game/wrapper/lappy/new/image_data.lua b/game/wrapper/lappy/new/image_data.lua
new file mode 100644
index 0000000..cb8c8bb
--- /dev/null
+++ b/game/wrapper/lappy/new/image_data.lua
@@ -0,0 +1,17 @@
+local cache = require("lib.reoof.cache")
+---@class lappy.image_data : Cache
+---@field cache any
+local _cache = cache:extend()
+
+--- new function
+function _cache:new()
+ _cache.super.new(self, love.image.newImageData, ".image_data")
+ self.cache = {}
+end
+
+local obj = _cache()
+
+return {
+ class = _cache,
+ obj = obj
+}
diff --git a/game/wrapper/lappy/new/image_font.lua b/game/wrapper/lappy/new/image_font.lua
new file mode 100644
index 0000000..fa116f1
--- /dev/null
+++ b/game/wrapper/lappy/new/image_font.lua
@@ -0,0 +1,17 @@
+local cache = require("lib.reoof.cache")
+---@class lappy.image_font : Cache
+---@field cache any
+local _cache = cache:extend()
+
+--- new function
+function _cache:new()
+ _cache.super.new(self, love.graphics.newImageFont, ".image_font")
+ self.cache = {}
+end
+
+local obj = _cache()
+
+return {
+ class = _cache,
+ obj = obj
+}
diff --git a/game/wrapper/lappy/new/quad.lua b/game/wrapper/lappy/new/quad.lua
new file mode 100644
index 0000000..5f323f7
--- /dev/null
+++ b/game/wrapper/lappy/new/quad.lua
@@ -0,0 +1,17 @@
+local cache = require("lib.reoof.cache")
+---@class lappy.quad : Cache
+---@field cache any
+local _cache = cache:extend()
+
+--- new function
+function _cache:new()
+ _cache.super.new(self, love.graphics.newQuad, ".quad")
+ self.cache = {}
+end
+
+local obj = _cache()
+
+return {
+ class = _cache,
+ obj = obj
+}
diff --git a/game/wrapper/lappy/new/source.lua b/game/wrapper/lappy/new/source.lua
new file mode 100644
index 0000000..b2457ee
--- /dev/null
+++ b/game/wrapper/lappy/new/source.lua
@@ -0,0 +1,17 @@
+local cache = require("lib.reoof.cache")
+---@class lappy.source : Cache
+---@field cache any
+local _cache = cache:extend()
+
+--- new function
+function _cache:new()
+ _cache.super.new(self, love.audio.newSource, ".source")
+ self.cache = {}
+end
+
+local obj = _cache()
+
+return {
+ class = _cache,
+ obj = obj
+}
diff --git a/game/wrapper/lappy/new/sprite_batch.lua b/game/wrapper/lappy/new/sprite_batch.lua
new file mode 100644
index 0000000..373de42
--- /dev/null
+++ b/game/wrapper/lappy/new/sprite_batch.lua
@@ -0,0 +1,17 @@
+local cache = require("lib.reoof.cache")
+---@class lappy.sprite_batch : Cache
+---@field cache any
+local _cache = cache:extend()
+
+--- new function
+function _cache:new()
+ _cache.super.new(self, love.graphics.newSpriteBatch, ".batch")
+ self.cache = {}
+end
+
+local obj = _cache()
+
+return {
+ class = _cache,
+ obj = obj
+}
diff --git a/game/wrapper/lappy/new/video.lua b/game/wrapper/lappy/new/video.lua
new file mode 100644
index 0000000..140d066
--- /dev/null
+++ b/game/wrapper/lappy/new/video.lua
@@ -0,0 +1,17 @@
+local cache = require("lib.reoof.cache")
+---@class lappy.video : Cache
+---@field cache any
+local _cache = cache:extend()
+
+--- new function
+function _cache:new()
+ _cache.super.new(self, love.graphics.newVideo, ".video")
+ self.cache = {}
+end
+
+local obj = _cache()
+
+return {
+ class = _cache,
+ obj = obj
+}
diff --git a/game/wrapper/lappy/world.lua b/game/wrapper/lappy/world.lua
new file mode 100644
index 0000000..deac244
--- /dev/null
+++ b/game/wrapper/lappy/world.lua
@@ -0,0 +1,79 @@
+local classic = require "lib.classic.classic"
+---@class lappy.world : lib.classic.class
+local wrapper = classic:extend()
+
+function wrapper:new(path, name)
+ self.path = path
+ self.name = name
+end
+
+function wrapper:load(_args)
+end
+
+function wrapper:draw()
+end
+
+function wrapper:update(dt)
+end
+
+-- mouse
+
+function wrapper:mousemoved( x, y, dx, dy, istouch)
+end
+
+function wrapper:mousepressed(x, y, button, istouch, presses)
+end
+
+function wrapper:mousereleased(x, y, button, istouch, presses)
+end
+
+function wrapper:wheelmoved(x, y)
+end
+
+-- keyboard
+
+function wrapper:keypressed(key, scancode, isrepeat)
+end
+
+function wrapper:keyreleased(key, scancode)
+end
+
+-- touchscreen
+
+function wrapper:touchpressed(id, x, y, dx, dy, pressure)
+end
+
+function wrapper:textinput(t)
+end
+
+function wrapper:resize(w,h)
+end
+
+function wrapper:focus(f)
+end
+
+function wrapper:quit()
+end
+
+-- for gamepad support also add the following:
+
+function wrapper:joystickadded(joystick)
+end
+
+function wrapper:joystickremoved(joystick)
+end
+
+function wrapper:gamepadpressed(joystick, button)
+end
+
+function wrapper:gamepadreleased(joystick, button)
+end
+
+function wrapper:gamepadaxis(joystick, axis, value)
+end
+
+function wrapper:__tostring()
+ return string.format("%s.%s", self.path, self.name)
+end
+
+return wrapper
diff --git a/readme.md b/readme.md
index 99d5171..c85c9ca 100644
--- a/readme.md
+++ b/readme.md
@@ -1,3 +1,12 @@
# About
-repo for love2d 2026 jam
\ No newline at end of file
+repo for love2d 2026 jam
+
+# Libs
+
+Vornmath (math)
+Concord (ecs)
+classic (object oriented)
+reap (relative path helper)
+reoof (cache + pool)
+lappy (love api wrapper)