mirror of
https://github.com/1bardesign/batteries.git
synced 2024-11-22 14:14:36 +00:00
Merge branch 'feature/vec2-friendly-api'
This commit is contained in:
commit
918c9438cd
2
init.lua
2
init.lua
@ -95,7 +95,7 @@ function _batteries:camelCase()
|
||||
end
|
||||
|
||||
--any acronyms to fully capitalise to avoid "Rgb" and the like
|
||||
local acronyms = _batteries.set{"rgb", "rgba", "argb", "hsl", "xy", "gc",}
|
||||
local acronyms = _batteries.set{"rgb", "rgba", "argb", "hsl", "xy", "gc", "aabb",}
|
||||
local function caps_acronym(s)
|
||||
if acronyms:has(s) then
|
||||
s = s:upper()
|
||||
|
309
intersect.lua
309
intersect.lua
@ -38,7 +38,9 @@ end
|
||||
|
||||
function intersect.circle_circle_collide(a_pos, a_rad, b_pos, b_rad, into)
|
||||
--get delta
|
||||
local delta = a_pos:pooled_copy():vsubi(b_pos)
|
||||
local delta = a_pos
|
||||
:pooled_copy()
|
||||
:vector_sub_inplace(b_pos)
|
||||
--squared threshold
|
||||
local rad = a_rad + b_rad
|
||||
local dist = delta:length_squared()
|
||||
@ -47,63 +49,77 @@ function intersect.circle_circle_collide(a_pos, a_rad, b_pos, b_rad, into)
|
||||
if dist == 0 then
|
||||
--singular case; just resolve vertically
|
||||
dist = 1
|
||||
delta:sset(0,1)
|
||||
delta:scalar_set(0, 1)
|
||||
else
|
||||
--get actual distance
|
||||
dist = math.sqrt(dist)
|
||||
end
|
||||
--allocate if needed
|
||||
if into == nil then
|
||||
into = vec2:zero()
|
||||
into = vec2(0)
|
||||
end
|
||||
--normalise, scale to separating distance
|
||||
res = into:vset(delta):sdivi(dist):smuli(rad - dist)
|
||||
res = into:set(delta)
|
||||
:scalar_div_inplace(dist)
|
||||
:scalar_mul_inplace(rad - dist)
|
||||
end
|
||||
delta:release()
|
||||
return res
|
||||
end
|
||||
|
||||
function intersect.circle_point_collide(a_pos, a_rad, b, into)
|
||||
return intersect.circle_circle_collide(a_pos, a_rad, b, 0, into)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
-- line segments
|
||||
-- todo: separate double-sided, one-sided, and pull-through (along normal) collisions?
|
||||
|
||||
--get the nearest point on the line segment a from point b
|
||||
function intersect.nearest_point_on_line(a_start, a_end, b_pos, into)
|
||||
if into == nil then into = vec2:zero() end
|
||||
if into == nil then into = vec2(0) end
|
||||
--direction of segment
|
||||
local segment = a_end:pooled_copy():vsubi(a_start)
|
||||
local segment = a_end:pooled_copy()
|
||||
:vector_sub_inplace(a_start)
|
||||
--detect degenerate case
|
||||
local lensq = segment:length_squared()
|
||||
if lensq <= COLLIDE_EPS then
|
||||
into:vset(a_start)
|
||||
into:set(a_start)
|
||||
else
|
||||
--solve for factor along segment
|
||||
local point_to_start = b_pos:pooled_copy():vsubi(a_start)
|
||||
local point_to_start = b_pos:pooled_copy()
|
||||
:vector_sub_inplace(a_start)
|
||||
local factor = math.clamp01(point_to_start:dot(segment) / lensq)
|
||||
point_to_start:release()
|
||||
into:vset(segment):smuli(factor):vaddi(a_start)
|
||||
into:set(segment)
|
||||
:scalar_mul_inplace(factor)
|
||||
:vector_add_inplace(a_start)
|
||||
end
|
||||
segment:release()
|
||||
return into
|
||||
end
|
||||
|
||||
--internal
|
||||
--vector from line seg to point
|
||||
--vector from line seg origin to point
|
||||
function intersect._line_to_point(a_start, a_end, b_pos, into)
|
||||
return intersect.nearest_point_on_line(a_start, a_end, b_pos, into):vsubi(b_pos)
|
||||
return intersect.nearest_point_on_line(a_start, a_end, b_pos, into)
|
||||
:vector_sub_inplace(b_pos)
|
||||
end
|
||||
|
||||
--internal
|
||||
--line displacement vector from separation vector
|
||||
function intersect._line_displacement_to_sep(a_start, a_end, separation, total_rad)
|
||||
local distance = separation:normalisei_len()
|
||||
local distance = separation:normalise_len_inplace()
|
||||
local sep = distance - total_rad
|
||||
if sep <= 0 then
|
||||
if distance <= COLLIDE_EPS then
|
||||
--point intersecting the line; push out along normal
|
||||
separation:vset(a_end):vsubi(a_start):normalisei():rot90li()
|
||||
separation:set(a_end)
|
||||
:vector_sub_inplace(a_start)
|
||||
:normalise_inplace()
|
||||
:rot90l_inplace()
|
||||
else
|
||||
separation:smuli(-sep)
|
||||
separation:scalar_mul_inplace(-sep)
|
||||
end
|
||||
return separation
|
||||
end
|
||||
@ -127,28 +143,31 @@ end
|
||||
--collide 2 line segments
|
||||
function intersect.line_line_collide(a_start, a_end, a_rad, b_start, b_end, b_rad, into)
|
||||
--segment directions from start points
|
||||
local a_dir = a_end:vsub(a_start)
|
||||
local b_dir = b_end:vsub(b_start)
|
||||
local a_dir = a_end
|
||||
:pooled_copy()
|
||||
:vector_sub_inplace(a_start)
|
||||
local b_dir = b_end
|
||||
:pooled_copy()
|
||||
:vector_sub_inplace(b_start)
|
||||
|
||||
--detect degenerate cases
|
||||
local a_degen = a_dir:length_squared() <= COLLIDE_EPS
|
||||
local b_degen = a_dir:length_squared() <= COLLIDE_EPS
|
||||
if a_degen and b_degen then
|
||||
--actually just circles
|
||||
return intersect.circle_circle_collide(a_start, a_rad, b_start, b_rad, into)
|
||||
elseif a_degen then
|
||||
-- a is just circle; annoying, need reversed msv
|
||||
local collided = intersect.line_circle_collide(b_start, b_end, b_rad, a_start, a_rad, into)
|
||||
if collided then
|
||||
collided:smuli(-1)
|
||||
if a_degen or b_degen then
|
||||
vec2.release(a_dir, b_dir)
|
||||
if a_degen and b_degen then
|
||||
--actually just circles
|
||||
return intersect.circle_circle_collide(a_start, a_rad, b_start, b_rad, into)
|
||||
elseif a_degen then
|
||||
--a is just circle
|
||||
return intersect.circle_line_collide(a_start, a_rad, b_start, b_end, b_rad, into)
|
||||
elseif b_degen then
|
||||
--b is just circle
|
||||
return intersect.line_circle_collide(a_start, a_end, a_rad, b_start, b_rad, into)
|
||||
end
|
||||
return collided
|
||||
elseif b_degen then
|
||||
--b is just circle
|
||||
return intersect.line_circle_collide(a_start, a_end, a_rad, b_start, b_rad, into)
|
||||
end
|
||||
--otherwise we're _actually_ 2 line segs :)
|
||||
if into == nil then into = vec2:zero() end
|
||||
if into == nil then into = vec2(0) end
|
||||
|
||||
--first, check intersection
|
||||
|
||||
@ -195,43 +214,50 @@ function intersect.line_line_collide(a_start, a_end, a_rad, b_start, b_end, b_ra
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if intersected == "both" then
|
||||
--simply displace along A normal
|
||||
return into:vset(a_dir):normalisei():smuli(a_rad + b_rad):rot90li()
|
||||
else
|
||||
--dumb as a rock check-corners approach
|
||||
--todo: pool storage
|
||||
--todo calculus from http://geomalgorithms.com/a07-_distance.html
|
||||
local search_tab = {}
|
||||
--only insert corners from the non-intersected line
|
||||
--since intersected line is potentially the apex
|
||||
if intersected ~= "a" then
|
||||
--a endpoints
|
||||
table.insert(search_tab, {intersect._line_to_point(b_start, b_end, a_start), 1})
|
||||
table.insert(search_tab, {intersect._line_to_point(b_start, b_end, a_end), 1})
|
||||
end
|
||||
if intersected ~= "b" then
|
||||
--b endpoints
|
||||
table.insert(search_tab, {intersect._line_to_point(a_start, a_end, b_start), -1})
|
||||
table.insert(search_tab, {intersect._line_to_point(a_start, a_end, b_end), -1})
|
||||
end
|
||||
|
||||
local best = nil
|
||||
local best_len = nil
|
||||
for _, v in ipairs(search_tab) do
|
||||
local len = v[1]:length_squared()
|
||||
if not best_len or len < best_len then
|
||||
best = v
|
||||
end
|
||||
end
|
||||
|
||||
--fix direction
|
||||
into:vset(best[1]):smuli(best[2])
|
||||
|
||||
return intersect._line_displacement_to_sep(a_start, a_end, into, a_rad + b_rad)
|
||||
into:set(a_dir)
|
||||
vec2.release(a_dir, b_dir)
|
||||
return into
|
||||
:normalise_inplace()
|
||||
:scalar_mul_inplace(a_rad + b_rad)
|
||||
:rot90l_inplace()
|
||||
end
|
||||
|
||||
return false
|
||||
vec2.release(a_dir, b_dir)
|
||||
|
||||
--dumb as a rock check-corners approach
|
||||
--todo: pool storage
|
||||
--todo proper calculus from http://geomalgorithms.com/a07-_distance.html
|
||||
local search_tab = {}
|
||||
--only insert corners from the non-intersected line
|
||||
--since intersected line is potentially the apex
|
||||
if intersected ~= "a" then
|
||||
--a endpoints
|
||||
table.insert(search_tab, {intersect._line_to_point(b_start, b_end, a_start), 1})
|
||||
table.insert(search_tab, {intersect._line_to_point(b_start, b_end, a_end), 1})
|
||||
end
|
||||
if intersected ~= "b" then
|
||||
--b endpoints
|
||||
table.insert(search_tab, {intersect._line_to_point(a_start, a_end, b_start), -1})
|
||||
table.insert(search_tab, {intersect._line_to_point(a_start, a_end, b_end), -1})
|
||||
end
|
||||
|
||||
local best = nil
|
||||
local best_len = nil
|
||||
for _, v in ipairs(search_tab) do
|
||||
local len = v[1]:length_squared()
|
||||
if not best_len or len < best_len then
|
||||
best = v
|
||||
end
|
||||
end
|
||||
|
||||
--fix direction
|
||||
into:set(best[1])
|
||||
:scalar_mul_inplace(best[2])
|
||||
|
||||
return intersect._line_displacement_to_sep(a_start, a_end, into, a_rad + b_rad)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
@ -239,7 +265,10 @@ end
|
||||
|
||||
--return true on overlap, false otherwise
|
||||
function intersect.aabb_point_overlap(pos, hs, v)
|
||||
local delta = pos:pooled_copy():vsubi(v):absi()
|
||||
local delta = pos
|
||||
:pooled_copy()
|
||||
:vector_sub_inplace(v)
|
||||
:abs_inplace()
|
||||
local overlap = delta.x < hs.x and delta.y < hs.y
|
||||
delta:release()
|
||||
return overlap
|
||||
@ -249,21 +278,25 @@ end
|
||||
-- return msv to push point to closest edge of aabb
|
||||
function intersect.aabb_point_collide(pos, hs, v, into)
|
||||
--separation between centres
|
||||
local delta_c = v:pooled_copy():vsubi(pos)
|
||||
local delta_c = v
|
||||
:pooled_copy()
|
||||
:vector_sub_inplace(pos)
|
||||
--absolute separation
|
||||
local delta_c_abs = delta_c:pooled_copy():absi()
|
||||
local delta_c_abs = delta_c
|
||||
:pooled_copy()
|
||||
:abs_inplace()
|
||||
local res = false
|
||||
if delta_c_abs.x < hs.x and delta_c_abs.y < hs.y then
|
||||
res = (into or vec2:zero())
|
||||
res = (into or vec2(0))
|
||||
--separating offset in both directions
|
||||
:vset(hs)
|
||||
:vsubi(delta_c_abs)
|
||||
:set(hs)
|
||||
:vector_sub_inplace(delta_c_abs)
|
||||
--minimum separating distance
|
||||
:minori()
|
||||
:minor_inplace()
|
||||
--in the right direction
|
||||
:vmuli(delta_c:signi())
|
||||
:vector_mul_inplace(delta_c:sign_inplace())
|
||||
--from the aabb's point of view
|
||||
:smuli(-1)
|
||||
:inverse_inplace()
|
||||
end
|
||||
vec2.release(delta_c, delta_c_abs)
|
||||
return res
|
||||
@ -271,8 +304,13 @@ end
|
||||
|
||||
--return true on overlap, false otherwise
|
||||
function intersect.aabb_aabb_overlap(a_pos, a_hs, b_pos, b_hs)
|
||||
local delta = a_pos:pooled_copy():vsubi(b_pos):absi()
|
||||
local total_size = a_hs:pooled_copy():vaddi(b_hs)
|
||||
local delta = a_pos
|
||||
:pooled_copy()
|
||||
:vector_sub_inplace(b_pos)
|
||||
:abs_inplace()
|
||||
local total_size = a_hs
|
||||
:pooled_copy()
|
||||
:vector_add_inplace(b_hs)
|
||||
local overlap = delta.x < total_size.x and delta.y < total_size.y
|
||||
vec2.release(delta, total_size)
|
||||
return overlap
|
||||
@ -281,20 +319,28 @@ end
|
||||
--discrete displacement
|
||||
--return msv on collision, false otherwise
|
||||
function intersect.aabb_aabb_collide(a_pos, a_hs, b_pos, b_hs, into)
|
||||
if not into then into = vec2:zero() end
|
||||
local delta = a_pos:pooled_copy():vsubi(b_pos)
|
||||
local abs_delta = delta:pooled_copy():absi()
|
||||
local size = a_hs:pooled_copy():vaddi(b_hs)
|
||||
local abs_amount = size:pooled_copy():vsubi(abs_delta)
|
||||
local delta = a_pos
|
||||
:pooled_copy()
|
||||
:vector_sub_inplace(b_pos)
|
||||
local abs_delta = delta
|
||||
:pooled_copy()
|
||||
:abs_inplace()
|
||||
local size = a_hs
|
||||
:pooled_copy()
|
||||
:vector_add_inplace(b_hs)
|
||||
local abs_amount = size
|
||||
:pooled_copy()
|
||||
:vector_sub_inplace(abs_delta)
|
||||
local res = false
|
||||
if abs_amount.x > COLLIDE_EPS and abs_amount.y > COLLIDE_EPS then
|
||||
if not into then into = vec2(0) end
|
||||
--actually collided
|
||||
if abs_amount.x <= abs_amount.y then
|
||||
--x min
|
||||
res = into:sset(abs_amount.x * math.sign(delta.x), 0)
|
||||
res = into:scalar_set(abs_amount.x * math.sign(delta.x), 0)
|
||||
else
|
||||
--y min
|
||||
res = into:sset(0, abs_amount.y * math.sign(delta.y))
|
||||
res = into:scalar_set(0, abs_amount.y * math.sign(delta.y))
|
||||
end
|
||||
end
|
||||
return res
|
||||
@ -302,10 +348,15 @@ end
|
||||
|
||||
-- helper function to clamp point to aabb
|
||||
function intersect.aabb_point_clamp(pos, hs, v, into)
|
||||
local v_min = pos:pooled_copy():vsubi(hs)
|
||||
local v_max = pos:pooled_copy():vaddi(hs)
|
||||
into = into or vec2:zero()
|
||||
into:vset(v):clampi(v_min, v_max)
|
||||
local v_min = pos
|
||||
:pooled_copy()
|
||||
:vector_sub_inplace(hs)
|
||||
local v_max = pos
|
||||
:pooled_copy()
|
||||
:vector_add_inplace(hs)
|
||||
into = into or vec2(0)
|
||||
into:set(v)
|
||||
:clamp_inplace(v_min, v_max)
|
||||
vec2.release(v_min, v_max)
|
||||
return into
|
||||
end
|
||||
@ -320,7 +371,10 @@ end
|
||||
|
||||
-- return msv on collision, false otherwise
|
||||
function intersect.aabb_circle_collide(a_pos, a_hs, b_pos, b_rad, into)
|
||||
local abs_delta = a_pos:pooled_copy():vsub(b_pos):absi()
|
||||
local abs_delta = a_pos
|
||||
:pooled_copy()
|
||||
:vector_sub_inplace(b_pos)
|
||||
:abs_inplace()
|
||||
--circle centre within aabb-like bounds, collide as an aabb
|
||||
local like_aabb = abs_delta.x < a_hs.x or abs_delta.y < a_hs.y
|
||||
--(clean up)
|
||||
@ -328,7 +382,7 @@ function intersect.aabb_circle_collide(a_pos, a_hs, b_pos, b_rad, into)
|
||||
--
|
||||
local result
|
||||
if like_aabb then
|
||||
local pretend_hs = vec2:pooled():sset()
|
||||
local pretend_hs = vec2:pooled(0, 0)
|
||||
result = intersect.aabb_aabb_collide(a_pos, a_hs, b_pos, pretend_hs, into)
|
||||
pretend_hs:release()
|
||||
else
|
||||
@ -349,11 +403,17 @@ function intersect.point_in_poly(point, poly)
|
||||
for i, a in ipairs(poly) do
|
||||
local b = poly[i + 1] or poly[1]
|
||||
if a.y <= point.y then
|
||||
if b.y > point.y and vec2.winding_side(a, b, point) > 0 then
|
||||
if
|
||||
b.y > point.y
|
||||
and vec2.winding_side(a, b, point) > 0
|
||||
then
|
||||
wn = wn + 1
|
||||
end
|
||||
else
|
||||
if b.y <= point.y and vec2.winding_side(a, b, point) < 0 then
|
||||
if
|
||||
b.y <= point.y
|
||||
and vec2.winding_side(a, b, point) < 0
|
||||
then
|
||||
wn = wn - 1
|
||||
end
|
||||
end
|
||||
@ -361,6 +421,49 @@ function intersect.point_in_poly(point, poly)
|
||||
return wn ~= 0
|
||||
end
|
||||
|
||||
--reversed versions
|
||||
--it's annoying to need to flip the order of operands depending on what
|
||||
--shapes you're working with
|
||||
--so these functions provide the
|
||||
|
||||
--todo: ensure this is all of them
|
||||
|
||||
--(helper for reversing only if there's actually a vector, preserving false)
|
||||
function intersect.reverse_msv(result)
|
||||
if result then
|
||||
result:inverse_inplace()
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function intersect.point_circle_overlap(a, b_pos, b_rad)
|
||||
return intersect.circle_point_overlap(b_pos, b_rad, a)
|
||||
end
|
||||
|
||||
function intersect.point_circle_collide(a, b_pos, b_rad, into)
|
||||
return intersect.reverse_msv(intersect.circle_circle_collide(b_pos, b_rad, a, 0, into))
|
||||
end
|
||||
|
||||
function intersect.point_aabb_overlap(a, b_pos, b_hs)
|
||||
return intersect.aabb_point_overlap(b_pos, b_hs, a)
|
||||
end
|
||||
|
||||
function intersect.point_aabb_collide(a, b_pos, b_hs, into)
|
||||
return intersect.reverse_msv(intersect.aabb_point_collide(b_pos, b_rad, a, into))
|
||||
end
|
||||
|
||||
function intersect.circle_aabb_overlap(a, a_rad, b_pos, b_hs)
|
||||
return intersect.aabb_circle_overlap(b_pos, b_rad, a, a_rad)
|
||||
end
|
||||
|
||||
function intersect.circle_aabb_collide(a, a_rad, b_pos, b_hs, into)
|
||||
return intersect.reverse_msv(intersect.aabb_circle_collide(b_pos, b_rad, a, a_rad, into))
|
||||
end
|
||||
|
||||
function intersect.circle_line_collide(a, a_rad, b_start, b_end, b_rad)
|
||||
return intersect.reverse_msv(intersect.line_circle_collide(b_start, b_end, b_rad, a, a_rad, into))
|
||||
end
|
||||
|
||||
--resolution helpers
|
||||
|
||||
--resolve a collision between two bodies, given a (minimum) separating vector
|
||||
@ -371,11 +474,11 @@ end
|
||||
-- 0 is only b_pos moving to resolve
|
||||
-- 0.5 is balanced between both (default)
|
||||
--note: this wont work as-is for line segments, which have two separate position coordinates
|
||||
-- you will need to understand what is going on and move the second coordinate yourself
|
||||
-- you will need to understand what is going on and move the both coordinates yourself
|
||||
function intersect.resolve_msv(a_pos, b_pos, msv, balance)
|
||||
balance = balance or 0.5
|
||||
a_pos:fmai(msv, balance)
|
||||
b_pos:fmai(msv, -(1 - balance))
|
||||
a_pos:fused_multiply_add_inplace(msv, balance)
|
||||
b_pos:fused_multiply_add_inplace(msv, -(1 - balance))
|
||||
end
|
||||
|
||||
-- gets a normalised balance factor from two mass inputs, and treats <=0 or infinite or nil masses as static bodies
|
||||
@ -403,12 +506,12 @@ function intersect.bounce_off(velocity, normal, conservation)
|
||||
--(default)
|
||||
conservation = conservation or 1
|
||||
--take a copy, we need it
|
||||
local old_vel = vec2.pooled_copy(velocity)
|
||||
local old_vel = velocity:pooled_copy()
|
||||
--reject on the normal (keep velocity tangential to the normal)
|
||||
velocity:vreji(normal)
|
||||
velocity:vector_rejection_inplace(normal)
|
||||
--add back the complement of the difference;
|
||||
--basically "flip" the velocity in line with the normal.
|
||||
velocity:fmai(old_vel:vsubi(velocity), -conservation)
|
||||
velocity:fused_multiply_add_inplace(old_vel:vector_sub_inplace(velocity), -conservation)
|
||||
--clean up
|
||||
old_vel:release()
|
||||
return velocity
|
||||
@ -419,18 +522,18 @@ function intersect.mutual_bounce(velocity_a, velocity_b, normal, conservation)
|
||||
--(default)
|
||||
conservation = conservation or 1
|
||||
--take copies, we need them
|
||||
local old_a_vel = vec2.pooled_copy(velocity_a)
|
||||
local old_b_vel = vec2.pooled_copy(velocity_b)
|
||||
local old_a_vel = velocity_a:pooled_copy()
|
||||
local old_b_vel = velocity_b:pooled_copy()
|
||||
--reject on the normal
|
||||
velocity_a:vreji(normal)
|
||||
velocity_b:vreji(normal)
|
||||
velocity_a:vector_rejection_inplace(normal)
|
||||
velocity_b:vector_rejection_inplace(normal)
|
||||
--calculate the amount remaining from the old velocity
|
||||
--(transfer ownership)
|
||||
local a_remaining = old_a_vel:vsubi(velocity_a)
|
||||
local b_remaining = old_b_vel:vsubi(velocity_b)
|
||||
--(transfer pool ownership)
|
||||
local a_remaining = old_a_vel:vector_sub_inplace(velocity_a)
|
||||
local b_remaining = old_b_vel:vector_sub_inplace(velocity_b)
|
||||
--transfer it to the other body
|
||||
velocity_a:fmai(b_remaining, conservation)
|
||||
velocity_b:fmai(a_remaining, conservation)
|
||||
velocity_a:fused_multiply_add_inplace(b_remaining, conservation)
|
||||
velocity_b:fused_multiply_add_inplace(a_remaining, conservation)
|
||||
--clean up
|
||||
vec2.release(a_remaining, b_remaining)
|
||||
end
|
||||
|
429
vec2.lua
429
vec2.lua
@ -16,22 +16,24 @@ function vec2:__tostring()
|
||||
return ("(%.2f, %.2f)"):format(self.x, self.y)
|
||||
end
|
||||
|
||||
--probably-too-flexible ctor
|
||||
--ctor
|
||||
function vec2:new(x, y)
|
||||
if type(x) == "number" or type(x) == "nil" then
|
||||
self:sset(x or 0, y)
|
||||
if type(x) == "number" then
|
||||
self:scalar_set(x, y)
|
||||
elseif type(x) == "table" then
|
||||
if type(x.type) == "function" and x:type() == "vec2" then
|
||||
self:vset(x)
|
||||
self:vector_set(x)
|
||||
elseif x[1] then
|
||||
self:sset(x[1], x[2])
|
||||
self:scalar_set(x[1], x[2])
|
||||
else
|
||||
self:sset(x.x, x.y)
|
||||
self:scalar_set(x.x, x.y)
|
||||
end
|
||||
else
|
||||
self:scalar_set(0)
|
||||
end
|
||||
end
|
||||
|
||||
--explicit ctors
|
||||
--explicit ctors; mostly vestigial at this point
|
||||
function vec2:copy()
|
||||
return vec2(self.x, self.y)
|
||||
end
|
||||
@ -40,8 +42,12 @@ function vec2:xy(x, y)
|
||||
return vec2(x, y)
|
||||
end
|
||||
|
||||
function vec2:polar(length, angle)
|
||||
return vec2(length, 0):rotate_inplace(angle)
|
||||
end
|
||||
|
||||
function vec2:filled(v)
|
||||
return vec2(v)
|
||||
return vec2(v, v)
|
||||
end
|
||||
|
||||
function vec2:zero()
|
||||
@ -63,28 +69,28 @@ make_pooled(vec2, 128)
|
||||
|
||||
--get a pooled copy of an existing vector
|
||||
function vec2:pooled_copy()
|
||||
return vec2:pooled():vset(self)
|
||||
return vec2:pooled(self)
|
||||
end
|
||||
|
||||
--modify
|
||||
|
||||
function vec2:sset(x, y)
|
||||
function vec2:vector_set(v)
|
||||
self.x = v.x
|
||||
self.y = v.y
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:scalar_set(x, y)
|
||||
if not y then y = x end
|
||||
self.x = x
|
||||
self.y = y
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:vset(v)
|
||||
self.x = v.x
|
||||
self.y = v.y
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:swap(v)
|
||||
local sx, sy = self.x, self.y
|
||||
self:vset(v)
|
||||
v:sset(sx, sy)
|
||||
self.x, self.y = v.x, v.y
|
||||
v.x, v.y = sx, sy
|
||||
return self
|
||||
end
|
||||
|
||||
@ -116,108 +122,68 @@ end
|
||||
--arithmetic
|
||||
-----------------------------------------------------------
|
||||
|
||||
--immediate mode
|
||||
|
||||
--vector
|
||||
function vec2:vaddi(v)
|
||||
function vec2:vector_add_inplace(v)
|
||||
self.x = self.x + v.x
|
||||
self.y = self.y + v.y
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:vsubi(v)
|
||||
function vec2:vector_sub_inplace(v)
|
||||
self.x = self.x - v.x
|
||||
self.y = self.y - v.y
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:vmuli(v)
|
||||
function vec2:vector_mul_inplace(v)
|
||||
self.x = self.x * v.x
|
||||
self.y = self.y * v.y
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:vdivi(v)
|
||||
function vec2:vector_div_inplace(v)
|
||||
self.x = self.x / v.x
|
||||
self.y = self.y / v.y
|
||||
return self
|
||||
end
|
||||
|
||||
--(a + (b * t))
|
||||
--useful for integrating physics and adding directional offsets
|
||||
function vec2:fused_multiply_add_inplace(v, t)
|
||||
self.x = self.x + (v.x * t)
|
||||
self.y = self.y + (v.y * t)
|
||||
return self
|
||||
end
|
||||
|
||||
--scalar
|
||||
function vec2:saddi(x, y)
|
||||
function vec2:scalar_add_inplace(x, y)
|
||||
if not y then y = x end
|
||||
self.x = self.x + x
|
||||
self.y = self.y + y
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:ssubi(x, y)
|
||||
function vec2:scalar_sub_inplace(x, y)
|
||||
if not y then y = x end
|
||||
self.x = self.x - x
|
||||
self.y = self.y - y
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:smuli(x, y)
|
||||
function vec2:scalar_mul_inplace(x, y)
|
||||
if not y then y = x end
|
||||
self.x = self.x * x
|
||||
self.y = self.y * y
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:sdivi(x, y)
|
||||
function vec2:scalar_div_inplace(x, y)
|
||||
if not y then y = x end
|
||||
self.x = self.x / x
|
||||
self.y = self.y / y
|
||||
return self
|
||||
end
|
||||
|
||||
--garbage mode
|
||||
|
||||
function vec2:vadd(v)
|
||||
return self:copy():vaddi(v)
|
||||
end
|
||||
|
||||
function vec2:vsub(v)
|
||||
return self:copy():vsubi(v)
|
||||
end
|
||||
|
||||
function vec2:vmul(v)
|
||||
return self:copy():vmuli(v)
|
||||
end
|
||||
|
||||
function vec2:vdiv(v)
|
||||
return self:copy():vdivi(v)
|
||||
end
|
||||
|
||||
function vec2:sadd(x, y)
|
||||
return self:copy():saddi(x, y)
|
||||
end
|
||||
|
||||
function vec2:ssub(x, y)
|
||||
return self:copy():ssubi(x, y)
|
||||
end
|
||||
|
||||
function vec2:smul(x, y)
|
||||
return self:copy():smuli(x, y)
|
||||
end
|
||||
|
||||
function vec2:sdiv(x, y)
|
||||
return self:copy():sdivi(x, y)
|
||||
end
|
||||
|
||||
--fused multiply-add (a + (b * t))
|
||||
|
||||
function vec2:fmai(v, t)
|
||||
self.x = self.x + (v.x * t)
|
||||
self.y = self.y + (v.y * t)
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:fma(v, t)
|
||||
return self:copy():fmai(v, t)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------
|
||||
-- geometric methods
|
||||
-----------------------------------------------------------
|
||||
@ -240,31 +206,31 @@ function vec2:distance(other)
|
||||
return math.sqrt(self:distance_squared(other))
|
||||
end
|
||||
|
||||
--immediate mode
|
||||
|
||||
function vec2:normalisei_both()
|
||||
function vec2:normalise_both_inplace()
|
||||
local len = self:length()
|
||||
if len == 0 then
|
||||
return self, 0
|
||||
end
|
||||
return self:sdivi(len), len
|
||||
return self:scalar_div_inplace(len), len
|
||||
end
|
||||
|
||||
function vec2:normalisei()
|
||||
local v, len = self:normalisei_both()
|
||||
function vec2:normalise_inplace()
|
||||
local v, len = self:normalise_both_inplace()
|
||||
return v
|
||||
end
|
||||
|
||||
function vec2:normalisei_len()
|
||||
local v, len = self:normalisei_both()
|
||||
function vec2:normalise_len_inplace()
|
||||
local v, len = self:normalise_both_inplace()
|
||||
return len
|
||||
end
|
||||
|
||||
function vec2:inversei()
|
||||
return self:smuli(-1)
|
||||
function vec2:inverse_inplace()
|
||||
return self:scalar_mul_inplace(-1)
|
||||
end
|
||||
|
||||
function vec2:rotatei(angle)
|
||||
-- angle/direction specific
|
||||
|
||||
function vec2:rotate_inplace(angle)
|
||||
local s = math.sin(angle)
|
||||
local c = math.cos(angle)
|
||||
local ox = self.x
|
||||
@ -274,7 +240,15 @@ function vec2:rotatei(angle)
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:rot90ri()
|
||||
function vec2:rotate_around_inplace(angle, pivot)
|
||||
self:vector_sub_inplace(pivot)
|
||||
self:rotate_inplace(angle)
|
||||
self:vector_add_inplace(pivot)
|
||||
return self
|
||||
end
|
||||
|
||||
--fast quarter/half rotations
|
||||
function vec2:rot90r_inplace()
|
||||
local ox = self.x
|
||||
local oy = self.y
|
||||
self.x = -oy
|
||||
@ -282,7 +256,7 @@ function vec2:rot90ri()
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:rot90li()
|
||||
function vec2:rot90l_inplace()
|
||||
local ox = self.x
|
||||
local oy = self.y
|
||||
self.x = oy
|
||||
@ -290,50 +264,7 @@ function vec2:rot90li()
|
||||
return self
|
||||
end
|
||||
|
||||
vec2.rot180i = vec2.inversei --alias
|
||||
|
||||
function vec2:rotate_aroundi(angle, pivot)
|
||||
self:vsubi(pivot)
|
||||
self:rotatei(angle)
|
||||
self:vaddi(pivot)
|
||||
return self
|
||||
end
|
||||
|
||||
--garbage mode
|
||||
|
||||
function vec2:normalised()
|
||||
return self:copy():normalisei()
|
||||
end
|
||||
|
||||
function vec2:normalised_len()
|
||||
local v = self:copy()
|
||||
local len = v:normalisei_len()
|
||||
return v, len
|
||||
end
|
||||
|
||||
function vec2:inverse()
|
||||
return self:copy():inversei()
|
||||
end
|
||||
|
||||
function vec2:rotate(angle)
|
||||
return self:copy():rotatei(angle)
|
||||
end
|
||||
|
||||
function vec2:rot90r()
|
||||
return self:copy():rot90ri()
|
||||
end
|
||||
|
||||
function vec2:rot90l()
|
||||
return self:copy():rot90li()
|
||||
end
|
||||
|
||||
vec2.rot180 = vec2.inverse --alias
|
||||
|
||||
function vec2:rotate_around(angle, pivot)
|
||||
return self:copy():rotate_aroundi(angle, pivot)
|
||||
end
|
||||
|
||||
-- angle/direction specific
|
||||
vec2.rot180_inplace = vec2.inverse_inplace --alias
|
||||
|
||||
--get the angle of this vector relative to (1, 0)
|
||||
function vec2:angle()
|
||||
@ -347,180 +278,125 @@ end
|
||||
|
||||
--lerp towards the direction of a provided vector
|
||||
--(length unchanged)
|
||||
function vec2:lerp_directioni(v, t)
|
||||
return self:rotatei(self:angle_difference(v) * t)
|
||||
end
|
||||
|
||||
function vec2:lerp_direction(v, t)
|
||||
return self:copy():lerp_directioni(v, t)
|
||||
function vec2:lerp_direction_inplace(v, t)
|
||||
return self:rotate_inplace(self:angle_difference(v) * t)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------
|
||||
-- per-component clamping ops
|
||||
-----------------------------------------------------------
|
||||
|
||||
function vec2:mini(v)
|
||||
function vec2:min_inplace(v)
|
||||
self.x = math.min(self.x, v.x)
|
||||
self.y = math.min(self.y, v.y)
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:maxi(v)
|
||||
function vec2:max_inplace(v)
|
||||
self.x = math.max(self.x, v.x)
|
||||
self.y = math.max(self.y, v.y)
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:clampi(min, max)
|
||||
function vec2:clamp_inplace(min, max)
|
||||
self.x = math.clamp(self.x, min.x, max.x)
|
||||
self.y = math.clamp(self.y, min.y, max.y)
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:min(v)
|
||||
return self:copy():mini(v)
|
||||
end
|
||||
|
||||
function vec2:max(v)
|
||||
return self:copy():maxi(v)
|
||||
end
|
||||
|
||||
function vec2:clamp(min, max)
|
||||
return self:copy():clampi(min, max)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------
|
||||
-- absolute value
|
||||
-----------------------------------------------------------
|
||||
|
||||
function vec2:absi()
|
||||
function vec2:abs_inplace()
|
||||
self.x = math.abs(self.x)
|
||||
self.y = math.abs(self.y)
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:abs()
|
||||
return self:copy():absi()
|
||||
end
|
||||
|
||||
-----------------------------------------------------------
|
||||
-- sign
|
||||
-----------------------------------------------------------
|
||||
|
||||
function vec2:signi()
|
||||
function vec2:sign_inplace()
|
||||
self.x = math.sign(self.x)
|
||||
self.y = math.sign(self.y)
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:sign()
|
||||
return self:copy():signi()
|
||||
end
|
||||
|
||||
-----------------------------------------------------------
|
||||
-- truncation/rounding
|
||||
-----------------------------------------------------------
|
||||
|
||||
function vec2:floori()
|
||||
function vec2:floor_inplace()
|
||||
self.x = math.floor(self.x)
|
||||
self.y = math.floor(self.y)
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:ceili()
|
||||
function vec2:ceil_inplace()
|
||||
self.x = math.ceil(self.x)
|
||||
self.y = math.ceil(self.y)
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:roundi()
|
||||
function vec2:round_inplace()
|
||||
self.x = math.round(self.x)
|
||||
self.y = math.round(self.y)
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:floor()
|
||||
return self:copy():floori()
|
||||
end
|
||||
|
||||
function vec2:ceil()
|
||||
return self:copy():ceili()
|
||||
end
|
||||
|
||||
function vec2:round()
|
||||
return self:copy():roundi()
|
||||
end
|
||||
|
||||
-----------------------------------------------------------
|
||||
-- interpolation
|
||||
-----------------------------------------------------------
|
||||
|
||||
function vec2:lerpi(other, amount)
|
||||
function vec2:lerp_inplace(other, amount)
|
||||
self.x = math.lerp(self.x, other.x, amount)
|
||||
self.y = math.lerp(self.y, other.y, amount)
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:lerp(other, amount)
|
||||
return self:copy():lerpi(other, amount)
|
||||
end
|
||||
|
||||
function vec2:lerp_epsi(other, amount, eps)
|
||||
function vec2:lerp_eps_inplace(other, amount, eps)
|
||||
self.x = math.lerp_eps(self.x, other.x, amount, eps)
|
||||
self.y = math.lerp_eps(self.y, other.y, amount, eps)
|
||||
return self
|
||||
end
|
||||
|
||||
function vec2:lerp_eps(other, amount, eps)
|
||||
return self:copy():lerp_epsi(other, amount, eps)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------
|
||||
-- vector products and projections
|
||||
-----------------------------------------------------------
|
||||
|
||||
function vec2.dot(a, b)
|
||||
return a.x * b.x + a.y * b.y
|
||||
function vec2:dot(other)
|
||||
return self.x * other.x + self.y * other.y
|
||||
end
|
||||
|
||||
--"fake", but useful - also called the wedge product apparently
|
||||
function vec2.cross(a, b)
|
||||
return a.x * b.y - a.y * b.x
|
||||
function vec2:cross(other)
|
||||
return self.x * other.y - self.y * other.x
|
||||
end
|
||||
|
||||
--scalar projection a onto b
|
||||
function vec2.sproj(a, b)
|
||||
local len = b:length()
|
||||
function vec2:scalar_projection(other)
|
||||
local len = other:length()
|
||||
if len == 0 then
|
||||
return 0
|
||||
end
|
||||
return a:dot(b) / len
|
||||
return self:dot(other) / len
|
||||
end
|
||||
|
||||
--vector projection a onto b (writes into a)
|
||||
function vec2.vproji(a, b)
|
||||
local div = b:dot(b)
|
||||
function vec2:vector_projection_inplace(other)
|
||||
local div = other:dot(other)
|
||||
if div == 0 then
|
||||
return a:sset(0,0)
|
||||
return self:scalar_set(0)
|
||||
end
|
||||
local fac = a:dot(b) / div
|
||||
return a:vset(b):smuli(fac)
|
||||
local fac = self:dot(other) / div
|
||||
return self:vector_set(other):scalar_mul_inplace(fac)
|
||||
end
|
||||
|
||||
function vec2.vproj(a, b)
|
||||
return a:copy():vproji(b)
|
||||
end
|
||||
|
||||
--vector rejection a onto b (writes into a)
|
||||
function vec2.vreji(a, b)
|
||||
local tx, ty = a.x, a.y
|
||||
a:vproji(b)
|
||||
a:sset(tx - a.x, ty - a.y)
|
||||
return a
|
||||
end
|
||||
|
||||
function vec2.vrej(a, b)
|
||||
return a:copy():vreji(b)
|
||||
function vec2:vector_rejection_inplace(other)
|
||||
local tx, ty = self.x, self.y
|
||||
self:vector_projection_inplace(other)
|
||||
self:scalar_set(tx - self.x, ty - self.y)
|
||||
return self
|
||||
end
|
||||
|
||||
--get the winding side of p, relative to the line a-b
|
||||
@ -540,19 +416,19 @@ end
|
||||
-----------------------------------------------------------
|
||||
|
||||
--"physical" friction
|
||||
local _v_friction = vec2:zero() --avoid alloc
|
||||
function vec2:apply_friction(mu, dt)
|
||||
_v_friction:vset(self):smuli(mu * dt)
|
||||
if _v_friction:length_squared() > self:length_squared() then
|
||||
self:sset(0, 0)
|
||||
function vec2:apply_friction_inplace(mu, dt)
|
||||
local friction = self:pooled_copy():scalar_mul_inplace(mu * dt)
|
||||
if friction:length_squared() > self:length_squared() then
|
||||
self:scalar_set(0, 0)
|
||||
else
|
||||
self:vsubi(_v_friction)
|
||||
self:vector_sub_inplace(friction)
|
||||
end
|
||||
friction:release()
|
||||
return self
|
||||
end
|
||||
|
||||
--"gamey" friction in one dimension
|
||||
local function apply_friction_1d(v, mu, dt)
|
||||
local function _friction_1d(v, mu, dt)
|
||||
local friction = mu * v * dt
|
||||
if math.abs(friction) > math.abs(v) then
|
||||
return 0
|
||||
@ -562,9 +438,9 @@ local function apply_friction_1d(v, mu, dt)
|
||||
end
|
||||
|
||||
--"gamey" friction in both dimensions
|
||||
function vec2:apply_friction_xy(mu_x, mu_y, dt)
|
||||
self.x = apply_friction_1d(self.x, mu_x, dt)
|
||||
self.y = apply_friction_1d(self.y, mu_y, dt)
|
||||
function vec2:apply_friction_xy_inplace(mu_x, mu_y, dt)
|
||||
self.x = _friction_1d(self.x, mu_x, dt)
|
||||
self.y = _friction_1d(self.y, mu_y, dt)
|
||||
return self
|
||||
end
|
||||
|
||||
@ -578,7 +454,7 @@ function vec2:maxcomp()
|
||||
end
|
||||
|
||||
-- mask out min component, with preference to keep x
|
||||
function vec2:majori()
|
||||
function vec2:major_inplace()
|
||||
if self.x > self.y then
|
||||
self.y = 0
|
||||
else
|
||||
@ -587,7 +463,7 @@ function vec2:majori()
|
||||
return self
|
||||
end
|
||||
-- mask out max component, with preference to keep x
|
||||
function vec2:minori()
|
||||
function vec2:minor_inplace()
|
||||
if self.x < self.y then
|
||||
self.y = 0
|
||||
else
|
||||
@ -596,14 +472,103 @@ function vec2:minori()
|
||||
return self
|
||||
end
|
||||
|
||||
--vector_ free alias; we're a vector library, so semantics should default to vector
|
||||
vec2.add_inplace = vec2.vector_add_inplace
|
||||
vec2.sub_inplace = vec2.vector_sub_inplace
|
||||
vec2.mul_inplace = vec2.vector_mul_inplace
|
||||
vec2.div_inplace = vec2.vector_div_inplace
|
||||
vec2.set = vec2.vector_set
|
||||
|
||||
--garbage generating versions
|
||||
function vec2:major(axis)
|
||||
return self:copy():majori(axis)
|
||||
--garbage generating functions that return a new vector rather than modifying self
|
||||
for _, inplace_name in ipairs({
|
||||
"vector_add_inplace",
|
||||
"vector_sub_inplace",
|
||||
"vector_mul_inplace",
|
||||
"vector_div_inplace",
|
||||
"fused_multiply_add_inplace",
|
||||
"add_inplace",
|
||||
"sub_inplace",
|
||||
"mul_inplace",
|
||||
"div_inplace",
|
||||
"scalar_add_inplace",
|
||||
"scalar_sub_inplace",
|
||||
"scalar_mul_inplace",
|
||||
"scalar_div_inplace",
|
||||
"normalise_both_inplace",
|
||||
"normalise_inplace",
|
||||
"normalise_len_inplace",
|
||||
"inverse_inplace",
|
||||
"rotate_inplace",
|
||||
"rotate_around_inplace",
|
||||
"rot90r_inplace",
|
||||
"rot90l_inplace",
|
||||
"lerp_direction_inplace",
|
||||
"min_inplace",
|
||||
"max_inplace",
|
||||
"clamp_inplace",
|
||||
"abs_inplace",
|
||||
"sign_inplace",
|
||||
"floor_inplace",
|
||||
"ceil_inplace",
|
||||
"round_inplace",
|
||||
"lerp_inplace",
|
||||
"lerp_eps_inplace",
|
||||
"vector_projection_inplace",
|
||||
"vector_rejection_inplace",
|
||||
"apply_friction_inplace",
|
||||
"apply_friction_xy_inplace",
|
||||
"major_inplace",
|
||||
"minor_inplace",
|
||||
}) do
|
||||
local garbage_name = inplace_name:gsub("_inplace", "")
|
||||
vec2[garbage_name] = function(self, ...)
|
||||
self = self:copy()
|
||||
return self[inplace_name](self, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function vec2:minor(axis)
|
||||
return self:copy():minori(axis)
|
||||
--"hungarian" shorthand aliases for compatibility and short names
|
||||
--
|
||||
--i do encourage using the longer versions above as it makes code easier
|
||||
--to understand when you come back, but i also appreciate wanting short code
|
||||
for _, v in ipairs({
|
||||
{"sset", "scalar_set"},
|
||||
{"sadd", "scalar_add"},
|
||||
{"ssub", "scalar_sub"},
|
||||
{"smul", "scalar_mul"},
|
||||
{"sdiv", "scalar_div"},
|
||||
{"vset", "vector_set"},
|
||||
{"vadd", "vector_add"},
|
||||
{"vsub", "vector_sub"},
|
||||
{"vmul", "vector_mul"},
|
||||
{"vdiv", "vector_div"},
|
||||
--(no plain addi etc, imo it's worth differentiating vaddi vs saddi)
|
||||
{"fma", "fused_multiply_add"},
|
||||
{"vproj", "vector_projection"},
|
||||
{"vrej", "vector_rejection"},
|
||||
--just for the _inplace -> i shorthand, mostly for backwards compatibility
|
||||
{"min", "min"},
|
||||
{"max", "max"},
|
||||
{"clamp", "clamp"},
|
||||
{"abs", "abs"},
|
||||
{"sign", "sign"},
|
||||
{"floor", "floor"},
|
||||
{"ceil", "ceil"},
|
||||
{"round", "round"},
|
||||
{"lerp", "lerp"},
|
||||
{"rotate", "rotate"},
|
||||
{"normalise", "normalise"},
|
||||
}) do
|
||||
local shorthand, original = v[1], v[2]
|
||||
if vec2[shorthand] == nil then
|
||||
vec2[shorthand] = vec2[original]
|
||||
end
|
||||
--and inplace version
|
||||
shorthand = shorthand .. "i"
|
||||
original = original .. "_inplace"
|
||||
if vec2[shorthand] == nil then
|
||||
vec2[shorthand] = vec2[original]
|
||||
end
|
||||
end
|
||||
|
||||
return vec2
|
||||
|
Loading…
Reference in New Issue
Block a user