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/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/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