Page 1 of 1

[Library] Commonlib [commonlib] [0.1]

PostPosted: Sat Feb 16, 2013 18:03
by rubenwardy
[h]Commonlib[/h]

This adds common functions that help Minetest modders achieve perfection in both their mod, and in interacting with other mods too.

You can include the whole library as a mod (recommended) or just include some modules in your mod.

Contains

  • Soft depend
  • Vector calculations, such as distance and speed
  • Terrain calculations, such as find the surface
  • See who owns a node
  • Table loading and saving
  • Player exists

Complete list

Image

[h]Download[/h]


Download the library
or view the code

License: CC-BY-SA

  • These conditions apply to the Commonlib code, and not to any mods using the library.
  • Commonlib is counted as a seperate library rather than part of the mod using it.
  • This means you can use Commonlib in a mod with a different license to this.

Image

[h]The Project[/h]

Aim: To promote and make easy mod making etiquette, in particular adding soft support/depends for other mods.

Thanks to: Sapier, Celeron55, PilzAdam, VanessaE

Image

[h]Help Us[/h]
This is meant to be a community project, so feel free to do some work to it.
Ideas are also good.

PostPosted: Sat Feb 16, 2013 18:09
by PilzAdam
Some vector stuff:
Your phone or window isn't wide enough to display the code box. If it's a phone, try rotating it to landscape mode.
Code: Select all
local v3 = {}
function v3.new(x, y, z)
    if x == nil then
        return {
            x = 0,
            y = 0,
            z = 0
        }
    end
    if type(x) == "table" then
        return {
            x = x.x,
            y = x.y,
            z = x.z,
        }
    end
    return {
        x = x,
        y = y,
        z = z,
    }
end
function v3.floor(v)
    return {
        x = math.floor(v.x),
        y = math.floor(v.y),
        z = math.floor(v.z),
    }
end
function v3.cmp(v, w)
    return (
        v.x == w.x and
        v.y == w.y and
        v.z == w.z
    )
end
function v3.add(v, w)
    return {
        x = v.x + w.x,
        y = v.y + w.y,
        z = v.z + w.z,
    }
end
function v3.sub(v, w)
    return {
        x = v.x - w.x,
        y = v.y - w.y,
        z = v.z - w.z,
    }
end
function v3.mul(v, a)
    return {
        x = v.x * a,
        y = v.y * a,
        z = v.z * a,
    }
end
function v3.len(v)
    return math.sqrt(
        math.pow(v.x, 2) +
        math.pow(v.y, 2) +
        math.pow(v.z, 2)
    )
end
function v3.norm(v)
    return v3.mul(v, 1.0 / v3.len(v))
end
function v3.distance(v, w)
    return math.sqrt(
        math.pow(v.x - w.x, 2) +
        math.pow(v.y - w.y, 2) +
        math.pow(v.z - w.z, 2)
    )
end
function v3.rotate_y(v, a)
    return {
        x = v.x * math.cos(a) - v.z * math.sin(a),
        y = v.y,
        z = v.x * math.sin(a) + v.z * math.cos(a),
    }
end

From celeron55's blockmobs mod, WTFPL.

I'd suggest to just create a global v3 table, where are this functions are stored.

PostPosted: Sat Feb 16, 2013 18:32
by rubenwardy
Thank you PilzAdam.

Code added

PostPosted: Sun Feb 17, 2013 17:45
by Sokomine
Good thing! Could you include common functions to store and retrieve data (tables) as well? Plus a function that checks if an area is owned would be good (VanessaE has one in homedecor).

Recently I noticed that copying tables in lua is a pain. The language lacks some very basic functionality that ought to be part of a language. Functions for tables that copy them (deepcopy), return the index of a value from a table, and that return if a value is part of a table or not would be helpful. Or maybe there's a lib out there already somewhere that adds these things?

[Edit] And please make sure this gets into default_game.[/Edit]

PostPosted: Sun Feb 17, 2013 17:53
by rarkenin
I'll see if I can make some more include libraries.

PostPosted: Sun Feb 17, 2013 18:36
by rubenwardy
Sokomine wrote:Good thing! Could you include common functions to store and retrieve data (tables) as well?


http://dev.minetest.net/serialize

Sokomine wrote:Plus a function that checks if an area is owned would be good (VanessaE has one in homedecor).


These have been added

PostPosted: Sun Feb 17, 2013 21:25
by rarkenin
I'm not planning much, as I am busy with other programming, but I might implement a binary search tree.

PostPosted: Mon Feb 18, 2013 18:10
by Sokomine
minetest.serialize is usually one part of storing table data. I was thinking more of a function that looks for the right world dir, creates the file there and dumps the data. In short, one location that can later be adopted to diffrend needs (i.e. storage of that data in a database, new, safer io-routines).

PostPosted: Tue Feb 19, 2013 04:10
by Bas080
Some suggestions for the library
* Save/Load function 3d area of nodes
* "selectors" to get nodes at (nth) top/bottom/east/south/west/north/area.
* Function for perlin noise something like perlin(seed, saturation, etc, etc)
* Check if area is free(of certain nodes)(air or other nodes) isfree({nodes},areaHeight,areaWidth) or isfree({nodes},{positions table})
* fillArea({node, chance},{positions table})

Just some ideas. Not all useful but maybe a bit useful

PostPosted: Wed Feb 20, 2013 03:10
by Sokomine
* check if a player exists (i.e. has been one the server at least once or even has interact)

PostPosted: Wed Feb 20, 2013 13:47
by rarkenin
Binary search tree for fast lookup(I abandoned my BST after my schedule went haywire with science competition)

PostPosted: Wed Feb 20, 2013 20:12
by Calinou
Bas080 wrote:* fillArea({node, chance},{positions table})

If this is implemented, it should be named fill_area, not fillArea.

Sokomine wrote:* check if a player exists (i.e. has been one the server at least once or even has interact)


You can do that somehow, check if a player has "shout" privilege. This however doesn't work if you disable shout by default. If you do, then create a dummy privilege that does nothing and is given to everyone then check whether a player has it.

PostPosted: Thu Feb 21, 2013 11:02
by rubenwardy
Calinou wrote:
Bas080 wrote:* fillArea({node, chance},{positions table})

If this is implemented, it should be named fill_area, not fillArea.

Sokomine wrote:* check if a player exists (i.e. has been one the server at least once or even has interact)


You can do that somehow, check if a player has "shout" privilege. This however doesn't work if you disable shout by default. If you do, then create a dummy privilege that does nothing and is given to everyone then check whether a player has it.


I have already added this

PostPosted: Thu Feb 21, 2013 12:17
by 0gb.us
If added to the default game, it shouldn't have functions that specifically apply to node_ownership, which is not in the default game. That's not the only way to own nodes. There's also 0gb_us, protector, protectorplus, landrush .... You get the point.

EDIT: Unless you add some way to register the "protected by check function", so all of these plugins could hook into yours and provide a way to tell who owns an area.

Then again, I'm fairly certain all these things already provide a built-in way to tell who owns a node.

PostPosted: Fri Feb 22, 2013 15:57
by Sokomine
The goal is to get commonlib to be part of the game. It belongs to default once it's complete enough.

PostPosted: Fri Feb 22, 2013 20:29
by jordan4ibanez
What is this useful for?

PostPosted: Fri Feb 22, 2013 23:13
by Mito551
jordan4ibanez wrote:What is this useful for?


mobs stuff, flat stuff, locking stuff, whatever

PostPosted: Sat Feb 23, 2013 00:33
by prestidigitator
In terms of some vector math, here is some ray/volume tracing code I wrote as part of a wands mod I have been working on (yeah, I thought appending two arrays would probably be part of the Lua API somewhere, but...).

WTFPL (I'm sure you'll want to change the names from camelCase and of course change the namespace.)

Your phone or window isn't wide enough to display the code box. If it's a phone, try rotating it to landscape mode.
Code: Select all
local appendList = function(lTable, rTable)
   for i, value in ipairs(rTable) do
      table.insert(lTable, value)
   end
end

-- Returns all combinations of +x, -x, +y, -y, +z, -z for the given vector,
-- reflecting it about each of the xy, xz, and yz planes.  If any component
-- is zero, it generates only one vector for each unique combination of the
-- other reflected components.  No guarantee is made about the order of the
-- returned vectors.
--
-- For example, the (shorthand) input (0, 1, 3) will return a list with the
-- (shorthand) values (0, -1, -3), (0, -1, +3), (0, +1, -3), (0, +1, +3),
-- though not necessarily in that order.
--
-- @param vec
--    The vector to be reflected.
local signCombos
do
   local flipZ = function(vec)
      local z = vec.z
      if z == 0 then
         return { vec }
      else
         return { vec, { x = vec.x, y = vec.y, z = -z } }
      end
   end

   local flipYz = function(vec)
      local y = vec.y
      if y == 0 then
         return flipZ(vec)
      else
         local vecs = flipZ(vec)
         local negVecs = {}
         for i, v in pairs(vecs) do
            table.insert(negVecs, { x = v.x, y = -y, z = v.z})
         end
         appendList(vecs, negVecs)
         return vecs
      end
   end

   signCombos = function(vec)
      local x = vec.x
      if x == 0 then
         return flipYz(vec)
      else
         local vecs = flipYz(vec)
         local negVecs = {}
         for i, v in pairs(vecs) do
            table.insert(negVecs, { x = -x, y = v.y, z = v.z })
         end
         appendList(vecs, negVecs)
         return vecs
      end
   end
end

--- Interpolates along a ray starting at a given position and heading in a
 -- given direction, calling the provided callback function for each step
 -- along the path.  Steps are in unit increments along the fastest changing
 -- dimension, so if the axis closest to the ray's direction is -x, each
 -- step will have x decremented by one until maxDist total distance along
 -- the ray is reached.
 --
 -- The callback function has the form function(pos, dist, userParam) and
 -- returns true if the traversal should continue.  The first parameter is
 -- the current interpolated position along the path.  The second parameter
 -- is the current distance along the path.  The third parameter is the
 -- userParam parameter passed to this function.
 --
 -- @param startPos
 --    The ray's starting position: {x = ..., y = ..., z = ...}
 -- @param tangentVec
 --    A unit vector (assumed) in the direction of the ray:
 --    {x = ..., y = ..., z = ...}
 -- @param minDist
 --    The minimum distance to which to start traversing the ray.  Will
 --    simply skip steps until the distance is at least this high.
 -- @param maxDist
 --    The maximum total distance along which to traverse the ray.  If
 --    negative, traversal will continue indefinitely until the callback
 --    indicates it is done.
 -- @param callback
 --    Function called at each position along the ray's path.
 -- @param userParam
 --    Value passed as the last argument to each invocation of the callback.
