Skip to content

Instantly share code, notes, and snippets.

@ftsf
Last active March 15, 2021 04:47
Show Gist options
  • Save ftsf/030f429f67807c2ad1395bb936fce934 to your computer and use it in GitHub Desktop.
Save ftsf/030f429f67807c2ad1395bb936fce934 to your computer and use it in GitHub Desktop.
nico separating axis theorem collision check
import nico
import nico/vec
type Object = ref object
color: int
pos: Vec2f
points: seq[Vec2f]
rotation: float32
var objs: seq[Object]
var overlapping = false
var overlapAxis: Vec2f
var overlapAmount: float32
var separate = false
iterator edges(self: Object): (Vec2f,Vec2f) =
let pos = self.pos
for i in 1..<self.points.len:
let a = self.points[i-1].rotate(self.rotation)
let b = self.points[i].rotate(self.rotation)
yield (pos + a, pos + b)
block:
let a = self.points[self.points.high].rotate(self.rotation)
let b = self.points[0].rotate(self.rotation)
yield (pos + a, pos + b)
proc getAxes(a,b: Object): seq[Vec2f] =
for edge in a.edges:
let axis = (edge[1] - edge[0]).perpendicular().normalized
if axis notin result:
result.add(axis)
for edge in b.edges:
let axis = (edge[1] - edge[0]).perpendicular().normalized
if axis notin result:
result.add(axis)
proc sat(a,b: Object): (bool,Vec2f,float32) =
## return true if objects are overlapping, if overlapping returns the axis of min overlap
var axisOfMinOverlap: Vec2f
var minOverlap: float32 = Inf
for axis in getAxes(a,b):
var amin = Inf
var bmin = Inf
var amax = -Inf
var bmax = -Inf
# project each edge against the current axis
for edge in a.edges:
for p in [edge[0],edge[1]]:
var v = dot(p,axis)
if v < amin:
amin = v
if v > amax:
amax = v
for edge in b.edges:
for p in [edge[0],edge[1]]:
var v = dot(p,axis)
if v < bmin:
bmin = v
if v > bmax:
bmax = v
if bmin > amax or amin > bmax:
# found axis of separation, we can exit early
result[0] = false
return
var overlap = 0f
if amax > bmin:
overlap = abs(bmin - amax)
elif bmax > amin:
overlap = abs(amin - bmax)
if abs(overlap) < abs(minOverlap):
minOverlap = overlap
axisOfMinOverlap = axis
return (true, axisOfMinOverlap, minOverlap)
proc draw(self: Object) =
setColor(self.color)
for edge in self.edges:
line(edge[0], edge[1])
proc gameInit() =
# we want a fixed sized screen with perfect square pixels
fixedSize(true)
integerScale(true)
# create the window
nico.createWindow("nico",128,128,4)
objs = @[]
objs.add(Object(color: 6, pos: vec2f(64,64), points: @[vec2f(-16f, -16f), vec2f(16f, -16f), vec2f(16f, 16f), vec2f(-16f, 16f)]))
objs.add(Object(color: 5, pos: vec2f(82,64), points: @[vec2f(-32f, -16f), vec2f(16f, -16f), vec2f(16f, 16f), vec2f(-16f, 16f)]))
proc gameUpdate(dt: float32) =
objs[0].rotation += 0.5f * dt
let (mx,my) = mouse()
if mousebtn(0):
objs[1].pos = vec2f(mx,my)
if btnp(pcA):
separate = not separate
let hit = sat(objs[0], objs[1])
overlapping = hit[0]
overlapAxis = hit[1]
overlapAmount = hit[2]
if overlapping and separate:
# push them apart
objs[0].pos += overlapAxis * (overlapAmount * -0.5f)
objs[1].pos += overlapAxis * (overlapAmount * 0.5f)
proc gameDraw() =
cls()
for obj in objs:
obj.draw()
if overlapping:
setColor(8)
print("overlapping", 4, 4)
if overlapping:
setColor(8)
line(objs[0].pos, objs[0].pos - overlapAxis * overlapAmount)
# initialization
nico.init("nico", "sat")
# start, say which functions to use for init, update and draw
nico.run(gameInit, gameUpdate, gameDraw)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment