commit 0835ad092a0933382c6ca8f0b8716524db6c1eee Author: entuland Date: Wed Jun 20 00:59:31 2018 +0200 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..07bf225 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 entuland + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..213c3fc --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# Rhotator Screwdriver (rhotator) + +A different twist at Minetest screwdriving. + +Developed and tested on Minetest 0.4.16 - try in other versions at your own risk :) + +If you like my contributions you may consider reading http://entuland.com/en/support-entuland + +# Why yet another screwdriver? + +The default screwdriver included in minetest_game, as well as any other screwdriver mod I have found, operate differently depending on the node's direction and rotation. This means that any given click on a node may produce different results which you cannot predict at a glance. + +The Rhotator Screwdriver uses a different approach: the direction and orientation of the node make absolutely no difference. + +These are the factors that affect the results of a click: + +- the face you point at +- where on that face you point +- what button you click + +You will always be able to predict exactly the effect of the Rhotator Screwdriver. + +Four consecutive clicks of the same button on the same position will always bring the node back to its original direction / orientation. + +### Why is it called "Rhotator" and not "Rotator"? + +In mathematics, the greek letter *Rho* is used to indicate some stuff associated to certain types of matrices. Since I'm using matrices to compute the various rotations in the game I thought about including it in the mod's name to reduce the chance of naming conflicts. + +# Appearance + +Here you can see the Rhotator Screwdriver along with the Testing Cube. + +*The testing cube is just an addition to help practicing with this screwdriver.* + +The Rhotator Screwdriver will rotate ANY node where `paramtype2 == "facedir"` + +More node types will be supported in the future. + +![Preview](/screenshots/preview.png) + +# Usage + +Pretty simple: + +- a right click will rotate the face you're pointing in clockwise direction + - the arrow in the Testing Cube shows how the face will rotate when right-clicked + - `RT` in the tool stands for `rotate` + +- a left click will rotate the node as if you "pushed" the closest edge you're pointing at + - the colored edges in the Testing Cube indicate the color of the face you'll see when left-clicking near that edge + - `PS` in the tool stands for `push` + +The left-click interaction area is not limited to the edges you can see in the Testing Cube. In reality you can click anywhere in a triangle like this (highlighted here just for convenience, you won't see anything like this in the game): + +![Interaction triangle](/screenshots/interaction-triangle.png) + +# Non-full nodes + +Nodes that don't occupy a full cube (such as slabs and stairs) can still be rotated properly, it's enough that you pay attention to the direction of the part you're pointing at - the "stomp" parts of the stairs will behave as the "top" face, the "rise" parts will behave as the "front" face. With the Rhotator Screwdriver there never really is a "top" or a "front" or whatever: the only thing that matters is the face you're pointing at. + +# Crafting + +Rhotator Screwdriver: a stick and a copper ingot; + +![Screwdriver crafting](/screenshots/screwdriver-crafting.png) + +Rhotator Testing Cube: a Rhotator Screwdriver and any wool block + +![Testing cube crafting](/screenshots/testcube-crafting.png) + +# Usage feedback + +At the beginning of [init.lua](/init.lua) there is a line reading `local enable_chat_notifications = false` - set it to `true` if you want the tool to send you chat messages about the operations it performs and in particular about nodes that aren't currently supported (if you enable the chat notifications you'll receive messages like these: + +- Pushed closest edge (left click) +- Rotated pointed face (right click) +- Cannot rotate node with paramtype2 == glasslikeliquidlevel diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..91e360d --- /dev/null +++ b/init.lua @@ -0,0 +1,268 @@ +rhotator = {} + +rhotator.mod_path = minetest.get_modpath(minetest.get_current_modname()) + +local matrix = dofile(rhotator.mod_path .. "/lib/matrix.lua") + +local enable_chat_notifications = true + +-- constants + +local POS = {} +local NEG = {} +POS.Y = 0 +POS.Z = 1 +NEG.Z = 2 +POS.X = 3 +NEG.X = 4 +NEG.Y = 5 + +PRIMARY_BTN = 1 +SECONDARY_BTN = 2 + +-- helper tables + +local rot_matrices = {} +local dir_matrices = {} + +-- init + +local function init_transforms() + local rot = {} + local dir = {} + + -- no rotation + rot[0] = matrix{{ 1, 0, 0}, + { 0, 1, 0}, + { 0, 0, 1}} + -- 90 degrees clockwise + rot[1] = matrix{{ 0, 0, 1}, + { 0, 1, 0}, + { -1, 0, 0}} + -- 180 degrees + rot[2] = matrix{{ -1, 0, 0}, + { 0, 1, 0}, + { 0, 0, -1}} + -- 270 degrees clockwise + rot[3] = matrix{{ 0, 0, -1}, + { 0, 1, 0}, + { 1, 0, 0}} + + rot_matrices = rot + + -- directions + -- Y+ + dir[0] = matrix{{ 1, 0, 0}, + { 0, 1, 0}, + { 0, 0, 1}} + -- Z+ + dir[1] = matrix{{ 1, 0, 0}, + { 0, 0, -1}, + { 0, 1, 0}} + -- Z- + dir[2] = matrix{{ 1, 0, 0}, + { 0, 0, 1}, + { 0, -1, 0}} + -- X+ + dir[3] = matrix{{ 0, 1, 0}, + { -1, 0, 0}, + { 0, 0, 1}} + -- X- + dir[4] = matrix{{ 0, -1, 0}, + { 1, 0, 0}, + { 0, 0, 1}} + -- Y- + dir[5] = matrix{{ -1, 0, 0}, + { 0, -1, 0}, + { 0, 0, 1}} + + dir_matrices = dir + + rhotator._facedir_transform = {} + rhotator._matrix_to_facedir = {} + + for facedir = 0, 23 do + local direction = math.floor(facedir / 4) + local rotation = facedir % 4 + local transform = dir[direction] * rot[rotation] + rhotator._facedir_transform[facedir] = transform + rhotator._matrix_to_facedir[transform:tostring():gsub("%-0", "0")] = facedir + end + +end + +init_transforms() + +-- helper functions + +local function cross_product(a, b) + return vector.new( + a.y * b.z - a.z * b.y, + a.z * b.x - a.x * b.z, + a.x * b.y - a.y * b.x + ) +end + +local function extract_main_axis(dir) + local axes = { "x", "y", "z" } + local axis = 1 + local max = 0 + for i = 1, 3 do + local abs = math.abs(dir[axes[i]]) + if abs > max then + axis = i + max = abs + end + end + return axes[axis] +end + +local function sign(num) + return (num < 0) and -1 or 1 +end + +local function extract_unit_vectors(player, pointed_thing) + assert(pointed_thing.type == "node") + local abs_face_pos = minetest.pointed_thing_to_face_pos(player, pointed_thing) + local pos = pointed_thing.under + local f = vector.subtract(abs_face_pos, pos) + local facedir = 0 + local primary = 0 + + local m1, m2 + + local unit_direction = vector.new() + local unit_rotation = vector.new() + local rotation = vector.new() + + if math.abs(f.y) == 0.5 then + unit_direction.y = sign(f.y) + rotation.x = f.x + rotation.z = f.z + elseif math.abs(f.z) == 0.5 then + unit_direction.z = sign(f.z) + rotation.x = f.x + rotation.y = f.y + else + unit_direction.x = sign(f.x) + rotation.y = f.y + rotation.z = f.z + end + + local main_axis = extract_main_axis(rotation) + + unit_rotation[main_axis] = sign(rotation[main_axis]) + + return { + back = unit_direction, + wrap = unit_rotation, + thumb = cross_product(unit_direction, unit_rotation), + } +end + +local function get_facedir_transform(facedir) + return rhotator._facedir_transform[facedir] or rhotator._facedir_transform[0] +end + +local function matrix_to_facedir(mtx) + local key = mtx:tostring():gsub("%-0", "0") + if not rhotator._matrix_to_facedir[key] then + error("Unsupported matrix:\n" .. key) + end + return rhotator._matrix_to_facedir[key] +end + +local function notify(playername, message) + if enable_chat_notifications then + minetest.chat_send_player(playername, "[rhotator] " .. message) + end +end + +local function vector_to_dir_index(vec) + local main_axis = extract_main_axis(vec) + if main_axis == "x" then return (vec.x > 0) and POS.X or NEG.X end + if main_axis == "z" then return (vec.z > 0) and POS.Z or NEG.Z end + return (vec.y > 0) and POS.Y or NEG.Y +end + +-- rhotator main + +local function interact(itemstack, player, pointed_thing, click) + if pointed_thing.type ~= "node" then + return + end + + local node = minetest.get_node_or_nil(pointed_thing.under) + local def = minetest.registered_nodes[node.name] + + if not node or not def then + notify(player:get_player_name(), "Unsupported node type: " .. node.name) + return + end + + if def.paramtype2 ~= "facedir" then + notify(player:get_player_name(), "Cannot rotate node with paramtype2 == " .. def.paramtype2) + return + end + + local unit = extract_unit_vectors(player, pointed_thing) + + local transform = false + + if click == PRIMARY_BTN then + transform = dir_matrices[vector_to_dir_index(unit.thumb)] + notify(player:get_player_name(), "Pushed closest edge (left click)") + else + transform = dir_matrices[vector_to_dir_index(unit.back)] + notify(player:get_player_name(), "Rotated pointed face (right click)") + end + + local start = get_facedir_transform(node.param2) + local stop = transform * rot_matrices[1] * transform:invert() * start + + minetest.set_node(pointed_thing.under,{ + name = node.name, + param1 = node.param1, + param2 = matrix_to_facedir(stop), + }) + +end + +minetest.register_tool("rhotator:screwdriver", { + description = "Rhotator Screwdriver (left-click pushes edge, right-click rotates face)", + inventory_image = "rhotator.png", + on_use = function(itemstack, player, pointed_thing) + interact(itemstack, player, pointed_thing, PRIMARY_BTN) + return itemstack + end, + on_place = function(itemstack, player, pointed_thing) + interact(itemstack, player, pointed_thing, SECONDARY_BTN) + return itemstack + end, +}) + +minetest.register_craft({ + output = "rhotator:screwdriver", + recipe = { + {"default:copper_ingot"}, + {"group:stick"} + } +}) + +minetest.register_node("rhotator:cube", { + drawtype = "mesh", + mesh = "rhotocube.obj", + tiles = { "rhotocube.png" }, + paramtype2 = "facedir", + description = "Rhotator Testing Cube", + walkable = true, + groups = { snappy = 2, choppy = 2, oddly_breakable_by_hand = 3 }, +}) + +minetest.register_craft({ + output = "rhotator:cube", + recipe = { + {"group:wool"}, + {"rhotator:screwdriver"}, + } +}) diff --git a/lib/matrix.lua b/lib/matrix.lua new file mode 100644 index 0000000..9e1ad67 --- /dev/null +++ b/lib/matrix.lua @@ -0,0 +1,1251 @@ +--[[ + +LUA MODULE + + matrix v$(_VERSION) - matrix functions implemented with Lua tables + +SYNOPSIS + + local matrix = require 'matrix' + m1 = matrix{{8,4,1},{6,8,3}} + m2 = matrix{{-8,1,3},{5,2,1}} + assert(m1 + m2 == matrix{{0,5,4},{11,10,4}}) + +DESCRIPTION + + With simple matrices this script is quite useful, though for more + exact calculations, one would probably use a program like Matlab instead. + Matrices of size 100x100 can still be handled very well. + The error for the determinant and the inverted matrix is around 10^-9 + with a 100x100 matrix and an element range from -100 to 100. + + Characteristics: + + - functions called via matrix. should be able to handle + any table matrix of structure t[i][j] = value + - can handle a type of complex matrix + - can handle symbolic matrices. (Symbolic matrices cannot be + used with complex matrices.) + - arithmetic functions do not change the matrix itself + but build and return a new matrix + - functions are intended to be light on checks + since one gets a Lua error on incorrect use anyways + - uses mainly Gauss-Jordan elimination + - for Lua tables optimised determinant calculation (fast) + but not invoking any checks for special types of matrices + - vectors can be set up via vec1 = matrix{{ 1,2,3 }}^'T' or matrix{1,2,3} + - vectors can be multiplied to a scalar via num = vec1^'T' * vec2 + where num will be a matrix with the result in mtx[1][1], + or use num = vec1:scalar( vec2 ), where num is a number + +API + + matrix function list: + + matrix.add + matrix.columns + matrix.concath + matrix.concatv + matrix.copy + matrix.cross + matrix.det + matrix.div + matrix.divnum + matrix.dogauss + matrix.elementstostring + matrix.getelement + matrix.gsub + matrix.invert + matrix.ipairs + matrix.latex + matrix.len + matrix.mul + matrix.mulnum + matrix:new + matrix.normf + matrix.normmax + matrix.pow + matrix.print + matrix.random + matrix.replace + matrix.root + matrix.rotl + matrix.rotr + matrix.round + matrix.rows + matrix.scalar + matrix.setelement + matrix.size + matrix.solve + matrix.sqrt + matrix.sub + matrix.subm + matrix.tostring + matrix.transpose + matrix.type + + See code and test_matrix.lua. + +DEPENDENCIES + + None (other than Lua 5.1 or 5.2). May be used with complex.lua. + +HOME PAGE + + http://luamatrix.luaforge.net + http://lua-users.org/wiki/LuaMatrix + +DOWNLOAD/INSTALL + + ./util.mk + cd tmp/* + luarocks make + +LICENSE + + Licensed under the same terms as Lua itself. + + Developers: + Michael Lutz (chillcode) - original author + David Manura http://lua-users.org/wiki/DavidManura +--]] + +--//////////// +--// matrix // +--//////////// + +local matrix = {_TYPE='module', _NAME='matrix', _VERSION='0.2.11.20120416'} + +-- access to the metatable we set at the end of the file +local matrix_meta = {} + +--///////////////////////////// +--// Get 'new' matrix object // +--///////////////////////////// + +--// matrix:new ( rows [, columns [, value]] ) +-- if rows is a table then sets rows as matrix +-- if rows is a table of structure {1,2,3} then it sets it as a vector matrix +-- if rows and columns are given and are numbers, returns a matrix with size rowsxcolumns +-- if num is given then returns a matrix with given size and all values set to num +-- if rows is given as number and columns is "I", will return an identity matrix of size rowsxrows +function matrix:new( rows, columns, value ) + -- check for given matrix + if type( rows ) == "table" then + -- check for vector + if type(rows[1]) ~= "table" then -- expect a vector + return setmetatable( {{rows[1]},{rows[2]},{rows[3]}},matrix_meta ) + end + return setmetatable( rows,matrix_meta ) + end + -- get matrix table + local mtx = {} + local value = value or 0 + -- build identity matrix of given rows + if columns == "I" then + for i = 1,rows do + mtx[i] = {} + for j = 1,rows do + if i == j then + mtx[i][j] = 1 + else + mtx[i][j] = 0 + end + end + end + -- build new matrix + else + for i = 1,rows do + mtx[i] = {} + for j = 1,columns do + mtx[i][j] = value + end + end + end + -- return matrix with shared metatable + return setmetatable( mtx,matrix_meta ) +end + +--// matrix ( rows [, comlumns [, value]] ) +-- set __call behaviour of matrix +-- for matrix( ... ) as matrix.new( ... ) +setmetatable( matrix, { __call = function( ... ) return matrix.new( ... ) end } ) + + +-- functions are designed to be light on checks +-- so we get Lua errors instead on wrong input +-- matrix. should handle any table of structure t[i][j] = value +-- we always return a matrix with scripts metatable +-- cause its faster than setmetatable( mtx, getmetatable( input matrix ) ) + +--/////////////////////////////// +--// matrix 'matrix' functions // +--/////////////////////////////// + +--// for real, complex and symbolic matrices //-- + +-- note: real and complex matrices may be added, subtracted, etc. +-- real and symbolic matrices may also be added, subtracted, etc. +-- but one should avoid using symbolic matrices with complex ones +-- since it is not clear which metatable then is used + +--// matrix.add ( m1, m2 ) +-- Add two matrices; m2 may be of bigger size than m1 +function matrix.add( m1, m2 ) + local mtx = {} + for i = 1,#m1 do + local m3i = {} + mtx[i] = m3i + for j = 1,#m1[1] do + m3i[j] = m1[i][j] + m2[i][j] + end + end + return setmetatable( mtx, matrix_meta ) +end + +--// matrix.sub ( m1 ,m2 ) +-- Subtract two matrices; m2 may be of bigger size than m1 +function matrix.sub( m1, m2 ) + local mtx = {} + for i = 1,#m1 do + local m3i = {} + mtx[i] = m3i + for j = 1,#m1[1] do + m3i[j] = m1[i][j] - m2[i][j] + end + end + return setmetatable( mtx, matrix_meta ) +end + +--// matrix.mul ( m1, m2 ) +-- Multiply two matrices; m1 columns must be equal to m2 rows +-- e.g. #m1[1] == #m2 +function matrix.mul( m1, m2 ) + -- multiply rows with columns + local mtx = {} + for i = 1,#m1 do + mtx[i] = {} + for j = 1,#m2[1] do + local num = m1[i][1] * m2[1][j] + for n = 2,#m1[1] do + num = num + m1[i][n] * m2[n][j] + end + mtx[i][j] = num + end + end + return setmetatable( mtx, matrix_meta ) +end + +--// matrix.div ( m1, m2 ) +-- Divide two matrices; m1 columns must be equal to m2 rows +-- m2 must be square, to be inverted, +-- if that fails returns the rank of m2 as second argument +-- e.g. #m1[1] == #m2; #m2 == #m2[1] +function matrix.div( m1, m2 ) + local rank; m2,rank = matrix.invert( m2 ) + if not m2 then return m2, rank end -- singular + return matrix.mul( m1, m2 ) +end + +--// matrix.mulnum ( m1, num ) +-- Multiply matrix with a number +-- num may be of type 'number' or 'complex number' +-- strings get converted to complex number, if that fails then to symbol +function matrix.mulnum( m1, num ) + local mtx = {} + -- multiply elements with number + for i = 1,#m1 do + mtx[i] = {} + for j = 1,#m1[1] do + mtx[i][j] = m1[i][j] * num + end + end + return setmetatable( mtx, matrix_meta ) +end + +--// matrix.divnum ( m1, num ) +-- Divide matrix by a number +-- num may be of type 'number' or 'complex number' +-- strings get converted to complex number, if that fails then to symbol +function matrix.divnum( m1, num ) + local mtx = {} + -- divide elements by number + for i = 1,#m1 do + local mtxi = {} + mtx[i] = mtxi + for j = 1,#m1[1] do + mtxi[j] = m1[i][j] / num + end + end + return setmetatable( mtx, matrix_meta ) +end + + +--// for real and complex matrices only //-- + +--// matrix.pow ( m1, num ) +-- Power of matrix; mtx^(num) +-- num is an integer and may be negative +-- m1 has to be square +-- if num is negative and inverting m1 fails +-- returns the rank of matrix m1 as second argument +function matrix.pow( m1, num ) + assert(num == math.floor(num), "exponent not an integer") + if num == 0 then + return matrix:new( #m1,"I" ) + end + if num < 0 then + local rank; m1,rank = matrix.invert( m1 ) + if not m1 then return m1, rank end -- singular + num = -num + end + local mtx = matrix.copy( m1 ) + for i = 2,num do + mtx = matrix.mul( mtx,m1 ) + end + return mtx +end + +local function number_norm2(x) + return x * x +end + +--// matrix.det ( m1 ) +-- Calculate the determinant of a matrix +-- m1 needs to be square +-- Can calc the det for symbolic matrices up to 3x3 too +-- The function to calculate matrices bigger 3x3 +-- is quite fast and for matrices of medium size ~(100x100) +-- and average values quite accurate +-- here we try to get the nearest element to |1|, (smallest pivot element) +-- os that usually we have |mtx[i][j]/subdet| > 1 or mtx[i][j]; +-- with complex matrices we use the complex.abs function to check if it is bigger or smaller +function matrix.det( m1 ) + + -- check if matrix is quadratic + assert(#m1 == #m1[1], "matrix not square") + + local size = #m1 + + if size == 1 then + return m1[1][1] + end + + if size == 2 then + return m1[1][1]*m1[2][2] - m1[2][1]*m1[1][2] + end + + if size == 3 then + return ( m1[1][1]*m1[2][2]*m1[3][3] + m1[1][2]*m1[2][3]*m1[3][1] + m1[1][3]*m1[2][1]*m1[3][2] + - m1[1][3]*m1[2][2]*m1[3][1] - m1[1][1]*m1[2][3]*m1[3][2] - m1[1][2]*m1[2][1]*m1[3][3] ) + end + + --// no symbolic matrix supported below here + local e = m1[1][1] + local zero = type(e) == "table" and e.zero or 0 + local norm2 = type(e) == "table" and e.norm2 or number_norm2 + + --// matrix is bigger than 3x3 + -- get determinant + -- using Gauss elimination and Laplace + -- start eliminating from below better for removals + -- get copy of matrix, set initial determinant + local mtx = matrix.copy( m1 ) + local det = 1 + -- get det up to the last element + for j = 1,#mtx[1] do + -- get smallest element so that |factor| > 1 + -- and set it as last element + local rows = #mtx + local subdet,xrow + for i = 1,rows do + -- get element + local e = mtx[i][j] + -- if no subdet has been found + if not subdet then + -- check if element it is not zero + if e ~= zero then + -- use element as new subdet + subdet,xrow = e,i + end + -- check for elements nearest to 1 or -1 + elseif e ~= zero and math.abs(norm2(e)-1) < math.abs(norm2(subdet)-1) then + subdet,xrow = e,i + end + end + -- only cary on if subdet is found + if subdet then + -- check if xrow is the last row, + -- else switch lines and multiply det by -1 + if xrow ~= rows then + mtx[rows],mtx[xrow] = mtx[xrow],mtx[rows] + det = -det + end + -- traverse all fields setting element to zero + -- we don't set to zero cause we don't use that column anymore then anyways + for i = 1,rows-1 do + -- factor is the dividor of the first element + -- if element is not already zero + if mtx[i][j] ~= zero then + local factor = mtx[i][j]/subdet + -- update all remaining fields of the matrix, with value from xrow + for n = j+1,#mtx[1] do + mtx[i][n] = mtx[i][n] - factor * mtx[rows][n] + end + end + end + -- update determinant and remove row + if math.fmod( rows,2 ) == 0 then + det = -det + end + det = det * subdet + table.remove( mtx ) + else + -- break here table det is 0 + return det * 0 + end + end + -- det ready to return + return det +end + +--// matrix.dogauss ( mtx ) +-- Gauss elimination, Gauss-Jordan Method +-- this function changes the matrix itself +-- returns on success: true, +-- returns on failure: false,'rank of matrix' + +-- locals +-- checking here for the element nearest but not equal to zero (smallest pivot element). +-- This way the `factor` in `dogauss` will be >= 1, which +-- can give better results. +local pivotOk = function( mtx,i,j,norm2 ) + -- find min value + local iMin + local normMin = math.huge + for _i = i,#mtx do + local e = mtx[_i][j] + local norm = math.abs(norm2(e)) + if norm > 0 and norm < normMin then + iMin = _i + normMin = norm + end + end + if iMin then + -- switch lines if not in position. + if iMin ~= i then + mtx[i],mtx[iMin] = mtx[iMin],mtx[i] + end + return true + end + return false +end + +local function copy(x) + return type(x) == "table" and x.copy(x) or x +end + +-- note: in --// ... //-- we have a way that does no divison, +-- however with big number and matrices we get problems since we do no reducing +function matrix.dogauss( mtx ) + local e = mtx[1][1] + local zero = type(e) == "table" and e.zero or 0 + local one = type(e) == "table" and e.one or 1 + local norm2 = type(e) == "table" and e.norm2 or number_norm2 + + local rows,columns = #mtx,#mtx[1] + -- stairs left -> right + for j = 1,rows do + -- check if element can be setted to one + if pivotOk( mtx,j,j,norm2 ) then + -- start parsing rows + for i = j+1,rows do + -- check if element is not already zero + if mtx[i][j] ~= zero then + -- we may add x*otherline row, to set element to zero + -- tozero - x*mtx[j][j] = 0; x = tozero/mtx[j][j] + local factor = mtx[i][j]/mtx[j][j] + --// this should not be used although it does no division, + -- yet with big matrices (since we do no reducing and other things) + -- we get too big numbers + --local factor1,factor2 = mtx[i][j],mtx[j][j] //-- + mtx[i][j] = copy(zero) + for _j = j+1,columns do + --// mtx[i][_j] = mtx[i][_j] * factor2 - factor1 * mtx[j][_j] //-- + mtx[i][_j] = mtx[i][_j] - factor * mtx[j][_j] + end + end + end + else + -- return false and the rank of the matrix + return false,j-1 + end + end + -- stairs right <- left + for j = rows,1,-1 do + -- set element to one + -- do division here + local div = mtx[j][j] + for _j = j+1,columns do + mtx[j][_j] = mtx[j][_j] / div + end + -- start parsing rows + for i = j-1,1,-1 do + -- check if element is not already zero + if mtx[i][j] ~= zero then + local factor = mtx[i][j] + for _j = j+1,columns do + mtx[i][_j] = mtx[i][_j] - factor * mtx[j][_j] + end + mtx[i][j] = copy(zero) + end + end + mtx[j][j] = copy(one) + end + return true +end + +--// matrix.invert ( m1 ) +-- Get the inverted matrix or m1 +-- matrix must be square and not singular +-- on success: returns inverted matrix +-- on failure: returns nil,'rank of matrix' +function matrix.invert( m1 ) + assert(#m1 == #m1[1], "matrix not square") + local mtx = matrix.copy( m1 ) + local ident = setmetatable( {},matrix_meta ) + local e = m1[1][1] + local zero = type(e) == "table" and e.zero or 0 + local one = type(e) == "table" and e.one or 1 + for i = 1,#m1 do + local identi = {} + ident[i] = identi + for j = 1,#m1 do + identi[j] = copy((i == j) and one or zero) + end + end + mtx = matrix.concath( mtx,ident ) + local done,rank = matrix.dogauss( mtx ) + if done then + return matrix.subm( mtx, 1,(#mtx[1]/2)+1,#mtx,#mtx[1] ) + else + return nil,rank + end +end + +--// matrix.sqrt ( m1 [,iters] ) +-- calculate the square root of a matrix using "Denman Beavers square root iteration" +-- condition: matrix rows == matrix columns; must have a invers matrix and a square root +-- if called without additional arguments, the function finds the first nearest square root to +-- input matrix, there are others but the error between them is very small +-- if called with agument iters, the function will return the matrix by number of iterations +-- the script returns: +-- as first argument, matrix^.5 +-- as second argument, matrix^-.5 +-- as third argument, the average error between (matrix^.5)^2-inputmatrix +-- you have to determin for yourself if the result is sufficent enough for you +-- local average error +local function get_abs_avg( m1, m2 ) + local dist = 0 + local e = m1[1][1] + local abs = type(e) == "table" and e.abs or math.abs + for i=1,#m1 do + for j=1,#m1[1] do + dist = dist + abs(m1[i][j]-m2[i][j]) + end + end + -- norm by numbers of entries + return dist/(#m1*2) +end +-- square root function +function matrix.sqrt( m1, iters ) + assert(#m1 == #m1[1], "matrix not square") + local iters = iters or math.huge + local y = matrix.copy( m1 ) + local z = matrix(#y, 'I') + local dist = math.huge + -- iterate, and get the average error + for n=1,iters do + local lasty,lastz = y,z + -- calc square root + -- y, z = (1/2)*(y + z^-1), (1/2)*(z + y^-1) + y, z = matrix.divnum((matrix.add(y,matrix.invert(z))),2), + matrix.divnum((matrix.add(z,matrix.invert(y))),2) + local dist1 = get_abs_avg(y,lasty) + if iters == math.huge then + if dist1 >= dist then + return lasty,lastz,get_abs_avg(matrix.mul(lasty,lasty),m1) + end + end + dist = dist1 + end + return y,z,get_abs_avg(matrix.mul(y,y),m1) +end + +--// matrix.root ( m1, root [,iters] ) +-- calculate any root of a matrix +-- source: http://www.dm.unipi.it/~cortona04/slides/bruno.pdf +-- m1 and root have to be given;(m1 = matrix, root = number) +-- conditions same as matrix.sqrt +-- returns same values as matrix.sqrt +function matrix.root( m1, root, iters ) + assert(#m1 == #m1[1], "matrix not square") + local iters = iters or math.huge + local mx = matrix.copy( m1 ) + local my = matrix.mul(mx:invert(),mx:pow(root-1)) + local dist = math.huge + -- iterate, and get the average error + for n=1,iters do + local lastx,lasty = mx,my + -- calc root of matrix + --mx,my = ((p-1)*mx + my^-1)/p, + -- ((((p-1)*my + mx^-1)/p)*my^-1)^(p-2) * + -- ((p-1)*my + mx^-1)/p + mx,my = mx:mulnum(root-1):add(my:invert()):divnum(root), + my:mulnum(root-1):add(mx:invert()):divnum(root) + :mul(my:invert():pow(root-2)):mul(my:mulnum(root-1) + :add(mx:invert())):divnum(root) + local dist1 = get_abs_avg(mx,lastx) + if iters == math.huge then + if dist1 >= dist then + return lastx,lasty,get_abs_avg(matrix.pow(lastx,root),m1) + end + end + dist = dist1 + end + return mx,my,get_abs_avg(matrix.pow(mx,root),m1) +end + + +--// Norm functions //-- + +--// matrix.normf ( mtx ) +-- calculates the Frobenius norm of the matrix. +-- ||mtx||_F = sqrt(SUM_{i,j} |a_{i,j}|^2) +-- http://en.wikipedia.org/wiki/Frobenius_norm#Frobenius_norm +function matrix.normf(mtx) + local mtype = matrix.type(mtx) + local result = 0 + for i = 1,#mtx do + for j = 1,#mtx[1] do + local e = mtx[i][j] + if mtype ~= "number" then e = e:abs() end + result = result + e^2 + end + end + local sqrt = (type(result) == "number") and math.sqrt or result.sqrt + return sqrt(result) +end + +--// matrix.normmax ( mtx ) +-- calculates the max norm of the matrix. +-- ||mtx||_{max} = max{|a_{i,j}|} +-- Does not work with symbolic matrices +-- http://en.wikipedia.org/wiki/Frobenius_norm#Max_norm +function matrix.normmax(mtx) + local abs = (matrix.type(mtx) == "number") and math.abs or mtx[1][1].abs + local result = 0 + for i = 1,#mtx do + for j = 1,#mtx[1] do + local e = abs(mtx[i][j]) + if e > result then result = e end + end + end + return result +end + + +--// only for number and complex type //-- +-- Functions changing the matrix itself + +--// matrix.round ( mtx [, idp] ) +-- perform round on elements +local numround = function( num,mult ) + return math.floor( num * mult + 0.5 ) / mult +end +local tround = function( t,mult ) + for i,v in ipairs(t) do + t[i] = math.floor( v * mult + 0.5 ) / mult + end + return t +end +function matrix.round( mtx, idp ) + local mult = 10^( idp or 0 ) + local fround = matrix.type( mtx ) == "number" and numround or tround + for i = 1,#mtx do + for j = 1,#mtx[1] do + mtx[i][j] = fround(mtx[i][j],mult) + end + end + return mtx +end + +--// matrix.random( mtx [,start] [, stop] [, idip] ) +-- fillmatrix with random values +local numfill = function( _,start,stop,idp ) + return math.random( start,stop ) / idp +end +local tfill = function( t,start,stop,idp ) + for i in ipairs(t) do + t[i] = math.random( start,stop ) / idp + end + return t +end +function matrix.random( mtx,start,stop,idp ) + local start,stop,idp = start or -10,stop or 10,idp or 1 + local ffill = matrix.type( mtx ) == "number" and numfill or tfill + for i = 1,#mtx do + for j = 1,#mtx[1] do + mtx[i][j] = ffill( mtx[i][j], start, stop, idp ) + end + end + return mtx +end + + +--////////////////////////////// +--// Object Utility Functions // +--////////////////////////////// + +--// for all types and matrices //-- + +--// matrix.type ( mtx ) +-- get type of matrix, normal/complex/symbol or tensor +function matrix.type( mtx ) + local e = mtx[1][1] + if type(e) == "table" then + if e.type then + return e:type() + end + return "tensor" + end + return "number" +end + +-- local functions to copy matrix values +local num_copy = function( num ) + return num +end +local t_copy = function( t ) + local newt = setmetatable( {}, getmetatable( t ) ) + for i,v in ipairs( t ) do + newt[i] = v + end + return newt +end + +--// matrix.copy ( m1 ) +-- Copy a matrix +-- simple copy, one can write other functions oneself +function matrix.copy( m1 ) + local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy + local mtx = {} + for i = 1,#m1[1] do + mtx[i] = {} + for j = 1,#m1 do + mtx[i][j] = docopy( m1[i][j] ) + end + end + return setmetatable( mtx, matrix_meta ) +end + +--// matrix.transpose ( m1 ) +-- Transpose a matrix +-- switch rows and columns +function matrix.transpose( m1 ) + local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy + local mtx = {} + for i = 1,#m1[1] do + mtx[i] = {} + for j = 1,#m1 do + mtx[i][j] = docopy( m1[j][i] ) + end + end + return setmetatable( mtx, matrix_meta ) +end + +--// matrix.subm ( m1, i1, j1, i2, j2 ) +-- Submatrix out of a matrix +-- input: i1,j1,i2,j2 +-- i1,j1 are the start element +-- i2,j2 are the end element +-- condition: i1,j1,i2,j2 are elements of the matrix +function matrix.subm( m1,i1,j1,i2,j2 ) + local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy + local mtx = {} + for i = i1,i2 do + local _i = i-i1+1 + mtx[_i] = {} + for j = j1,j2 do + local _j = j-j1+1 + mtx[_i][_j] = docopy( m1[i][j] ) + end + end + return setmetatable( mtx, matrix_meta ) +end + +--// matrix.concath( m1, m2 ) +-- Concatenate two matrices, horizontal +-- will return m1m2; rows have to be the same +-- e.g.: #m1 == #m2 +function matrix.concath( m1,m2 ) + assert(#m1 == #m2, "matrix size mismatch") + local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy + local mtx = {} + local offset = #m1[1] + for i = 1,#m1 do + mtx[i] = {} + for j = 1,offset do + mtx[i][j] = docopy( m1[i][j] ) + end + for j = 1,#m2[1] do + mtx[i][j+offset] = docopy( m2[i][j] ) + end + end + return setmetatable( mtx, matrix_meta ) +end + +--// matrix.concatv ( m1, m2 ) +-- Concatenate two matrices, vertical +-- will return m1 +-- m2 +-- columns have to be the same; e.g.: #m1[1] == #m2[1] +function matrix.concatv( m1,m2 ) + assert(#m1[1] == #m2[1], "matrix size mismatch") + local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy + local mtx = {} + for i = 1,#m1 do + mtx[i] = {} + for j = 1,#m1[1] do + mtx[i][j] = docopy( m1[i][j] ) + end + end + local offset = #mtx + for i = 1,#m2 do + local _i = i + offset + mtx[_i] = {} + for j = 1,#m2[1] do + mtx[_i][j] = docopy( m2[i][j] ) + end + end + return setmetatable( mtx, matrix_meta ) +end + +--// matrix.rotl ( m1 ) +-- Rotate Left, 90 degrees +function matrix.rotl( m1 ) + local mtx = matrix:new( #m1[1],#m1 ) + local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy + for i = 1,#m1 do + for j = 1,#m1[1] do + mtx[#m1[1]-j+1][i] = docopy( m1[i][j] ) + end + end + return mtx +end + +--// matrix.rotr ( m1 ) +-- Rotate Right, 90 degrees +function matrix.rotr( m1 ) + local mtx = matrix:new( #m1[1],#m1 ) + local docopy = matrix.type( m1 ) == "number" and num_copy or t_copy + for i = 1,#m1 do + for j = 1,#m1[1] do + mtx[j][#m1-i+1] = docopy( m1[i][j] ) + end + end + return mtx +end + +local function tensor_tostring( t,fstr ) + if not fstr then return "["..table.concat(t,",").."]" end + local tval = {} + for i,v in ipairs( t ) do + tval[i] = string.format( fstr,v ) + end + return "["..table.concat(tval,",").."]" +end +local function number_tostring( e,fstr ) + return fstr and string.format( fstr,e ) or e +end + +--// matrix.tostring ( mtx, formatstr ) +-- tostring function +function matrix.tostring( mtx, formatstr ) + local ts = {} + local mtype = matrix.type( mtx ) + local e = mtx[1][1] + local tostring = mtype == "tensor" and tensor_tostring or + type(e) == "table" and e.tostring or number_tostring + for i = 1,#mtx do + local tstr = {} + for j = 1,#mtx[1] do + tstr[j] = tostring(mtx[i][j],formatstr) + end + ts[i] = table.concat(tstr, "\t") + end + return table.concat(ts, "\n") +end + +--// matrix.print ( mtx [, formatstr] ) +-- print out the matrix, just calls tostring +function matrix.print( ... ) + print( matrix.tostring( ... ) ) +end + +--// matrix.latex ( mtx [, align] ) +-- LaTeX output +function matrix.latex( mtx, align ) + -- align : option to align the elements + -- c = center; l = left; r = right + -- \usepackage{dcolumn}; D{.}{,}{-1}; aligns number by . replaces it with , + local align = align or "c" + local str = "$\\left( \\begin{array}{"..string.rep( align, #mtx[1] ).."}\n" + local getstr = matrix.type( mtx ) == "tensor" and tensor_tostring or number_tostring + for i = 1,#mtx do + str = str.."\t"..getstr(mtx[i][1]) + for j = 2,#mtx[1] do + str = str.." & "..getstr(mtx[i][j]) + end + -- close line + if i == #mtx then + str = str.."\n" + else + str = str.." \\\\\n" + end + end + return str.."\\end{array} \\right)$" +end + + +--// Functions not changing the matrix + +--// matrix.rows ( mtx ) +-- return number of rows +function matrix.rows( mtx ) + return #mtx +end + +--// matrix.columns ( mtx ) +-- return number of columns +function matrix.columns( mtx ) + return #mtx[1] +end + +--// matrix.size ( mtx ) +-- get matrix size as string rows,columns +function matrix.size( mtx ) + if matrix.type( mtx ) == "tensor" then + return #mtx,#mtx[1],#mtx[1][1] + end + return #mtx,#mtx[1] +end + +--// matrix.getelement ( mtx, i, j ) +-- return specific element ( row,column ) +-- returns element on success and nil on failure +function matrix.getelement( mtx,i,j ) + if mtx[i] and mtx[i][j] then + return mtx[i][j] + end +end + +--// matrix.setelement( mtx, i, j, value ) +-- set an element ( i, j, value ) +-- returns 1 on success and nil on failure +function matrix.setelement( mtx,i,j,value ) + if matrix.getelement( mtx,i,j ) then + -- check if value type is number + mtx[i][j] = value + return 1 + end +end + +--// matrix.ipairs ( mtx ) +-- iteration, same for complex +function matrix.ipairs( mtx ) + local i,j,rows,columns = 1,0,#mtx,#mtx[1] + local function iter() + j = j + 1 + if j > columns then -- return first element from next row + i,j = i + 1,1 + end + if i <= rows then + return i,j + end + end + return iter +end + +--/////////////////////////////// +--// matrix 'vector' functions // +--/////////////////////////////// + +-- a vector is defined as a 3x1 matrix +-- get a vector; vec = matrix{{ 1,2,3 }}^'T' + +--// matrix.scalar ( m1, m2 ) +-- returns the Scalar Product of two 3x1 matrices (vectors) +function matrix.scalar( m1, m2 ) + return m1[1][1]*m2[1][1] + m1[2][1]*m2[2][1] + m1[3][1]*m2[3][1] +end + +--// matrix.cross ( m1, m2 ) +-- returns the Cross Product of two 3x1 matrices (vectors) +function matrix.cross( m1, m2 ) + local mtx = {} + mtx[1] = { m1[2][1]*m2[3][1] - m1[3][1]*m2[2][1] } + mtx[2] = { m1[3][1]*m2[1][1] - m1[1][1]*m2[3][1] } + mtx[3] = { m1[1][1]*m2[2][1] - m1[2][1]*m2[1][1] } + return setmetatable( mtx, matrix_meta ) +end + +--// matrix.len ( m1 ) +-- returns the Length of a 3x1 matrix (vector) +function matrix.len( m1 ) + return math.sqrt( m1[1][1]^2 + m1[2][1]^2 + m1[3][1]^2 ) +end + + +--// matrix.replace (mtx, func, ...) +-- for each element e in the matrix mtx, replace it with func(mtx, ...). +function matrix.replace( m1, func, ... ) + local mtx = {} + for i = 1,#m1 do + local m1i = m1[i] + local mtxi = {} + for j = 1,#m1i do + mtxi[j] = func( m1i[j], ... ) + end + mtx[i] = mtxi + end + return setmetatable( mtx, matrix_meta ) +end + +--// matrix.remcomplex ( mtx ) +-- set the matrix elements to strings +-- IMPROVE: tostring v.s. tostringelements confusing +function matrix.elementstostrings( mtx ) + local e = mtx[1][1] + local tostring = type(e) == "table" and e.tostring or tostring + return matrix.replace(mtx, tostring) +end + +--// matrix.solve ( m1 ) +-- solve; tries to solve a symbolic matrix to a number +function matrix.solve( m1 ) + assert( matrix.type( m1 ) == "symbol", "matrix not of type 'symbol'" ) + local mtx = {} + for i = 1,#m1 do + mtx[i] = {} + for j = 1,#m1[1] do + mtx[i][j] = tonumber( loadstring( "return "..m1[i][j][1] )() ) + end + end + return setmetatable( mtx, matrix_meta ) +end + +--////////////////////////-- +--// METATABLE HANDLING //-- +--////////////////////////-- + +--// MetaTable +-- as we declaired on top of the page +-- local/shared metatable +-- matrix_meta + +-- note '...' is always faster than 'arg1,arg2,...' if it can be used + +-- Set add "+" behaviour +matrix_meta.__add = function( ... ) + return matrix.add( ... ) +end + +-- Set subtract "-" behaviour +matrix_meta.__sub = function( ... ) + return matrix.sub( ... ) +end + +-- Set multiply "*" behaviour +matrix_meta.__mul = function( m1,m2 ) + if getmetatable( m1 ) ~= matrix_meta then + return matrix.mulnum( m2,m1 ) + elseif getmetatable( m2 ) ~= matrix_meta then + return matrix.mulnum( m1,m2 ) + end + return matrix.mul( m1,m2 ) +end + +-- Set division "/" behaviour +matrix_meta.__div = function( m1,m2 ) + if getmetatable( m1 ) ~= matrix_meta then + return matrix.mulnum( matrix.invert(m2),m1 ) + elseif getmetatable( m2 ) ~= matrix_meta then + return matrix.divnum( m1,m2 ) + end + return matrix.div( m1,m2 ) +end + +-- Set unary minus "-" behavior +matrix_meta.__unm = function( mtx ) + return matrix.mulnum( mtx,-1 ) +end + +-- Set power "^" behaviour +-- if opt is any integer number will do mtx^opt +-- (returning nil if answer doesn't exist) +-- if opt is 'T' then it will return the transpose matrix +-- only for complex: +-- if opt is '*' then it returns the complex conjugate matrix + local option = { + -- only for complex + ["*"] = function( m1 ) return matrix.conjugate( m1 ) end, + -- for both + ["T"] = function( m1 ) return matrix.transpose( m1 ) end, + } +matrix_meta.__pow = function( m1, opt ) + return option[opt] and option[opt]( m1 ) or matrix.pow( m1,opt ) +end + +-- Set equal "==" behaviour +matrix_meta.__eq = function( m1, m2 ) + -- check same type + if matrix.type( m1 ) ~= matrix.type( m2 ) then + return false + end + -- check same size + if #m1 ~= #m2 or #m1[1] ~= #m2[1] then + return false + end + -- check elements equal + for i = 1,#m1 do + for j = 1,#m1[1] do + if m1[i][j] ~= m2[i][j] then + return false + end + end + end + return true +end + +-- Set tostring "tostring( mtx )" behaviour +matrix_meta.__tostring = function( ... ) + return matrix.tostring( ... ) +end + +-- set __call "mtx( [formatstr] )" behaviour, mtx [, formatstr] +matrix_meta.__call = function( ... ) + matrix.print( ... ) +end + +--// __index handling +matrix_meta.__index = {} +for k,v in pairs( matrix ) do + matrix_meta.__index[k] = v +end + + +--///////////////////////////////// +--// symbol class implementation +--///////////////////////////////// + +-- access to the symbolic metatable +local symbol_meta = {}; symbol_meta.__index = symbol_meta +local symbol = symbol_meta + +function symbol_meta.new(o) + return setmetatable({tostring(o)}, symbol_meta) +end +symbol_meta.to = symbol_meta.new + +-- symbol( arg ) +-- same as symbol.to( arg ) +-- set __call behaviour of symbol +setmetatable( symbol_meta, { __call = function( _,s ) return symbol_meta.to( s ) end } ) + + +-- Converts object to string, optionally with formatting. +function symbol_meta.tostring( e,fstr ) + return string.format( fstr,e[1] ) +end + +-- Returns "symbol" if object is a symbol type, else nothing. +function symbol_meta:type() + if getmetatable(self) == symbol_meta then + return "symbol" + end +end + +-- Performs string.gsub on symbol. +-- for use in matrix.replace +function symbol_meta:gsub(from, to) + return symbol.to( string.gsub( self[1],from,to ) ) +end + +-- creates function that replaces one letter by something else +-- makereplacer( "a",4,"b",7, ... )(x) +-- will replace a with 4 and b with 7 in symbol x. +-- for use in matrix.replace +function symbol_meta.makereplacer( ... ) + local tosub = {} + local args = {...} + for i = 1,#args,2 do + tosub[args[i]] = args[i+1] + end + local function func( a ) return tosub[a] or a end + return function(sym) + return symbol.to( string.gsub( sym[1], "%a", func ) ) + end +end + +-- applies abs function to symbol +function symbol_meta.abs(a) + return symbol.to("(" .. a[1] .. "):abs()") +end + +-- applies sqrt function to symbol +function symbol_meta.sqrt(a) + return symbol.to("(" .. a[1] .. "):sqrt()") +end + +function symbol_meta.__add(a,b) + return symbol.to(a .. "+" .. b) +end + +function symbol_meta.__sub(a,b) + return symbol.to(a .. "-" .. b) +end + +function symbol_meta.__mul(a,b) + return symbol.to("(" .. a .. ")*(" .. b .. ")") +end + +function symbol_meta.__div(a,b) + return symbol.to("(" .. a .. ")/(" .. b .. ")") +end + +function symbol_meta.__pow(a,b) + return symbol.to("(" .. a .. ")^(" .. b .. ")") +end + +function symbol_meta.__eq(a,b) + return a[1] == b[1] +end + +function symbol_meta.__tostring(a) + return a[1] +end + +function symbol_meta.__concat(a,b) + return tostring(a) .. tostring(b) +end + +matrix.symbol = symbol + + +-- return matrix +return matrix + +--///////////////-- +--// chillcode //-- +--///////////////-- \ No newline at end of file diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..0327d56 --- /dev/null +++ b/mod.conf @@ -0,0 +1 @@ +name = rhotator diff --git a/models/rhotocube.obj b/models/rhotocube.obj new file mode 100644 index 0000000..1fe837a --- /dev/null +++ b/models/rhotocube.obj @@ -0,0 +1,43 @@ +v -0.500000 -0.500000 0.500000 +v -0.500000 -0.500000 -0.500000 +v 0.500000 -0.500000 -0.500000 +v 0.500000 -0.500000 0.500000 +v -0.500000 0.500000 0.500000 +v -0.499999 0.500000 -0.500000 +v 0.500000 0.500000 -0.500000 +v 0.500000 0.500000 0.500000 +vt 0.666667 -0.000000 +vt 0.666667 0.500000 +vt 0.333333 0.500000 +vt 0.333333 -0.000000 +vt 0.666667 1.000000 +vt 0.333333 1.000000 +vt 0.333333 0.500000 +vt 0.666667 0.500000 +vt 0.333333 0.500000 +vt 0.333333 1.000000 +vt -0.000000 1.000000 +vt 0.000000 0.500000 +vt 1.000000 0.000000 +vt 1.000000 0.500000 +vt 0.666667 0.500000 +vt 0.666667 -0.000000 +vt 0.333333 -0.000000 +vt 0.000000 0.500000 +vt -0.000000 -0.000000 +vt 0.666667 0.500000 +vt 1.000000 0.500000 +vt 1.000000 1.000000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn -1.0000 0.0000 -0.0000 +vn 0.0000 -0.0000 -1.0000 +vn 1.0000 -0.0000 0.0000 +vn -0.0000 0.0000 1.0000 +s off +f 1/1/1 2/2/1 3/3/1 4/4/1 +f 5/5/2 8/6/2 7/7/2 6/8/2 +f 1/9/3 5/10/3 6/11/3 2/12/3 +f 2/13/4 6/14/4 7/15/4 3/16/4 +f 3/17/5 7/7/5 8/18/5 4/19/5 +f 5/5/6 1/20/6 4/21/6 8/22/6 diff --git a/screenshots/interaction-triangle.png b/screenshots/interaction-triangle.png new file mode 100644 index 0000000..05a9ae0 Binary files /dev/null and b/screenshots/interaction-triangle.png differ diff --git a/screenshots/preview.png b/screenshots/preview.png new file mode 100644 index 0000000..d95caac Binary files /dev/null and b/screenshots/preview.png differ diff --git a/screenshots/screwdriver-crafting.png b/screenshots/screwdriver-crafting.png new file mode 100644 index 0000000..9d18d67 Binary files /dev/null and b/screenshots/screwdriver-crafting.png differ diff --git a/screenshots/testcube-crafting.png b/screenshots/testcube-crafting.png new file mode 100644 index 0000000..0074686 Binary files /dev/null and b/screenshots/testcube-crafting.png differ diff --git a/textures/rhotator.png b/textures/rhotator.png new file mode 100644 index 0000000..825e884 Binary files /dev/null and b/textures/rhotator.png differ diff --git a/textures/rhotocube.png b/textures/rhotocube.png new file mode 100644 index 0000000..44d27d8 Binary files /dev/null and b/textures/rhotocube.png differ diff --git a/textures/rhotocube.svg b/textures/rhotocube.svg new file mode 100644 index 0000000..3e07802 --- /dev/null +++ b/textures/rhotocube.svg @@ -0,0 +1,568 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +