├── LICENSE.md ├── README.md ├── broadphase.p8 ├── charcontrol.p8 ├── constraints.p8 ├── demo1.p8 ├── demo2.p8 ├── demo3.p8 ├── demo4.p8 ├── demo5.p8 ├── demo6.p8 ├── demo7.p8 ├── demo8.p8 ├── forces.p8 ├── geometry.p8 ├── math2d.p8 └── physics.p8 /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 James Edge 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pico8 physics 2 | 3 | This is an implementation of a constraint solver for rigid body physics in pico8/lua. This approach is based upon Erin Catto's work on sequential impulses, see these [slides](https://box2d.org/files/ErinCatto_SequentialImpulses_GDC2006.pdf). 4 | 5 | ## example 6 | 7 | All physics objects (rigid bodies) are managed within a scene object. The following code can be used to create a scene, and add some rigid bodies. 8 | ``` 9 | function _init() 10 | myscene = scene{ size=15 } 11 | floor = myscene.add_body{ x=0, y=-4, mass=0, moi=0, verts=rectangle(10, 1) } 12 | box = myscene.add_body{ x=0, y=4, mass=1, verts=rectangle(1, 1) } 13 | end 14 | ``` 15 | The parameter to the add_body method is a table containing values defining the rigid body in the physics scene. In this case the position (x, y), mass/inertia (mass, moi) and geometry (verts). The method returns an integer id for the body which can be used to retrieve information. In this example because the floor has mass and inertia 0, it is interpreted as infinite mass and so will not react to forces applied to it. All parameters are in SI units, metres and kg. 16 | 17 | Every frame the update method of the scene must be called to cause the simulation to move forward. Update will apply gravity, perform collision detection and response, integrate velocity and move objects in the scene. The time step will be determined by the value of stat(7) - you should aim to keep the framerate constant because a fluctuating framerate can affect the simulation. 18 | ``` 19 | function _update60() 20 | myscene.update() 21 | end 22 | ``` 23 | As positions are stored in metres in the scene, a viewport object can be created to transform these to screen space pixel units. Scene also has a draw method which will display the colliders given a viewport object. 24 | ``` 25 | function _draw() 26 | cls() 27 | vp = viewport{ scale=16 } 28 | myscene.draw(vp) 29 | end 30 | ``` 31 | Several demos have been included to show how to use the code. 32 | 33 | ## links 34 | 35 | - [pico-8 fantasy console](https://www.lexaloffle.com/pico-8.php) 36 | - [Erin Catto's box2d](https://box2d.org/) 37 | -------------------------------------------------------------------------------- /broadphase.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 18 3 | __lua__ 4 | -- broadphase collision detection 5 | --[[ 6 | broadphase collision detection using sweep and prune with axis aligned bounding boxes 7 | ]] 8 | 9 | -->8 10 | -- sweep and prune 11 | 12 | function sweep_and_prune() 13 | local nump, x_id, y_id, x_val, y_val, x_minmax, y_minmax, x_spans, y_spans, count, cand, iter = 14 | 2, { -1, -1 }, { -1, -1 }, { 0x8000, 0x7fff }, { 0x8000, 0x7fff }, { 0, 1 }, { 0, 1 }, {}, {}, {}, {} 15 | 16 | return { 17 | add_body=function(id, box) 18 | local function insert(len, ids, vals, minmaxs, spans, id, val, minmax) 19 | ids[len+1], vals[len+1], minmaxs[len+1] = ids[len], vals[len], minmaxs[len] 20 | local idx, prev_id, cid, pidx = len 21 | while vals[idx-1]>val do 22 | pidx = idx-1 23 | prev_id = ids[pidx] 24 | cid = shl(min(id, prev_id), 8)+max(id, prev_id) 25 | count[cid] = (count[cid] and count[cid] or 0) + (minmaxs[pidx]-minmax) 26 | cand[cid] = count[cid]==2 and true or nil 27 | ids[idx], vals[idx], minmaxs[idx], spans[prev_id*2+minmaxs[pidx]] = 28 | ids[pidx], vals[pidx], minmaxs[pidx], idx 29 | idx -= 1 30 | end 31 | ids[idx], vals[idx], minmaxs[idx], spans[id*2+minmax] = id, val, minmax, idx 32 | end 33 | 34 | insert(nump, x_id, x_val, x_minmax, x_spans, id, box.x1, 0) 35 | insert(nump+1, x_id, x_val, x_minmax, x_spans, id, box.x2, 1) 36 | insert(nump, y_id, y_val, y_minmax, y_spans, id, box.y1, 0) 37 | insert(nump+1, y_id, y_val, y_minmax, y_spans, id, box.y2, 1) 38 | nump += 2 39 | end, 40 | remove_body=function(id) 41 | local function remove(len, ids, vals, minmaxs, spans, idx) 42 | for i=idx+1, len do 43 | if (ids[i]>0) spans[ids[i]*2+minmaxs[i]] = i-1 44 | ids[i-1], vals[i-1], minmaxs[i-1] = ids[i], vals[i], minmaxs[i] 45 | end 46 | end 47 | remove(nump, x_id, x_val, x_minmax, x_spans, x_spans[id*2]) 48 | remove(nump-1, x_id, x_val, x_minmax, x_spans, x_spans[id*2+1]) 49 | remove(nump, y_id, y_val, y_minmax, y_spans, y_spans[id*2]) 50 | remove(nump-1, y_id, y_val, y_minmax, y_spans, y_spans[id*2+1]) 51 | nump -= 2 52 | 53 | local id1, id2 = shl(id, 8), id 54 | for cid in pairs(count) do 55 | if (band(cid, 0xff00)==id1 or band(cid, 0x00ff)==id2) count[cid] = nil 56 | end 57 | for cid in pairs(cand) do 58 | if (band(cid, 0xff00)==id1 or band(cid, 0x00ff)==id2) cand[cid] = nil 59 | end 60 | end, 61 | update_body=function(id, box) 62 | local function update(ids, vals, minmaxs, spans, idx, val) 63 | local off, id1, id2, cid, nidx 64 | vals[idx] = val 65 | while mid(vals[idx-1], val, vals[idx+1])!=val do 66 | off = vals[idx+1]8 10 | 11 | --[[ 12 | 13 | -- directions 14 | RIGHT = 0x01 15 | LEFT = 0x02 16 | UP = 0x03 17 | DOWN = 0x04 18 | 19 | -- states 20 | STANDING = 0x00 21 | RIGHT = 0x01 22 | LEFT = 0x02 23 | JUMPING = 0x04 24 | FALLING = 0x08 25 | PUSHING = 0x10 26 | 27 | ]]-- 28 | 29 | function charcontroller(scene, args) 30 | args = args or {} 31 | 32 | local listener, leftkey, rightkey, jumpkey = 33 | args.listener, args.leftkey or 0, args.rightkey or 1, args.jumpkey or 4 34 | local ground_speed, jump_speed = args.ground_speed or 5, args.jump_speed or 5 35 | local drift_speed = args.drift_speed or ground_speed/2 36 | local jump_count, ground_count, hang_count, contacts, directions, forward, px, py, mvx, mvy, bodyid = 37 | 0, 0, 0, {}, { 0, 0, 0, 0 }, 0, 0, 0, 0, 0 38 | 39 | local function is_grounded() return ground_count<2 end 40 | local function is_hanging() return not is_grounded() and hang_count<2 end -- used for wall jumps 41 | 42 | -- move, dir=-1 (left), dir=1 (right) 43 | -- walk if grounded, drift if not 44 | local function move(dir) 45 | local vx, speed = scene.velocity(bodyid), is_grounded() and ground_speed or is_hanging() and 0 or drift_speed 46 | scene.apply_impulse(bodyid, dir*max(0, speed-dir*vx), 0, 0) 47 | end 48 | 49 | -- jump/double-jump/wall jump 50 | local function jump() 51 | local vx, vy = scene.velocity(bodyid) 52 | if is_grounded() then jump_count=1 scene.apply_impulse(bodyid, 0, jump_speed, 0) 53 | elseif is_hanging() then 54 | local dx, dy = directions[2]>0 and -0.3 or 0.3, 0.6 55 | local len = sqrt(dx*dx+dy*dy) 56 | dx /= len dy /= len 57 | jump_count += 1 58 | scene.apply_impulse(bodyid, dx*jump_speed, dy*jump_speed, 0) 59 | elseif vy<=0 and jump_count<2 then 60 | jump_count+=1 61 | scene.apply_impulse(bodyid, 0, jump_speed, 0) 62 | end 63 | end 64 | 65 | local self 66 | self = { 67 | id=function() return bodyid end, 68 | state=function() -- returns the state of the character 69 | if (abs(mvx)<0x0.01 and abs(mvy)<0x0.01) return 0x00 70 | return (forward>0 and 0x01 or 0x02)+ 71 | (not is_grounded() and (mvy>0x0.01 and 0x04 or mvy<-0x0.01 and 0x08 or 0x00) or 0x00)+ 72 | (((forward>0 and directions[2]>0) or (forward<0 and directions[1]>0)) and 0x10 or 0x00) 73 | end, 74 | contact=function() -- returns which directions the character is in contact 75 | return (direction[1]>0 and 0x01 or 0x00)+(direction[2]>0 and 0x02 or 0x00)+ 76 | (direction[3]>0 and 0x04 or 0x00)+(direction[4]>0 and 0x08 or 0x00) 77 | end, 78 | left=function() move(-1) end, -- move left 79 | right=function() move(1) end, -- move right 80 | jump=jump, 81 | is_grounded=is_grounded, 82 | is_hanging=is_hanging, 83 | on_event=function(args) -- handle contacts 84 | if args.event==0x01 then 85 | local cid, nx, ny, maxv, dir = args.cid, args.nx, args.ny, 0x8000, 0 86 | for i,v in pairs({ nx, -nx, -ny, ny }) do if (v>maxv) maxv, contacts[cid] = v, i end 87 | directions[contacts[cid]] += 1 88 | if contacts[cid]==4 then ground_count=0 elseif contacts[cid]<3 then hang_count=0 end 89 | elseif args.event==0x02 then 90 | local cid = args.cid 91 | directions[contacts[cid]] -= 1 92 | contacts[cid] = nil 93 | end 94 | if (listener) listener.on_event(args) -- forward events to listener 95 | end, 96 | update=function() -- call once per frame 97 | local x, y = scene.position(bodyid) 98 | mvx, mvy, px, py = x-px, y-py, x, y 99 | 100 | if (directions[4]==0) ground_count = ground_count%0x7fff+1 101 | if (directions[1]+directions[2]==0) hang_count = hang_count%0x7fff+1 102 | 103 | -- set facing direction 104 | if (btn(0) or btn(1)) forward = (btn(0) and -1 or 0)+(btn(1) and 1 or 0) 105 | 106 | -- handle inputs 107 | if btnp(4) then jump() 108 | elseif btn(0) or btn(1) then move(forward) end 109 | end 110 | } 111 | 112 | bodyid = scene.add_body{ x=args.x or 0, y=args.y or 0, mass=args.mass, moi=0, rest=0, frict=1, 113 | verts=args.verts or capsule(0.5, 0.5, 8), listener=self } 114 | px, py = scene.position(bodyid) 115 | 116 | return self 117 | end 118 | -------------------------------------------------------------------------------- /constraints.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 18 3 | __lua__ 4 | 5 | -->8 6 | -- joint constraint 7 | 8 | function joint(scene, id1, x1, y1, id2, x2, y2, beta) 9 | local lambda, j1, j2, j3, j4, j5, j6, b, jm, imass1, imoi1, imass2, imoi2 = 0 10 | 11 | imass1, imoi1 = scene.inv_mass(id1) 12 | imass2, imoi2 = scene.inv_mass(id2) 13 | 14 | return { 15 | eval=function(dt) 16 | local x, y, a, ca, sa, jx1, jy1, jx2, jy2, px1, py1, px2, py2, dx, dy, c 17 | x, y, a = scene.position(id1) 18 | ca, sa = cos_sin(a) 19 | jx1, jy1 = (x1*ca-y1*sa)+x, (x1*sa+y1*ca)+y 20 | px1, py1 = jx1-x, jy1-y 21 | x, y, a = scene.position(id2) 22 | ca, sa = cos_sin(a) 23 | jx2, jy2 = (x2*ca-y2*sa)+x, (x2*sa+y2*ca)+y 24 | px2, py2 = jx2-x, jy2-y 25 | dx, dy = jx1-jx2, jy1-jy2 26 | 27 | c = dx*dx+dy*dy 28 | 29 | if (c<0x0.001) return false 30 | 31 | -- warmstart 32 | local px, py = dx*lambda, dy*lambda 33 | scene.apply_impulse(id1, px, py, px1*py-py1*px) 34 | scene.apply_impulse(id2, -px, -py, -(px2*py-py2*px)) 35 | 36 | j1, j2, j3, j4, j5, j6, b = 37 | 2*dx, 2*dy, -2*(dx*py1-dy*px1), -2*dx, -2*dy, 2*(dx*py2-dy*px2), c*beta/dt 38 | jm = j1*imass1*j1+j2*imass1*j2+j3*imoi1*j3+j4*imass2*j4+j5*imass2*j5+j6*imoi2*j6 39 | 40 | return true 41 | end, 42 | solve=function() 43 | local vx1, vy1, va1 = scene.velocity(id1) 44 | local vx2, vy2, va2 = scene.velocity(id2) 45 | 46 | local del = -(j1*vx1+j2*vy1+j3*va1+j4*vx2+j5*vy2+j6*va2+b)/jm 47 | lambda += del 48 | 49 | scene.apply_impulse(id1, del*j1, del*j2, del*j3) 50 | scene.apply_impulse(id2, del*j4, del*j5, del*j6) 51 | 52 | return del*del<0x0.001 53 | end 54 | } 55 | end 56 | -------------------------------------------------------------------------------- /demo1.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 18 3 | __lua__ 4 | 5 | #include math2d.p8 6 | #include physics.p8 7 | #include broadphase.p8 8 | #include geometry.p8 9 | 10 | vp = viewport() 11 | s = scene{} 12 | 13 | vlist = { [0]=ngon(0.5, 3), ngon(0.5, 4), ngon(0.5, 5), ngon(0.5, 6), ngon(0.5, 7), ngon(0.5, 8), ngon(0.5, 9), ngon(0.5, 10) } 14 | 15 | listener = { 16 | on_event=function(args) 17 | if args.event==0x01 then 18 | printh('COLLISION ENTERED '..args.id..'x'..args.body) 19 | elseif args.event==0x02 then 20 | printh('COLLISION EXITED '..args.id..'x'..args.body) 21 | elseif args.event==0x03 then 22 | printh('SLEEPS '..args.id) 23 | elseif args.event==0x04 then 24 | printh('WAKES '..args.id) 25 | elseif args.event==0x05 then 26 | printh('DEAD '..args.id) 27 | end 28 | end 29 | } 30 | 31 | function _init() 32 | s.add_body{ x=-1.6, y=3.1, mass=0, moi=5, rest=0.6, verts=rectangle(3, 1) } 33 | s.add_body{ x=1.8, y=0.7, mass=0, moi=5, rest=0.6, verts=rectangle(3, 1) } 34 | s.add_body{ x=-1.8, y=-0.7, mass=0, moi=5, rest=0.6, verts=rectangle(3, 1) } 35 | s.add_body{ x=1.6, y=-3.1, mass=0, moi=5, rest=0.6, verts=rectangle(3, 1) } 36 | end 37 | 38 | function _update60() 39 | if (rnd(1)>0.95) s.add_body{ x=rnd(4)-2, y=5+rnd(2), verts=vlist[flr(rnd()*6)], listener=listener } 40 | s.update() 41 | end 42 | 43 | function _draw() 44 | cls() 45 | s.draw(vp) 46 | 47 | print(flr(stat(0)) .. ' kib', 5, 5, 0x7) 48 | print(flr(stat(1)*100) .. '% ' .. stat(7) .. 'fps', 5, 15, 0x7) 49 | rect(0, 0, 127, 127, 0x7) 50 | end 51 | -------------------------------------------------------------------------------- /demo2.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 18 3 | __lua__ 4 | 5 | #include math2d.p8 6 | #include physics.p8 7 | #include broadphase.p8 8 | #include geometry.p8 9 | 10 | vp = viewport() 11 | s = scene{} 12 | 13 | listener = { 14 | on_event=function(args) 15 | if args.event==0x01 then 16 | --printh('COLLISION ENTERED '..args.id..'x'..args.body) 17 | elseif args.event==0x02 then 18 | --printh('COLLISION EXITED '..args.id..'x'..args.body) 19 | elseif args.event==0x03 then 20 | printh('SLEEPS '..args.id) 21 | elseif args.event==0x04 then 22 | printh('WAKES '..args.id) 23 | elseif args.event==0x05 then 24 | printh('DEAD '..args.id) 25 | end 26 | end 27 | } 28 | 29 | function _init() 30 | vp.scale(0.8) 31 | --vp.translate(16, 0) 32 | s.add_body{ x=0, y=-3.5, mass=0, moi=0, rest=0.0, verts=rectangle(8, 1) } 33 | 34 | for i=0,6 do 35 | s.add_body{ x=-1.5, y=i-2.5, listener=listener } 36 | s.add_body{ x=0, y=i-2.5, listener=listener } 37 | s.add_body{ x=1.5, y=i-2.5, listener=listener } 38 | end 39 | 40 | for i=1,100 do s.update(1/60) end 41 | end 42 | 43 | function _update60() 44 | if (btnp(4)) s.apply_impulse(s.add_body{ x=-9, y=0 }, 50, 0, 0) 45 | s.update() 46 | end 47 | 48 | function _draw() 49 | cls() 50 | s.draw(vp) 51 | print(flr(stat(0)) .. ' kib', 5, 5, 0x7) 52 | print(flr(stat(1)*100) .. '% ' .. stat(7) .. 'fps', 5, 15, 0x7) 53 | rect(0, 0, 127, 127, 0x7) 54 | end 55 | -------------------------------------------------------------------------------- /demo3.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 18 3 | __lua__ 4 | 5 | #include math2d.p8 6 | #include physics.p8 7 | #include broadphase.p8 8 | #include geometry.p8 9 | 10 | vp = viewport() 11 | s = scene{} 12 | 13 | function _init() 14 | s.add_body{ x=0, y=-3.5, mass=0, moi=0, rest=0.0, verts=rectangle(8, 1) } 15 | 16 | s.add_body{ x=-2, y=-2.5 } 17 | s.add_body{ x=-1, y=-2.5 } 18 | s.add_body{ x=0, y=-2.5 } 19 | s.add_body{ x=1, y=-2.5 } 20 | s.add_body{ x=2, y=-2.5 } 21 | 22 | s.add_body{ x=-1.5, y=-1.5 } 23 | s.add_body{ x=-0.5, y=-1.5 } 24 | s.add_body{ x=0.5, y=-1.5 } 25 | s.add_body{ x=1.5, y=-1.5 } 26 | 27 | s.add_body{ x=-1, y=-0.5 } 28 | s.add_body{ x=0, y=-0.5 } 29 | s.add_body{ x=1, y=-0.5 } 30 | 31 | s.add_body{ x=-0.5, y=0.5 } 32 | s.add_body{ x=0.5, y=0.5 } 33 | 34 | s.add_body{ x=0, y=1.5 } 35 | 36 | for i=1,100 do s.update(1/60) end 37 | end 38 | 39 | function _update60() 40 | if (btnp(4)) s.apply_impulse(s.add_body{ x=-9, y=0 }, 50, 0, 0) 41 | s.update() 42 | end 43 | 44 | function _draw() 45 | cls() 46 | s.draw(vp) 47 | print(flr(stat(0)) .. ' kib', 5, 5, 0x7) 48 | print(flr(stat(1)*100) .. '% ' .. stat(7) .. 'fps', 5, 15, 0x7) 49 | rect(0, 0, 127, 127, 0x7) 50 | end 51 | -------------------------------------------------------------------------------- /demo4.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 18 3 | __lua__ 4 | 5 | #include math2d.p8 6 | #include physics.p8 7 | #include broadphase.p8 8 | #include geometry.p8 9 | #include charcontrol.p8 10 | 11 | local frame, anim, collide, vp, s, cc, box1, box2 = 0, 0, false 12 | 13 | function _init() 14 | vp = viewport() 15 | s = scene{} 16 | cc = charcontroller(s, { x=-0.5 }) 17 | 18 | s.add_body{ x=0, y=-3.5, mass=0, moi=0, rest=0, verts=rectangle(8, 1) } 19 | s.add_body{ x=-4, y=0, mass=0, moi=0, rest=0, verts=rectangle(1, 8) } 20 | s.add_body{ x=4, y=0, mass=0, moi=0, rest=0, verts=rectangle(1, 8) } 21 | 22 | s.add_body{ x=-3, y=-1.25, mass=0, moi=0, rest=0, verts=rectangle(3, 0.5) } 23 | s.add_body{ x=2, y=0.25, mass=0, moi=0, rest=0, verts=rectangle(5, 0.5) } 24 | s.add_body{ x=-0.5, y=2.75, mass=0, moi=0, rest=0, verts=rectangle(3, 0.5) } 25 | 26 | box1 = s.add_body{ x=1.5, y=1 } 27 | box2 = s.add_body{ x=-1.5, y=3.5 } 28 | 29 | s.add_body{ x=2, y=-3.5, a=1.2, mass=0, moi=0, rest=0, verts=rectangle(1.3, 4) } 30 | 31 | for i=1,100 do s.update(1/60) end 32 | end 33 | 34 | function _update60() 35 | frame += 1 36 | if (frame%8==0) anim = (anim+1)%3 37 | cc.update() 38 | s.update() 39 | if (btnp(5)) collide = not collide 40 | end 41 | 42 | -- adapted from @freds72 code 43 | function rspr(sx,sy,x,y,a,w) 44 | local ca,sa=cos_sin(a) 45 | local srcx,srcy,addr,pixel_pair 46 | local ddx0,ddy0=ca,sa 47 | local mask=shl(0xfff8,(w-1)) 48 | w*=4 49 | ca*=w-0.5 50 | sa*=w-0.5 51 | local dx0,dy0=sa-ca+w,-ca-sa+w 52 | w=2*w-1 53 | for ix=0,w do 54 | srcx,srcy=dx0,dy0 55 | for iy=0,w do 56 | if band(bor(srcx,srcy),mask)==0 then 57 | local c=sget(sx+srcx,sy+srcy) 58 | if (c>0) pset(x+ix,y+iy,c) 59 | end 60 | srcx-=ddy0 61 | srcy+=ddx0 62 | end 63 | dx0+=ddx0 64 | dy0+=ddy0 65 | end 66 | end 67 | 68 | function _draw() 69 | cls() 70 | 71 | local x, y, a, px, py 72 | 73 | x, y, a = s.position(box1) 74 | px, py = vp.to_screen(x, y) 75 | rspr(0, 104, px-16, py-16, a, 4) 76 | 77 | x, y, a = s.position(box2) 78 | px, py = vp.to_screen(x, y) 79 | rspr(0, 104, px-16, py-16, a, 4) 80 | 81 | local wx, wy = s.position(cc.id()) 82 | local x, y = vp.to_screen(wx, wy) 83 | local state = cc.state() 84 | 85 | if state==0 then spr(anim*2, x-8, y-8, 2, 2, band(state, 0x02)>0) 86 | else 87 | if band(state, 0x04)>0 then spr(96+anim*2, x-8, y-8, 2, 2, band(state, 0x02)>0) 88 | elseif band(state, 0x08)>0 then spr(128+anim*2, x-8, y-8, 2, 2, band(state, 0x02)>0) 89 | elseif band(state, 0x10)>0 then spr(64+anim*2, x-8, y-8, 2, 2, band(state, 0x02)>0) 90 | else spr(32+anim*2, x-8, y-8, 2, 2, band(state, 0x02)>0) end 91 | end 92 | 93 | map(0, 0, 0, 0, 16, 16) 94 | 95 | if (collide) s.draw(vp) 96 | 97 | rect(0, 0, 127, 127, 0x7) 98 | end 99 | __gfx__ 100 | 00000000000000000000000000000000000000000000000077777777000000000000000000000000000000000000000000000000000000000000000000000000 101 | 00000000000000000000000000000000000000000000000070000007000000000000000000000000000000000000000000000000000000000000000000000000 102 | 00000007700000000000000770000000000000077000000070707707000000000000000000000000000000000000000000000000000000000000000000000000 103 | 00000007700000000000000770000000000000077000000070077707000000000000000000000000000000000000000000000000000000000000000000000000 104 | 00000000000000000000000000000000000000000000000070777007000000000000000000000000000000000000000000000000000000000000000000000000 105 | 00000007700000000000000770000000000000077000000070770707000000000000000000000000000000000000000000000000000000000000000000000000 106 | 00000007700000000000000770000000000000077000000070000007000000000000000000000000000000000000000000000000000000000000000000000000 107 | 00000007700000000000000770000000000000077000000077777777000000000000000000000000000000000000000000000000000000000000000000000000 108 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 109 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 110 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 111 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 112 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 113 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 114 | 00000007700000000000007770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 115 | 00000077770000000000000777000000000000777700000000000000000000000000000000000000000000000000000000000000000000000000000000000000 116 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 117 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 118 | 00000000000000000000000000000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 119 | 00000007700000000000000770000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 120 | 00000007700000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 121 | 00000000000000000000000000000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 122 | 00000077000000000000007700000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 123 | 00000077000000000000007700000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 124 | 00000077000000000000007700000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 125 | 00000077000000000000007700000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 126 | 00000077000000000000007700000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 127 | 00000707000000000000007700000000000000707000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 128 | 00000700700000000000007070000000000077777000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 129 | 00007000070700000007777007000000000070700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 130 | 00070000007000000007000007000000000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 131 | 00007000000000000000000007700000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 132 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 133 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 134 | 00000000000000000000000000000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 135 | 00000007700000000000000770000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 136 | 00000007700000000000000770000000000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000 137 | 00000000000700000000000000070000000000777777000000000000000000000000000000000000000000000000000000000000000000000000000000000000 138 | 00000077777700000000007777770000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 139 | 00000077000000000000007700000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 140 | 00000077000000000000007700000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 141 | 00000077000000000000007700000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 142 | 00000077000000000000007700000000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 143 | 00000707000000000000007700000000000000707000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 144 | 00000700700000000000007070000000000077777000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 145 | 00007000070700000007777007000000000070700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 146 | 00070000007000000007000007000000000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 147 | 00007000000000000000000007700000000000770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 148 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 149 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 150 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 151 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 152 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 153 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 154 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 155 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 156 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 157 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 158 | 00000077770000000000007777000000000000777700000000000000000000000000000000000000000000000000000000000000000000000000000000000000 159 | 00007770777000000000777077700000000077707770000000000000000000000000000000000000000000000000000000000000000000000000000000000000 160 | 00007000700000000000700070000000000070007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 161 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 162 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 163 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 164 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 165 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 166 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 167 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 168 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 169 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 170 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 171 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 172 | 00000007700000000000000770000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 173 | 00000007770000000000000777000000000000077700000000000000000000000000000000000000000000000000000000000000000000000000000000000000 174 | 00000007770000000000000777000000000000077700000000000000000000000000000000000000000000000000000000000000000000000000000000000000 175 | 00000000700000000000000070000000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 176 | 00000007000000000000000700000000000000070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 177 | 00000000700000000000000070000000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 178 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 179 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 180 | 77777777700700777007007077777777700700707777777777777777000000000000000000000000000000000000000000000000000000000000000000000000 181 | 07007007007007077070070007007007007007000700700777007007000000000000000000000000000000000000000000000000000000000000000000000000 182 | 70070070070070077700700770070070070070077007007770070070000000000000000000000000000000000000000000000000000000000000000000000000 183 | 00700700700700777007007000700700700700700070070770700700000000000000000000000000000000000000000000000000000000000000000000000000 184 | 07007007007007077070070007007007007007000700700777007007000000000000000000000000000000000000000000000000000000000000000000000000 185 | 70070070070070077700700770070070070070077007007770070070000000000000000000000000000000000000000000000000000000000000000000000000 186 | 00700700700700777007007000700700700700700070070777700700000000000000000000000000000000000000000000000000000000000000000000000000 187 | 07007007007007077070070077777777007007007777777777777777000000000000000000000000000000000000000000000000000000000000000000000000 188 | 00770070000000000000000077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 189 | 00700700000000000000000077777000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 190 | 07007007000077000000000000700777000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 191 | 07070070000707700000000007007007777770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 192 | 07700700000770077000000070070070070077770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 193 | 77007007000700707770000000700700700700700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 194 | 70070070007007007077700007007007007007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 195 | 70700700007070070707777770070070070070070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 196 | 00080000777777770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 197 | 008a8000700000070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 198 | 00083000707077070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 199 | 00030000700777070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 200 | 00330000707770070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 201 | 00033000707707070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 202 | 00030000700000070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 203 | 00030000777777770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 204 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 205 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 206 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 207 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 208 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 209 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 210 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 211 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 212 | 00000000777777777777777700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 213 | 00000000700000000000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 214 | 00000000707770077700770700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 215 | 00000000707700777007770700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 216 | 00000000707007770077700700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 217 | 00000000700077700777000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 218 | 00000000700777007770070700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 219 | 00000000707770077700770700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 220 | 00000000707700777007770700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 221 | 00000000707007770077700700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 222 | 00000000700077700777000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 223 | 00000000700777007770070700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 224 | 00000000707770077700770700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 225 | 00000000707700777007770700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 226 | 00000000700000000000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 227 | 00000000777777777777777700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 228 | __map__ 229 | a10000000000000000000000000000a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 230 | a100000000c0000000000000000000a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 231 | a1000000a6a3a3a3a3a50000000000a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 232 | a10000000000000000000000000000a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 233 | a10000000000000000000000000000a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 234 | a10000000000000000000000000000a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 235 | a10000000000000000c00000b80000a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 236 | a1b7b7b7b7b7b7a6a3a3a3a3a3a3a3a400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 237 | a100000000000000b8000000000000a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 238 | a1a70000c000000000000000000000a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 239 | a4a3a3a3a500000000000000000000a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 240 | a10000000000000000000000000000a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 241 | a1b7000000000000b1b2a7a7a7a7a7a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 242 | a100000000000000b0a4b3b4b2a7a7a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 243 | a4a0a0a0a0a0a0a0a4a4a4a4a4a0a0a4a7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 244 | a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 245 | -------------------------------------------------------------------------------- /demo5.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 18 3 | __lua__ 4 | 5 | #include math2d.p8 6 | #include physics.p8 7 | #include broadphase.p8 8 | #include geometry.p8 9 | #include constraints.p8 10 | 11 | vp = viewport{ scale=8 } 12 | s = scene{ size=100 } 13 | 14 | function _init() 15 | local prev = s.add_body{ x=0, y=7, mass=0, moi=0, layer=2 } 16 | for i=1,8 do 17 | local link = s.add_body{ x=2*i-1, y=7, layer=(i+1)%2, verts=rectangle(2, 0.5) } 18 | s.add_constraint(joint(s, prev, i>1 and 1 or 0, 0, link, -1, 0, 0.8)) 19 | prev = link 20 | end 21 | end 22 | 23 | function _update60() 24 | s.update() 25 | end 26 | 27 | function _draw() 28 | cls() 29 | s.draw(vp) 30 | 31 | print(flr(stat(0)) .. ' kib', 5, 5, 0x7) 32 | print(flr(stat(1)*100) .. '% ' .. stat(7) .. 'fps', 5, 15, 0x7) 33 | rect(0, 0, 127, 127, 0x7) 34 | end 35 | -------------------------------------------------------------------------------- /demo6.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 18 3 | __lua__ 4 | 5 | #include math2d.p8 6 | #include physics.p8 7 | #include broadphase.p8 8 | #include geometry.p8 9 | #include forces.p8 10 | 11 | function _init() 12 | vp = viewport{} 13 | s = scene{} 14 | 15 | body = s.add_body{ x=0, y=3 } 16 | sp1 = spring{ scene=s, id1=s.add_body{ x=-3, y=3, mass=0, moi=0 }, id2=body, x2=-0.2, y2=0.2, k=10 } 17 | sp2 = spring{ scene=s, id1=s.add_body{ x=3, y=3, mass=0, moi=0 }, id2=body, x2=0.2, y2=0.2, k=10 } 18 | s.add_force(sp1) 19 | s.add_force(sp2) 20 | 21 | end 22 | 23 | function _update60() 24 | s.update() 25 | end 26 | 27 | function _draw() 28 | cls() 29 | s.draw(vp) 30 | 31 | local x1, y1, x2, y2 = sp1.anchors() 32 | x1, y1 = vp.to_screen(x1, y1) 33 | x2, y2 = vp.to_screen(x2, y2) 34 | line(x1, y1, x2, y2, 0x7) 35 | 36 | x1, y1, x2, y2 = sp2.anchors() 37 | x1, y1 = vp.to_screen(x1, y1) 38 | x2, y2 = vp.to_screen(x2, y2) 39 | line(x1, y1, x2, y2, 0x7) 40 | 41 | print(flr(stat(0)) .. ' kib', 5, 5, 0x7) 42 | print(flr(stat(1)*100) .. '% ' .. stat(7) .. 'fps', 5, 15, 0x7) 43 | rect(0, 0, 127, 127, 0x7) 44 | end 45 | -------------------------------------------------------------------------------- /demo7.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 18 3 | __lua__ 4 | 5 | #include math2d.p8 6 | #include physics.p8 7 | #include broadphase.p8 8 | #include geometry.p8 9 | #include forces.p8 10 | 11 | function _init() 12 | vp = viewport{ scale=8 } 13 | s = scene{ g=0, size=100 } 14 | 15 | vlist = { [0]=ngon(1, 3), ngon(1, 4), ngon(1, 5), ngon(1, 6), ngon(1, 7), ngon(1, 8), ngon(1, 9), ngon(1, 10) } 16 | 17 | local num, bodies = 10, {} 18 | for i=1,num do 19 | bodies[i] = s.add_body{ x=rnd(32)-16, y=rnd(32)-16, mass=rnd(50), verts=vlist[flr(rnd()*6)] } 20 | end 21 | 22 | for i=1,num do 23 | for j=i+1,num do 24 | s.add_force(gravity{ scene=s, id1=bodies[i], id2=bodies[j], g=2 }) 25 | end 26 | end 27 | end 28 | 29 | function _update60() 30 | s.update() 31 | end 32 | 33 | function _draw() 34 | cls() 35 | s.draw(vp) 36 | 37 | print(flr(stat(0)) .. ' kib', 5, 5, 0x7) 38 | print(flr(stat(1)*100) .. '% ' .. stat(7) .. 'fps', 5, 15, 0x7) 39 | rect(0, 0, 127, 127, 0x7) 40 | end 41 | -------------------------------------------------------------------------------- /demo8.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 18 3 | __lua__ 4 | 5 | #include math2d.p8 6 | #include physics.p8 7 | #include broadphase.p8 8 | #include geometry.p8 9 | #include forces.p8 10 | 11 | function _init() 12 | vp = viewport{ scale=8 } 13 | s = scene{ g=0, size=100 } 14 | 15 | bodies, trails = { 16 | s.add_body{ x=0, y=0, mass=2000, verts=ngon(1, 10) }, 17 | s.add_body{ x=0, y=4, mass=1, verts=ngon(0.5, 10) }, 18 | s.add_body{ x=5, y=0, mass=1, verts=ngon(0.5, 10) }, 19 | s.add_body{ x=0, y=-7, mass=1, verts=ngon(0.5, 10) } 20 | }, {} 21 | 22 | foreach(bodies, function(b1) 23 | trails[b1] = {} 24 | foreach(bodies, function(b2) 25 | if (b2>b1) s.add_force(gravity{ scene=s, id1=b1, id2=b2 }) 26 | end) 27 | end) 28 | 29 | s.apply_impulse(bodies[2], 20, 0, 0) 30 | s.apply_impulse(bodies[3], 0, -20, 0) 31 | s.apply_impulse(bodies[4], -18, 0, 0) 32 | end 33 | 34 | function _update60() 35 | s.update() 36 | 37 | local function extend_trail(trail, body, length) 38 | if #trail8 6 | -- spring force 7 | 8 | function spring(args) 9 | local scene, id1, x1, y1, id2, x2, y2, k, damp, 10 | px1, py1, px2, py2, vx1, vy1, vx2, vy2, dx, dy, rlen, len, f, fx, fy = 11 | args.scene, args.id1, args.x1 or 0, args.y1 or 0, 12 | args.id2, args.x2 or 0, args.y2 or 0, args.k or 0.1, args.damp or 0.1 13 | 14 | px1, py1 = transform(x1, y1, scene.position(id1)) 15 | px2, py2 = transform(x2, y2, scene.position(id2)) 16 | rlen = sqrt((px1-px2)^2+(py1-py2)^2) 17 | 18 | return { 19 | anchors=function() return px1, py1, px2, py2 end, 20 | apply=function(dt) 21 | px1, py1 = transform(x1, y1, scene.position(id1)) 22 | vx1, vy1 = scene.velocity(id1) 23 | px2, py2 = transform(x2, y2, scene.position(id2)) 24 | vx2, vy2 = scene.velocity(id2) 25 | dx, dy = px2-px1, py2-py1 26 | len = sqrt(dx^2+dy^2) 27 | dx /= len dy /= len 28 | f = k*(len-rlen) 29 | fx, fy = dx*f+(vx2-vx1)*damp, dy*f+(vy2-vy1)*damp 30 | scene.apply_force(id1, dt, fx, fy, px1, py1) 31 | scene.apply_force(id2, dt, -fx, -fy, px2, py2) 32 | end 33 | } 34 | end 35 | 36 | -->8 37 | -- gravity force 38 | 39 | function gravity(args) 40 | local scene, id1, id2, g = args.scene, args.id1, args.id2, args.g or 1 41 | g *= scene.mass(id1)*scene.mass(id2) 42 | 43 | return { 44 | apply=function(dt) 45 | local x1, y1, x2, y2, dx, dy, dist2, dist, f, fx, fy 46 | x1, y1 = scene.position(id1) 47 | x2, y2 = scene.position(id2) 48 | dx, dy = x2-x1, y2-y1 49 | dist2 = dx^2+dy^2 50 | f = g/dist2 51 | if f>0x0.001 then 52 | dist = sqrt(dist2) 53 | dx /= dist dy /= dist 54 | fx, fy = f*dx, f*dy 55 | scene.apply_force(id1, dt, fx, fy) 56 | scene.apply_force(id2, dt, -fx, -fy) 57 | end 58 | end 59 | } 60 | end 61 | -------------------------------------------------------------------------------- /geometry.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 18 3 | __lua__ 4 | -- geometry 5 | --[[ 6 | geometry for collision detection, uses SAT for collisions 7 | ]] 8 | 9 | -->8 10 | -- convex geometry collider 11 | 12 | function geometry(numv, vx, vy) 13 | local tvx, tvy, nx, ny, tnx, tny, maxp, box, x, y, len, idx = {}, {}, {}, {}, {}, {}, {}, aabb() 14 | 15 | for i=1,numv do 16 | idx, x, y = i%numv+1, vx[i], vy[i] 17 | tvx[i], tvy[i], nx[i], ny[i] = x, y, y-vy[idx], vx[idx]-x 18 | len = sqrt(nx[i]^2+ny[i]^2) 19 | nx[i] /= len ny[i] /= len 20 | end 21 | 22 | return { 23 | numv=numv, 24 | x=tvx, y=tvy, 25 | nx=tnx, ny=tny, 26 | maxp=maxp, 27 | aabb=box, 28 | collides=function(g) 29 | local function sat(nv1, x1, y1, nx1, ny1, maxp, nv2, x2, y2, nx2, ny2) 30 | local cid, dist, nrmx, nrmy, rx, ry, minp, p, idx, d, 31 | px, py, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y, p1, p2, p3, p4, 32 | lidx, ridx, ldot, rdot, ex, ey, minn, maxn, mine, maxe, alpha = -1, 0x7fff 33 | for i=1,nv1 do 34 | minp, idx = 0x7fff, -1 35 | for j=1,nv2 do 36 | p = x2[j]*nx1[i]+y2[j]*ny1[i] 37 | if (pmaxp[i]) return nil 41 | 42 | d = maxp[i]-minp 43 | if d0xffff.ffe0 and 0 or x end 14 | 15 | -- returns min and max of x, y 16 | function minmax(x, y) return min(x, y), max(x, y) end 17 | 18 | -- trig function takes angle in radians 19 | function cos_sin(a) a*=0x0.28be return cos(a), sin(-a) end 20 | 21 | -- 2d transform 22 | function transform(x, y, tx, ty, ta) 23 | local ca, sa = cos_sin(ta) 24 | return x*ca-y*sa+tx, x*sa+y*ca+ty 25 | end 26 | -------------------------------------------------------------------------------- /physics.p8: -------------------------------------------------------------------------------- 1 | pico-8 cartridge // http://www.pico-8.com 2 | version 18 3 | __lua__ 4 | -- core physics 5 | --[[ 6 | core physics code 7 | ]] 8 | 9 | -->8 10 | -- aabb 11 | 12 | function aabb(x1, y1, x2, y2) 13 | local self 14 | self = { 15 | x1=x1 or 0x7fff, y1=y1 or 0x7fff, 16 | x2=x2 or 0x8000, y2=y2 or 0x8000, 17 | set=function(x1, y1, x2, y2) 18 | self.x1, self.y1, self.x2, self.y2 = 19 | x1 or 0x7fff, y1 or 0x7fff, x2 or 0x8000, y2 or 0x8000 20 | end, 21 | overlaps=function(b) 22 | return self.x2>=b.x1 and self.x1<=b.x2 and 23 | self.y2>=b.y1 and self.y1<=b.y2 24 | end, 25 | contains=function(b) 26 | return b.x1>=self.x1 and b.y1>=self.y1 and 27 | b.x2<=self.x2 and b.y2<=self.y2 28 | end 29 | } 30 | return self 31 | end 32 | 33 | -->8 34 | -- viewport 35 | --[[ 36 | viewport allows transformations between world space (metres), and screen space (pixels) 37 | params: 38 | args - table of optional parameters 39 | ox, oy - origin in screen space 40 | scale - pixels per metre 41 | ]] 42 | 43 | function viewport(args) 44 | args = args or {} 45 | 46 | local scale = args.scale or 16 47 | local m11, m12, m13, m21, m22, m23 = 48 | scale, 0, args.ox or 64, 49 | 0, -scale, args.ox or 64 50 | 51 | return { 52 | translate=function(tx, ty) 53 | --tx/=scale ty/=scale 54 | m13, m23 = m11*tx+m12*ty+m13, m21*tx+m22*ty+m23 55 | end, 56 | scale=function(s) scale*=s m11*=s m12*=s m21*=s m22*=s end, 57 | to_screen=function(x, y) -- converts metres to pixels 58 | return m11*x+m12*y+m13, m21*x+m22*y+m23 59 | end 60 | } 61 | end 62 | 63 | -->8 64 | -- scene 65 | --[[ 66 | scene stores all information/functionality for a physics scene. 67 | measurements in a scene are in SI units, metres/kg 68 | params: 69 | args - table of optional parameters 70 | g - gravity 71 | slop - allowed overlap before collision response 72 | damp - damping 73 | isteps - solver steps 74 | sframes - frames before bodies are put to sleep 75 | beta - for baumgarte stabilisation, proportion of positional correction 76 | size - the size of the simulation area, bodies outside this region are killed 77 | ]] 78 | 79 | --[[ 80 | -- ids for events 81 | ON_COLLISION_ENTER = 0x01 82 | ON_COLLISION_EXIT = 0x02 83 | ON_BODY_SLEEP = 0x03 84 | ON_BODY_WAKE = 0x04 85 | ON_BODY_DEAD = 0x05 86 | ]]-- 87 | 88 | function scene(args) 89 | args = args or {} 90 | 91 | local size = args.size or 10 92 | local g, slop, damp, isteps, sframes, beta, cmanager, box, constraints, forces = 93 | args.g or -9.8, args.slop or 0x0.08, args.damp or 1, args.isteps or 4, args.sframes or 100, args.beta or 0.2, 94 | sweep_and_prune(), aabb(-size, -size, size, size), {}, {} 95 | local nextid, alive, dead, awake, dynamic, x, y, a, vx, vy, va, mass, imass, imoi, 96 | geom, layer, rest, frict, contact_ids, listeners, 97 | island, island_vx, island_vy, island_va, island_count, island_sframes = 98 | 1, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} 99 | 100 | local function send_messages(args) 101 | foreach(args, function(evt) 102 | if (listeners[evt.id]) listeners[evt.id].on_event(evt) 103 | end) 104 | end 105 | local function find(id) return id==island[id] and id or find(island[id]) end 106 | local function union(id1, id2) id2, id1 = minmax(find(id1), find(id2)) island[id1] = id2 end 107 | 108 | --[[ 109 | removes a body from the system 110 | params: 111 | id - id of the body 112 | ]] 113 | local function remove_body(id) 114 | send_messages{ { id=id, event=0x05 } } 115 | dead[id], alive[id], geom[id], listeners[id] = true 116 | for cid in pairs(contact_ids) do -- remove contact ids 117 | if (id==shr(band(cid, 0xff00), 8) or id==band(cid, 0xff)) contact_ids[cid] = nil 118 | end 119 | cmanager.remove_body(id) 120 | end 121 | 122 | --[[ 123 | identifies if a body is awake 124 | params: 125 | id - id of the body 126 | ]] 127 | local function is_awake(id) return dynamic[id] and awake[find(id)] end 128 | 129 | local function sleep(id) 130 | local isle = island[id] 131 | if awake[isle] then 132 | island_sframes[isle], awake[isle] = 0 133 | for id in pairs(alive) do if (island[id]==isle) send_messages{ { id=id, event=0x03 } } end 134 | end 135 | end 136 | 137 | local function wake(id) 138 | local isle = island[id] 139 | if not awake[isle] and dynamic[isle] then 140 | awake[isle] = true 141 | for id in pairs(alive) do if (island[id]==isle) send_messages{ { id=id, event=0x04 } } end 142 | end 143 | end 144 | 145 | --[[ 146 | apply a force to a body 147 | params: 148 | id - id of the body 149 | dt - time step 150 | fx, fy - force in x, y direction 151 | px, py - point at which force is applied 152 | ]] 153 | local function apply_force(id, dt, fx, fy, px, py) 154 | wake(id) 155 | px, py = px and px-x[id] or 0, py and py-y[id] or 0 156 | vx[id] += fx*imass[id]*dt 157 | vy[id] += fy*imass[id]*dt 158 | va[id] -= (fx*py-fy*px)*imoi[id]*dt 159 | return vx[id], vy[id], va[id] 160 | end 161 | 162 | --[[ 163 | apply an impulse to a body 164 | params: 165 | id - id of the body 166 | px, py, pa - x, y, angular components of the impulse 167 | ]] 168 | local function apply_impulse(id, px, py, pa) 169 | wake(id) 170 | vx[id] += px*imass[id] 171 | vy[id] += py*imass[id] 172 | va[id] += pa*imoi[id] 173 | return vx[id], vy[id], va[id] 174 | end 175 | 176 | -- contact solver 177 | local function contact(id1, id2, nx, ny) 178 | local lambdan, lambdat, r, f, jn3, jn6, jmn, b, jt3, jt6, jmt = 179 | 0, 0, 0.5*(rest[id1]+rest[id2]), sqrt(frict[id1]*frict[id2]) 180 | 181 | return { 182 | eval=function(dt, dist, px1, py1, px2, py2) 183 | local rx1, ry1, rx2, ry2, vx1, vy1, va1, vx2, vy2, va2, imass1, imoi1, imass2, imoi2 = 184 | zero(px1-x[id1]), zero(py1-y[id1]), zero(px2-x[id2]), zero(py2-y[id2]), 185 | vx[id1], vy[id1], va[id1], vx[id2], vy[id2], va[id2], 186 | imass[id1], imoi[id1], imass[id2], imoi[id2] 187 | 188 | jn3, jn6 = rx1*ny-ry1*nx, ry2*nx-rx2*ny 189 | 190 | if (nx*vx1+ny*vy1+jn3*va1-nx*vx2-ny*vy2+jn6*va2>=0) return false 191 | 192 | -- warmstart 193 | if abs(lambdan)+abs(lambdat)>0 then 194 | local px, py = nx*lambdan-ny*lambdat, ny*lambdan+nx*lambdat 195 | vx1, vy1, va1 = apply_impulse(id1, px, py, rx1*py-ry1*px) 196 | vx2, vy2, va2 = apply_impulse(id2, -px, -py, ry2*px-rx2*py) 197 | end 198 | 199 | local nx2, ny2 = nx^2, ny^2 200 | jmn, b = nx2*imass1+ny2*imass1+jn3*imoi1*jn3+nx2*imass2+ny2*imass2+jn6*imoi2*jn6, 201 | -(beta/dt)*dist+r*min(0, ((vx1-py1*va1)-(vx2-py2*va2))*nx+ 202 | ((vy1+px1*va1)-(vy2-px2*va2))*ny) 203 | 204 | jt3, jt6 = rx1*nx+ry1*ny, -(rx2*nx+ry2*ny) 205 | jmt = ny2*imass1+nx2*imass1+jt3*imoi1*jt3+ny2*imass2+nx2*imass2+jt6*imoi2*jt6 206 | 207 | return true 208 | end, 209 | solve=function() 210 | local vx1, vy1, va1, vx2, vy2, va2, deln, delt = 211 | vx[id1], vy[id1], va[id1], vx[id2], vy[id2], va[id2] 212 | 213 | deln = max(lambdan-(nx*vx1+ny*vy1+jn3*va1-nx*vx2-ny*vy2+jn6*va2+b)/jmn, 0)-lambdan 214 | lambdan += deln 215 | 216 | delt = mid(-f*lambdan, lambdat-(-ny*vx1+nx*vy1+jt3*va1+ny*vx2-nx*vy2+jt6*va2)/jmt, f*lambdan)-lambdat 217 | lambdat += delt 218 | 219 | apply_impulse(id1, deln*nx-delt*ny, deln*ny+delt*nx, deln*jn3+delt*jt3) 220 | apply_impulse(id2, delt*ny-deln*nx, -deln*ny-delt*nx, deln*jn6+delt*jt6) 221 | 222 | return deln^2+delt^2<0x0.001 223 | end 224 | } 225 | end 226 | 227 | local contacts, prev_contacts = {}, {} 228 | 229 | return { 230 | --[[ 231 | adds a body to the system 232 | params: 233 | args - table of optional parameters 234 | x, y, a - position and rotation of the body 235 | mass - mass of the body, 0 indicates infinite mass 236 | moi - inertia of the body, 0 indicates infinite inertia 237 | rest - restitution 238 | frict - friction 239 | layer - which layer/layers the object is present in 240 | verts - table containing geometry 241 | listener - table containing an on_event(args) method for events 242 | ]] 243 | add_body=function(args) 244 | args = args or {} 245 | 246 | local id = next(dead) 247 | if (id) dead[id] = nil else id = nextid nextid += 1 248 | 249 | local verts = args.verts and args.verts or rectangle(1, 1) 250 | mass[id], geom[id] = args.mass or 1, geometry(verts.numv, verts.x, verts.y) 251 | local moi = args.moi 252 | 253 | if not moi then 254 | moi = 0 255 | if mass[id]>0 then 256 | local nv, vx, vy = geom[id].numv 257 | for i=1,nv do 258 | vx, vy = geom[id].x[i], geom[id].y[i] 259 | moi += vx^2+vy^2 260 | end 261 | moi *= mass[id]/nv 262 | end 263 | end 264 | 265 | dynamic[id] = mass[id]>0 or moi>0 266 | 267 | alive[id], awake[id], island[id], island_sframes[id], 268 | x[id], y[id], a[id], vx[id], vy[id], va[id], 269 | imass[id], imoi[id], layer[id], rest[id], frict[id], listeners[id] = 270 | true, dynamic[id] or nil, id, 0, 271 | args.x or 0, args.y or 0, args.a or 0, 0, 0, 0, 272 | mass[id]>0 and 1/mass[id] or 0, moi>0 and 1/moi or 0, 273 | args.layer or 255, args.rest or 0.1, args.frict or 1, 274 | args.listener or nil 275 | 276 | geom[id].transform(x[id], y[id], a[id]) 277 | cmanager.add_body(id, geom[id].aabb) 278 | 279 | return id 280 | end, 281 | remove_body=remove_body, 282 | position=function(id) return x[id], y[id], a[id] end, -- returns position/angle of a body 283 | velocity=function(id) return vx[id], vy[id], va[id] end, -- returns velocity of a body 284 | mass=function(id) return mass[id] end, 285 | inv_mass=function(id) return imass[id], imoi[id] end, -- returns inverse mass/inertia of a body 286 | is_dynamic=function(id) return dynamic[id] end, -- returns true if the body reacts to forces 287 | add_constraint=function(c) constraints[#constraints+1] = c end, 288 | add_force=function(f) forces[#forces+1] = f end, 289 | apply_force=apply_force, 290 | apply_impulse=apply_impulse, 291 | update=function(dt) -- update function called once per frame 292 | dt = dt or 1/stat(7) 293 | local solvers, id1, id2, cid, rid, dist, nx, ny, x1, y1, x2, y2 = {} 294 | 295 | -- apply gravity and initialise islands 296 | for id in pairs(alive) do 297 | if is_awake(id) then 298 | awake[id], island[id], island_vx[id], island_vy[id], island_va[id], island_count[id] = 299 | true, id, 0, 0, 0, 0 300 | apply_force(id, dt, 0, g*mass[id]) 301 | end 302 | end 303 | 304 | -- apply forces 305 | foreach(forces, function(f) f.apply(dt) end) 306 | 307 | -- initialise constraints 308 | foreach(constraints, function(c) 309 | if(c.eval(dt)) solvers[#solvers+1] = c 310 | end) 311 | 312 | -- compute contacts, create solvers (including warmstarting) 313 | contacts, prev_contacts = prev_contacts, contacts 314 | while cmanager.has_more() do 315 | id1, id2 = cmanager.next() 316 | if band(layer[id1], layer[id2])>0 and (is_awake(id1) or is_awake(id2)) then 317 | rid, dist, nx, ny, x1, y1, x2, y2 = geom[id1].collides(geom[id2]) 318 | if rid then 319 | wake(id1) wake(id2) 320 | if (dynamic[id1] and dynamic[id2]) union(id1, id2) 321 | cid = shl(id1, 8)+id2+shr(rid, 16) 322 | contacts[cid], prev_contacts[cid] = prev_contacts[cid] 323 | if dist>slop then 324 | if not contacts[cid] then 325 | contacts[cid] = contact(id1, id2, nx, ny) 326 | if not contact_ids[cid] then 327 | contact_ids[cid] = true 328 | send_messages{ { id=id1, event=0x01, cid=cid, body=id2, x=x1, y=y1, nx=nx, ny=ny }, 329 | { id=id2, event=0x01, cid=cid, body=id1, x=x2, y=y2, nx=-nx, ny=-ny } } 330 | end 331 | end 332 | if (contacts[cid].eval(dt, dist-slop, x1, y1, x2, y2)) solvers[#solvers+1] = contacts[cid] 333 | end 334 | end 335 | end 336 | end 337 | 338 | -- free all old contacts 339 | for cid,c in pairs(prev_contacts) do 340 | id1, id2 = shr(band(cid, 0xff00), 8), band(cid, 0xff) 341 | if is_awake(id1) or is_awake(id2) then 342 | contact_ids[cid] = nil 343 | send_messages{ { id=id1, cid=cid, event=0x02, body=id2 }, 344 | { id=id2, cid=cid, event=0x02, body=id1 } } 345 | end 346 | prev_contacts[cid] = nil 347 | end 348 | 349 | -- solve all constraints 350 | for i=1,isteps do 351 | for j,s in pairs(solvers) do if (s.solve()) solvers[j] = nil end 352 | if (#solvers==0) break -- if no constraints remain end early 353 | end 354 | 355 | -- integrate velocities, transform bodies, compute island information 356 | -- remove bodies outside of simulation area 357 | for id in pairs(alive) do 358 | if is_awake(id) then 359 | local isle = find(id) 360 | awake[id], island[id] = id==isle and true or nil, isle 361 | x[id] += vx[id]*dt 362 | y[id] += vy[id]*dt 363 | a[id] += va[id]*dt 364 | island_vx[isle] += vx[id]^2 365 | island_vy[isle] += vy[id]^2 366 | island_va[isle] += va[id]^2 367 | island_count[isle] += 1 368 | vx[id] *= damp 369 | vy[id] *= damp 370 | va[id] *= damp 371 | geom[id].transform(x[id], y[id], a[id]) 372 | 373 | if (box.overlaps(geom[id].aabb)) cmanager.update_body(id, geom[id].aabb) else remove_body(id) 374 | end 375 | end 376 | 377 | -- sleep islands with low movement 378 | for id in pairs(awake) do 379 | local count = island_count[id] 380 | local ivx, ivy, iva = island_vx[id]/count, island_vy[id]/count, island_va[id]/count 381 | island_sframes[id] = ivx+ivy+iva<0x0.4 and island_sframes[id]+1 or 0 382 | if (island_sframes[id]>sframes) sleep(id) 383 | end 384 | end, 385 | draw=function(vp) 386 | for id in pairs(alive) do 387 | color(island[id]%7+8) 388 | local cx, cy = vp.to_screen(x[id], y[id]) 389 | local upy, upx = cos_sin(a[id]) 390 | upx *= 2 upy *= 2 391 | line(cx-upx, cy-upy, cx+upx, cy+upy) 392 | line(cx-upy, cy+upx, cx+upy, cy-upx) 393 | geom[id].draw(vp) 394 | end 395 | end 396 | } 397 | end 398 | 399 | -->8 400 | -- shape functions 401 | 402 | --[[ 403 | creates an ngon 404 | params: 405 | r - radius 406 | nv - num vertices 407 | sx, sy - scale in x/y directions 408 | ox, oy - offset 409 | ]] 410 | function ngon(r, nv, sx, sy, ox, oy) 411 | sx, sy, ox, oy = r*(sx or 1), r*(sy or 1), ox or 0, oy or 0 412 | local x, y, angle, da = {}, {}, (nv==4) and 0x0.c90c or 0, -0x6.487e/nv 413 | for i=1,nv do 414 | x[i], y[i] = transform(1, 0, ox, oy, angle) 415 | x[i] *= sx y[i] *= sy 416 | angle += da 417 | end 418 | return { numv=nv, x=x, y=y } 419 | end 420 | 421 | function triangle(w, h, ox, oy) return ngon(0x1.6109, 3, w/2, h/2, ox, oy) end 422 | function rectangle(w, h, ox, oy) return ngon(0x1.6109, 4, w/2, h/2, ox, oy) end 423 | 424 | function capsule(w, h, nv, ox, oy) 425 | nv += nv%2 426 | 427 | local sphere = ngon(w/2, nv, 1, 1, ox, oy) 428 | local numv, sx, sy, x, y = sphere.numv, sphere.x, sphere.y, {}, {} 429 | 430 | for i=1,nv/2+1 do x[#x+1], y[#y+1] = sx[i], sy[i]-h/2 end 431 | x[#x+1], y[#y+1] = sx[nv/2+1], sy[nv/2+1]+h/2 432 | for i=nv/2+2,nv do x[#x+1], y[#y+1] = sx[i], sy[i]+h/2 end 433 | x[#x+1], y[#y+1] = sx[1], sy[1]+h/2 434 | 435 | return { numv=nv+2, x=x, y=y } 436 | end 437 | --------------------------------------------------------------------------------