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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ straight | + curves | + hills | + final +
+ +
+ +
+ +
+

Use the arrow keys to drive the car.

+
+ +
+ + Sorry, this example cannot be run because your browser does not support the <canvas> element + + 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) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ straight | + curves | + hills | + final +
+ +
+ +
+ +
+

Use the arrow keys to drive the car.

+
+ +
+ + Sorry, this example cannot be run because your browser does not support the <canvas> element + + 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) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ straight | + curves | + hills | + final +
+ +
+ +
+ +
+

Use the arrow keys to drive the car.

+
+ +
+ + Sorry, this example cannot be run because your browser does not support the <canvas> element + + 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) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ straight | + curves | + hills | + final +
+ +
+ +
+ +
+

Use the arrow keys to drive the car.

+
+ +
+
+ 0 mph + Time: 0.0 + Last Lap: 0.0 + Fastest Lap: 0.0 +
+ + Sorry, this example cannot be run because your browser does not support the <canvas> element + + 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)