function wands.rayTrace(
            startPos, tangentVec, minDist, maxDist, callback, userParam)
   local uVar = "x"
   local duDt = tangentVec[uVar];
   local magDuDt = math.abs(duDt)

   for vVar, dvDt in pairs(tangentVec) do
      local magDvDt = math.abs(dvDt)
      if magDvDt > magDuDt then
         uVar = vVar
         duDt = dvDt
         magDuDt = magDvDt
      end
   end

   local dt = 1.0/magDuDt
   local t
   if minDist > 0.0 then t = math.ceil(minDist/dt)*dt else t = 0.0 end
   repeat
      local cont = callback(
                      {
                         x = startPos.x + t * tangentVec.x,
                         y = startPos.y + t * tangentVec.y,
                         z = startPos.z + t * tangentVec.z
                      },
                      t,
                      userParam)

      if not cont then break end

      t = t + dt
   until maxDist >= 0 and t > maxDist
end



--- Interpolates throughout the volume of a sphere centered at a given
 -- position with a given radius, calling the provided callback function for
 -- each voxel touched by the sphere.
 --
 -- The given position for the center of the sphere is first rounded to the
 -- nearest integer coordinates, and the center of each voxel is assumed to
 -- be offset from this by integer values in each coordinate direction.  A
 -- voxel is traversed if any of its corners is inside or on the surface of
 -- the perfect sphere.  It is considered "inside" if all of its corners are
 -- completely inside and not touching the surface of the perfect sphere.
 --
 -- No order is guaranteed for the traversal, so the callback function
 -- should only stop traversal if, for example, it just needs to find one
 -- thing in the volume and has found it in the current step.
 --
 -- The callback function has the form function(pos, rSq, inside, userParam)
 -- and returns true if the traversal should continue.  The first parameter
 -- is the position of the center of the current voxel.  The second
 -- parameter is the square of the distance from the center of the sphere to
 -- the center of the current voxel.  The third parameter is a boolean
 -- indicating whether the voxel is considered "inside" the sphere.  The
 -- fourth parameter is the userParam parameter passed to this function.
 --
 -- @param centerPos
 --    The sphere's center position: {x = ..., y = ..., z = ...}
 -- @param radius
 --    The radius of the sphere.  Must be a positive number (assumed), but
 --    need not be an integer.
 -- @param callback
 --    Function called at each voxel touched by the sphere.
 -- @param userParam
 --    Value passed as the last argument to each invocation of the callback.
function wands.sphereTrace(centerPos, radius, callback, userParam)
   local center = { x = math.floor(centerPos.x+0.5),
                    y = math.floor(centerPos.y+0.5),
                    z = math.floor(centerPos.z+0.5) }
   local sphereRadSq = radius^2

   for rx = 0, radius+1 do
      local rxSq = rx^2

      for ry = 0, radius+1 do
         local rySq = ry^2
         local hrSq = rxSq + rySq

         -- make up differences between ri^2 and (ri-0.5)^2
         local nearHrSq = hrSq - rx - ry + 0.5

         local hSq = sphereRadSq-nearHrSq
         if hSq >= 0.0 then
            local h = math.ceil(math.sqrt(hSq))

            for rz = 0, h+1 do
               local rzSq = rz^2
               local rSq = hrSq + rzSq

               -- make up differences between ri^2 and (ri-0.5)^2
               local nearRSq = rSq - rx - ry - rz + 0.75
               if nearRSq > sphereRadSq then break end

               -- make up differences between ri^2 and (ri+0.5)^2
               local farRSq = rSq + rx + ry + rz + 0.75
               local inside = (farRSq < sphereRadSq)

               for i, v in pairs(signCombos({ x = rx, y = ry, z = rz })) do
                  local cont =
                     callback(
                        {
                           x = center.x + v.x,
                           y = center.y + v.y,
                           z = center.z + v.z
                        },
                        rSq,
                        inside,
                        userParam)

                  if not cont then return end
               end
            end
         end
      end
   end
end

PostPosted: Sat Feb 23, 2013 01:11
by 0gb.us
Sokomine wrote:The goal is to get commonlib to be part of the game. It belongs to default once it's complete enough.


Yes, I understand that. Which is why the stuff that is in commonlib but is specific to node_ownership should be removed. Things that are specific to node_ownership do not belong in the default game.

PostPosted: Sun Feb 24, 2013 01:35
by prestidigitator
I have created a full Vec3 "class" that is 100% compatible with the vector tables Minetest uses. It provides basically everything you could want to do with a vector: construction, copying, addition, subtraction, negation, getting length, dot, cross, multiplication by a scalar, etc. Also, it uses operator overloading so you can write, for example, v1+v2 in addition to v1:add(v2) or Vec3.add(v1, v2).

All code is WTFPL.

Your phone or window isn't wide enough to display the code box. If it's a phone, try rotating it to landscape mode.
Code: Select all
local Vec3_obj_meta = {}

