13 Commits

Author SHA1 Message Date
f400a91e90 Remove functions which are deprecated and which I do not recommend to use
Removing deprecated functionality can help to clean up the code a bit.

I remove the following deprecated functions and values:
* vector.zero
* vector.plane()
* vector.pos_to_string()
* vector.quickadd()
* vector.scalar()
* vector.get_data_from_pos()
* vector.set_data_to_pos()
* vector.set_data_to_pos_optional()
* vector.remove_data_from_pos()
* vector.get_data_pos_table()

Furthermore, I remove the unused, experimental vector_meta.lua file
and make luacheck linting more strict so that it can show accidental
uses of deprecated functions in the code of this mod if there are any.

This change can break compatility with old mods which use vector_extras.
2024-08-27 17:38:30 +02:00
63edf837d7 depends.txt -> mod.conf 2021-10-10 12:57:57 +02:00
ed6b514057 Make vector.zero callable
This is needed to keep compatibility with new minetest builtin code
2021-09-05 19:04:02 +02:00
f8c12047d5 Add .luacheckrc and some code cleanup 2021-03-01 20:37:59 +01:00
fdbcc2e425 A small comment 2021-03-01 19:26:24 +01:00
8c7aaf6c0b Fix a bug in deprecated vector.plane 2020-08-25 11:03:17 +02:00
e826bbd9b9 Replace vector.scalar with vector.dot 2020-08-25 11:03:17 +02:00
6f2bc919db vector.triangle: (try to) avoid holes in neighbouring triangles 2020-04-18 14:16:35 +02:00
bc08421e20 Add documentation 2020-04-07 20:49:10 +02:00
8ddb3879fb Update README 2020-03-27 18:41:15 +01:00
275ec4af3b Remove unused vector.triangle code and try to make it a bit faster 2020-03-27 18:30:19 +01:00
f7dbb1a884 Add (unoptimized) vector.triangle 2020-03-27 18:17:25 +01:00
d1a3c95286 Remove the vector.cross and deprecate vector.scalar; those functions are in minetest now 2020-02-17 13:04:53 +01:00
7 changed files with 269 additions and 477 deletions

11
.luacheckrc Normal file
View File

@ -0,0 +1,11 @@
read_globals = {
-- Defined by Minetest
"minetest", "PseudoRandom", "VoxelArea", "string", "dump", "math",
vector = {
fields = {
"add", "cross", "direction", "distance", "dot", "multiply",
"new", "normalize", "round", "subtract",
"from_number", "get_max_coord", "twoline", "threeline", "rayIter"
}
}
}

View File

@ -1,2 +1,6 @@
TODO:
maybe make the explosion table function return a perlin explosion table
* maybe make the explosion table function return a perlin explosion table
* Figure out and implement 3D scanline search
* Add vector.hollowsphere, less positions than WorldEdit hollowsphere
* Add unit tests
* Use %a string format for vector.serialize so that it is reversible

136
doc.md Normal file
View File

