Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example of concave polys and their physics #231

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/17-concave-phys-objects/.vewpostactivate
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kh2
163 changes: 163 additions & 0 deletions examples/17-concave-phys-objects/concave2convex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
from itertools import cycle
import json
import os
import signal

from kivy.logger import Logger
from kivy.vector import Vector
from Polygon import Polygon

#from debugdraw import gv

ZERO = 0.0001
def azero(f):
""" almost zero """
return abs(f) < ZERO

def cross(v1, v2):
return v1.x*v2.y - v1.y*v2.x

def area(verts):
area = 0.0
for v1,v2,v3 in zip(verts, verts[1:] + verts[:1], verts[2:] + verts[:2]):
vec1 = Vector(v2) - Vector(v1)
vec2 = Vector(v3) - Vector(v2)

area += cross(vec1, vec2)
return area

def fix_winding(verts):

poly_area = area(verts)

if poly_area < 0:
return verts
else:
return verts[::-1]

def simplify_poly(poly):
""" sometimes, poly IS convex, but cymunk/chipmunk claims that is not. It's
when eg. v1 x v2 are negative (then clockwise), but so small that chipmunk
probably calculates it differently, treat them as
counterclockwise. In such case, such segments should be joined into
one.
"""

simp_needed = True
verts = poly[:]
while simp_needed:
simp_needed = False
for v1,v2,v3 in zip(verts, verts[1:] + verts[:1], verts[2:] + verts[:2]):
vec1 = Vector(v2) - Vector(v1)
vec2 = Vector(v3) - Vector(v2)

v1x2 = cross(vec1, vec2)
if abs(v1x2) < ZERO: #almost parallel, simplify to to one
simp_needed = True
verts.remove(v2) #put middle vertex in trash
continue

return verts




def is_convex(verts):
for v1,v2,v3 in zip(verts, verts[1:] + verts[:1], verts[2:] + verts[:2]):
vec1 = Vector(v2) - Vector(v1)
vec2 = Vector(v3) - Vector(v2)

cr = cross(vec1, vec2)
if azero(cr) and vec1.dot(vec2) < 0:
return False
if cross(vec1, vec2) > - ZERO:
return False

return True


def add_polys(ppoly, triangle):
if not ppoly:
return triangle
poly = ppoly[:]
adjacent = False
for v1,v2,v3 in zip(triangle, triangle[1:] + triangle[:1], triangle[2:] + triangle[:2]):
if not (v1 in poly and v2 in poly):
continue
adjacent = True
v1idx = poly.index(v1)
if v1idx + 1 < len(poly) and poly[v1idx + 1] == v2:
poly[v1idx + 1:v1idx + 1] = [v3]
break
elif v1idx > 0 and poly[v1idx -1] == v2:
poly[v1idx: v1idx] = [v3]
break
else:
return None
if not adjacent:
return None
poly = simplify_poly(poly)
return poly

def calc_triangles(poly):
opoly = Polygon(poly)
for trisentry in opoly.triStrip():
for triangle in zip(trisentry[:-2], trisentry[1:-1], trisentry[2:]):
yield fix_winding(list(triangle))



def merge_triangles(poly, min_area=None):
#if is concave, do nothing, just return poly
if is_convex(poly):
yield poly
return
triangles = list(calc_triangles(poly))
#gv(triangles)
remaining = []
while triangles:
piece = []
for i, triangle in enumerate(triangles):
if not is_convex(triangle):
continue
print "processing triangle#%s"%i
cand = add_polys(piece, triangle)

if cand != None and is_convex(cand):
piece = cand
continue
remaining.append(triangle)
#gv(triangles, [piece], [triangle], [triangle, piece], remaining)
assert piece
assert is_convex(piece)
assert area(piece) != 0
yield fix_winding(piece)
piece = []
triangles = remaining
remaining = []


def cached_mtr(path, cache_dir=".convexpolys", **kwargs):
pathhash = str(hash(tuple(path + kwargs.items())))
fname = os.path.join(cache_dir, pathhash)
try:
fp = open(fname, "r")
return json.load(fp)
except IOError:
try:
os.makedirs(cache_dir)
except OSError:
if not os.path.isdir(cache_dir):
raise
fp = open(fname, "w")
ret = list(merge_triangles(path, **kwargs))
json.dump(ret, fp)
fp.close()
return ret







50 changes: 50 additions & 0 deletions examples/17-concave-phys-objects/debugdraw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import itertools
from math import ceil, sqrt
import os


def gv(*groups, **kwargs):
pdf = kwargs.get("pdf", True)
single = kwargs.get("single", False)
closed = kwargs.get("closed", True)
f = open("/tmp/ee.plt", "w")

minr, maxr = 999999, -9999999
for group in groups:
for path in group:
for vert in path:
for coord in vert:
minr = min(minr, coord)
maxr = max(maxr, coord)

numplots = len(groups)
inrow = int(ceil(sqrt(numplots)))
numrows = int(ceil(float(numplots)/inrow))