--- 3D vector class/operations.
 --
 -- Note that methods can be called in either an object-oriented way:
 --    v1 = Vec3(1, 2, 3)
 --    v2 = v1:add({ x = 2, y = 2, z = 0 })
 -- or as simple functions:
 --    Vec3.add({ x = 1, y = 2, z = 3 }, { x = 2, y = 2, z = 0 })
 --
 -- All methods that can be called on a Vec3 using ":" may be called on a table
 -- using the second functional syntax, but the first parameter MUST have the
 -- expected components "x", "y", and "z".  If a vector is used as the second
 -- paramter, it may instead be a list/array with numeric indices, like
 -- { 1.0, 2.0, 3.0 } in place of { x = 1.0, y = 2.0, z = 3.0 }.
 --
 -- @author prestidigitator (as registered at forum.minetest.net)
 -- @copyright 2013, licensed under WTFPL
 --
Vec3 =
   {
      --- Constructs a Vec3 from three numbers.
       --
       -- Call with one of:
       --    Vec3.new(x, y, z)
       --    Vec3(x, y, z)
       --
       -- @return a new Vec3 object
      new = function(x, y, z)
         local obj = { x = x or 0.0, y = y or 0.0, z = z or 0.0 }
         setmetatable(obj, Vec3_obj_meta)
         return obj
      end,

      --- Constructs a new copy of a Vec3.
       --
       -- Call with one of:
       --    vec:new_copy()
       --    Vec3.new_copy(vec)
       --    Vec3(vec)
       --
       -- @return a new Vec3 object that is a copy of the parameter
      new_copy = function(v)
         local obj = { x = v.x or v[1] or 0.0,
                       y = v.y or v[2] or 0.0,
                       z = v.z or v[3] or 0.0 }
         setmetatable(obj, Vec3_obj_meta)
         return obj
      end,

      --- Computes the square of the length of a Vec3.
       --
       -- Call with one of:
       --    vec:len_sq()
       --    Vec3.len_sq(vec)
       --
       -- @return A number
      len_sq = function(v)
         return v.x^2 + v.y^2 + v.z^2
      end,

      --- Computes the length of a Vec3.
       --
       -- Call with one of:
       --    vec:len()
       --    Vec3.len(vec)
       --
       -- @return A number
      len = function(v)
         return math.sqrt(v.x^2 + v.y^2 + v.z^2)
      end,

      --- Computes a unit vector pointing in the same direction as a Vec3.
       -- Undefined for a zero-vector and may throw an error.
       --
       -- Call with one of:
       --    vec:unit()
       --    Vec3.unit(vec)
       --
       -- @return A new Vec3 with length 1.0
      unit = function(v)
         local len = math.sqrt(v.x^2 + v.y^2 + v.z^2)
         return Vec3.new(v.x/len, v.y/len, v.z/len)
      end,

      --- Multiplies a Vec3 by a number.
       --
       -- Call with one of:
       --    vec:mul(m)
       --    Vec3.mul(vec, m)
       --    vec*m
       --    m*vec
       --
       -- @return a new Vec3 object with the result of the operation
      mul = function(v, m)
         return Vec3.new(v.x*m, v.y*m, v.z*m)
      end,

      --- Divides a Vec3 by a number.
       --
       -- Call with one of:
       --    vec:div(m)
       --    Vec3.div(vec, m)
       --    vec/m
       --
       -- @return a new Vec3 object with the result of the operation
      div = function(v, m)
         return Vec3.new(v.x/m, v.y/m, v.z/m)
      end,

      --- Negates a Vec3 (signs of all components are inverted).
       --
       -- Call with one of:
       --    vec:unm()
       --    Vec3.unm(vec)
       --    -vec
       --
       -- @return a new Vec3 object with the result of the operation
      unm = function(v)
         return Vec3.new(-v.x, -v.y, -v.z)
      end,

      --- Adds two Vec3s or a Vec3 composed of three given components.
       --
       -- Call with one of:
       --    vec1:add(vec2)
       --    vec1:add(x, y, z)
       --    Vec3.add(vec1, vec2)
       --    Vec3.add(vec1, x, y, z)
       --    vec1 + vec2
       --
       -- @return a new Vec3 object with the result of the operation
      add = function(v, a, b, c)
         if type(a) == "table" then
            return Vec3.new(v.x + (a.x or a[1] or 0.0),
                            v.y + (a.y or a[2] or 0.0),
                            v.z + (a.z or a[3] or 0.0))
         else
            return Vec3.new(v.x + a, v.y + b, v.z + c)
         end
      end,

      --- Subtracts two Vec3s or a Vec3 composed of three given components.
       --
       -- Call with one of:
       --    vec1:sub(vec2)
       --    vec1:sub(x, y, z)
       --    Vec3.sub(vec1, vec2)
       --    Vec3.sub(vec1, x, y, z)
       --    vec1 - vec2
       --
       -- @return a new Vec3 object with the result of the operation
      sub = function(v, a, b, c)
         if type(a) == "table" then
            return Vec3.new(v.x - (a.x or a[1] or 0.0),
                            v.y - (a.y or a[2] or 0.0),
                            v.z - (a.z or a[3] or 0.0))
         else
            return Vec3.new(v.x - a, v.y - b, v.z - c)
         end
      end,

      --- Tests two Vec3s or a Vec3 composed of three given components for
       -- exact component-wise equality.
       --
       -- Call with one of:
       --    vec1:eq(vec2)
       --    vec1:eq(x, y, z)
       --    Vec3.eq(vec1, vec2)
       --    Vec3.eq(vec1, x, y, z)
       --    vec1 == vec2
       --    vec1 ~= vec2
       -- Note that because of built-in Lua logic "==" and "~=" work ONLY if
       -- vec1 and vec2 are actually Vec3s (not tables).
       --
       -- @return a new Vec3 object with the result of the operation
      eq = function(v, a, b, c)
         if type(a) == "table" then
            return v.x == (a.x or a[1] or 0.0) and
                   v.y == (a.y or a[2] or 0.0) and
                   v.z == (a.z or a[3] or 0.0)
         else
            return v.x == a and v.y == b and v.z == c
         end
      end,

      --- Takes the dot product of a Vec3 and a Vec3s or a Vec3 composed of
       -- three given components.
       --
       -- Call with one of:
       --    vec1:dot(vec2)
       --    vec1:dot(x, y, z)
       --    Vec3.dot(vec1, vec2)
       --    Vec3.dot(vec1, x, y, z)
       --
       -- @return a number
      dot = function(v, a, b, c)
         if type(a) == "table" then
            return v.x * (a.x or a[1] or 0.0) +
                   v.y * (a.y or a[2] or 0.0) +
                   v.z * (a.z or a[3] or 0.0)
         else
            return v.x * a + v.y * b + v.z * c
         end
      end,

      --- Takes the cross product of a Vec3 and a Vec3s or a Vec3 composed of
       -- three given components.
       --
       -- Call with one of:
       --    vec1:cross(vec2)
       --    vec1:cross(x, y, z)
       --    Vec3.cross(vec1, vec2)
       --    Vec3.cross(vec1, x, y, z)
       --
       -- @return a new Vec3 with the result of the operation
      cross = function(v, a, b, c)
         local ux, uy, uz
         if type(a) == "table" then
            ux = a.x or a[1] or 0.0
            uy = a.y or a[2] or 0.0
            uz = a.z or a[3] or 0.0
         else
            ux = a or 0.0
            uy = b or 0.0
            uz = c or 0.0
         end

         return Vec3.new(v.y*uz - v.z*uy, v.z*ux - v.x*uz, v.x*uy - v.y*ux)
      end,

      --- Rotates this (the first) vector around the second vector by the
       -- given angle.
       --
       -- Call with one of:
       --    vec:rot_around(axis, angle)
       --    Vec3.rot_around(vec, axis, angle)
       --
       -- @param axis
       --    The axis about which to rotate.
       -- @param angle
       --    The angle by which to rotate this vector, in radians.
       -- @return
       --    a new Vec3 with the result of the operation.
      rot_around = function(v, axis, angle)
         local uaxis = Vec3.new_copy(axis):unit()

         local alen = uaxis:dotvec(v)
         local avec = uaxis:mul(alen)

         local pvec = Vec3.subvec(v, avec)
         local rvec = uaxis:crossvec(v)

         local v1 = pvec:mul(math.cos(angle))
         local v2 = rvec:mul(math.sin(angle))

         return avec:addvec(v1):addvec(v2)
      end,

      --- Adds two Vec3s. Optimized for pure Vec3/table operations by removing
       -- type checking and conditionals.  If called with Vec3-likes table(s),
       -- ensure all expected components "x", "y", and "z" exist.
       --
       -- Call with one of:
       --    vec1:addvec(vec2)
       --    Vec3.addvec(vec1, vec2)
       --
       -- @return a new Vec3 object with the result of the operation
      addvec = function(v1, v2)
         return Vec3.new(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z)
      end,

      --- Subtracts two Vec3s. Optimized for pure Vec3/table operations by
       -- removing type checking and conditionals.  If called with Vec3-likes
       -- table(s), ensure all expected components "x", "y", and "z" exist.
       --
       -- Call with one of:
       --    vec1:subvec(vec2)
       --    Vec3.subvec(vec1, vec2)
       --
       -- @return a new Vec3 object with the result of the operation
      subvec = function(v1, v2)
         return Vec3.new(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z)
      end,

      --- Tests two Vec3s for exact component-wise equality. Optimized for pure
       -- Vec3/table operations by removing type checking and conditionals.
       -- If called with Vec3-likes table(s), ensure all expected components
       -- "x", "y", and "z" exist.
       --
       -- Call with one of:
       --    vec1:eqvec(vec2)
       --    Vec3.eqvec(vec1, vec2)
       --
       -- @return a new Vec3 object with the result of the operation
      eqvec = function(v1, v2)
         return v1.x == v2.x and v1.y == v2.y and v1.z == v2.z
      end,

      --- Takes the dot product of two Vec3s. Optimized for pure Vec3/table
       -- operations by removing type checking and conditionals.  If called
       -- with Vec3-likes table(s), ensure all expected components "x", "y",
       -- and "z" exist.
       --
       -- Call with one of:
       --    vec1:dotvec(vec2)
       --    Vec3.dotvec(vec1, vec2)
       --
       -- @return a number
      dotvec = function(v1, v2)
         return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
      end,

      --- Takes the cross product of two Vec3s. Optimized for pure Vec3/table
       -- operations by removing type checking and conditionals.  If called
       -- with Vec3-likes table(s), ensure all expected components "x", "y",
       -- and "z" exist.
       --
       -- Call with one of:
       --    vec1:crossvec(vec2)
       --    Vec3.crossvec(vec1, vec2)
       --
       -- @return a new Vec3 with the result of the operation
      crossvec = function(v1, v2)
         return Vec3.new(v1.y*v2.z - v1.z*v2.y,
                         v1.z*v2.x - v1.x*v2.z,
                         v1.x*v2.y - v1.y*v2.x)
      end,

      --- Converts Vec3 to a string with format "(x,y,z)".
       --
       -- @return a string
      tostring = function(v)
         return "("..
                (v.x or v[1] or "0")
                ..","..
                (v.y or v[2] or "0")
                ..","..
                (v.z or v[3] or "0")
                ..")"
      end
   }

