Commits

Xathos committed 98f50ff

WIP Update

Comments (0)

Files changed (14)

CollisionDetectionAlgorithm.txt

+Calculate line components:
+
+y = mx + b
+m = (v1.y - v2.y) / (v1.x - v2.x)
+=> y = (v1.y - v2.y) / (v1.x - v2.x) * x + b
+=> y * (v1.x - v2.x) - x * (v1.y - v2.y) = b / (v1.x - v2.x)
+
+y = mx + b -> v1.y = m * v1.x + b
+=> b = v1.y - m * v1.x
+=> b = v1.y - (v1.y - v2.y) / (v1.x - v2.x) * v1.x
+
+=> y*(v1.x-v2.x) - x*(v1.y-v2.y) = (v1.y-
+
+
+Given vectors v1, v2:
+cy*y + cx*x = cv
+(v1.x - v2.x)y - (v1.y - v2.y)x = v1.y*(v1.x - v2.x) - v1.x*(v1.y - v2.y)
+cy = v1.x - v2.x
+cx = v2.y - v1.y
+cv = v1.y*(v1.x - v2.x) - v1.x*(v1.y - v2.y)
+
+Collision 
+-------------------------------------------------------
+Calculate location of intersection between two lines,
+then determine if this location is within x-y bounds:
+if outside bounds, collision does not occur; if inside,
+location of collision is given by (x, y)
+-------------------------------------------------------
+a  b | x
+c  d | y
+
+1  b/a | x/a
+c  d   | y
+
+1  b/a     | x/a
+0  d-c*b/a | y-c*x/a
+
+1  b/a | x/a
+0  1   | (y-c*x/a)/(d-c*b/a)
+
+1  0 | x/a-b/a*(y-c*x/a)/(d-c*b/a)
+0  1 | (y-c*x/a)/(d-c*b/a)
      # m2.cx m2.cy | m2.cv -> c d | y
      # x = x / a - b / a * (y - c * x / a) / (d - c * b / a)
      # y = (y - c * x / a) / (d - c * b / a)
+    #print("%0.2f %0.2f | %0.2f\n%0.2f %0.2f | %0.2f"%(segment1.cx, segment1.cy,
+     #           segment1.cv, segment2.cx, segment2.cy, segment2.cv))
     if segment1.cx != 0:
          a = segment1.cx; c = segment2.cx
          b = segment1.cy; d = segment2.cy

collision.pyc

Binary file modified.

gui/KyttenMenuWrapper.py

+from Kytten import Menu
+
+def MenuWrapper (options = {}, align = None, padding = 4):
+    if align is not None:
+        menu = Menu(options.keys(), align, padding)
+    else:
+        menu = Menu(options.keys(), padding = padding)
+    def on_select (choice):
+        for name, func in options.iteritems():
+            if choice == name:
+                func(menu)
+    menu.on_select = on_select
+    return menu
+        
+
+CtWidget:
+    CtWidget parent
+    [CtWidget] children
+    Vector2 position
+    float rotation
+
+    #Layout:
+    Rect min_dimensions
+    Rect min_border
+    Rect min_margin
+    Vector2 anchor
+
+    #internal:
+    void switch_graphics_state (int state_index)
+    void draw (Rect bounds)
+    bool handle_event (Event event)
+
+# EVENT SCENARIOS:
+# Object hierarchy:
+#   Frame:
+#       Label:
+#           text
+#       TextRegion:
+#           text
+#           Scrollbar:
+#               button_up
+#               button_down
+#               scroll_bar
+#
+# User clicks scrollbar 'up' button:
+#   -Input is passed to Frame -> TextRegion (Label is out of bounds)
+#       -> Scrollbar (text is out of bounds) -> button_down (everything else
+#           is out of bounds)
+#   -button_down has an active event handler, so it handles the input and all
+#       other potential queries stop there
+#   -button_down's on_select (lmb) function sends a request to it's parent
+#       Scrollbar and tells it to scroll by x units determined by delta_time
+#       -This method in turn sends an on_scroll message to its parent (the TextRegion),
+#           which in turn changes the position of its text
+#       
+#
+#
+#
+#
+
+def CtButton (**kwargs):
+    button = CtWidget(kwargs)
+    button.on_click = None
+    button.on_
+
+    
+# Theoretical gui design:
+# One very flexible gui object, with specialist subclasses
+# This class handles input routing and generic drawing;
+# subclasses would implement specifics
+# (unfinished)
+
+
+class GuiObject (object):
+    def __init__ (self, parent, children = []):
+        self.parent = parent
+        self.children = children
+        self.bounds =
+
+    def get_dim (self):
+        content_bounds = Rect()
+        for child in self.children:
+            # inflate: expand the rect only if the passed rect is wider/higher
+            content_bounds.inflate(child.get_dim())
+        # expand: add the 2d size of rect1 and rect2
+        return content_bounds.expand(self.border).expand(self.padding)
+
+    def draw (self):
+        pass
+        #draw using layout info, content_bounds, etc., of self and children
+
+    def handle_input (self, cursor_pos, input_request):
+        # When recieving an input request (eg. click on a button; scroll on a field),
+        # input is routed through the gui 'tree'
+        # In the case that the mouse cursor is located over three objects: a button,
+        # its parent (a menu), and its parent (a layout thing), the button (topmost object)
+        # should recieve the input first; if does not handle it then input passes to the next
+        # highest object (the menu), and so on and so forth; if the input is successfully handled
+        # and resolved, then it is disgarded.
+        # Children that are in bounds are checked first, so the 'signal' travels upwards first.
+        for child in self.children:
+            if child.bounds.contains(cursor_pos):
+                if child.handle_input(cursor_pos, input_request):
+                    return True
+                    # If resolved above, return early
+        # If not, attempt to handle it here
+        if input_request == PRIMARY_MOUSE_DOWN:
+            if self.onclick is not None:
+                self.onclick(self)
+                return True
+        elif input_request == SCROLL:
+            try:
+                self.scroll(input_request)
+                return True
+            except AttributeError:
+                pass
+        # If incapable, pass it on to the next (lower) object
+        return False
+
+class GuiObject (XObject):
+    def __init__ (self, parent, children = []):
+        self.parent = parent
+        self.children = children
+        self.on_click = None
+        self.on_scroll = None
+        self.on_draw = None
+
+    def handle_input (self, handler_name, cursor_pos, **kwargs):
+        if not self.bounds.contains(cursor_pos):
+            return False
+        for child in self.children:
+            if child.handle_input(cursor_pos, handler_name, kwargs):
+                return True
+        if self.__dict__[handler_name] is not None:
+            self.__dict__[handler_name](kwargs)
+            return True
+        return False
+
+    def handle_click (self, cursor_pos, modifiers):
+        self.handle_input("on_click", cursor_pos, modifiers) 
+
+    def handle_scroll (self, cursor_pos, scroll_dir, modifiers):
+        self.handle_input("on_scroll", cursor_pos, scroll_dir, modifiers)
+
+    def get_bounds (self):
+        self.content_bounds = Rect()
+        for child in self.children:
+            content_bounds.inflate(child.calculate_bounds())
+            
+    
+    def handle_draw (self):
+        content_bounds = Rect()
+        for child in self.children:
+            content_bounds.inflate(child.calculate_bounds())
+        
+
+class GuiBox (GuiObject):
+    pass
+
+class GuiButton (GuiBox):
+    pass
+
+#Decorators:
+def add_close_button (obj):
+    obj.add_child(GuiButton(graphic = obj.theme.xbutton,
+                             position = TOP_LEFT_ABS, offset = (12, 12),
+                             on_click = lambda self: self.parent.close()))
+
+def add_horizontal_scrollbar (obj):
+    obj.add_child(Scrollbar(graphic
+        
+
+
+
+
+

gui/kytten_test.py

+import os
+import pyglet
+import kytten
+
+window = pyglet.window.Window()
+
+theme = kytten.Theme(os.path.join(os.getcwd(), 'theme'))
+
+batch = pyglet.graphics.Batch()
+fg_group = pyglet.graphics.OrderedGroup(1)
+
+@window.event
+def on_draw ():
+    window.clear()
+    batch.draw()
+
+window.register_event_type('on_update')
+def update(dt):
+    window.dispatch_event('on_update', dt)
+pyglet.clock.schedule(update, 1.0 / 24.0)
+
+dialog = kytten.Dialog(
+    kitten.TitleFrame("Kytten Test",
+        kytten.VerticalLayout([
+            kytten.Label("<Label>"),
+            kytten.Checkbox("Enable x", id = 'x'),
+            kytten.Checkbox("Enable y", id = 'y')
+        ]),
+    ),
+    window = window, batch = batch, group = fg_group,
+    anchor = kytten.ANCHOR_TOP_LEFT,
+    theme = theme)
+
+pyglet.app.run()

prototype2/prototype2.py

 # v: Turn visualization on
 # o: Turn visualization off (for stress testing)
 
-window = pyglet.window.Window()
+window = pyglet.window.Window(800, 700)
 keys = key.KeyStateHandler()
 window.push_handlers(keys)
 world = PhysicsWorld()

prototype2/stress_test.py

                 ('v2f', (ray_hit.x - 5, ray_hit.y, ray_hit.x + 5, ray_hit.y)))
         pyglet.graphics.draw(2, GL_LINES,
                 ('v2f', (ray_hit.x, ray_hit.y - 5, ray_hit.x, ray_hit.y + 5)))
-            
+
 def update (dt):
     global ray, draw_bounds, ray_hit, viz_on
 

prototype3/prototype3.py

+import sys
+sys.path.append("..")
+from vector2 import Vector2
+from math import sqrt
+from temp import Polygon, GameObject, PhysicsWorld
+from collision import LineSegment, detect_line_collision
+import pyglet
+from pyglet.gl import *
+from pyglet.window import key
+
+window = pyglet.window.Window()
+keys = key.KeyStateHandler()
+window.push_handlers(keys)
+world = PhysicsWorld()
+
+cursor_position = Vector2()
+obj = GameObject()
+world.add(obj)
+
+@window.event
+def on_mouse_motion (x, y, dx, dy):
+    global cursor_position
+    cursor_position = Vector2(x, y)
+
+@window.event
+def on_draw ():
+#    redraw()
+    pass
+
+def redraw ():
+    window.clear()
+    world.debug_draw(False)
+
+def update (dt):
+    if keys[key.D]:
+        #Only add a point if the distance between cursor_position and the last added
+        #point is greater than an arbitrary number (10, in this case)
+        if len(obj.geometry.points) > 0:
+            last_point = obj.geometry.points[len(obj.geometry.points) - 1]
+            dist = sqrt((last_point.x - cursor_position.x) ** 2 + (last_point.y - cursor_position.y) ** 2)
+        else:
+            dist = 100
+        if dist > 10:
+            obj.geometry.points += [cursor_position]
+            obj.geometry.rebuild()
+            redraw()
+
+    if keys[key.C]:
+        obj.geometry.points = []
+        obj.geometry.rebuild()
+        redraw()
+
+redraw()
+pyglet.clock.schedule_interval(update, 1.0 / 24.0)
+pyglet.app.run()

prototype3/temp.py

+import sys
+sys.path.append("..")
+from vector2 import Vector2
+from math import sqrt, sin, cos, asin, pi
+from collision import detect_line_collision
+import pyglet
+from pyglet.gl import *
+
+
+class Line (object):
+    def __init__ (self, v1, v2):
+        # Two vector points define the line segment:
+        self.v1 = v1
+        self.v2 = v2
+        # The following is used internally:
+        self.local_v = v2 - v1
+        self.normal = Vector2(-self.local_v.y, self.local_v.x)
+        # cx, cy, and cv represent entries in an augumented matrix
+        # (Line stands in for a matrix row)
+        self.cx = -1 * (v1.y - v2.y)
+        self.cy = v1.x - v2.x
+        self.cv = v1.y * (v1.x - v2.x) - v1.x * (v1.y - v2.y)
+        # xbounds and ybounds define the limit of the line segment.
+        if v1.x < v2.x:
+            self.xbounds = (v1.x, v2.x)
+        else:
+            self.xbounds = (v2.x, v1.x)
+        if v1.y < v2.y:
+            self.ybounds = (v1.y, v2.y)
+        else:
+            self.ybounds = (v2.y, v1.y)
+
+    def point_at_x (self, x):
+        y = (self.cv - self.cx * x) / self.cy
+        if x < self.xbounds[0] or x > self.xbounds[1]:
+            return None, Vector2(x, y)
+        if y < self.ybounds[0] or y > self.ybounds[1]:
+            return None, Vector2(x, y)
+        return Vector2(x, y)
+
+    def point_at_y (self, y):
+        x = (self.cv - self.cy * y) / self.cx
+        if x < self.xbounds[0] or x > self.xbounds[1]:
+            return None, Vector2(x, y)
+        if y < self.ybounds[0] or y > self.ybounds[1]:
+            return None, Vector2(x, y)
+        return Vector2(x, y)
+
+    def __repr__ (self):
+        return "Line(%s, %s)"%(repr(self.v1), repr(self.v2))
+
+    def __str__ (self):
+        return "(%s, %s)\n%0.2f %0.2f | %0.2f\nx bounds: %s\ny bounds: %s"%(self.v1,
+                                self.v2, self.cx, self.cy, self.cv, self.xbounds, self.ybounds)
+
+
+class GameObject (object):
+    def __init__ (self, position = None, rotation = 0.0):
+        if position is None:
+            self.position = Vector2(0, 0)
+        else:
+            self.position = position
+        self.rotation = rotation
+        self.geometry = Polygon(self)
+        self.move_vector = Vector2()
+
+    def debug_draw (self, draw_bounds = False):
+        self.geometry.debug_draw(draw_bounds)
+
+        #draw movement vector
+        pyglet.graphics.draw(2, GL_LINES,
+                    ('v2f', (self.position.x, self.position.y, self.position.x + self.move_vector.x,
+                             self.position.y + self.move_vector.y)))
+            
+class Polygon (object):
+    def __init__ (self, parent, points = []):
+        self.parent = parent
+        self.points = points
+        self.rebuild()
+
+    def rebuild (self):
+        self.lines = []
+        if len(self.points) == 0:
+            self.xbounds = [self.parent.position.x, self.parent.position.x]
+            self.ybounds = [self.parent.position.y, self.parent.position.y]
+            return
+        self.xbounds = [self.points[0].x, self.points[0].x]
+        self.ybounds = [self.points[0].y, self.points[0].y]
+        j = len(self.points) - 1
+        for i in range(len(self.points)):
+            self.lines += [Line(self.points[j], self.points[i])]
+            if self.points[i].x > self.xbounds[1]:
+                self.xbounds[1] = self.points[i].x
+            elif self.points[i].x < self.xbounds[0]:
+                self.xbounds[0] = self.points[i].x
+            if self.points[i].y > self.ybounds[1]:
+                self.ybounds[1] = self.points[i].y
+            elif self.points[i].y < self.ybounds[0]:
+                self.ybounds[0] = self.points[i].y
+            j = i
+
+    def debug_draw (self, draw_bounds = False):
+        #Draw polygon bounds
+        if draw_bounds:
+            glColor4f(0.0, 0.5, 0.5, 1.0)
+            pyglet.graphics.draw(2, GL_LINES,
+                    ('v2f', (self.xbounds[0], self.ybounds[0], self.xbounds[1], self.ybounds[0])))
+            pyglet.graphics.draw(2, GL_LINES,
+                    ('v2f', (self.xbounds[1], self.ybounds[0], self.xbounds[1], self.ybounds[1])))
+            pyglet.graphics.draw(2, GL_LINES,
+                    ('v2f', (self.xbounds[1], self.ybounds[1], self.xbounds[0], self.ybounds[1])))
+            pyglet.graphics.draw(2, GL_LINES,
+                    ('v2f', (self.xbounds[0], self.ybounds[1], self.xbounds[0], self.ybounds[0])))
+        glColor4f(1.0, 1.0, 0.0, 1.0)
+        for line in self.lines:
+            #Draw Line bounds
+            if draw_bounds:
+                glColor4f(0.2, 0.2, 0.0, 1.0)
+                pyglet.graphics.draw(2, GL_LINES,
+                        ('v2f', (line.xbounds[0], line.ybounds[0], line.xbounds[1], line.ybounds[0])))
+                pyglet.graphics.draw(2, GL_LINES,
+                        ('v2f', (line.xbounds[1], line.ybounds[0], line.xbounds[1], line.ybounds[1])))
+                pyglet.graphics.draw(2, GL_LINES,
+                        ('v2f', (line.xbounds[1], line.ybounds[1], line.xbounds[0], line.ybounds[1])))
+                pyglet.graphics.draw(2, GL_LINES,
+                        ('v2f', (line.xbounds[0], line.ybounds[1], line.xbounds[0], line.ybounds[0])))
+                glColor4f(1.0, 1.0, 0.0, 1.0)
+            #Draw Line
+            pyglet.graphics.draw(2, GL_LINES,
+                    ('v2f', (line.v1.x, line.v1.y, line.v2.x, line.v2.y)))
+            #Draw Normal
+            glColor4f(0.5, 0.0, 0.5, 1.0)
+            point = (line.v1 + line.v2) / 2
+            pyglet.graphics.draw(2, GL_LINES,
+                    ('v2f', (point.x, point.y, line.normal.x + point.x, line.normal.y + point.y)))
+            glColor4f(1.0, 1.0, 0.0, 1.0)
+            
+
+def line_overlap (line1, line2):
+    if line1.xbounds[0] > line2.xbounds[1] or line1.xbounds[1] < line2.xbounds[0]:
+        return False
+    if line1.ybounds[0] > line2.ybounds[1] or line1.ybounds[1] < line2.ybounds[0]:
+        return False
+    return True
+
+class PhysicsWorld (object):
+    def __init__ (self):
+        self.objs = []
+
+    def add (self, obj):
+        if type(obj) != GameObject:
+            raise TypeError, "Only GameObjects can be added to a PhysicsWorld"
+        self.objs += [obj]
+
+    def debug_draw (self, draw_bounds = False):
+        for obj in self.objs:
+            obj.debug_draw(draw_bounds)
+
+    def raycast_return_all (self, ray, debug = False):
+        hits = []
+        if debug:
+            print("Raycasting:\n%s"%ray)
+        calcs = 0
+        for obj in self.objs:
+           # if not obj.geometry.overlap_with_line(ray):
+           #     continue
+            for line in obj.geometry.lines:
+                if not line_overlap(line, ray):
+                    continue
+                calcs += 1
+                hit = detect_line_collision(ray, line)
+                if hit is not None:
+                    hits += [hit]
+        if debug:
+           # print("Found %i points of intersection after %i calculations:\n%s"%(len(hits), calcs, hits))
+           print("Found %i points of intersection after %i calculations:\n"%(len(hits), calcs))
+        return hits
+    
+    def raycast (self, ray, debug = False):
+        return self.raycast_cull(ray, self.raycast_return_all(ray, debug))
+
+    def raycast_cull (self, ray, hits):
+        closest = None
+        dist = 100000.0
+        for point in hits:
+            d = sqrt((point.x - ray.v1.x) ** 2 + (point.y - ray.v1.y) ** 2)
+            if d < dist:
+                closest = point
+                dist = d
+        return closest
+
+
+
+        
+        
     def __init__ (self, x = 0.0, y = 0.0):
         self.x = float(x)
         self.y = float(y)
-
+    
     def __str__ (self):
         return "(%0.2f, %0.2f)"%(self.x, self.y)
 
Binary file modified.