if pdf:
f.write('set terminal pdfcairo font "arial, 9" size 30cm,30cm \n')
f.write('set output "/tmp/ee.pdf"\n')
f.write("set multiplot layout %s, %s\n"%(numrows, inrow))
f.write("set size ratio -1\n")
f.write("set xrange[%s:%s]\n"%(minr-1, maxr+1))
f.write("set yrange[%s:%s]\n"%(minr-1, maxr+1))
for group in groups:
f.write("plot '-' u 1:2:3:4 w vectors\n")

for vs in group:
for (fx, fy),(tx, ty) in (zip(vs, vs[1:] + vs[:1]) if closed else zip(vs[:-1], vs[1:])):
f.write("%s %s %s %s\n"%(fx, fy, (tx-fx), (ty-fy)))
f.write("e\n")

f.close()
os.system("gnuplot /tmp/ee.plt -persist")


if __name__ == '__main__':
gv([[(-85.18263244628906, -194.4100341796875), (48.45586013793945, -193.8280029296875), (45.539608954794595, -194.4100341796875), (1.3924713134765625, -203.22100830078125)],
[(-80, -80), (-80, 80), (80, 80), (80, -80)]],
[
[(1,2), (2,3), (3,4), (4,5)],
[]
]
, closed=False)
150 changes: 150 additions & 0 deletions examples/17-concave-phys-objects/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
from functools import partial
from os.path import dirname, join, abspath
from random import randint, choice
import signal
from math import radians, pi, sin, cos

from kivy.app import App
from kivy.logger import Logger
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.vector import Vector
import kivent_core
import kivent_cymunk
from kivent_core.gameworld import GameWorld
from kivent_core.managers.resource_managers import texture_manager

from kivent_core.rendering.svg_loader import SVGModelInfo
from kivent_core.systems.renderers import RotateRenderer
from kivent_core.systems.position_systems import PositionSystem2D
from kivent_core.systems.rotate_systems import RotateSystem2D
from kivent_cymunk.interaction import CymunkTouchSystem
from kivy.properties import StringProperty, NumericProperty

from concave2convex import merge_triangles, cached_mtr
from debugdraw import gv

texture_manager.load_atlas(join(dirname(dirname(abspath(__file__))), 'assets',
'background_objects.atlas'))



class TestGame(Widget):
def __init__(self, **kwargs):
super(TestGame, self).__init__(**kwargs)
self.gameworld.init_gameworld(
['cymunk_physics', 'poly_renderer', 'rotate', 'position', 'cymunk_touch' ],
callback=self.init_game)

def init_game(self):
self.setup_states()
self.draw_some_stuff()
self.set_state()

def destroy_created_entity(self, ent_id, dt):
self.gameworld.remove_entity(ent_id)
self.app.count -= 1

def draw_some_stuff(self):
self.gameworld.clear_entities()

self.load_svg('objects.svg', self.gameworld)

def load_svg(self, fname, gameworld):
mm = gameworld.model_manager
data = mm.get_model_info_for_svg(fname)

posvel = {
'spiral': ((300, 300), (0, 0)),
'ball': ((600, 130), (-800, 0))
}

for info in data['model_info']:

pos, vel = posvel[info.element_id]

Logger.debug("adding object with title/element_id=%s/%s and desc=%s", info.title, info.element_id, info.description)
model_name = mm.load_model_from_model_info(info, data['svg_name'])

shapeno = 0
shapes = []
for poly in cached_mtr(info.path_vertices):
#for poly in merge_triangles(info.path_vertices):

shape = {
'shape_type': 'poly',
'elasticity': 0.8,
'collision_type': 1,
'friction': 0.1,
'shape_info': {
'mass': 50,
'offset': (0, 0),
'vertices': poly
}

}
Logger.debug("shape %s added", shapeno)
shapeno += 1
shapes.append(shape)

#shapepolys = [x['shape_info']['vertices'] for x in shapes]
#gv(shapepolys, pdf=False)



physics = {
'main_shape': 'poly',
'velocity': vel,
'position': pos,
'angle': 0,
'angular_velocity': radians(0),
'ang_vel_limit': radians(0),
'mass': 0 if info.element_id == 'spiral' else 50,
'col_shapes': shapes
}

create_dict = {
'position': pos,
'poly_renderer': {'model_key': model_name},
'cymunk_physics': physics,
'rotate': radians(0),
}

#need to pause it a bit
Clock.schedule_once(partial(self.init_entity, create_dict))
self.app.count += 1

def init_entity(self, create_dict, dt):
self.gameworld.init_entity(create_dict, ['position', 'rotate', 'poly_renderer', 'cymunk_physics'])

def update(self, dt):
self.gameworld.update(dt)

def setup_states(self):
self.gameworld.add_state(state_name='main',
systems_added=['poly_renderer', 'cymunk_physics'],
systems_removed=[], systems_paused=[],
systems_unpaused=['poly_renderer', 'cymunk_physics'],
screenmanager_screen='main')

def set_state(self):
self.gameworld.state = 'main'


class DebugPanel(Widget):
fps = StringProperty(None)

def __init__(self, **kwargs):
super(DebugPanel, self).__init__(**kwargs)
Clock.schedule_once(self.update_fps)

def update_fps(self,dt):
self.fps = str(int(Clock.get_fps()))
Clock.schedule_once(self.update_fps, .05)

class YourAppNameApp(App):
count = NumericProperty(0)

if __name__ == '__main__':
YourAppNameApp().run()
Loading