Vec3_obj_meta.__index = Vec3
Vec3_obj_meta.__add = Vec3.addvec
Vec3_obj_meta.__sub = Vec3.subvec
Vec3_obj_meta.__div = Vec3.div
Vec3_obj_meta.__unm = Vec3.unm
Vec3_obj_meta.__eq = Vec3.eq
Vec3_obj_meta.__tostring = Vec3.tostring

Vec3_obj_meta.__mul = function(a, b)
   if type(a) == "table" then
      return Vec3.mul(a, b)
   else
      return Vec3.mul(b, a)
   end
end

setmetatable(
   Vec3,
   {
      __call = function(dummy, a, b, c)
         if type(a) == "table" then
            return Vec3.new_copy(a)
         else
            return Vec3.new(a, b, c)
         end
      end
   })


EDIT: Fixed multiplication of scalar by vector (m*v vs. v*m).
EDIT: Added rot_around(...) method to rotate around an arbitrary axis.

PostPosted: Sun Feb 24, 2013 01:37
by prestidigitator
Here are some unit tests for the above Vec3 class. This should also provide some examples of how to use the class.

All code is again WTFPL.

Your phone or window isn't wide enough to display the code box. If it's a phone, try rotating it to landscape mode.
Code: Select all
dofile("Vec3.lua")