@ -0,0 +1,136 @@
# Vector helpers added by this mod
## Helpers which return many positions for a shape, e.g. a line
### Line functions
These may be deprecated since raycasting has been added to minetest.
See e.g. `minetest.line_of_sight`.
* `vector.line([pos, dir[, range][, alt]])`: returns a table of vectors
* `dir` is either a direction (when range is a number) or
the start position (when range is the end position).
* If alt is true, an old path calculation is used.
* `vector.twoline(x, y)`: can return e.g. `{{0,0}, {0,1}}`
* This is a lower-level function than `vector.line`; it can be used for
a 2D line.
* `vector.threeline(x, y, z)`: can return e.g. `{{0,0,0}, {0,1,0}}`
* Similar to `vector.twoline`; this one is for the 3D case.
* The parameters should be integers.
* `vector.rayIter(pos, dir)`: returns an iterator for a for loop
* `pos` can have non-integer values
* `vector.fine_line([pos, dir[, range], scale])`: returns a table of vectors
* Like `vector.line` but allows non-integer positions
* It uses `vector.rayIter`.
### Flood Fill
* `vector.search_2d(go_test, x0, y0, allow_revisit, give_map)`: returns e.g.
`{{0,0}, {0,1}}`
* This function uses a Flood Fill algorithm, so it can be used to detect
positions connected to each other in 2D.
* `go_test(x, y)` should be a function which returns true iff the algorithm
can "fill" at the position `(x, y)`.
* `(x0, y0)` defines the start position.
* If `allow_revisit` is false (the default), the function
invokes `go_test` only once at every potential position.
* If `give_map` is true (default is false), the function returns the
marked table, whose indices are 2D vector indices, instead of a list of
2D positions.
* `vector.search_3d(can_go, startpos, apply_move, moves)`: returns FIXME
* FIXME
### Other Shapes
* `vector.explosion_table(r)`: returns e.g. `{{pos1}, {pos2, true}}`
* The returned list of positions and boolean represents a sphere;
if the boolean is true, the position is on the outer side of the sphere.
* It might be used for explosion calculations; but `vector.explosion_perlin`
should make more realistic holes.
* `vector.explosion_perlin(rmin, rmax[, nparams])`: returns e.g.
`{{pos1}, {pos2, true}}`
* This function is similar to `vector.explosion_table`; the positions
do not represent a sphere but a more complex hole which is calculated
with the help of perlin noise.
* `rmin` and `rmax` represent the minimum and maximum radius,
and `nparams` (which has a default value) are parameters for the perlin
noise.
* `vector.circle(r)`: returns a table of vectors
* The returned positions represent a circle of radius `r` along the x and z
directions; the y coordinates are all zero.
* `vector.ring(r)`: returns a table of vectors
* This function is similar to `vector.circle`; the positions are all
touching each other (i.e. they are connected on whole surfaces and not
only infinitely thin edges), so it is called `ring` instead of `circle`
* `r` can be a non-integer number.
* `vector.throw_parabola(pos, vel, gravity, point_count, time)`
* FIXME: should return positions along a parabola so that moving objects
collisions can be calculated
* `vector.triangle(pos1, pos2, pos3)`: returns a table of positions, a number
and a table with barycentric coordinates
* This function calculates integer positions for a triangle defined by
`pos1`, `pos2` and `pos3`, so it can be used to place polygons in
minetest.
* The returned number is the number of positions.
* The barycentric coordinates are specified in a table with three elements;
the first one corresponds to `pos1`, etc.
## Helpers for various vector calculations
* `vector.sort_positions(ps[, preferred_coords])`
* Sorts a table of vectors `ps` along the coordinates specified in the
table `preferred_coords` in-place.
* If `preferred_coords` is omitted, it sorts along z, y and x in this order,
where z has the highest priority.
* `vector.maxnorm(v)`: returns the Tschebyshew norm of `v`
* `vector.sumnorm(v)`: returns the Manhattan norm of `v`
* `vector.pnorm(v, p)`: returns the `p` norm of `v`
* `vector.inside(pos, minp, maxp)`: returns a boolean
* Returns true iff `pos` is within the closed AABB defined by `minp`
and `maxp`.
* `vector.minmax(pos1, pos2)`: returns two vectors
* This does the same as `worldedit.sort_pos`.
* The components of the second returned vector are all bigger or equal to
those of the first one.
* `vector.move(pos1, pos2, length)`: returns a vector
* Go from `pos1` `length` metres to `pos2` and then round to the nearest
integer position.
* Made for rubenwardy
* `vector.from_number(i)`: returns `{x=i, y=i, z=i}`
* `vector.chunkcorner(pos)`: returns a vector
* Returns the mapblock position of the mapblock which contains
the integer position `pos`
* `vector.point_distance_minmax(p1, p2)`: returns two numbers
* Returns the minimum and maximum of the absolute component-wise distances
* `vector.collision(p1, p2)` FIXME
* `vector.update_minp_maxp(minp, maxp, pos)`
* Can change `minp` and `maxp` so that `pos` is within the AABB defined by
`minp` and `maxp`
* `vector.unpack(v)`: returns three numbers
* Returns `v.z, v.y, v.x`
* `vector.get_max_coord(v)`: returns a string
* Returns `"x"`, `"y"` or `"z"`, depending on which component has the
biggest value
* `vector.get_max_coords(v)`: returns three strings
* Similar to `vector.get_max_coord`; it returns the coordinates in the order
of their component values
* Example: `vector.get_max_coords{x=1, y=5, z=3}` returns `"y", "z", "x"`
* `vector.serialize(v)`: returns a string
* In comparison to `minetest.serialize`, this function uses a more compact
string for the serialization.
## Minetest-specific helper functions
* `vector.straightdelay([length, vel[, acc]])`: returns a number
* Returns the time an object takes to move `length` if it has velocity `vel`
and acceleration `acc`
* `vector.sun_dir([time])`: returns a vector or nil
* Returns the vector which points to the sun
* If `time` is omitted, it uses the current time.
* This function does not yet support the moon;
at night it simply returns `nil`.

321
init.lua
View File

