vec2 api finalisation and testing, updating intersect to new api

This commit is contained in:
Max Cahill 2021-07-23 14:15:30 +10:00
parent 635bf506dc
commit f835dd6e66
2 changed files with 267 additions and 165 deletions

View File

@ -38,7 +38,8 @@ end
function intersect.circle_circle_collide(a_pos, a_rad, b_pos, b_rad, into) function intersect.circle_circle_collide(a_pos, a_rad, b_pos, b_rad, into)
--get delta --get delta
local delta = a_pos:pooled_copy():vsubi(b_pos) local delta = a_pos:pooled_copy()
:vector_sub_inplace(b_pos)
--squared threshold --squared threshold
local rad = a_rad + b_rad local rad = a_rad + b_rad
local dist = delta:length_squared() local dist = delta:length_squared()
@ -47,17 +48,19 @@ function intersect.circle_circle_collide(a_pos, a_rad, b_pos, b_rad, into)
if dist == 0 then if dist == 0 then
--singular case; just resolve vertically --singular case; just resolve vertically
dist = 1 dist = 1
delta:sset(0,1) delta:scalar_set(0, 1)
else else
--get actual distance --get actual distance
dist = math.sqrt(dist) dist = math.sqrt(dist)
end end
--allocate if needed --allocate if needed
if into == nil then if into == nil then
into = vec2:zero() into = vec2(0)
end end
--normalise, scale to separating distance --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 end
delta:release() delta:release()
return res return res
@ -69,41 +72,49 @@ end
--get the nearest point on the line segment a from point b --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) 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 --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 --detect degenerate case
local lensq = segment:length_squared() local lensq = segment:length_squared()
if lensq <= COLLIDE_EPS then if lensq <= COLLIDE_EPS then
into:vset(a_start) into:set(a_start)
else else
--solve for factor along segment --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) local factor = math.clamp01(point_to_start:dot(segment) / lensq)
point_to_start:release() 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 end
segment:release() segment:release()
return into return into
end end
--internal --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) 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 end
--internal --internal
--line displacement vector from separation vector --line displacement vector from separation vector
function intersect._line_displacement_to_sep(a_start, a_end, separation, total_rad) 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 local sep = distance - total_rad
if sep <= 0 then if sep <= 0 then
if distance <= COLLIDE_EPS then if distance <= COLLIDE_EPS then
--point intersecting the line; push out along normal --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 else
separation:smuli(-sep) separation:scalar_mul_inplace(-sep)
end end
return separation return separation
end end
@ -127,28 +138,31 @@ end
--collide 2 line segments --collide 2 line segments
function intersect.line_line_collide(a_start, a_end, a_rad, b_start, b_end, b_rad, into) function intersect.line_line_collide(a_start, a_end, a_rad, b_start, b_end, b_rad, into)
--segment directions from start points --segment directions from start points
local a_dir = a_end:vsub(a_start) local a_dir = a_end:pooled_copy():vector_sub_inplace(a_start)
local b_dir = b_end:vsub(b_start) local b_dir = b_end:pooled_copy():vector_sub_inplace(b_start)
--detect degenerate cases --detect degenerate cases
local a_degen = a_dir:length_squared() <= COLLIDE_EPS local a_degen = a_dir:length_squared() <= COLLIDE_EPS
local b_degen = a_dir:length_squared() <= COLLIDE_EPS local b_degen = a_dir:length_squared() <= COLLIDE_EPS
if a_degen and b_degen then if a_degen and b_degen then
--actually just circles --actually just circles
vec2.release(a_dir, b_dir)
return intersect.circle_circle_collide(a_start, a_rad, b_start, b_rad, into) return intersect.circle_circle_collide(a_start, a_rad, b_start, b_rad, into)
elseif a_degen then elseif a_degen then
-- a is just circle; annoying, need reversed msv -- 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) local collided = intersect.line_circle_collide(b_start, b_end, b_rad, a_start, a_rad, into)
if collided then if collided then
collided:smuli(-1) collided:scalar_mul_inplace(-1)
end end
vec2.release(a_dir, b_dir)
return collided return collided
elseif b_degen then elseif b_degen then
--b is just circle --b is just circle
vec2.release(a_dir, b_dir)
return intersect.line_circle_collide(a_start, a_end, a_rad, b_start, b_rad, into) return intersect.line_circle_collide(a_start, a_end, a_rad, b_start, b_rad, into)
end end
--otherwise we're _actually_ 2 line segs :) --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 --first, check intersection
@ -195,13 +209,22 @@ function intersect.line_line_collide(a_start, a_end, a_rad, b_start, b_end, b_ra
end end
end end
end end
if intersected == "both" then if intersected == "both" then
--simply displace along A normal --simply displace along A normal
return into:vset(a_dir):normalisei():smuli(a_rad + b_rad):rot90li() into:set(a_dir)
else vec2.release(a_dir, b_dir)
return into
:normalise_inplace()
:scalar_mul_inplace(a_rad + b_rad)
:rot90l_inplace()
end
vec2.release(a_dir, b_dir)
--dumb as a rock check-corners approach --dumb as a rock check-corners approach
--todo: pool storage --todo: pool storage
--todo calculus from http://geomalgorithms.com/a07-_distance.html --todo proper calculus from http://geomalgorithms.com/a07-_distance.html
local search_tab = {} local search_tab = {}
--only insert corners from the non-intersected line --only insert corners from the non-intersected line
--since intersected line is potentially the apex --since intersected line is potentially the apex
@ -226,20 +249,20 @@ function intersect.line_line_collide(a_start, a_end, a_rad, b_start, b_end, b_ra
end end
--fix direction --fix direction
into:vset(best[1]):smuli(best[2]) into:set(best[1])
:scalar_mul_inplace(best[2])
return intersect._line_displacement_to_sep(a_start, a_end, into, a_rad + b_rad) return intersect._line_displacement_to_sep(a_start, a_end, into, a_rad + b_rad)
end end
return false
end
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
-- axis aligned bounding boxes -- axis aligned bounding boxes
--return true on overlap, false otherwise --return true on overlap, false otherwise
function intersect.aabb_point_overlap(pos, hs, v) 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 local overlap = delta.x < hs.x and delta.y < hs.y
delta:release() delta:release()
return overlap return overlap
@ -249,21 +272,21 @@ end
-- return msv to push point to closest edge of aabb -- return msv to push point to closest edge of aabb
function intersect.aabb_point_collide(pos, hs, v, into) function intersect.aabb_point_collide(pos, hs, v, into)
--separation between centres --separation between centres
local delta_c = v:pooled_copy():vsubi(pos) local delta_c = v:pooled_copy():vector_sub_inplace(pos)
--absolute separation --absolute separation
local delta_c_abs = delta_c:pooled_copy():absi() local delta_c_abs = delta_c:pooled_copy():abs_inplace()
local res = false local res = false
if delta_c_abs.x < hs.x and delta_c_abs.y < hs.y then 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 --separating offset in both directions
:vset(hs) :set(hs)
:vsubi(delta_c_abs) :vector_sub_inplace(delta_c_abs)
--minimum separating distance --minimum separating distance
:minori() :minor_inplace()
--in the right direction --in the right direction
:vmuli(delta_c:signi()) :vector_mul_inplace(delta_c:sign_inplace())
--from the aabb's point of view --from the aabb's point of view
:smuli(-1) :inverse_inplace()
end end
vec2.release(delta_c, delta_c_abs) vec2.release(delta_c, delta_c_abs)
return res return res
@ -271,8 +294,10 @@ end
--return true on overlap, false otherwise --return true on overlap, false otherwise
function intersect.aabb_aabb_overlap(a_pos, a_hs, b_pos, b_hs) function intersect.aabb_aabb_overlap(a_pos, a_hs, b_pos, b_hs)
local delta = a_pos:pooled_copy():vsubi(b_pos):absi() local delta = a_pos:pooled_copy()
local total_size = a_hs:pooled_copy():vaddi(b_hs) :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 local overlap = delta.x < total_size.x and delta.y < total_size.y
vec2.release(delta, total_size) vec2.release(delta, total_size)
return overlap return overlap
@ -281,20 +306,20 @@ end
--discrete displacement --discrete displacement
--return msv on collision, false otherwise --return msv on collision, false otherwise
function intersect.aabb_aabb_collide(a_pos, a_hs, b_pos, b_hs, into) function intersect.aabb_aabb_collide(a_pos, a_hs, b_pos, b_hs, into)
if not into then into = vec2:zero() end if not into then into = vec2(0) end
local delta = a_pos:pooled_copy():vsubi(b_pos) local delta = a_pos:pooled_copy():vector_sub_inplace(b_pos)
local abs_delta = delta:pooled_copy():absi() local abs_delta = delta:pooled_copy():abs_inplace()
local size = a_hs:pooled_copy():vaddi(b_hs) local size = a_hs:pooled_copy():vector_add_inplace(b_hs)
local abs_amount = size:pooled_copy():vsubi(abs_delta) local abs_amount = size:pooled_copy():vector_sub_inplace(abs_delta)
local res = false local res = false
if abs_amount.x > COLLIDE_EPS and abs_amount.y > COLLIDE_EPS then if abs_amount.x > COLLIDE_EPS and abs_amount.y > COLLIDE_EPS then
--actually collided --actually collided
if abs_amount.x <= abs_amount.y then if abs_amount.x <= abs_amount.y then
--x min --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 else
--y min --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
end end
return res return res
@ -302,10 +327,10 @@ end
-- helper function to clamp point to aabb -- helper function to clamp point to aabb
function intersect.aabb_point_clamp(pos, hs, v, into) function intersect.aabb_point_clamp(pos, hs, v, into)
local v_min = pos:pooled_copy():vsubi(hs) local v_min = pos:pooled_copy():vector_sub_inplace(hs)
local v_max = pos:pooled_copy():vaddi(hs) local v_max = pos:pooled_copy():vector_add_inplace(hs)
into = into or vec2:zero() into = into or vec2(0)
into:vset(v):clampi(v_min, v_max) into:set(v):clamp_inplace(v_min, v_max)
vec2.release(v_min, v_max) vec2.release(v_min, v_max)
return into return into
end end
@ -320,7 +345,9 @@ end
-- return msv on collision, false otherwise -- return msv on collision, false otherwise
function intersect.aabb_circle_collide(a_pos, a_hs, b_pos, b_rad, into) 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 --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 local like_aabb = abs_delta.x < a_hs.x or abs_delta.y < a_hs.y
--(clean up) --(clean up)
@ -328,7 +355,7 @@ function intersect.aabb_circle_collide(a_pos, a_hs, b_pos, b_rad, into)
-- --
local result local result
if like_aabb then 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) result = intersect.aabb_aabb_collide(a_pos, a_hs, b_pos, pretend_hs, into)
pretend_hs:release() pretend_hs:release()
else else
@ -349,11 +376,17 @@ function intersect.point_in_poly(point, poly)
for i, a in ipairs(poly) do for i, a in ipairs(poly) do
local b = poly[i + 1] or poly[1] local b = poly[i + 1] or poly[1]
if a.y <= point.y then 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 wn = wn + 1
end end
else 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 wn = wn - 1
end end
end end
@ -371,11 +404,11 @@ end
-- 0 is only b_pos moving to resolve -- 0 is only b_pos moving to resolve
-- 0.5 is balanced between both (default) -- 0.5 is balanced between both (default)
--note: this wont work as-is for line segments, which have two separate position coordinates --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) function intersect.resolve_msv(a_pos, b_pos, msv, balance)
balance = balance or 0.5 balance = balance or 0.5
a_pos:fmai(msv, balance) a_pos:fused_multiply_add_inplace(msv, balance)
b_pos:fmai(msv, -(1 - balance)) b_pos:fused_multiply_add_inplace(msv, -(1 - balance))
end end
-- gets a normalised balance factor from two mass inputs, and treats <=0 or infinite or nil masses as static bodies -- gets a normalised balance factor from two mass inputs, and treats <=0 or infinite or nil masses as static bodies
@ -405,10 +438,10 @@ function intersect.bounce_off(velocity, normal, conservation)
--take a copy, we need it --take a copy, we need it
local old_vel = vec2.pooled_copy(velocity) local old_vel = vec2.pooled_copy(velocity)
--reject on the normal (keep velocity tangential to the normal) --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; --add back the complement of the difference;
--basically "flip" the velocity in line with the normal. --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 --clean up
old_vel:release() old_vel:release()
return velocity return velocity
@ -419,18 +452,18 @@ function intersect.mutual_bounce(velocity_a, velocity_b, normal, conservation)
--(default) --(default)
conservation = conservation or 1 conservation = conservation or 1
--take copies, we need them --take copies, we need them
local old_a_vel = vec2.pooled_copy(velocity_a) local old_a_vel = velocity_a:pooled_copy()
local old_b_vel = vec2.pooled_copy(velocity_b) local old_b_vel = velocity_b:pooled_copy()
--reject on the normal --reject on the normal
velocity_a:vreji(normal) velocity_a:vector_rejection_inplace(normal)
velocity_b:vreji(normal) velocity_b:vector_rejection_inplace(normal)
--calculate the amount remaining from the old velocity --calculate the amount remaining from the old velocity
--(transfer ownership) --(transfer pool ownership)
local a_remaining = old_a_vel:vsubi(velocity_a) local a_remaining = old_a_vel:vector_sub_inplace(velocity_a)
local b_remaining = old_b_vel:vsubi(velocity_b) local b_remaining = old_b_vel:vector_sub_inplace(velocity_b)
--transfer it to the other body --transfer it to the other body
velocity_a:fmai(b_remaining, conservation) velocity_a:fused_multiply_add_inplace(b_remaining, conservation)
velocity_b:fmai(a_remaining, conservation) velocity_b:fused_multiply_add_inplace(a_remaining, conservation)
--clean up --clean up
vec2.release(a_remaining, b_remaining) vec2.release(a_remaining, b_remaining)
end end