local v1, v2, v3

-- Constructors

v1 = Vec3.new()
assert(0.0 == v1.x)
assert(0.0 == v1.y)
assert(0.0 == v1.z)

v1 = Vec3.new_copy({})
assert(0.0 == v1.x)
assert(0.0 == v1.y)
assert(0.0 == v1.z)

v1 = Vec3()
assert(0.0 == v1.x)
assert(0.0 == v1.y)
assert(0.0 == v1.z)

v1 = Vec3.new(1.0, 2.0, 3.0)
assert(1.0 == v1.x)
assert(2.0 == v1.y)
assert(3.0 == v1.z)

v1 = Vec3(4.0, 5.0, 6.0)
assert(4.0 == v1.x)
assert(5.0 == v1.y)
assert(6.0 == v1.z)

v1 = Vec3.new_copy({ x = 7.0, y = 8.0, z = 9.0 })
assert(7.0 == v1.x)
assert(8.0 == v1.y)
assert(9.0 == v1.z)

v1 = Vec3.new_copy({ 10.0, 11.0, 12.0 })
assert(10.0 == v1.x)
assert(11.0 == v1.y)
assert(12.0 == v1.z)

v2 = Vec3.new_copy(v1)
assert(10.0 == v2.x)
assert(11.0 == v2.y)
assert(12.0 == v2.z)
assert(not rawequal(v1, v2))

v2 = v1:new_copy()
assert(10.0 == v2.x)
assert(11.0 == v2.y)
assert(12.0 == v2.z)
assert(not rawequal(v1, v2))

v2 = Vec3(v1)
assert(10.0 == v2.x)
assert(11.0 == v2.y)
assert(12.0 == v2.z)
assert(not rawequal(v1, v2))

v1 = Vec3({ x = 13.0, y = 14.0, z = 15.0 })
assert(13.0 == v1.x)
assert(14.0 == v1.y)
assert(15.0 == v1.z)

v1 = Vec3({ 16.0, 17.0, 18.0 })
assert(16.0 == v1.x)
assert(17.0 == v1.y)
assert(18.0 == v1.z)

-- Length

v1 = Vec3(0.0, 0.0, 0.0)
assert(v1:len_sq() == 0.0)
assert(v1:len() == 0.0)

v1 = Vec3(1.0, 0.0, 0.0)
assert(v1:len_sq() == 1.0)
assert(v1:len() == 1.0)

v1 = Vec3(1.0, 1.0, 1.0)
assert(v1:len_sq() == 3.0)
assert(math.abs(v1:len() - math.sqrt(3.0)) < 0.001)

-- Unit Vectors

v1 = Vec3(7.0, 0.0, 0.0)
v2 = v1:unit()
assert(math.abs(v2.x - 1.0) < 0.001)
assert(math.abs(v2.y - 0.0) < 0.001)
assert(math.abs(v2.z - 0.0) < 0.001)
assert(math.abs(v2:len_sq() - 1.0) < 0.001)
assert(math.abs(v2:len() - 1.0) < 0.001)

v1 = Vec3(1.0, 1.0, 1.0)
v2 = v1:unit()
assert(math.abs(math.sqrt(3.0)*v2.x - 1.0) < 0.001)
assert(math.abs(math.sqrt(3.0)*v2.y - 1.0) < 0.001)
assert(math.abs(math.sqrt(3.0)*v2.z - 1.0) < 0.001)
assert(math.abs(v2:len_sq() - 1.0) < 0.001)
assert(math.abs(v2:len() - 1.0) < 0.001)