@ -2,11 +2,8 @@ local path = minetest.get_modpath"vector_extras"
local funcs = {}
function funcs.pos_to_string(pos)
return "("..pos.x.."|"..pos.y.."|"..pos.z..")"
end
local r_corr = 0.25 --remove a bit more nodes (if shooting diagonal) to let it look like a hole (sth like antialiasing)
local r_corr = 0.25 --remove a bit more nodes (if shooting diagonal) to let it
-- look like a hole (sth like antialiasing)
-- this doesn't need to be calculated every time
local f_1 = 0.5-r_corr
@ -51,6 +48,7 @@ local function return_line(pos, dir, range) --range ~= length
local num = 1
local t_dir = get_used_dir(dir)
local dir_typ = t_dir[1]
local f_tab
if t_dir[3] == "+" then
f_tab = {0, range, 1}
else
@ -121,7 +119,7 @@ function funcs.rayIter(pos, dir)
for i in pairs(step) do
choose[i] = vector.new(p)
choose[i][i] = choose[i][i] + step[i]
choosefit[i] = vector.scalar(vector.normalize(vector.subtract(choose[i], pos)), dir)
choosefit[i] = vector.dot(vector.normalize(vector.subtract(choose[i], pos)), dir)
end
p = choose[vector.get_max_coord(choosefit)]
@ -255,18 +253,6 @@ function funcs.sort_positions(ps, preferred_coords)
table.sort(ps, ps_sorting)
end
function funcs.scalar(v1, v2)
return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z
end
function funcs.cross(v1, v2)
return {
x = v1.y*v2.z - v1.z*v2.y,
y = v1.z*v2.x - v1.x*v2.z,
z = v1.x*v2.y - v1.y*v2.x
}
end
-- Tschebyschew norm
function funcs.maxnorm(v)
return math.max(math.max(math.abs(v.x), math.abs(v.y)), math.abs(v.z))
@ -280,63 +266,6 @@ function funcs.pnorm(v, p)
return (math.abs(v.x)^p + math.abs(v.y)^p + math.abs(v.z)^p)^(1 / p)
end
--not optimized
--local areas = {}
function funcs.plane(ps)
-- sort positions and imagine the first one (A) as vector.zero
ps = vector.sort_positions(ps)
local pos = ps[1]
local B = vector.subtract(ps[2], pos)
local C = vector.subtract(ps[3], pos)
-- get the positions for the fors
local cube_p1 = {x=0, y=0, z=0}
local cube_p2 = {x=0, y=0, z=0}
for i in pairs(cube_p1) do
cube_p1[i] = math.min(B[i], C[i], 0)
cube_p2[i] = math.max(B[i], C[i], 0)
end
cube_p1 = vector.apply(cube_p1, math.floor)
cube_p2 = vector.apply(cube_p2, math.ceil)
local vn = vector.normalize(vector.cross(B, C))
local nAB = vector.normalize(B)
local nAC = vector.normalize(C)
local angle_BAC = math.acos(vector.scalar(nAB, nAC))
local nBA = vector.multiply(nAB, -1)
local nBC = vector.normalize(vector.subtract(C, B))
local angle_ABC = math.acos(vector.scalar(nBA, nBC))
for z = cube_p1.z, cube_p2.z do
for y = cube_p1.y, cube_p2.y do
for x = cube_p1.x, cube_p2.x do
local p = {x=x, y=y, z=z}
local n = -vector.scalar(p, vn)/vector.scalar(vn, vn)
if math.abs(n) <= 0.5 then
local ep = vector.add(p, vector.multiply(vn, n))
local nep = vector.normalize(ep)
local angle_BAep = math.acos(vector.scalar(nAB, nep))
local angle_CAep = math.acos(vector.scalar(nAC, nep))
local angldif = angle_BAC - (angle_BAep+angle_CAep)
if math.abs(angldif) < 0.001 then
ep = vector.subtract(ep, B)
nep = vector.normalize(ep)
local angle_ABep = math.acos(vector.scalar(nBA, nep))
local angle_CBep = math.acos(vector.scalar(nBC, nep))
local angldif = angle_ABC - (angle_ABep+angle_CBep)
if math.abs(angldif) < 0.001 then
table.insert(ps, vector.add(pos, p))
end
end
end
end
end
end
return ps
end
function funcs.straightdelay(s, v, a)
if not a then
return s/v
@ -344,8 +273,6 @@ function funcs.straightdelay(s, v, a)
return (math.sqrt(v*v+2*a*s)-v)/a
end
vector.zero = vector.new()
function funcs.sun_dir(time)
if not time then
time = minetest.get_timeofday()
@ -369,9 +296,9 @@ function funcs.inside(pos, minp, maxp)
return true
end
function funcs.minmax(p1, p2)
local p1 = vector.new(p1)
local p2 = vector.new(p2)
function funcs.minmax(pos1, pos2)
local p1 = vector.new(pos1)
local p2 = vector.new(pos2)
for _,i in ipairs({"x", "y", "z"}) do
if p1[i] > p2[i] then
p1[i], p2[i] = p2[i], p1[i]
@ -401,7 +328,7 @@ end
local adammil_fill = dofile(path .. "/adammil_flood_fill.lua")
function funcs.search_2d(go_test, x0, y0, allow_revisit, give_map)
marked_places = adammil_fill(go_test, x0, y0, allow_revisit)
local marked_places = adammil_fill(go_test, x0, y0, allow_revisit)
if give_map then
return marked_places
end
@ -417,7 +344,7 @@ end
local fallings_search = dofile(path .. "/fill_3d.lua")
local moves_touch = {
{x = -1, y = 0, z = 0},
{x = 0, y = 0, z = 0},
{x = 0, y = 0, z = 0}, -- FIXME should this be here?
{x = 1, y = 0, z = 0},
{x = 0, y = -1, z = 0},
{x = 0, y = 1, z = 0},
@ -509,7 +436,8 @@ function funcs.explosion_perlin(rmin, rmax, nparams)
nparams.spread = nparams.spread or vector.from_number(r*5)
local pos = {x=math.random(-30000, 30000), y=math.random(-30000, 30000), z=math.random(-30000, 30000)}
local map = minetest.get_perlin_map(nparams, vector.from_number(r+r+1)):get3dMap_flat(pos)
local map = minetest.get_perlin_map(nparams, vector.from_number(r+r+1)
):get3dMap_flat(pos)
local id = 1
@ -524,11 +452,11 @@ function funcs.explosion_perlin(rmin, rmax, nparams)
local tab, n = {}, 1
for z=-r,r do
local bare_dist = z*z
local bare_dist_z = z*z
for y=-r,r do
local bare_dist = bare_dist+y*y
local bare_dist_yz = bare_dist_z + y*y
for x=-r,r do
local bare_dist = bare_dist+x*x
local bare_dist = bare_dist_yz + x*x
local add = bare_dist < bare_mindist
local pval, distdiv
if not add
@ -554,22 +482,19 @@ function funcs.explosion_perlin(rmin, rmax, nparams)
end
end
map = nil
collectgarbage()
-- change strange values
local pval_diff = pval_max - pval_min
pval_min = pval_min/pval_diff
for n,i in pairs(tab) do
for k,i in pairs(tab) do
if i[2] then
local new_pval = math.abs(i[2]/pval_diff - pval_min)
if i[3]+0.33 < new_pval then
tab[n] = {i[1]}
tab[k] = {i[1]}
elseif i[3] < new_pval then
tab[n] = {i[1], true}
tab[k] = {i[1], true}
else
tab[n] = nil
tab[k] = nil
end
end
end
@ -644,67 +569,6 @@ function funcs.ring(r)
return tab2
end
--~ posy(t) = att + bt + c
--~ vely(t) = 2at + b
--~ accy(t) = 2a
--~ a = -0.5gravity
--~ vely(0) = b = vel.y
--~ posy(0) = c = pos.y
--~ posy(t) = -0.5 * gravity * t * t + vel.y * t + pos.y
--~ vely(t) = -gravity*t + vel.y
--~ Scheitel:
--~ vely(t) = 0 = -gravity*t + vel.y
--~ t = vel.y / gravity
--~ 45°
--~ vely(t)^2 = velx(t)^2 + velz(t)^2
--~ (-gravity*t + vel.y)^2 = vel.x * vel.x + vel.z * vel.z
--~ gravity^2 * t^2 + vel.y^2 - -2*gravity*t*vel.y = vel.x * vel.x + vel.z * vel.z
--~ gravity^2 * t^2 - 2*gravity*vel.y * t + (vel.y^2 - vel.x^2 - vel.z^2) = 0
--~ t = (2*gravity*vel.y .. rt((2*gravity*vel.y)^2 - 4*gravity^2*(vel.y^2 - vel.x^2 - vel.z^2))) / (2*gravity^2)
--~ t = (2*gravity*vel.y .. rt(4*gravity^2*vel.y^2 - 4*gravity^2*(vel.y^2) + 4*gravity^2*(vel.x^2 + vel.z^2))) / (2*gravity^2)
--~ t = (2*gravity*vel.y .. 2*gravity*rt(vel.x^2 + vel.z^2)) / (2*gravity^2)
--~ t = (vel.y .. rt(vel.x^2 + vel.z^2)) / gravity
--~ t1 = (vel.y - math.sqrt(vel.x * vel.x + vel.z * vel.z)) / gravity
--~ t2 = (vel.y + math.sqrt(vel.x * vel.x + vel.z * vel.z)) / gravity
--~ yswitch = posy(t1) (= posy(t2)) //links und rechts gleich
--~ yswitch = -0.5 * gravity * ((vel.y + math.sqrt(vel.x * vel.x + vel.z * vel.z)) / gravity)^2 + vel.y * ((vel.y + math.sqrt(vel.x * vel.x + vel.z * vel.z)) / gravity) + pos.y
--~ yswitch = -0.5 * gravity * (vel.y + math.sqrt(vel.x * vel.x + vel.z * vel.z))^2 / gravity^2 + vel.y * ((vel.y + math.sqrt(vel.x * vel.x + vel.z * vel.z)) / gravity) + pos.y
--~ yswitch = -0.5 * (vel.y^2 + 2*vel.y*math.sqrt(vel.x * vel.x + vel.z * vel.z) + vel.x^2 + vel.z^2) / gravity + ((vel.y^2 + vel.y*math.sqrt(vel.x * vel.x + vel.z * vel.z)) / gravity) + pos.y
--~ yswitch = (-0.5 * (vel.y^2 + 2*vel.y*math.sqrt(vel.x * vel.x + vel.z * vel.z) + vel.x^2 + vel.z^2) + ((vel.y^2 + vel.y*math.sqrt(vel.x * vel.x + vel.z * vel.z)))) / gravity + pos.y
--~ yswitch = (-0.5 * vel.y^2 - vel.y*math.sqrt(vel.x * vel.x + vel.z * vel.z) - 0.5 * vel.x^2 - 0.5 * vel.z^2 + vel.y^2 + vel.y*math.sqrt(vel.x * vel.x + vel.z * vel.z)) / gravity + pos.y
--~ yswitch = (-0.5 * vel.y^2 - 0.5 * vel.x^2 - 0.5 * vel.z^2 + vel.y^2) / gravity + pos.y
--~ yswitch = (0.5 * vel.y^2 - 0.5 * vel.x^2 - 0.5 * vel.z^2) / gravity + pos.y
--~ yswitch = -0.5 * (vel.x * vel.x + vel.z * vel.z - vel.y * vel.y) / gravity + pos.y
--~ 45° Zeitpunkte kleineres beim Aufstieg, größeres beim Fall
--~ (-gravity*t + vel.y)^2 = vel.x * vel.x + vel.z * vel.z
--~ -gravity*t + vel.y = ..math.sqrt(vel.x * vel.x + vel.z * vel.z)
--~ t = (..math.sqrt(vel.x * vel.x + vel.z * vel.z) + vel.y) / gravity
--~ t_raise = (-math.sqrt(vel.x * vel.x + vel.z * vel.z) + vel.y) / gravity
--~ t_fall = (math.sqrt(vel.x * vel.x + vel.z * vel.z) + vel.y) / gravity
--~ posy nach t umstellen
--~ y = -0.5 * gravity * t * t + vel.y * t + pos.y
--~ 0 = -0.5 * gravity * t * t + vel.y * t + pos.y - y
--~ t = (-vel.y .. math.sqrt(vel.y^2 + 2 * gravity * (pos.y - y))) / (-gravity)
--~ t = (vel.y .. math.sqrt(vel.y^2 + 2 * gravity * (pos.y - y))) / gravity
--~ t_up = (vel.y - math.sqrt(vel.y^2 + 2 * gravity * (pos.y - y))) / gravity
--~ t_down = (vel.y + math.sqrt(vel.y^2 + 2 * gravity * (pos.y - y))) / gravity
--~ posx(t) = vel.x * t + pos.x
--~ posz(t) = vel.z * t + pos.z
--~ posx nach t umstellen
--~ posx - pos.x = vel.x * t
--~ t = (posx - pos.x) / vel.x
local function get_parabola_points(pos, vel, gravity, waypoints, max_pointcount,
time)
local pointcount = 0
@ -714,9 +578,9 @@ local function get_parabola_points(pos, vel, gravity, waypoints, max_pointcount,
/ gravity + pos.y
-- the times of the 45° angle point
local i = math.sqrt(vel.x^2 + vel.z^2)
local t_raise_end = (-i + vel.y) / gravity
local t_fall_start = (i + vel.y) / gravity
local vel_len = math.sqrt(vel.x^2 + vel.z^2)
local t_raise_end = (-vel_len + vel.y) / gravity
local t_fall_start = (vel_len + vel.y) / gravity
if t_fall_start > 0 then
-- the right 45° angle point wasn't passed yet
if t_raise_end > 0 then
@ -845,11 +709,11 @@ function funcs.throw_parabola(pos, vel, gravity, point_count, time)
-- get a list of possible positions between
local diff = vector.subtract(p2, p)
local possible_positions = {}
for i,v in pairs(diff) do
for c,v in pairs(diff) do
if v ~= 0 then
local p = vector.new(p)
p[i] = p[i] + v
possible_positions[#possible_positions+1] = p
local pos_moved = vector.new(p)
pos_moved[c] = pos_moved[c] + v
possible_positions[#possible_positions+1] = pos_moved
end
end
-- test which one fits best
@ -860,12 +724,12 @@ function funcs.throw_parabola(pos, vel, gravity, point_count, time)
z = vel.z * t + pos.z,
}
local d = math.huge
for i = 1,2 do
local pos = possible_positions[i]
local dist = vector.distance(pos, near_p)
if dist < d then
p = pos
d = dist
for k = 1,2 do
local pos_moved = possible_positions[k]
local dist_current = vector.distance(pos_moved, near_p)
if dist_current < d then
p = pos_moved
d = dist_current
end
end
-- add it
@ -876,11 +740,11 @@ function funcs.throw_parabola(pos, vel, gravity, point_count, time)
-- get a list of possible positions between
local diff = vector.subtract(p2, p)
local possible_positions = {}
for i,v in pairs(diff) do
for c,v in pairs(diff) do
if v ~= 0 then
local p = vector.new(p)
p[i] = p[i] + v
possible_positions[#possible_positions+1] = p
local pos_moved = vector.new(p)
pos_moved[c] = pos_moved[c] + v
possible_positions[#possible_positions+1] = pos_moved
end
end
-- test which one fits best
@ -892,12 +756,12 @@ function funcs.throw_parabola(pos, vel, gravity, point_count, time)
}
local d = math.huge
assert(#possible_positions == 4-k, "how, number positions?")
for i = 1,4-k do
local pos = possible_positions[i]
local dist = vector.distance(pos, near_p)
if dist < d then
p = pos
d = dist
for j = 1,4-k do
local pos_moved = possible_positions[j]
local dist_current = vector.distance(pos_moved, near_p)
if dist_current < d then
p = pos_moved
d = dist_current
end
end
-- add it
@ -918,9 +782,9 @@ function funcs.chunkcorner(pos)
return {x=pos.x-pos.x%16, y=pos.y-pos.y%16, z=pos.z-pos.z%16}
end
function funcs.point_distance_minmax(p1, p2)
local p1 = vector.new(p1)
local p2 = vector.new(p2)
function funcs.point_distance_minmax(pos1, pos2)
local p1 = vector.new(pos1)
local p2 = vector.new(pos2)
local min, max, vmin, vmax, num
for _,i in ipairs({"x", "y", "z"}) do
num = math.abs(p1[i] - p2[i])
@ -937,21 +801,20 @@ function funcs.point_distance_minmax(p1, p2)
end
function funcs.collision(p1, p2)
local clear, node_pos, collision_pos, max, dmax, dcmax, pt
clear, node_pos = minetest.line_of_sight(p1, p2)
local clear, node_pos = minetest.line_of_sight(p1, p2)
if clear then
return false
end
collision_pos = {}
min, max = funcs.point_distance_minmax(node_pos, p2)
local collision_pos = {}
local _, max = funcs.point_distance_minmax(node_pos, p2)
if node_pos[max] > p2[max] then
collision_pos[max] = node_pos[max] - 0.5
else
collision_pos[max] = node_pos[max] + 0.5
end
dmax = p2[max] - node_pos[max]
dcmax = p2[max] - collision_pos[max]
pt = dcmax/dmax
local dmax = p2[max] - node_pos[max]
local dcmax = p2[max] - collision_pos[max]
local pt = dcmax / dmax
for _,i in ipairs({"x", "y", "z"}) do
collision_pos[i] = p2[i] - (p2[i] - node_pos[i]) * pt
@ -966,18 +829,6 @@ function funcs.update_minp_maxp(minp, maxp, pos)
end
end
function funcs.quickadd(pos, z,y,x)
if z then
pos.z = pos.z+z
end
if y then
pos.y = pos.y+y
end
if x then
pos.x = pos.x+x
end
end
function funcs.unpack(pos)
return pos.z, pos.y, pos.x
end
@ -1018,20 +869,80 @@ function funcs.serialize(vec)
return "{x=" .. vec.x .. ",y=" .. vec.y .. ",z=" .. vec.z .. "}"
end
function funcs.triangle(pos1, pos2, pos3)
local normal = vector.cross(vector.subtract(pos2, pos1),
vector.subtract(pos3, pos1))
-- Find the biggest absolute component of the normal vector
local dir = vector.get_max_coord({
x = math.abs(normal.x),
y = math.abs(normal.y),
z = math.abs(normal.z),
})
-- Find the other directions for the for loops
local all_other_dirs = {
x = {"z", "y"},
y = {"z", "x"},
z = {"y", "x"},
}
local other_dirs = all_other_dirs[dir]
local odir1, odir2 = other_dirs[1], other_dirs[2]
vector_extras_functions = funcs
dofile(path .. "/legacy.lua")
--dofile(minetest.get_modpath("vector_extras").."/vector_meta.lua")
vector_extras_functions = nil
local pos1_2d = {pos1[odir1], pos1[odir2]}
local pos2_2d = {pos2[odir1], pos2[odir2]}
local pos3_2d = {pos3[odir1], pos3[odir2]}
-- The boundaries of the 2D AABB along other_dirs
local p1 = {}
local p2 = {}
for i = 1,2 do
p1[i] = math.floor(math.min(pos1_2d[i], pos2_2d[i], pos3_2d[i]))
p2[i] = math.ceil(math.max(pos1_2d[i], pos2_2d[i], pos3_2d[i]))
end
-- https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation/rasterization-stage
local function edgefunc(vert1, vert2, pos)
return (pos[1] - vert1[1]) * (vert2[2] - vert1[2])
- (pos[2] - vert1[2]) * (vert2[1] - vert1[1])
end
-- eps is used to prevend holes in neighbouring triangles
-- It should be smaller than the smallest possible barycentric value
-- FIXME: I'm not sure if it really does what it should.
local eps = 0.5 / math.max(p2[1] - p1[1], p2[2] - p1[2])
local a_all_inv = 1.0 / edgefunc(pos1_2d, pos2_2d, pos3_2d)
local step_k3 = - (pos2_2d[1] - pos1_2d[1]) * a_all_inv
local step_k1 = - (pos3_2d[1] - pos2_2d[1]) * a_all_inv
-- Calculate the triangle points
local points = {}
local barycentric_coords = {}
local n = 0
-- It is possible to further optimize this
for v1 = p1[1], p2[1] do
local p = {v1, p1[2]}
local k3 = edgefunc(pos1_2d, pos2_2d, p) * a_all_inv
local k1 = edgefunc(pos2_2d, pos3_2d, p) * a_all_inv
for _ = p1[2], p2[2] do
local k2 = 1 - k1 - k3
if k1 >= -eps and k2 >= -eps and k3 >= -eps then
-- On triangle
local h = math.floor(k1 * pos1[dir] + k2 * pos2[dir] +
k3 * pos3[dir] + 0.5)
n = n+1
points[n] = {[odir1] = v1, [odir2] = p[2], [dir] = h}
barycentric_coords[n] = {k1, k2, k3}
end
p[2] = p[2]+1
k3 = k3 + step_k3
k1 = k1 + step_k1
end
end
return points, n, barycentric_coords
end
for name,func in pairs(funcs) do
if vector[name] then
minetest.log("error", "[vector_extras] vector."..name..
" already exists.")
else
-- luacheck: globals vector
vector[name] = func
end
end

View File

@ -1,88 +0,0 @@
local funcs = vector_extras_functions
function funcs.get_data_from_pos(tab, z,y,x)
minetest.log("deprecated", "[vector_extras] get_data_from_pos is " ..
"deprecated, use the minetest pos hash function instead.")
local data = tab[z]
if data then
data = data[y]
if data then
return data[x]
end
end
end
function funcs.set_data_to_pos(tab, z,y,x, data)
minetest.log("deprecated", "[vector_extras] set_data_to_pos is " ..
"deprecated, use the minetest pos hash function instead.")
if tab[z] then
if tab[z][y] then
tab[z][y][x] = data
return
end
tab[z][y] = {[x] = data}
return
end
tab[z] = {[y] = {[x] = data}}
end
function funcs.set_data_to_pos_optional(tab, z,y,x, data)
minetest.log("deprecated", "[vector_extras] set_data_to_pos_optional is " ..
"deprecated, use the minetest pos hash function instead.")
if vector.get_data_from_pos(tab, z,y,x) ~= nil then
return
end
funcs.set_data_to_pos(tab, z,y,x, data)
end
function funcs.remove_data_from_pos(tab, z,y,x)
minetest.log("deprecated", "[vector_extras] remove_data_from_pos is " ..
"deprecated, use the minetest pos hash function instead.")
if vector.get_data_from_pos(tab, z,y,x) == nil then
return
end
tab[z][y][x] = nil
if not next(tab[z][y]) then
tab[z][y] = nil
end
if not next(tab[z]) then
tab[z] = nil
end
end
function funcs.get_data_pos_table(tab)
minetest.log("deprecated", "[vector_extras] get_data_pos_table likely " ..
"is deprecated, use the minetest pos hash function instead.")
local t,n = {},1
local minz, miny, minx, maxz, maxy, maxx
for z,yxs in pairs(tab) do
if not minz then
minz = z
maxz = z
else
minz = math.min(minz, z)
maxz = math.max(maxz, z)
end
for y,xs in pairs(yxs) do
if not miny then
miny = y
maxy = y
else
miny = math.min(miny, y)
maxy = math.max(maxy, y)
end
for x,v in pairs(xs) do
if not minx then
minx = x
maxx = x
else
minx = math.min(minx, x)
maxx = math.max(maxx, x)
end
t[n] = {z,y,x, v}
n = n+1
end
end
end
return t, {x=minx, y=miny, z=minz}, {x=maxx, y=maxy, z=maxz}, n-1
end

1
mod.conf Normal file
View File

@ -0,0 +1 @@
name = vector_extras

View File

@ -1,183 +0,0 @@
vector.meta = vector.meta or {}
vector.meta.nodes = {}
vector.meta.nodes_file = {
load = function()
local nodesfile = io.open(minetest.get_worldpath()..'/vector_nodes.txt', "r")
if nodesfile then
local contents = nodesfile:read('*all')
io.close(nodesfile)
if contents ~= nil then
local lines = string.split(contents, "\n")
for _,entry in ipairs(lines) do
local name, px, py, pz, meta = unpack(string.split(entry, "°"))
vector.meta.set_node({x=px, y=py, z=pz}, name, meta)
end
end
end
end,
save = function() --WRITE CHANGES TO FILE
local output = ''
for x,ys in pairs(vector.meta.nodes) do
for y,zs in pairs(ys) do
for z,names in pairs(zs) do
for name,meta in pairs(names) do
output = name.."°"..x.."°"..y.."°"..z.."°"..dump(meta).."\n"
end
end
end
end
local f = io.open(minetest.get_worldpath()..'/vector_nodes.txt', "w")
f:write(output)
io.close(f)
end
}
local function table_empty(tab) --looks if it's an empty table
if next(tab) == nil then
return true
end
return false
end
function vector.meta.nodes_info() --returns an info string of the node table
local tmp = "[vector] "..dump(vector.meta.nodes).."\n[vector]:\n"
for x,a in pairs(vector.meta.nodes) do
for y,b in pairs(a) do
for z,c in pairs(b) do
for name,meta in pairs(c) do
tmp = tmp..">\t"..name.." "..x.." "..y.." "..z.." "..dump(meta).."\n"
end
end
end
end
return tmp
end
function vector.meta.clean_node_table() --replaces {} with nil
local again = true
while again do
again = false
for x,ys in pairs(vector.meta.nodes) do
if table_empty(ys) then
vector.meta.nodes[x] = nil
again = true
else
for y,zs in pairs(ys) do
if table_empty(zs) then
vector.meta.nodes[x][y] = nil
again = true
else
for z,names in pairs(zs) do
if table_empty(names) then
vector.meta.nodes[x][y][z] = nil
again = true
else
for name,meta in pairs(names) do
if table_empty(meta)
or meta == "" then
vector.meta.nodes[x][y][z][name] = nil
again = true
end
end
end
end
end
end
end
end
end
end
function vector.meta.complete_node_table(pos, name) --neccesary because tab[1] wouldn't work if tab is not a table
local tmp = vector.meta.nodes[pos.x]
if not tmp then
vector.meta.nodes[pos.x] = {}
end
local tmp = vector.meta.nodes[pos.x][pos.y]
if not tmp then
vector.meta.nodes[pos.x][pos.y] = {}
end
local tmp = vector.meta.nodes[pos.x][pos.y][pos.z]
if not tmp then
vector.meta.nodes[pos.x][pos.y][pos.z] = {}
end
local tmp = vector.meta.nodes[pos.x][pos.y][pos.z][name]
if not tmp then
vector.meta.nodes[pos.x][pos.y][pos.z][name] = {}
end
end
function vector.meta.get_node(pos, name)
if not pos then
return false
end
local tmp = vector.meta.nodes[pos.x]
if not tmp
or table_empty(tmp) then
return false
end
local tmp = vector.meta.nodes[pos.x][pos.y]
if not tmp
or table_empty(tmp) then
return false
end
local tmp = vector.meta.nodes[pos.x][pos.y][pos.z]
if not tmp
or table_empty(tmp) then
return false
end
-- if name isn't mentioned, just look if there's a node
if not name then
return true
end
local tmp = vector.meta.nodes[pos.x][pos.y][pos.z][name]
if not tmp
or table_empty(tmp) then
return false
end
return tmp
end
function vector.meta.remove_node(pos)
if not pos then
return false
end
if vector.meta.get_node(pos) then
vector.meta.nodes[pos.x][pos.y][pos.z] = nil
local xarr = vector.meta.nodes[pos.x]
if table_empty(xarr[pos.y]) then
vector.meta.nodes[pos.x][pos.y] = nil
end
if table_empty(xarr) then
vector.meta.nodes[pos.x] = nil
end
else
print("[vector_extras] Warning: The node at "..vector.pos_to_string(pos).." wasn't stored in vector.meta.nodes.")
end
end
function vector.meta.set_node(pos, name, meta)
if not (name or pos) then
return false
end
vector.meta.complete_node_table(pos, name)
meta = meta or true
vector.meta.nodes[pos.x][pos.y][pos.z][name] = meta
end
minetest.register_chatcommand('cleanvectormetatable',{
description = 'Tidy up it.',
params = "",
privs = {},
func = function(name)
vector.meta.clean_node_table()
local tmp = vector.meta.nodes_info()
minetest.chat_send_player(name, tmp)
print("[vector_extras] "..tmp)
end
})
vector.meta.nodes_file.load()