215
vec2.lua
View File

@ -43,7 +43,7 @@ function vec2:xy(x, y)
end end
function vec2:polar(length, angle) function vec2:polar(length, angle)
return vec2(length, 0):rotatei(angle) return vec2(length, 0):rotate_inplace(angle)
end end
function vec2:filled(v) function vec2:filled(v)
@ -69,11 +69,17 @@ make_pooled(vec2, 128)
--get a pooled copy of an existing vector --get a pooled copy of an existing vector
function vec2:pooled_copy() function vec2:pooled_copy()
return vec2:pooled():vector_set(self) return vec2:pooled(self)
end end
--modify --modify
function vec2:vector_set(v)
self.x = v.x
self.y = v.y
return self
end
function vec2:scalar_set(x, y) function vec2:scalar_set(x, y)
if not y then y = x end if not y then y = x end
self.x = x self.x = x
@ -81,16 +87,10 @@ function vec2:scalar_set(x, y)
return self return self
end end
function vec2:vector_set(v)
self.x = v.x
self.y = v.y
return self
end
function vec2:swap(v) function vec2:swap(v)
local sx, sy = self.x, self.y local sx, sy = self.x, self.y
self:vector_set(v) self.x, self.y = v.x, v.y
v:scalar_set(sx, sy) v.x, v.y = sx, sy
return self return self
end end
@ -123,59 +123,53 @@ end
----------------------------------------------------------- -----------------------------------------------------------
--vector --vector
function vec2:vector_add(v) function vec2:vector_add_inplace(v)
self.x = self.x + v.x self.x = self.x + v.x
self.y = self.y + v.y self.y = self.y + v.y
return self return self
end end
function vec2:vector_sub(v) function vec2:vector_sub_inplace(v)
self.x = self.x - v.x self.x = self.x - v.x
self.y = self.y - v.y self.y = self.y - v.y
return self return self
end end
function vec2:vector_mul(v) function vec2:vector_mul_inplace(v)
self.x = self.x * v.x self.x = self.x * v.x
self.y = self.y * v.y self.y = self.y * v.y
return self return self
end end
function vec2:vector_div(v) function vec2:vector_div_inplace(v)
self.x = self.x / v.x self.x = self.x / v.x
self.y = self.y / v.y self.y = self.y / v.y
return self return self
end end
--alias; we're a vector library so arithmetic defaults to vector
vec2.add = vec2.vector_add
vec2.sub = vec2.vector_sub
vec2.mul = vec2.vector_mul
vec2.div = vec2.vector_div
--scalar --scalar
function vec2:scalar_add(x, y) function vec2:scalar_add_inplace(x, y)
if not y then y = x end if not y then y = x end
self.x = self.x + x self.x = self.x + x
self.y = self.y + y self.y = self.y + y
return self return self
end end
function vec2:scalar_sub(x, y) function vec2:scalar_sub_inplace(x, y)
if not y then y = x end if not y then y = x end
self.x = self.x - x self.x = self.x - x
self.y = self.y - y self.y = self.y - y
return self return self
end end
function vec2:scalar_mul(x, y) function vec2:scalar_mul_inplace(x, y)
if not y then y = x end if not y then y = x end
self.x = self.x * x self.x = self.x * x
self.y = self.y * y self.y = self.y * y
return self return self
end end
function vec2:scalar_div(x, y) function vec2:scalar_div_inplace(x, y)
if not y then y = x end if not y then y = x end
self.x = self.x / x self.x = self.x / x
self.y = self.y / y self.y = self.y / y
@ -184,7 +178,7 @@ end
--(a + (b * t)) --(a + (b * t))
--useful for integrating physics and adding directional offsets --useful for integrating physics and adding directional offsets
function vec2:fused_multiply_add(v, t) function vec2:fused_multiply_add_inplace(v, t)
self.x = self.x + (v.x * t) self.x = self.x + (v.x * t)
self.y = self.y + (v.y * t) self.y = self.y + (v.y * t)
return self return self
@ -212,31 +206,31 @@ function vec2:distance(other)
return math.sqrt(self:distance_squared(other)) return math.sqrt(self:distance_squared(other))
end end
function vec2:normalise_both() function vec2:normalise_both_inplace()
local len = self:length() local len = self:length()
if len == 0 then if len == 0 then
return self, 0 return self, 0
end end
return self:scalar_div(len), len return self:scalar_div_inplace(len), len
end end
function vec2:normalise() function vec2:normalise_inplace()
local v, len = self:normalise_both() local v, len = self:normalise_both_inplace()
return v return v
end end
function vec2:normalise_len() function vec2:normalise_len_inplace()
local v, len = self:normalise_both() local v, len = self:normalise_both_inplace()
return len return len
end end
function vec2:inverse() function vec2:inverse_inplace()
return self:scalar_mul(-1) return self:scalar_mul_inplace(-1)
end end
-- angle/direction specific -- angle/direction specific
function vec2:rotate(angle) function vec2:rotate_inplace(angle)
local s = math.sin(angle) local s = math.sin(angle)
local c = math.cos(angle) local c = math.cos(angle)
local ox = self.x local ox = self.x
@ -246,15 +240,15 @@ function vec2:rotate(angle)
return self return self
end end
function vec2:rotate_around(angle, pivot) function vec2:rotate_around_inplace(angle, pivot)
self:vector_sub(pivot) self:vector_sub_inplace(pivot)
self:rotate(angle) self:rotate_inplace(angle)
self:vector_add(pivot) self:vector_add_inplace(pivot)
return self return self
end end
--fast quarter/half rotations --fast quarter/half rotations
function vec2:rot90r() function vec2:rot90r_inplace()
local ox = self.x local ox = self.x
local oy = self.y local oy = self.y
self.x = -oy self.x = -oy
@ -262,7 +256,7 @@ function vec2:rot90r()
return self return self
end end
function vec2:rot90l() function vec2:rot90l_inplace()
local ox = self.x local ox = self.x
local oy = self.y local oy = self.y
self.x = oy self.x = oy
@ -270,7 +264,7 @@ function vec2:rot90l()
return self return self
end end
vec2.rot180 = vec2.inverse --alias vec2.rot180_inplace = vec2.inverse_inplace --alias
--get the angle of this vector relative to (1, 0) --get the angle of this vector relative to (1, 0)
function vec2:angle() function vec2:angle()
@ -284,27 +278,27 @@ end
--lerp towards the direction of a provided vector --lerp towards the direction of a provided vector
--(length unchanged) --(length unchanged)
function vec2:lerp_direction(v, t) function vec2:lerp_direction_inplace(v, t)
return self:rotate(self:angle_difference(v) * t) return self:rotate_inplace(self:angle_difference(v) * t)
end end
----------------------------------------------------------- -----------------------------------------------------------
-- per-component clamping ops -- per-component clamping ops
----------------------------------------------------------- -----------------------------------------------------------
function vec2:min(v) function vec2:min_inplace(v)
self.x = math.min(self.x, v.x) self.x = math.min(self.x, v.x)
self.y = math.min(self.y, v.y) self.y = math.min(self.y, v.y)
return self return self
end end
function vec2:max(v) function vec2:max_inplace(v)
self.x = math.max(self.x, v.x) self.x = math.max(self.x, v.x)
self.y = math.max(self.y, v.y) self.y = math.max(self.y, v.y)
return self return self
end end
function vec2:clamp(min, max) function vec2:clamp_inplace(min, max)
self.x = math.clamp(self.x, min.x, max.x) self.x = math.clamp(self.x, min.x, max.x)
self.y = math.clamp(self.y, min.y, max.y) self.y = math.clamp(self.y, min.y, max.y)
return self return self
@ -314,7 +308,7 @@ end
-- absolute value -- absolute value
----------------------------------------------------------- -----------------------------------------------------------
function vec2:abs() function vec2:abs_inplace()
self.x = math.abs(self.x) self.x = math.abs(self.x)
self.y = math.abs(self.y) self.y = math.abs(self.y)
return self return self
@ -324,7 +318,7 @@ end
-- sign -- sign
----------------------------------------------------------- -----------------------------------------------------------
function vec2:sign() function vec2:sign_inplace()
self.x = math.sign(self.x) self.x = math.sign(self.x)
self.y = math.sign(self.y) self.y = math.sign(self.y)
return self return self
@ -334,19 +328,19 @@ end
-- truncation/rounding -- truncation/rounding
----------------------------------------------------------- -----------------------------------------------------------
function vec2:floor() function vec2:floor_inplace()
self.x = math.floor(self.x) self.x = math.floor(self.x)
self.y = math.floor(self.y) self.y = math.floor(self.y)
return self return self
end end
function vec2:ceil() function vec2:ceil_inplace()
self.x = math.ceil(self.x) self.x = math.ceil(self.x)
self.y = math.ceil(self.y) self.y = math.ceil(self.y)
return self return self
end end
function vec2:round() function vec2:round_inplace()
self.x = math.round(self.x) self.x = math.round(self.x)
self.y = math.round(self.y) self.y = math.round(self.y)
return self return self
@ -356,13 +350,13 @@ end
-- interpolation -- interpolation
----------------------------------------------------------- -----------------------------------------------------------
function vec2:lerp(other, amount) function vec2:lerp_inplace(other, amount)
self.x = math.lerp(self.x, other.x, amount) self.x = math.lerp(self.x, other.x, amount)
self.y = math.lerp(self.y, other.y, amount) self.y = math.lerp(self.y, other.y, amount)
return self return self
end end
function vec2:lerp_eps(other, amount, eps) function vec2:lerp_eps_inplace(other, amount, eps)
self.x = math.lerp_eps(self.x, other.x, amount, eps) self.x = math.lerp_eps(self.x, other.x, amount, eps)
self.y = math.lerp_eps(self.y, other.y, amount, eps) self.y = math.lerp_eps(self.y, other.y, amount, eps)
return self return self
@ -389,18 +383,18 @@ function vec2:scalar_projection(other)
return self:dot(other) / len return self:dot(other) / len
end end
function vec2:vector_projection(other) function vec2:vector_projection_inplace(other)
local div = other:dot(other) local div = other:dot(other)
if div == 0 then if div == 0 then
return self:scalar_set(0) return self:scalar_set(0)
end end
local fac = self:dot(other) / div local fac = self:dot(other) / div
return self:vector_set(other):scalar_muli(fac) return self:vector_set(other):scalar_mul_inplace(fac)
end end
function vec2:vector_rejection(o) function vec2:vector_rejection_inplace(other)
local tx, ty = self.x, self.y local tx, ty = self.x, self.y
self:vector_proji(other) self:vector_projection_inplace(other)
self:scalar_set(tx - self.x, ty - self.y) self:scalar_set(tx - self.x, ty - self.y)
return self return self
end end
@ -422,14 +416,14 @@ end
----------------------------------------------------------- -----------------------------------------------------------
--"physical" friction --"physical" friction
local _v_friction = vec2() --avoid alloc function vec2:apply_friction_inplace(mu, dt)
function vec2:apply_friction(mu, dt) local friction = self:pooled_copy():scalar_mul_inplace(mu * dt)
_v_friction:vector_set(self):scalar_muli(mu * dt) if friction:length_squared() > self:length_squared() then
if _v_friction:length_squared() > self:length_squared() then
self:scalar_set(0, 0) self:scalar_set(0, 0)
else else
self:vector_subi(_v_friction) self:vector_sub_inplace(friction)
end end
friction:release()
return self return self
end end
@ -444,7 +438,7 @@ local function _friction_1d(v, mu, dt)
end end
--"gamey" friction in both dimensions --"gamey" friction in both dimensions
function vec2:apply_friction_xy(mu_x, mu_y, dt) function vec2:apply_friction_xy_inplace(mu_x, mu_y, dt)
self.x = _friction_1d(self.x, mu_x, dt) self.x = _friction_1d(self.x, mu_x, dt)
self.y = _friction_1d(self.y, mu_y, dt) self.y = _friction_1d(self.y, mu_y, dt)
return self return self
@ -460,7 +454,7 @@ function vec2:maxcomp()
end end
-- mask out min component, with preference to keep x -- mask out min component, with preference to keep x
function vec2:major() function vec2:major_inplace()
if self.x > self.y then if self.x > self.y then
self.y = 0 self.y = 0
else else
@ -469,7 +463,7 @@ function vec2:major()
return self return self
end end
-- mask out max component, with preference to keep x -- mask out max component, with preference to keep x
function vec2:minor() function vec2:minor_inplace()
if self.x < self.y then if self.x < self.y then
self.y = 0 self.y = 0
else else
@ -478,24 +472,99 @@ function vec2:minor()
return self return self
end 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 functions that return a new vector rather than modifying self --garbage generating functions that return a new vector rather than modifying self
for _, v in ipairs({ for _, inplace_name in ipairs({
"vector_add_inplace",
"vector_sub_inplace",
"vector_mul_inplace",
"vector_div_inplace",
"scalar_add_inplace",
"scalar_sub_inplace",
"scalar_mul_inplace",
"scalar_div_inplace",
"fused_multiply_add_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 }) do
vec2[name] = function(self, ...) local garbage_name = inplace_name:gsub("_inplace", "")
vec2[garbage_name] = function(self, ...)
self = self:copy() self = self:copy()
self[v](self, ...) return self[inplace_name](self, ...)
end end
end end
--"hungarian" shorthand aliases --"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({ for _, v in ipairs({
{"saddi", "scalar_add"}, {"sset", "scalar_set"},
{"sadd", "scalar_add_copy"}, {"vset", "vector_set"},
{"sadd", "scalar_add"},
{"ssub", "scalar_sub"},
{"smul", "scalar_mul"},
{"sdiv", "scalar_div"},
{"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 }) do
local shorthand, original = v[1], v[2] local shorthand, original = v[1], v[2]
if vec2[shorthand] == nil then
vec2[shorthand] = vec2[original] vec2[shorthand] = vec2[original]
end end
--and inplace version
shorthand = shorthand .. "i"
original = original .. "_inplace"
if vec2[shorthand] == nil then
vec2[shorthand] = vec2[original]
end
end
return vec2 return vec2