-- Multiplication

v1 = Vec3(1.0, 2.0, 3.0)
v2 = v1:mul(5.0)
assert(5.0 == v2.x)
assert(10.0 == v2.y)
assert(15.0 == v2.z)
assert(math.sqrt(25.0*v1:len_sq() - v2:len_sq()) < 0.001)
assert(math.sqrt(5.0*v1:len() - v2:len()) < 0.001)

v2 = v1 * 7.0
assert(7.0 == v2.x)
assert(14.0 == v2.y)
assert(21.0 == v2.z)
assert(math.sqrt(49.0*v1:len_sq() - v2:len_sq()) < 0.001)
assert(math.sqrt(7.0*v1:len() - v2:len()) < 0.001)

v2 = 11.0 * v1
assert(11.0 == v2.x)
assert(22.0 == v2.y)
assert(33.0 == v2.z)
assert(math.sqrt(121.0*v1:len_sq() - v2:len_sq()) < 0.001)
assert(math.sqrt(11.0*v1:len() - v2:len()) < 0.001)

-- Division

v1 = Vec3(1.0, 2.0, 3.0)
v2 = v1:div(2.0)
assert(0.5 == v2.x)
assert(1.0 == v2.y)
assert(1.5 == v2.z)
assert(math.sqrt(v1:len_sq() - 4.0*v2:len_sq()) < 0.001)
assert(math.sqrt(v1:len() - 2.0*v2:len()) < 0.001)

-- Negation

v1 = Vec3(5.0, 4.0, 3.0)
v2 = v1:unm()
assert(-5.0 == v2.x)
assert(-4.0 == v2.y)
assert(-3.0 == v2.z)

v2 = -v1
assert(-5.0 == v2.x)
assert(-4.0 == v2.y)
assert(-3.0 == v2.z)

-- Addition

v1 = Vec3(10.0, 20.0, 30.0)
v2 = Vec3(1.0, 2.0, 3.0)
v3 = v1:add(v2)
assert(11.0 == v3.x)
assert(22.0 == v3.y)
assert(33.0 == v3.z)

v3 = v1:add(5.0, 6.0, 7.0)
assert(15.0 == v3.x)
assert(26.0 == v3.y)
assert(37.0 == v3.z)

v2 = Vec3(-1.0, -2.0, -3.0)
v3 = v1 + v2
assert(9.0 == v3.x)
assert(18.0 == v3.y)
assert(27.0 == v3.z)


-- Subtraction

v1 = Vec3(10.0, 20.0, 30.0)
v2 = Vec3(1.0, 2.0, 3.0)
v3 = v1:sub(v2)
assert(9.0 == v3.x)
assert(18.0 == v3.y)
assert(27.0 == v3.z)

v3 = v1:sub(5.0, 6.0, 7.0)
assert(5.0 == v3.x)
assert(14.0 == v3.y)
assert(23.0 == v3.z)

v2 = Vec3(-1.0, -2.0, -3.0)
v3 = v1 - v2
assert(11.0 == v3.x)
assert(22.0 == v3.y)
assert(33.0 == v3.z)

-- Equality

v1 = Vec3(-3.0, -4.0, -5.0)
v2 = Vec3(-3.0, -4.0, -5.0)
v3 = v1:new_copy()
assert(v1:eq(v1))
assert(v1:eq(v2))
assert(v2:eq(v1))
assert(v1:eq(v3))
assert(v3:eq(v1))
assert(v1:eq(-3.0, -4.0, -5.0))
assert(v1:eq({ x = -3.0, y = -4.0, z = -5.0 }))
assert(v1:eq({ -3.0, -4.0, -5.0 }))

v1 = Vec3(6.0, 17.0, 83.0)
v2 = Vec3(6.0, 17.0, 83.0)
v3 = Vec3(v1)
assert(v1 == v1)
assert(v1 == v2)
assert(v2 == v1)
assert(v1 == v3)
assert(v3 == v1)
assert(v1:eq({ x = 6.0, y = 17.0, z = 83.0 }))
assert(not (v1 ~= v1))
assert(not (v1 ~= v2))
assert(not (v2 ~= v1))
assert(not (v1 ~= v3))
assert(not (v3 ~= v1))

-- Dot Product

v1 = Vec3(2.0, 3.0, 5.0)
v2 = Vec3(7.0, 11.0, 13.0)
assert(v1:dot(v2) == 112.0)
assert(v1:dotvec(v2) == 112.0)
assert(v1:dot(v1) == v1:len_sq())
assert(v1:dot(4.0, 3.0, 2.0) == 27.0)

v1 = Vec3(1.0, 0.0, 0.0)
assert(v1:dot(0.0, 1.0, 0.0) == 0.0)
assert(v1:dot(0.0, 0.0, 1.0) == 0.0)
assert(v1:dot(0.0, 1.0, 1.0) == 0.0)

-- Cross Product

v1 = Vec3(1.0, 0.0, 0.0)
v2 = Vec3(0.0, 1.0, 0.0)
assert(v1:cross(v1):eq(Vec3()))
assert(v2:cross(v2):eq(Vec3()))
assert(v1:cross(v2):eq({ 0.0, 0.0, 1.0 }))
assert(v2:cross(v1):eq({ 0.0, 0.0, -1.0 }))
assert(v1:cross(0.0, 0.0, 1.0):eq({ 0.0, -1.0, 0.0 }))
assert(Vec3.cross(Vec3(0.0, 1.0, 0.0), { 0.0, 0.0, 1.0 }):eq({ 1.0, 0.0, 0.0 }))

v1 = Vec3(3.0, 7.0, 17.0)
v2 = Vec3(2.0, 11.0, 19.0)
assert(v1:cross(v1):eq(Vec3()))
assert(v2:cross(v2):eq(Vec3()))
assert(v1:cross(v2):eq({ -54.0, -23.0, 19.0 }))
assert(v1:crossvec(v2):eq({ -54.0, -23.0, 19.0 }))
assert(v2:cross(v1):eq({ 54.0, 23.0, -19.0 }))
assert(v2:crossvec(v1):eq({ 54.0, 23.0, -19.0 }))

-- Rotation

v1 = Vec3(1.0, 0.0, 0.0)
v2 = Vec3(0.0, 1.0, 0.0)
v3 = v1:rot_around(v2, 0.5*math.pi)
assert(v3:sub({ 0.0, 0.0, -1.0 }):len_sq() < 0.001)
v3 = v1:rot_around(v2, math.pi)
assert(v3:sub(-v1):len_sq() < 0.001)
v3 = v1:rot_around(v2, 1.5*math.pi)
assert(v3:sub({ 0.0, 0.0, 1.0 }):len_sq() < 0.001)
v3 = v1:rot_around(v2, 2.0*math.pi)
assert(v3:sub(v1):len_sq() < 0.001)

v3 = v2:rot_around(v1, 0.5*math.pi)
assert(v3:sub({ 0.0, 0.0, 1.0 }):len_sq() < 0.001)
v3 = v2:rot_around(v1, math.pi)
assert(v3:sub(-v2):len_sq() < 0.001)
v3 = v2:rot_around(v1, 1.5*math.pi)
assert(v3:sub({ 0.0, 0.0, -1.0 }):len_sq() < 0.001)
v3 = v2:rot_around(v1, 2.0*math.pi)
assert(v3:sub(v2):len_sq() < 0.001)

v1 = Vec3(1.0, 0.0, 0.0)
v2 = Vec3(0.0, 0.0, 1.0)
v3 = v1:rot_around(v2, 0.5*math.pi)
assert(v3:sub({ 0.0, 1.0, 0.0 }):len_sq() < 0.001)
v3 = v1:rot_around(v2, math.pi)
assert(v3:sub(-v1):len_sq() < 0.001)
v3 = v1:rot_around(v2, 1.5*math.pi)
assert(v3:sub({ 0.0, -1.0, 0.0 }):len_sq() < 0.001)

v1 = Vec3(1.0, 0.0, 0.0)
v2 = Vec3(1.0, 1.0, 1.0)
v3 = v1:rot_around(v2, 2.0*math.pi/3.0)
assert(v3:sub({ 0.0, 1.0, 0.0 }):len_sq() < 0.001)
v3 = v3:rot_around(v2, 2.0*math.pi/3.0)
assert(v3:sub({ 0.0, 0.0, 1.0 }):len_sq() < 0.001)
v3 = v3:rot_around(v2, 2.0*math.pi/3.0)
assert(v3:sub(v1):len_sq() < 0.001)

print("Vec3 unit test PASSED")


EDIT: Fixed to test multiplication of scalar by vector (m*v vs. v*m).
EDIT: Added some tests for rot_around(...) method to rotate around an arbitrary axis.

PostPosted: Mon Feb 25, 2013 19:15
by kaeza
I don't know if this would be useful for everyone, but the pixelnodebox() function in my computers mod is an easy way to define node boxes based on a texture (i.e. using pixel (more like texel) coordinates instead of [-0.5 - 0.5]).

PostPosted: Mon Feb 25, 2013 21:47
by 4aiman
Will this be useful:
Your phone or window isn't wide enough to display the code box. If it's a phone, try rotating it to landscape mode.
Code: Select all
-- returna a table with registered users names
function get_registred_players()     
    local list = {}   
    local input = io.open(minetest.get_worldpath().."/auth.txt", "r")
    if input then           
       while true do
          local r = input:read("*l")
          if r == nil then break end
          i = string.find(r, "::")
          r = string.sub(r, 1, i-1)
          table.insert(list,r)     
       end       
       io.close(input)
    end   
    return list
end

?

PostPosted: Sat Mar 02, 2013 11:26
by prestidigitator
FYI, I've cleaned up the above Vec3 code a bit and published it as its own library. I've also created and published a library called ModLib that is solely for loading other libraries included with mods.

PostPosted: Sat Mar 16, 2013 20:27
by kaeza
prestidigitator wrote:FYI, I've cleaned up the above Vec3 code a bit and published it as its own library. I've also created and published a library called ModLib that is solely for loading other libraries included with mods.

The point in creating a "common library" is that mod developers and users don't have to install/depend-on lots of mods :)
EDIT: Is this still being worked on?

PostPosted: Mon Mar 18, 2013 12:48
by rubenwardy
I have not done much for a while, but it is not dead.