├── pyscumm-engine ├── Item1.png ├── Item2.png ├── Background1.png ├── pyscumm │ ├── artwork │ │ ├── efmi.ttf │ │ ├── cursor.png │ │ └── pyscumm_logo.tga │ ├── __init__.py │ ├── sdl.py │ ├── obj.py │ ├── constant.py │ ├── room.py │ ├── box.py │ ├── base.py │ ├── engine.py │ ├── driver.py │ └── euclid.py ├── .pydevproject ├── .project └── demo.py └── demo.py /pyscumm-engine/Item1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyscumm/master/pyscumm-engine/Item1.png -------------------------------------------------------------------------------- /pyscumm-engine/Item2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyscumm/master/pyscumm-engine/Item2.png -------------------------------------------------------------------------------- /pyscumm-engine/Background1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyscumm/master/pyscumm-engine/Background1.png -------------------------------------------------------------------------------- /pyscumm-engine/pyscumm/artwork/efmi.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyscumm/master/pyscumm-engine/pyscumm/artwork/efmi.ttf -------------------------------------------------------------------------------- /pyscumm-engine/pyscumm/artwork/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyscumm/master/pyscumm-engine/pyscumm/artwork/cursor.png -------------------------------------------------------------------------------- /pyscumm-engine/pyscumm/artwork/pyscumm_logo.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyscumm/master/pyscumm-engine/pyscumm/artwork/pyscumm_logo.tga -------------------------------------------------------------------------------- /pyscumm-engine/pyscumm/__init__.py: -------------------------------------------------------------------------------- 1 | from .room import Room 2 | from .engine import engine 3 | from .obj import Item 4 | from .constant import * 5 | from .base import resource 6 | 7 | # Third's 8 | from .euclid import Vector3 9 | 10 | -------------------------------------------------------------------------------- /pyscumm-engine/.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | python 2.5 6 | 7 | /pyscumm-engine/pyscumm 8 | 9 | 10 | -------------------------------------------------------------------------------- /pyscumm-engine/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | pyscumm-engine 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /pyscumm-engine/pyscumm/sdl.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from . import euclid 3 | from .euclid import Vector2 4 | 5 | 6 | class Drawable(object): 7 | 8 | def __init__(self): 9 | self._image = None 10 | self._image_bck = None 11 | self.screen_position = Vector2(0, 0) 12 | 13 | def set_image(self, img): 14 | self._image = img 15 | self._image_bck = img.copy() 16 | self._collider = self._image.get_rect(topleft=self.screen_position) # Dont apply the topleft!! bug 17 | 18 | def get_image(self): 19 | return self._image 20 | 21 | def scale(self, value): 22 | # NEED ALSO SCALE THE RECT !! 23 | self._image = pygame.transform.rotozoom(self._image_bck, 0, value) 24 | 25 | def rotate(self, angle): 26 | # NEED ALSO ROTATE THE RECT !! 27 | self._image = pygame.transform.rotate(self._image_bck, angle) 28 | 29 | def draw(self): 30 | pygame.display.get_surface().blit(self._image, self.screen_position) 31 | 32 | image = property(get_image, set_image) -------------------------------------------------------------------------------- /pyscumm-engine/pyscumm/obj.py: -------------------------------------------------------------------------------- 1 | from .sdl import Drawable 2 | from .base import debugger 3 | from .engine import engine 4 | from .euclid import Vector3 5 | 6 | 7 | class BaseObject(object): 8 | 9 | def __init__(self): 10 | self._collider = None 11 | self.position = Vector3(0, 0, 0) 12 | self.description = 'NotDescripted' 13 | 14 | def update(self): 15 | # Update the Drawable screen_position with the camera position of the room 16 | camera = engine.room.camera 17 | self.screen_position = self.position - (camera.position.x, camera.position.y, 0) 18 | self.screen_position = self.screen_position[:2] 19 | self._collider.topleft = self.screen_position 20 | 21 | def get_collider(self): 22 | return self._collider 23 | 24 | collider = property(get_collider, None) 25 | 26 | 27 | 28 | class Item(Drawable, BaseObject): 29 | 30 | def __init__(self): 31 | Drawable.__init__(self) 32 | BaseObject.__init__(self) 33 | 34 | def on_look(self): 35 | debugger.warn('on_look() method called. NotImplemented') 36 | 37 | def on_get(self): 38 | debugger.warn('on_get() method called. NotImplemented') 39 | 40 | def on_talk(self): 41 | debugger.warn('on_talk() method called. NotImplemented') -------------------------------------------------------------------------------- /pyscumm-engine/pyscumm/constant.py: -------------------------------------------------------------------------------- 1 | from pygame.constants import KMOD_ALT, KMOD_CAPS, KMOD_CTRL, KMOD_LALT, KMOD_LCTRL, KMOD_LMETA, \ 2 | KMOD_LSHIFT, KMOD_META, KMOD_MODE, KMOD_NONE, KMOD_NUM, KMOD_RALT, \ 3 | KMOD_RCTRL, KMOD_RMETA, KMOD_RSHIFT, KMOD_SHIFT, K_0, K_1, K_2, K_3, \ 4 | K_4, K_5, K_6, K_7, K_8, K_9, K_AMPERSAND, K_ASTERISK, K_AT, K_BACKQUOTE, \ 5 | K_BACKSLASH, K_BACKSPACE, K_BREAK, K_CAPSLOCK, K_CARET, K_CLEAR, K_COLON, \ 6 | K_COMMA, K_DELETE, K_DOLLAR, K_DOWN, K_END, K_EQUALS, K_ESCAPE, K_EURO, \ 7 | K_EXCLAIM, K_F1, K_F10, K_F11, K_F12, K_F13, K_F14, K_F15, K_F2, K_F3, K_F4, \ 8 | K_F5, K_F6, K_F7, K_F8, K_F9, K_GREATER, K_HASH, K_HELP, K_HOME, \ 9 | K_INSERT, K_KP0, K_KP1, K_KP2, K_KP3, K_KP4, K_KP5, K_KP6, K_KP7, K_KP8, \ 10 | K_KP9, K_KP_DIVIDE, K_KP_ENTER, K_KP_EQUALS, K_KP_MINUS, K_KP_MULTIPLY, \ 11 | K_KP_PERIOD, K_KP_PLUS, K_LALT, K_LCTRL, K_LEFT, K_LEFTBRACKET, \ 12 | K_LEFTPAREN, K_LESS, K_LMETA, K_LSHIFT, K_LSUPER, K_MENU, K_MINUS, K_MODE, \ 13 | K_NUMLOCK, K_PAGEDOWN, K_PAGEUP, K_PAUSE, K_PERIOD, K_PLUS, K_POWER, \ 14 | K_PRINT, K_QUESTION, K_QUOTE, K_QUOTEDBL, K_RALT, K_RCTRL, K_RETURN, K_RIGHT, \ 15 | K_RIGHTBRACKET, K_RIGHTPAREN, K_RMETA, K_RSHIFT, K_RSUPER, K_SCROLLOCK, \ 16 | K_SEMICOLON, K_SLASH, K_SPACE, K_SYSREQ, K_TAB, K_UNDERSCORE, K_UNKNOWN, \ 17 | K_UP, K_a, K_b, K_c, K_d, K_e, K_f, K_g, K_h, K_i, K_j, K_k, K_l, K_m, K_n, \ 18 | K_o, K_p, K_q, K_r, K_s, K_t, K_u, K_v, K_w, K_x, K_y, K_z 19 | 20 | MOUSE_MOTION = 1 21 | DOUBLE_CLICK = 2 22 | SINGLE_CLICK = 3 23 | KEY_DOWN = 4 24 | KEY_UP = 5 25 | DRAG_START = 6 26 | DRAG_END = 7 27 | 28 | # K_FIRST, K_LAST 29 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | # PySCUMM Engine. SCUMM based engine for Python 2 | # Copyright (C) 2006 PySCUMM Engine. http://pyscumm.org 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | 18 | import pyscumm, random 19 | 20 | 21 | 22 | class Playa( pyscumm.Room ): 23 | 24 | def init( self ): 25 | # Here write the sentences that Playa need to run right 26 | self.logo = pyscumm.sdl.Image( 'pyscumm/artwork/pyscumm_logo.tga' ) 27 | #self.logo.set_pos( ( pyscumm.Engine().display.get_size()[0]/2, pyscumm.Engine().display.get_size()[1]/2, 0 ) ) 28 | self.container.append( self.logo ) 29 | 30 | def on_event( self, event_dict ): 31 | # Here intercept pyscumm events 32 | # if any double click event... 33 | if event_dict["type"] == pyscumm.DOUBLE_CLICK and event_dict["btn"] == 1: 34 | s = self.logo.get_size() 35 | # Change size image 36 | self.logo.set_size( (s[0]+50,s[1]+50) ) 37 | elif event_dict["type"] == pyscumm.DOUBLE_CLICK and event_dict["btn"] == 3: 38 | s = self.logo.get_size() 39 | # Change size image 40 | self.logo.set_size( (s[0]+150,s[1]+50) ) 41 | elif event_dict["type"] == pyscumm.DRAG_START: 42 | cur_pos = pyscumm.Engine().mouse.get_pos() 43 | self.logo.set_pos( (cur_pos[0], cur_pos[1], 0) ) 44 | 45 | 46 | 47 | # Mute the debugger 48 | #pyscumm.Debugger().console = False 49 | 50 | # Create a Engine 51 | demo = pyscumm.Engine() 52 | # Run the Engine with Playa Room 53 | demo.run( Playa ) 54 | -------------------------------------------------------------------------------- /pyscumm-engine/demo.py: -------------------------------------------------------------------------------- 1 | # PySCUMM Engine. SCUMM based engine for Python 2 | # Copyright (C) 2008 PySCUMM Engine. http://pyscumm.org 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | 18 | import pyscumm, pygame 19 | print(dir(pyscumm)) 20 | 21 | 22 | class PosterItem(pyscumm.Item): 23 | 24 | def __init__(self): 25 | pyscumm.Item.__init__(self) 26 | self.image = pyscumm.resource.load('Item1.png') 27 | self.position.x, self.position.y, self.position.z = 70, 225, 1 28 | self.description = 'Welcome poster' 29 | 30 | 31 | class BoneItem(pyscumm.Item): 32 | 33 | def __init__(self): 34 | pyscumm.Item.__init__(self) 35 | self.image = pyscumm.resource.load('Item2.png') 36 | self.position.x, self.position.y, self.position.z = 700, 400, 2 37 | self.description = 'Ridiculous arm bone' 38 | 39 | 40 | class BeachRoom(pyscumm.Room): 41 | 42 | def __init__(self): 43 | pyscumm.Room.__init__(self) 44 | self.background = pyscumm.resource.load('Background1.png') 45 | self.add(PosterItem()) 46 | self.add(BoneItem()) 47 | 48 | def update(self): 49 | #for d in self.container: 50 | # print(d.position.z 51 | pass 52 | 53 | def on_event(self, e): 54 | if e['type'] == pyscumm.KEY_DOWN: 55 | if e['key'] == pyscumm.K_RIGHT: 56 | self.camera.position.x += 20 57 | elif e['key'] == pyscumm.K_LEFT: 58 | self.camera.position.x -= 20 59 | elif e['key'] == pyscumm.K_F12: 60 | print(pyscumm.engine.clock.fps) 61 | 62 | # Create a Engine, and run the Playa room 63 | pyscumm.engine.display.title = 'PySCUMM Demo Adventure' 64 | pyscumm.engine.run(BeachRoom) 65 | 66 | -------------------------------------------------------------------------------- /pyscumm-engine/pyscumm/room.py: -------------------------------------------------------------------------------- 1 | # PySCUMM Engine. SCUMM based engine for Python 2 | # Copyright (C) 2006 PySCUMM Engine. http://pyscumm.org 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | 18 | """ 19 | @author: Juan Jose Alonso Lara (KarlsBerg, kernel.no.found@gmail.com) 20 | @since: 23/02/2008 21 | """ 22 | 23 | 24 | from .base import State 25 | from .engine import engine 26 | 27 | import pygame 28 | from .euclid import Vector2 29 | 30 | 31 | 32 | class Room(State, object): 33 | 34 | def __init__(self): 35 | State.__init__(self) 36 | self.background = None 37 | self.camera = Camera() 38 | self._container = {} 39 | 40 | def get_container(self): 41 | return self._container 42 | 43 | def add(self, obj): 44 | self._container[obj] = obj 45 | 46 | def remove(self, obj): 47 | del self._container[obj] 48 | 49 | def on_event(self, event_dict): 50 | pass 51 | 52 | def update(self): 53 | for obj in self.container: 54 | obj.update() 55 | 56 | def draw(self): 57 | # Draw background 58 | pygame.display.get_surface().blit(self.background, -self.camera.position) 59 | # Draw objects 60 | # THIS MUST ORDER BY Z PLANE !!! 61 | obj_sorted = list(self._container.values()) 62 | obj_sorted.sort(key=lambda d: d.position.z) 63 | for obj in obj_sorted: 64 | obj.update() 65 | obj.draw() 66 | 67 | container = property(get_container, None) 68 | 69 | 70 | 71 | class Camera(object): 72 | 73 | def __init__(self): 74 | self._position = Vector2(0, 0) 75 | #self._rect = pygame.Rect(self._position, Engine().display.get_size()) 76 | #pygame.display.get_surface().set_clip() 77 | 78 | def _get_position(self): 79 | return self._position 80 | 81 | def _set_position(self, p): 82 | print('setting pos camera', p) 83 | self._position = Vector2(*p) 84 | 85 | position = property(_get_position, _set_position) 86 | 87 | -------------------------------------------------------------------------------- /pyscumm-engine/pyscumm/box.py: -------------------------------------------------------------------------------- 1 | # PySCUMM Engine. SCUMM based engine for Python 2 | # Copyright (C) 2006 PySCUMM Engine. http://pyscumm.org 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | 18 | """ 19 | @author: Juan Jose Alonso Lara (KarlsBerg, jjalonso@pyscumm.org) 20 | @since: 18/02/2007 21 | """ 22 | 23 | class WalkableBox(Box): 24 | 25 | def __init__(self, points, zplane, scale, light): 26 | Box.__init__(self, points) 27 | self.z = zplane 28 | self.scale = scale 29 | self.light = light 30 | 31 | 32 | class Box: 33 | 34 | def __init__(self, points): 35 | self._vertex = points 36 | 37 | def vertex(self): 38 | return self._vertex 39 | 40 | def point_inside(self, point): 41 | """ 42 | Check if a point are inside the box 43 | @param point: The point to check collition 44 | @type point: Tuple (x,y) 45 | @return: True if the point are colliding or False if not colliding 46 | @rtype: boolean 47 | """ 48 | n = len(self._vertex) 49 | inside = False 50 | p1x, p1y = self._vertex[0] 51 | for i in range(n+1): 52 | p2x, p2y = self._vertex[i % n] 53 | if point[1] > min(p1y, p2y): 54 | if point[1] <= max(p1y, p2y): 55 | if point[0] <= max(p1x, p2x): 56 | if p1y != p2y: 57 | xinters = (point[1] - p1y) * (p2x - p1x) / \ 58 | (p2y - p1y) + p1x 59 | if p1x == p2x or point[0] <= xinters: 60 | inside = not inside 61 | p1x, p1y = p2x, p2y 62 | return inside 63 | 64 | def box_inside(self, other): 65 | """ 66 | Check if a box are colliding with other box 67 | @param other: The box for check 68 | @type point: Box 69 | @return: True if are colliding or False if not colliding 70 | @rtype: boolean 71 | """ 72 | for point in other.vertex(): 73 | print 'checking', point, 'in', self._vertex 74 | if self.point_inside(point): 75 | return True 76 | return False 77 | 78 | 79 | 80 | a=Box( ((0,0), (4,0), (4,4), (0,4)) ) 81 | b=Box( ((4,0), (10,0), (10,7), (7,8)) ) 82 | print a.box_inside(b) 83 | -------------------------------------------------------------------------------- /pyscumm-engine/pyscumm/base.py: -------------------------------------------------------------------------------- 1 | # PySCUMM Engine. SCUMM based engine for Python 2 | # Copyright (C) 2006 PySCUMM Engine. http://pyscumm.org 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | 18 | """ 19 | @author: Juan Jose Alonso Lara (KarlsBerg, jjalonso@pyscumm.org) 20 | @author: Juan Carlos Rodrigo Garcia (Brainsucker, jrodrigo@pyscumm.org) 21 | @since: 20/11/2006 22 | """ 23 | 24 | from .sdl import Drawable 25 | import time 26 | import pygame 27 | 28 | 29 | class ResourceLoader: 30 | 31 | def __init__(self): 32 | self.cache = {} 33 | 34 | def load(self, filename): 35 | if not filename in self.cache: 36 | self.cache[filename] = pygame.image.load(filename).convert_alpha() 37 | return self.cache[filename] 38 | 39 | def load_grid(self, filename, size): 40 | grid = self.load(filename) 41 | cols = grid.get_width() // size[0] 42 | rows = grid.get_height() // size[1] 43 | result = [] 44 | for r in range(rows): 45 | for c in range(cols): 46 | rect = pygame.Rect(size[0]*c, size[1]*r, size[0], size[1]) 47 | result.append(grid.subsurface(rect)) 48 | return result 49 | 50 | resource = ResourceLoader() 51 | 52 | 53 | 54 | 55 | class StateMachine: 56 | """ 57 | Abstract state machine (See: state pattern). 58 | """ 59 | 60 | def __init__( self ): 61 | self.state = None 62 | 63 | def init(self): 64 | """ 65 | Start the state machine, this method need reimplementation 66 | on machine subclass, where you can start a machine with a state. 67 | if call this method without reimplement, 68 | a NotImplementedError excepcion is launched 69 | """ 70 | raise NotImplementedError 71 | 72 | 73 | 74 | class State(object): 75 | """ 76 | Abstract state (See: state pattern). 77 | """ 78 | pass 79 | 80 | 81 | 82 | class StopEngine(Exception): 83 | pass 84 | 85 | 86 | 87 | class ChangeRoom(Exception): 88 | pass 89 | 90 | 91 | 92 | 93 | class Debugger: 94 | """Debugger class""" 95 | 96 | def __init__(self): 97 | """ 98 | Build a Debugger object with Singleton pattern. 99 | """ 100 | self.visual = True 101 | self.console = True 102 | 103 | def warn(self, message): 104 | """ 105 | Log a warning message. 106 | @param message: The message to log 107 | @type state: String 108 | """ 109 | if self.console: 110 | print(time.strftime('%H:%M:%S:')," warning : %s" % message) 111 | 112 | def info(self, message): 113 | """ 114 | Log an info message. 115 | @param message: The message to log 116 | @type state: String 117 | """ 118 | if self.console: 119 | print(time.strftime('%H:%M:%S:')," info : %s" % message) 120 | 121 | def error(self, message): 122 | """ 123 | Log an error message. 124 | @param message: The message to log 125 | @type state: String 126 | """ 127 | if self.console: 128 | print(time.strftime('%H:%M:%S:')," error : %s" % message) 129 | 130 | 131 | debugger=Debugger() 132 | -------------------------------------------------------------------------------- /pyscumm-engine/pyscumm/engine.py: -------------------------------------------------------------------------------- 1 | # PySCUMM Engine. SCUMM based engine for Python 2 | # Copyright (C) 2006 PySCUMM Engine. http://pyscumm.org 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | 18 | """ 19 | @author: Juan Jose Alonso Lara (KarlsBerg, jjalonso@pyscumm.org) 20 | @since: 18/02/2007 21 | """ 22 | 23 | from .base import StateMachine, StopEngine, ChangeRoom, debugger 24 | from .driver import Mouse, Display, Clock 25 | from .constant import MOUSE_MOTION, DOUBLE_CLICK, SINGLE_CLICK, KEY_DOWN, KEY_UP, DRAG_START, DRAG_END 26 | 27 | import pygame.time 28 | import pygame.event 29 | 30 | 31 | 32 | class Engine(StateMachine): 33 | 34 | def __init__( self ): 35 | StateMachine.__init__(self) 36 | self.mouse = Mouse() 37 | self.display = Display() 38 | self.clock = Clock() 39 | self.__dragging = [ 0, 0, 0 ] 40 | self.__button_time = [ 0, 0, 0 ] 41 | 42 | def run(self, room): 43 | pygame.init() 44 | debugger.info("turning ON engine...") 45 | try: 46 | import psyco 47 | psyco.full() 48 | debugger.info("python-psyco module found, performance acelerarion enabled") 49 | except ImportError: 50 | debugger.warn("python-psyco module not found, performance aceleration unavaliable") 51 | 52 | self.display.open() 53 | self.room = room() 54 | leave = False 55 | while not leave: 56 | try: 57 | self.clock.tick() 58 | for event in pygame.event.get(): 59 | if event.type == pygame.QUIT: 60 | raise StopEngine() 61 | elif event.type == pygame.MOUSEMOTION: 62 | event_dict = { "type":MOUSE_MOTION, "pos":self.mouse.position, "obj":self._get_picked() } 63 | debugger.info("MOUSE_MOTION event launched %s" % event_dict) 64 | self.room.on_event(event_dict) 65 | elif event.type == pygame.MOUSEBUTTONDOWN: 66 | self.__process_mouse_button_down( event ) 67 | elif event.type == pygame.MOUSEBUTTONUP: 68 | self.__process_mouse_button_up( event ) 69 | #self.room.on_event(event) 70 | elif event.type == pygame.KEYDOWN: 71 | event_dict = { "type":KEY_DOWN, "key":event.key, "mod":"NotImplemented" } 72 | debugger.info("KEY_DOWN event launched %s" % event_dict) 73 | self.room.on_event(event_dict) 74 | elif event.type == pygame.KEYUP: 75 | event_dict = { "type":KEY_UP, "key":event.key, "mod":"NotImplemented" } 76 | debugger.info("KEY_UP event launched %s" % event_dict) 77 | self.room.on_event(event_dict) 78 | self.__update() 79 | # Sort the container list by Z axis. 80 | #self.room.container.sort(key=lambda d: d.position.z) 81 | # Call update the room 82 | self.room.update() 83 | self.room.draw() 84 | self.mouse.update() 85 | self.mouse.draw() 86 | self.display.flip() 87 | except ChangeRoom: 88 | debugger.info("ChangeRoom exception raised, changing room to %s" % e.__class__.__name__) 89 | self.room = e 90 | except StopEngine: 91 | debugger.info("stopping Engine...") 92 | leave = True 93 | self.display.close() 94 | debugger.info("engine totally stopped, exiting") 95 | 96 | def __process_mouse_button_down(self, event): 97 | if event.button not in [1,2,3]: 98 | return 99 | # Check if DOUBLE_CLICK 100 | if self.clock.time - self.__button_time[event.button-1] < self.mouse.doubleclick_time: 101 | event_dict = { "type":DOUBLE_CLICK, "btn":event.button, "obj":self._get_picked(), "pos":self.mouse.position } 102 | debugger.info("DOUBLE_CLICK event launched %s" % event_dict) 103 | self.room.on_event(event_dict) 104 | self.__dragging[event.button-1] = 0 105 | else: 106 | self.__button_time[event.button-1] = self.clock.time 107 | # Prepare a possible a DRAG_START 108 | self.__dragging[event.button-1] = self.clock.time 109 | 110 | def _get_picked(self): 111 | picked = [] 112 | m_pos = self.mouse.position 113 | for obj in self.room.container: 114 | if obj.collider.collidepoint(m_pos.x, m_pos.y): 115 | picked.append(obj) 116 | picked.sort(key=lambda d: d.position.z) 117 | return picked 118 | 119 | def __process_mouse_button_up(self, event): 120 | if self.__dragging[event.button-1]: 121 | event_dict = { "type":SINGLE_CLICK, "btn":event.button, "obj":self._get_picked(), "pos":self.mouse.position } 122 | debugger.info("SINGLE_CLICK event launched %s" % event_dict) 123 | self.room.on_event( event_dict ) 124 | self.__dragging[event.button-1] = 0 125 | else: 126 | event_dict = { "type":DRAG_END, "btn":event.button, "obj":self._get_picked(), "pos":self.mouse.position } 127 | debugger.info("DRAG_END event launched %s" % event_dict) 128 | self.room.on_event( event_dict ) 129 | 130 | def __update(self): 131 | for list_pos, x in enumerate(self.__dragging): 132 | if self.__dragging[list_pos]: 133 | if ( self.clock.time - self.__dragging[list_pos] ) >= self.mouse.drag_time: 134 | event_dict = { "type":DRAG_START, "btn":"NotImplementedError", "obj":self._get_picked(), "pos":self.mouse.position } 135 | debugger.info("DRAG_START event launched %s" % event_dict) 136 | self.room.on_event( event_dict ) 137 | self.__dragging[list_pos] = 0 138 | 139 | 140 | 141 | engine = Engine() 142 | -------------------------------------------------------------------------------- /pyscumm-engine/pyscumm/driver.py: -------------------------------------------------------------------------------- 1 | # PySCUMM Engine. SCUMM based engine for Python 2 | # Copyright (C) 2006 PySCUMM Engine. http://pyscumm.org 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | 18 | """ 19 | @author: Juan Jose Alonso Lara (KarlsBerg, jjalonso@pyscumm.org) 20 | @since: 18/02/2007 21 | """ 22 | 23 | from .euclid import Vector2 24 | from .base import debugger 25 | from .sdl import Drawable 26 | import pygame.mouse, time 27 | 28 | 29 | 30 | class Cursor(Drawable): 31 | 32 | def __init__(self): 33 | Drawable.__init__(self) 34 | self.offset = Vector2(0, 0) 35 | 36 | def draw(self): 37 | pygame.display.get_surface().blit(self._image, self.screen_position - self.offset) 38 | 39 | 40 | 41 | class DefaultCursor(Cursor): 42 | def __init__(self): 43 | Cursor.__init__(self) 44 | self.image = pygame.image.load('pyscumm/artwork/cursor.png') 45 | 46 | 47 | 48 | class Mouse( object ): 49 | """A Mouse Controller Class.""" 50 | 51 | def __init__( self, display=None ): 52 | """Build a Mouse object""" 53 | self.doubleclick_time = 200 54 | self.drag_time = 150 55 | self.cursor = None 56 | 57 | def get_position( self ): 58 | """ 59 | Get the current location of the mouse cursor. 60 | @return: Mouse cursor location 61 | @rtype: list 62 | """ 63 | return Vector2(*pygame.mouse.get_pos()) 64 | 65 | def set_position( self, pos ): 66 | """ 67 | Set the location of the mouse cursor. 68 | @param location: Mouse cursor location 69 | @type location: list 70 | """ 71 | pygame.mouse.set_pos( pos ) 72 | 73 | def update( self ): 74 | """ 75 | Updates the mouse location for this frame 76 | based on the display. 77 | """ 78 | if self.cursor: 79 | self.cursor.screen_position = self.get_position() 80 | 81 | def draw(self): 82 | if not self.cursor: 83 | self.cursor = DefaultCursor() 84 | self.cursor.draw() 85 | 86 | position = property(get_position, set_position) 87 | 88 | 89 | 90 | class Display( object ): 91 | """pyscumm Display object""" 92 | 93 | def __init__(self): 94 | """Build a Display object""" 95 | self._title = "pyscumm display" 96 | self._size = Vector2(640, 480) 97 | self._icon = None 98 | self._opened = False 99 | 100 | def open(self): 101 | """ 102 | Open the display window with the setted size. 103 | @return: None 104 | """ 105 | self._opened = True 106 | pygame.init() 107 | pygame.mouse.set_visible(False) 108 | pygame.display.init() 109 | self._set_title(self._title) 110 | self._set_size(self._size) 111 | debugger.info("display openned") 112 | 113 | def close(self): 114 | """ 115 | Close que display window. 116 | @return None 117 | """ 118 | if not self._opened: 119 | return 120 | pygame.display.quit() 121 | debugger.info("display closed") 122 | 123 | 124 | def get_modes(self): 125 | """ 126 | Get a list of possible dimensions to the current/best color depth. 127 | @return: A list of possible dimensions. 128 | @rtype: List 129 | """ 130 | return pygame.display.list_modes() 131 | 132 | def toggle_fullscreen(self): 133 | """ 134 | Swith between fullscreen or windowed mode. 135 | @return: if available and successfull, will return True, else return False. 136 | @rtype: Boolean 137 | """ 138 | pygame.display.toggle_fullscreen() 139 | debugger.info("toggling fullscreen mode") 140 | 141 | def _get_size(self): 142 | """ 143 | Get current display size. 144 | @return: The current display size. 145 | @rtype: pygame.base.Vector3D 146 | """ 147 | return self._size 148 | 149 | def _set_size(self, size): 150 | """ 151 | Set a new display size. 152 | @param size: The new size of the display (X,Y,*). 153 | @type size: pygame.base.Vector3D 154 | @return: None 155 | """ 156 | self._size = size 157 | if not self._opened: return 158 | screen = pygame.display.set_mode(self._size, pygame.HWSURFACE) 159 | screen.set_clip(pygame.Rect((0, 0), self._size)) 160 | debugger.info("display size setted [%s]" % (str(self._size))) 161 | 162 | def _get_title(self): 163 | """ 164 | Get the current display title. 165 | @return: The current title of the new display. 166 | @rtype: String 167 | """ 168 | return self._title 169 | 170 | def _set_title(self, title): 171 | """ 172 | Set the display title. 173 | @param title: The name title of the display 174 | @type title: string 175 | @return: None 176 | """ 177 | self._title = title 178 | pygame.display.set_caption(title) 179 | 180 | def _get_icon(self): 181 | """ 182 | Get the current image icon setted. 183 | @return: a icon image that are current setted on a window 184 | @rtype: pygame.Surface 185 | """ 186 | return self._icon 187 | 188 | def _set_icon(self, image): 189 | """ 190 | Set the display icon. 191 | Some window managers on X11 don't allow you to change the icon 192 | after the window has been shown the first time. 193 | @param image: A icon image 194 | @type image: pygame.Surface 195 | @return: None 196 | """ 197 | self._icon = image 198 | if not self._opened: return 199 | pygame.display.set_icon(image) 200 | 201 | def flip(self): 202 | """ 203 | Update the screen 204 | """ 205 | pygame.display.flip() 206 | 207 | size = property(_get_size, _set_size) 208 | title = property(_get_title, _set_title) 209 | icon = property(_get_icon, _set_icon) 210 | 211 | 212 | 213 | MSEC = 1000. 214 | 215 | class Clock( object ): 216 | 217 | def __init__( self ): 218 | """Build a Clock object.""" 219 | # Next frame tick time 220 | self.time = 0 221 | # Interval between frame ticks 222 | self.tick_interval = 0 223 | # Frame count 224 | self.frame_count = 0 225 | # Frame rate limit 226 | self.limit = 60 227 | 228 | def set_limit( self, fps ): 229 | """ 230 | Set the frame rate limit. 231 | @param fps: Maximum fps 232 | @type fps: float 233 | """ 234 | self._limit = fps 235 | self.tick_interval = round( MSEC / self._limit ) 236 | self.tick_interval *= 2. # double speed 237 | 238 | def get_limit( self ): 239 | """ 240 | Get the frame rate limit 241 | @return: float 242 | """ 243 | return self._limit 244 | 245 | def tick( self ): 246 | """ 247 | Tick a frame and wait till next frame 248 | if we are drawing too fast. 249 | @return: None 250 | """ 251 | self.frame_count += 1 252 | time.sleep( self._get_raw_time() / MSEC ) 253 | 254 | def _get_raw_time( self ): 255 | """ 256 | Get the raw time, time pending untill the next frame. 257 | @return: float 258 | """ 259 | now = pygame.time.get_ticks() 260 | if self.time <= now: 261 | self.time = now + self.tick_interval 262 | return 0. 263 | return self.time - now 264 | 265 | def get_fps( self ): 266 | """ 267 | Get the frames per second. 268 | @return: float 269 | """ 270 | return self.frame_count / ( pygame.time.get_ticks() / MSEC ); 271 | 272 | fps = property( get_fps ) 273 | limit = property( get_limit, set_limit ) 274 | -------------------------------------------------------------------------------- /pyscumm-engine/pyscumm/euclid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # euclid graphics maths module 4 | # 5 | # Copyright (c) 2006 Alex Holkner 6 | # Alex.Holkner@mail.google.com 7 | # 8 | # This library is free software; you can redistribute it and/or modify it 9 | # under the terms of the GNU Lesser General Public License as published by the 10 | # Free Software Foundation; either version 2.1 of the License, or (at your 11 | # option) any later version. 12 | # 13 | # This library is distributed in the hope that it will be useful, but WITHOUT 14 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 16 | # for more details. 17 | # 18 | # You should have received a copy of the GNU Lesser General Public License 19 | # along with this library; if not, write to the Free Software Foundation, 20 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 21 | 22 | '''euclid graphics maths module 23 | 24 | Documentation and tests are included in the file "euclid.txt", or online 25 | at http://code.google.com/p/pyeuclid 26 | ''' 27 | 28 | __docformat__ = 'restructuredtext' 29 | __version__ = '$Id$' 30 | __revision__ = '$Revision$' 31 | 32 | import math 33 | import operator 34 | import types 35 | 36 | # Some magic here. If _use_slots is True, the classes will derive from 37 | # object and will define a __slots__ class variable. If _use_slots is 38 | # False, classes will be old-style and will not define __slots__. 39 | # 40 | # _use_slots = True: Memory efficient, probably faster in future versions 41 | # of Python, "better". 42 | # _use_slots = False: Ordinary classes, much faster than slots in current 43 | # versions of Python (2.4 and 2.5). 44 | _use_slots = True 45 | 46 | # If True, allows components of Vector2 and Vector3 to be set via swizzling; 47 | # e.g. v.xyz = (1, 2, 3). This is much, much slower than the more verbose 48 | # v.x = 1; v.y = 2; v.z = 3, and slows down ordinary element setting as 49 | # well. Recommended setting is False. 50 | _enable_swizzle_set = False 51 | 52 | # Requires class to derive from object. 53 | if _enable_swizzle_set: 54 | _use_slots = True 55 | 56 | # Implement _use_slots magic. 57 | class _EuclidMetaclass(type): 58 | def __new__(cls, name, bases, dct): 59 | if '__slots__' in dct: 60 | dct['__getstate__'] = cls._create_getstate(dct['__slots__']) 61 | dct['__setstate__'] = cls._create_setstate(dct['__slots__']) 62 | if _use_slots: 63 | return type.__new__(cls, name, bases + (object,), dct) 64 | else: 65 | if '__slots__' in dct: 66 | del dct['__slots__'] 67 | return types.ClassType.__new__(types.ClassType, name, bases, dct) 68 | 69 | @classmethod 70 | def _create_getstate(cls, slots): 71 | def __getstate__(self): 72 | d = {} 73 | for slot in slots: 74 | d[slot] = getattr(self, slot) 75 | return d 76 | return __getstate__ 77 | 78 | @classmethod 79 | def _create_setstate(cls, slots): 80 | def __setstate__(self, state): 81 | for name, value in state.items(): 82 | setattr(self, name, value) 83 | return __setstate__ 84 | 85 | __metaclass__ = _EuclidMetaclass 86 | 87 | class Vector2: 88 | __slots__ = ['x', 'y'] 89 | 90 | def __init__(self, x, y): 91 | self.x = x 92 | self.y = y 93 | 94 | def __copy__(self): 95 | return self.__class__(self.x, self.y) 96 | 97 | copy = __copy__ 98 | 99 | def __repr__(self): 100 | return 'Vector2(%.2f, %.2f)' % (self.x, self.y) 101 | 102 | def __eq__(self, other): 103 | if isinstance(other, Vector2): 104 | return self.x == other.x and \ 105 | self.y == other.y 106 | else: 107 | assert hasattr(other, '__len__') and len(other) == 2 108 | return self.x == other[0] and \ 109 | self.y == other[1] 110 | 111 | def __ne__(self, other): 112 | return not self.__eq__(other) 113 | 114 | def __nonzero__(self): 115 | return self.x != 0 or self.y != 0 116 | 117 | def __len__(self): 118 | return 2 119 | 120 | def __getitem__(self, key): 121 | return (self.x, self.y)[key] 122 | 123 | def __setitem__(self, key, value): 124 | l = [self.x, self.y] 125 | l[key] = value 126 | self.x, self.y = l 127 | 128 | def __iter__(self): 129 | return iter((self.x, self.y)) 130 | 131 | def __getattr__(self, name): 132 | try: 133 | return tuple([(self.x, self.y)['xy'.index(c)] \ 134 | for c in name]) 135 | except ValueError: 136 | raise AttributeError 137 | 138 | if _enable_swizzle_set: 139 | # This has detrimental performance on ordinary setattr as well 140 | # if enabled 141 | def __setattr__(self, name, value): 142 | if len(name) == 1: 143 | object.__setattr__(self, name, value) 144 | else: 145 | try: 146 | l = [self.x, self.y] 147 | for c, v in map(None, name, value): 148 | l['xy'.index(c)] = v 149 | self.x, self.y = l 150 | except ValueError: 151 | raise AttributeError 152 | 153 | def __add__(self, other): 154 | if isinstance(other, Vector2): 155 | return Vector2(self.x + other.x, 156 | self.y + other.y) 157 | else: 158 | assert hasattr(other, '__len__') and len(other) == 2 159 | return Vector2(self.x + other[0], 160 | self.y + other[1]) 161 | __radd__ = __add__ 162 | 163 | def __iadd__(self, other): 164 | if isinstance(other, Vector2): 165 | self.x += other.x 166 | self.y += other.y 167 | else: 168 | self.x += other[0] 169 | self.y += other[1] 170 | return self 171 | 172 | def __sub__(self, other): 173 | if isinstance(other, Vector2): 174 | return Vector2(self.x - other.x, 175 | self.y - other.y) 176 | else: 177 | assert hasattr(other, '__len__') and len(other) == 2 178 | return Vector2(self.x - other[0], 179 | self.y - other[1]) 180 | 181 | 182 | def __rsub__(self, other): 183 | if isinstance(other, Vector2): 184 | return Vector2(other.x - self.x, 185 | other.y - self.y) 186 | else: 187 | assert hasattr(other, '__len__') and len(other) == 2 188 | return Vector2(other.x - self[0], 189 | other.y - self[1]) 190 | 191 | def __mul__(self, other): 192 | assert type(other) in (int, long, float) 193 | return Vector2(self.x * other, 194 | self.y * other) 195 | 196 | __rmul__ = __mul__ 197 | 198 | def __imul__(self, other): 199 | assert type(other) in (int, long, float) 200 | self.x *= other 201 | self.y *= other 202 | return self 203 | 204 | def __div__(self, other): 205 | assert type(other) in (int, long, float) 206 | return Vector2(operator.div(self.x, other), 207 | operator.div(self.y, other)) 208 | 209 | 210 | def __rdiv__(self, other): 211 | assert type(other) in (int, long, float) 212 | return Vector2(operator.div(other, self.x), 213 | operator.div(other, self.y)) 214 | 215 | def __floordiv__(self, other): 216 | assert type(other) in (int, long, float) 217 | return Vector2(operator.floordiv(self.x, other), 218 | operator.floordiv(self.y, other)) 219 | 220 | 221 | def __rfloordiv__(self, other): 222 | assert type(other) in (int, long, float) 223 | return Vector2(operator.floordiv(other, self.x), 224 | operator.floordiv(other, self.y)) 225 | 226 | def __truediv__(self, other): 227 | assert type(other) in (int, long, float) 228 | return Vector2(operator.truediv(self.x, other), 229 | operator.truediv(self.y, other)) 230 | 231 | 232 | def __rtruediv__(self, other): 233 | assert type(other) in (int, long, float) 234 | return Vector2(operator.truediv(other, self.x), 235 | operator.truediv(other, self.y)) 236 | 237 | def __neg__(self): 238 | return Vector2(-self.x, 239 | -self.y) 240 | 241 | __pos__ = __copy__ 242 | 243 | def __abs__(self): 244 | return math.sqrt(self.x ** 2 + \ 245 | self.y ** 2) 246 | 247 | magnitude = __abs__ 248 | 249 | def magnitude_squared(self): 250 | return self.x ** 2 + \ 251 | self.y ** 2 252 | 253 | def normalize(self): 254 | d = self.magnitude() 255 | if d: 256 | self.x /= d 257 | self.y /= d 258 | return self 259 | 260 | def normalized(self): 261 | d = self.magnitude() 262 | if d: 263 | return Vector2(self.x / d, 264 | self.y / d) 265 | return self.copy() 266 | 267 | def dot(self, other): 268 | assert isinstance(other, Vector2) 269 | return self.x * other.x + \ 270 | self.y * other.y 271 | 272 | def cross(self): 273 | return Vector2(self.y, -self.x) 274 | 275 | def reflect(self, normal): 276 | # assume normal is normalized 277 | assert isinstance(normal, Vector2) 278 | d = 2 * (self.x * normal.x + self.y * normal.y) 279 | return Vector2(self.x - d * normal.x, 280 | self.y - d * normal.y) 281 | 282 | class Vector3: 283 | __slots__ = ['x', 'y', 'z'] 284 | 285 | def __init__(self, x, y, z): 286 | self.x = x 287 | self.y = y 288 | self.z = z 289 | 290 | def __copy__(self): 291 | return self.__class__(self.x, self.y, self.z) 292 | 293 | copy = __copy__ 294 | 295 | def __repr__(self): 296 | return 'Vector3(%.2f, %.2f, %.2f)' % (self.x, 297 | self.y, 298 | self.z) 299 | 300 | def __eq__(self, other): 301 | if isinstance(other, Vector3): 302 | return self.x == other.x and \ 303 | self.y == other.y and \ 304 | self.z == other.z 305 | else: 306 | assert hasattr(other, '__len__') and len(other) == 3 307 | return self.x == other[0] and \ 308 | self.y == other[1] and \ 309 | self.z == other[2] 310 | 311 | def __ne__(self, other): 312 | return not self.__eq__(other) 313 | 314 | def __nonzero__(self): 315 | return self.x != 0 or self.y != 0 or self.z != 0 316 | 317 | def __len__(self): 318 | return 3 319 | 320 | def __getitem__(self, key): 321 | return (self.x, self.y, self.z)[key] 322 | 323 | def __setitem__(self, key, value): 324 | l = [self.x, self.y, self.z] 325 | l[key] = value 326 | self.x, self.y, self.z = l 327 | 328 | def __iter__(self): 329 | return iter((self.x, self.y, self.z)) 330 | 331 | def __getattr__(self, name): 332 | try: 333 | return tuple([(self.x, self.y, self.z)['xyz'.index(c)] \ 334 | for c in name]) 335 | except ValueError: 336 | raise AttributeError 337 | 338 | if _enable_swizzle_set: 339 | # This has detrimental performance on ordinary setattr as well 340 | # if enabled 341 | def __setattr__(self, name, value): 342 | if len(name) == 1: 343 | object.__setattr__(self, name, value) 344 | else: 345 | try: 346 | l = [self.x, self.y, self.z] 347 | for c, v in map(None, name, value): 348 | l['xyz'.index(c)] = v 349 | self.x, self.y, self.z = l 350 | except ValueError: 351 | raise AttributeError 352 | 353 | 354 | def __add__(self, other): 355 | if isinstance(other, Vector3): 356 | # Vector + Vector -> Vector 357 | # Vector + Point -> Point 358 | # Point + Point -> Vector 359 | if self.__class__ is other.__class__: 360 | _class = Vector3 361 | else: 362 | _class = Point3 363 | return _class(self.x + other.x, 364 | self.y + other.y, 365 | self.z + other.z) 366 | else: 367 | assert hasattr(other, '__len__') and len(other) == 3 368 | return Vector3(self.x + other[0], 369 | self.y + other[1], 370 | self.z + other[2]) 371 | __radd__ = __add__ 372 | 373 | def __iadd__(self, other): 374 | if isinstance(other, Vector3): 375 | self.x += other.x 376 | self.y += other.y 377 | self.z += other.z 378 | else: 379 | self.x += other[0] 380 | self.y += other[1] 381 | self.z += other[2] 382 | return self 383 | 384 | def __sub__(self, other): 385 | if isinstance(other, Vector3): 386 | # Vector - Vector -> Vector 387 | # Vector - Point -> Point 388 | # Point - Point -> Vector 389 | if self.__class__ is other.__class__: 390 | _class = Vector3 391 | else: 392 | _class = Point3 393 | return Vector3(self.x - other.x, 394 | self.y - other.y, 395 | self.z - other.z) 396 | else: 397 | assert hasattr(other, '__len__') and len(other) == 3 398 | return Vector3(self.x - other[0], 399 | self.y - other[1], 400 | self.z - other[2]) 401 | 402 | 403 | def __rsub__(self, other): 404 | if isinstance(other, Vector3): 405 | return Vector3(other.x - self.x, 406 | other.y - self.y, 407 | other.z - self.z) 408 | else: 409 | assert hasattr(other, '__len__') and len(other) == 3 410 | return Vector3(other.x - self[0], 411 | other.y - self[1], 412 | other.z - self[2]) 413 | 414 | def __mul__(self, other): 415 | if isinstance(other, Vector3): 416 | # TODO component-wise mul/div in-place and on Vector2; docs. 417 | if self.__class__ is Point3 or other.__class__ is Point3: 418 | _class = Point3 419 | else: 420 | _class = Vector3 421 | return _class(self.x * other.x, 422 | self.y * other.y, 423 | self.z * other.z) 424 | else: 425 | assert type(other) in (int, long, float) 426 | return Vector3(self.x * other, 427 | self.y * other, 428 | self.z * other) 429 | 430 | __rmul__ = __mul__ 431 | 432 | def __imul__(self, other): 433 | assert type(other) in (int, long, float) 434 | self.x *= other 435 | self.y *= other 436 | self.z *= other 437 | return self 438 | 439 | def __div__(self, other): 440 | assert type(other) in (int, long, float) 441 | return Vector3(operator.div(self.x, other), 442 | operator.div(self.y, other), 443 | operator.div(self.z, other)) 444 | 445 | 446 | def __rdiv__(self, other): 447 | assert type(other) in (int, long, float) 448 | return Vector3(operator.div(other, self.x), 449 | operator.div(other, self.y), 450 | operator.div(other, self.z)) 451 | 452 | def __floordiv__(self, other): 453 | assert type(other) in (int, long, float) 454 | return Vector3(operator.floordiv(self.x, other), 455 | operator.floordiv(self.y, other), 456 | operator.floordiv(self.z, other)) 457 | 458 | 459 | def __rfloordiv__(self, other): 460 | assert type(other) in (int, long, float) 461 | return Vector3(operator.floordiv(other, self.x), 462 | operator.floordiv(other, self.y), 463 | operator.floordiv(other, self.z)) 464 | 465 | def __truediv__(self, other): 466 | assert type(other) in (int, long, float) 467 | return Vector3(operator.truediv(self.x, other), 468 | operator.truediv(self.y, other), 469 | operator.truediv(self.z, other)) 470 | 471 | 472 | def __rtruediv__(self, other): 473 | assert type(other) in (int, long, float) 474 | return Vector3(operator.truediv(other, self.x), 475 | operator.truediv(other, self.y), 476 | operator.truediv(other, self.z)) 477 | 478 | def __neg__(self): 479 | return Vector3(-self.x, 480 | -self.y, 481 | -self.z) 482 | 483 | __pos__ = __copy__ 484 | 485 | def __abs__(self): 486 | return math.sqrt(self.x ** 2 + \ 487 | self.y ** 2 + \ 488 | self.z ** 2) 489 | 490 | magnitude = __abs__ 491 | 492 | def magnitude_squared(self): 493 | return self.x ** 2 + \ 494 | self.y ** 2 + \ 495 | self.z ** 2 496 | 497 | def normalize(self): 498 | d = self.magnitude() 499 | if d: 500 | self.x /= d 501 | self.y /= d 502 | self.z /= d 503 | return self 504 | 505 | def normalized(self): 506 | d = self.magnitude() 507 | if d: 508 | return Vector3(self.x / d, 509 | self.y / d, 510 | self.z / d) 511 | return self.copy() 512 | 513 | def dot(self, other): 514 | assert isinstance(other, Vector3) 515 | return self.x * other.x + \ 516 | self.y * other.y + \ 517 | self.z * other.z 518 | 519 | def cross(self, other): 520 | assert isinstance(other, Vector3) 521 | return Vector3(self.y * other.z - self.z * other.y, 522 | -self.x * other.z + self.z * other.x, 523 | self.x * other.y - self.y * other.x) 524 | 525 | def reflect(self, normal): 526 | # assume normal is normalized 527 | assert isinstance(normal, Vector3) 528 | d = 2 * (self.x * normal.x + self.y * normal.y + self.z * normal.z) 529 | return Vector3(self.x - d * normal.x, 530 | self.y - d * normal.y, 531 | self.z - d * normal.z) 532 | 533 | # a b c 534 | # e f g 535 | # i j k 536 | 537 | class Matrix3: 538 | __slots__ = list('abcefgijk') 539 | 540 | def __init__(self): 541 | self.identity() 542 | 543 | def __copy__(self): 544 | M = Matrix3() 545 | M.a = self.a 546 | M.b = self.b 547 | M.c = self.c 548 | M.e = self.e 549 | M.f = self.f 550 | M.g = self.g 551 | M.i = self.i 552 | M.j = self.j 553 | M.k = self.k 554 | return M 555 | 556 | copy = __copy__ 557 | def __repr__(self): 558 | return ('Matrix3([% 8.2f % 8.2f % 8.2f\n' \ 559 | ' % 8.2f % 8.2f % 8.2f\n' \ 560 | ' % 8.2f % 8.2f % 8.2f])') \ 561 | % (self.a, self.b, self.c, 562 | self.e, self.f, self.g, 563 | self.i, self.j, self.k) 564 | 565 | def __getitem__(self, key): 566 | return [self.a, self.e, self.i, 567 | self.b, self.f, self.j, 568 | self.c, self.g, self.k][key] 569 | 570 | def __setitem__(self, key, value): 571 | L = self[:] 572 | L[key] = value 573 | (self.a, self.e, self.i, 574 | self.b, self.f, self.j, 575 | self.c, self.g, self.k) = L 576 | 577 | def __mul__(self, other): 578 | if isinstance(other, Matrix3): 579 | # Caching repeatedly accessed attributes in local variables 580 | # apparently increases performance by 20%. Attrib: Will McGugan. 581 | Aa = self.a 582 | Ab = self.b 583 | Ac = self.c 584 | Ae = self.e 585 | Af = self.f 586 | Ag = self.g 587 | Ai = self.i 588 | Aj = self.j 589 | Ak = self.k 590 | Ba = other.a 591 | Bb = other.b 592 | Bc = other.c 593 | Be = other.e 594 | Bf = other.f 595 | Bg = other.g 596 | Bi = other.i 597 | Bj = other.j 598 | Bk = other.k 599 | C = Matrix3() 600 | C.a = Aa * Ba + Ab * Be + Ac * Bi 601 | C.b = Aa * Bb + Ab * Bf + Ac * Bj 602 | C.c = Aa * Bc + Ab * Bg + Ac * Bk 603 | C.e = Ae * Ba + Af * Be + Ag * Bi 604 | C.f = Ae * Bb + Af * Bf + Ag * Bj 605 | C.g = Ae * Bc + Af * Bg + Ag * Bk 606 | C.i = Ai * Ba + Aj * Be + Ak * Bi 607 | C.j = Ai * Bb + Aj * Bf + Ak * Bj 608 | C.k = Ai * Bc + Aj * Bg + Ak * Bk 609 | return C 610 | elif isinstance(other, Point2): 611 | A = self 612 | B = other 613 | P = Point2(0, 0) 614 | P.x = A.a * B.x + A.b * B.y + A.c 615 | P.y = A.e * B.x + A.f * B.y + A.g 616 | return P 617 | elif isinstance(other, Vector2): 618 | A = self 619 | B = other 620 | V = Vector2(0, 0) 621 | V.x = A.a * B.x + A.b * B.y 622 | V.y = A.e * B.x + A.f * B.y 623 | return V 624 | else: 625 | other = other.copy() 626 | other._apply_transform(self) 627 | return other 628 | 629 | def __imul__(self, other): 630 | assert isinstance(other, Matrix3) 631 | # Cache attributes in local vars (see Matrix3.__mul__). 632 | Aa = self.a 633 | Ab = self.b 634 | Ac = self.c 635 | Ae = self.e 636 | Af = self.f 637 | Ag = self.g 638 | Ai = self.i 639 | Aj = self.j 640 | Ak = self.k 641 | Ba = other.a 642 | Bb = other.b 643 | Bc = other.c 644 | Be = other.e 645 | Bf = other.f 646 | Bg = other.g 647 | Bi = other.i 648 | Bj = other.j 649 | Bk = other.k 650 | self.a = Aa * Ba + Ab * Be + Ac * Bi 651 | self.b = Aa * Bb + Ab * Bf + Ac * Bj 652 | self.c = Aa * Bc + Ab * Bg + Ac * Bk 653 | self.e = Ae * Ba + Af * Be + Ag * Bi 654 | self.f = Ae * Bb + Af * Bf + Ag * Bj 655 | self.g = Ae * Bc + Af * Bg + Ag * Bk 656 | self.i = Ai * Ba + Aj * Be + Ak * Bi 657 | self.j = Ai * Bb + Aj * Bf + Ak * Bj 658 | self.k = Ai * Bc + Aj * Bg + Ak * Bk 659 | return self 660 | 661 | def identity(self): 662 | self.a = self.f = self.k = 1. 663 | self.b = self.c = self.e = self.g = self.i = self.j = 0 664 | return self 665 | 666 | def scale(self, x, y): 667 | self *= Matrix3.new_scale(x, y) 668 | return self 669 | 670 | def translate(self, x, y): 671 | self *= Matrix3.new_translate(x, y) 672 | return self 673 | 674 | def rotate(self, angle): 675 | self *= Matrix3.new_rotate(angle) 676 | return self 677 | 678 | # Static constructors 679 | def new_identity(cls): 680 | self = cls() 681 | return self 682 | new_identity = classmethod(new_identity) 683 | 684 | def new_scale(cls, x, y): 685 | self = cls() 686 | self.a = x 687 | self.f = y 688 | return self 689 | new_scale = classmethod(new_scale) 690 | 691 | def new_translate(cls, x, y): 692 | self = cls() 693 | self.c = x 694 | self.g = y 695 | return self 696 | new_translate = classmethod(new_translate) 697 | 698 | def new_rotate(cls, angle): 699 | self = cls() 700 | s = math.sin(angle) 701 | c = math.cos(angle) 702 | self.a = self.f = c 703 | self.b = -s 704 | self.e = s 705 | return self 706 | new_rotate = classmethod(new_rotate) 707 | 708 | # a b c d 709 | # e f g h 710 | # i j k l 711 | # m n o p 712 | 713 | class Matrix4: 714 | __slots__ = list('abcdefghijklmnop') 715 | 716 | def __init__(self): 717 | self.identity() 718 | 719 | def __copy__(self): 720 | M = Matrix4() 721 | M.a = self.a 722 | M.b = self.b 723 | M.c = self.c 724 | M.d = self.d 725 | M.e = self.e 726 | M.f = self.f 727 | M.g = self.g 728 | M.h = self.h 729 | M.i = self.i 730 | M.j = self.j 731 | M.k = self.k 732 | M.l = self.l 733 | M.m = self.m 734 | M.n = self.n 735 | M.o = self.o 736 | M.p = self.p 737 | return M 738 | 739 | copy = __copy__ 740 | 741 | def __repr__(self): 742 | return ('Matrix4([% 8.2f % 8.2f % 8.2f % 8.2f\n' \ 743 | ' % 8.2f % 8.2f % 8.2f % 8.2f\n' \ 744 | ' % 8.2f % 8.2f % 8.2f % 8.2f\n' \ 745 | ' % 8.2f % 8.2f % 8.2f % 8.2f])') \ 746 | % (self.a, self.b, self.c, self.d, 747 | self.e, self.f, self.g, self.h, 748 | self.i, self.j, self.k, self.l, 749 | self.m, self.n, self.o, self.p) 750 | 751 | def __getitem__(self, key): 752 | return [self.a, self.e, self.i, self.m, 753 | self.b, self.f, self.j, self.n, 754 | self.c, self.g, self.k, self.o, 755 | self.d, self.h, self.l, self.p][key] 756 | 757 | def __setitem__(self, key, value): 758 | assert not isinstance(key, slice) or \ 759 | key.stop - key.start == len(value), 'key length != value length' 760 | L = self[:] 761 | L[key] = value 762 | (self.a, self.e, self.i, self.m, 763 | self.b, self.f, self.j, self.n, 764 | self.c, self.g, self.k, self.o, 765 | self.d, self.h, self.l, self.p) = L 766 | 767 | def __mul__(self, other): 768 | if isinstance(other, Matrix4): 769 | # Cache attributes in local vars (see Matrix3.__mul__). 770 | Aa = self.a 771 | Ab = self.b 772 | Ac = self.c 773 | Ad = self.d 774 | Ae = self.e 775 | Af = self.f 776 | Ag = self.g 777 | Ah = self.h 778 | Ai = self.i 779 | Aj = self.j 780 | Ak = self.k 781 | Al = self.l 782 | Am = self.m 783 | An = self.n 784 | Ao = self.o 785 | Ap = self.p 786 | Ba = other.a 787 | Bb = other.b 788 | Bc = other.c 789 | Bd = other.d 790 | Be = other.e 791 | Bf = other.f 792 | Bg = other.g 793 | Bh = other.h 794 | Bi = other.i 795 | Bj = other.j 796 | Bk = other.k 797 | Bl = other.l 798 | Bm = other.m 799 | Bn = other.n 800 | Bo = other.o 801 | Bp = other.p 802 | C = Matrix4() 803 | C.a = Aa * Ba + Ab * Be + Ac * Bi + Ad * Bm 804 | C.b = Aa * Bb + Ab * Bf + Ac * Bj + Ad * Bn 805 | C.c = Aa * Bc + Ab * Bg + Ac * Bk + Ad * Bo 806 | C.d = Aa * Bd + Ab * Bh + Ac * Bl + Ad * Bp 807 | C.e = Ae * Ba + Af * Be + Ag * Bi + Ah * Bm 808 | C.f = Ae * Bb + Af * Bf + Ag * Bj + Ah * Bn 809 | C.g = Ae * Bc + Af * Bg + Ag * Bk + Ah * Bo 810 | C.h = Ae * Bd + Af * Bh + Ag * Bl + Ah * Bp 811 | C.i = Ai * Ba + Aj * Be + Ak * Bi + Al * Bm 812 | C.j = Ai * Bb + Aj * Bf + Ak * Bj + Al * Bn 813 | C.k = Ai * Bc + Aj * Bg + Ak * Bk + Al * Bo 814 | C.l = Ai * Bd + Aj * Bh + Ak * Bl + Al * Bp 815 | C.m = Am * Ba + An * Be + Ao * Bi + Ap * Bm 816 | C.n = Am * Bb + An * Bf + Ao * Bj + Ap * Bn 817 | C.o = Am * Bc + An * Bg + Ao * Bk + Ap * Bo 818 | C.p = Am * Bd + An * Bh + Ao * Bl + Ap * Bp 819 | return C 820 | elif isinstance(other, Point3): 821 | A = self 822 | B = other 823 | P = Point3(0, 0, 0) 824 | P.x = A.a * B.x + A.b * B.y + A.c * B.z + A.d 825 | P.y = A.e * B.x + A.f * B.y + A.g * B.z + A.h 826 | P.z = A.i * B.x + A.j * B.y + A.k * B.z + A.l 827 | return P 828 | elif isinstance(other, Vector3): 829 | A = self 830 | B = other 831 | V = Vector3(0, 0, 0) 832 | V.x = A.a * B.x + A.b * B.y + A.c * B.z 833 | V.y = A.e * B.x + A.f * B.y + A.g * B.z 834 | V.z = A.i * B.x + A.j * B.y + A.k * B.z 835 | return V 836 | else: 837 | other = other.copy() 838 | other._apply_transform(self) 839 | return other 840 | 841 | def __imul__(self, other): 842 | assert isinstance(other, Matrix4) 843 | # Cache attributes in local vars (see Matrix3.__mul__). 844 | Aa = self.a 845 | Ab = self.b 846 | Ac = self.c 847 | Ad = self.d 848 | Ae = self.e 849 | Af = self.f 850 | Ag = self.g 851 | Ah = self.h 852 | Ai = self.i 853 | Aj = self.j 854 | Ak = self.k 855 | Al = self.l 856 | Am = self.m 857 | An = self.n 858 | Ao = self.o 859 | Ap = self.p 860 | Ba = other.a 861 | Bb = other.b 862 | Bc = other.c 863 | Bd = other.d 864 | Be = other.e 865 | Bf = other.f 866 | Bg = other.g 867 | Bh = other.h 868 | Bi = other.i 869 | Bj = other.j 870 | Bk = other.k 871 | Bl = other.l 872 | Bm = other.m 873 | Bn = other.n 874 | Bo = other.o 875 | Bp = other.p 876 | self.a = Aa * Ba + Ab * Be + Ac * Bi + Ad * Bm 877 | self.b = Aa * Bb + Ab * Bf + Ac * Bj + Ad * Bn 878 | self.c = Aa * Bc + Ab * Bg + Ac * Bk + Ad * Bo 879 | self.d = Aa * Bd + Ab * Bh + Ac * Bl + Ad * Bp 880 | self.e = Ae * Ba + Af * Be + Ag * Bi + Ah * Bm 881 | self.f = Ae * Bb + Af * Bf + Ag * Bj + Ah * Bn 882 | self.g = Ae * Bc + Af * Bg + Ag * Bk + Ah * Bo 883 | self.h = Ae * Bd + Af * Bh + Ag * Bl + Ah * Bp 884 | self.i = Ai * Ba + Aj * Be + Ak * Bi + Al * Bm 885 | self.j = Ai * Bb + Aj * Bf + Ak * Bj + Al * Bn 886 | self.k = Ai * Bc + Aj * Bg + Ak * Bk + Al * Bo 887 | self.l = Ai * Bd + Aj * Bh + Ak * Bl + Al * Bp 888 | self.m = Am * Ba + An * Be + Ao * Bi + Ap * Bm 889 | self.n = Am * Bb + An * Bf + Ao * Bj + Ap * Bn 890 | self.o = Am * Bc + An * Bg + Ao * Bk + Ap * Bo 891 | self.p = Am * Bd + An * Bh + Ao * Bl + Ap * Bp 892 | return self 893 | 894 | def identity(self): 895 | self.a = self.f = self.k = self.p = 1. 896 | self.b = self.c = self.d = self.e = self.g = self.h = \ 897 | self.i = self.j = self.l = self.m = self.n = self.o = 0 898 | return self 899 | 900 | def scale(self, x, y, z): 901 | self *= Matrix4.new_scale(x, y, z) 902 | return self 903 | 904 | def translate(self, x, y, z): 905 | self *= Matrix4.new_translate(x, y, z) 906 | return self 907 | 908 | def rotatex(self, angle): 909 | self *= Matrix4.new_rotatex(angle) 910 | return self 911 | 912 | def rotatey(self, angle): 913 | self *= Matrix4.new_rotatey(angle) 914 | return self 915 | 916 | def rotatez(self, angle): 917 | self *= Matrix4.new_rotatez(angle) 918 | return self 919 | 920 | def rotate_axis(self, angle, axis): 921 | self *= Matrix4.new_rotate_axis(angle, axis) 922 | return self 923 | 924 | def rotate_euler(self, heading, attitude, bank): 925 | self *= Matrix4.new_rotate_euler(heading, attitude, bank) 926 | return self 927 | 928 | # Static constructors 929 | def new_identity(cls): 930 | self = cls() 931 | return self 932 | new_identity = classmethod(new_identity) 933 | 934 | def new_scale(cls, x, y, z): 935 | self = cls() 936 | self.a = x 937 | self.f = y 938 | self.k = z 939 | return self 940 | new_scale = classmethod(new_scale) 941 | 942 | def new_translate(cls, x, y, z): 943 | self = cls() 944 | self.d = x 945 | self.h = y 946 | self.l = z 947 | return self 948 | new_translate = classmethod(new_translate) 949 | 950 | def new_rotatex(cls, angle): 951 | self = cls() 952 | s = math.sin(angle) 953 | c = math.cos(angle) 954 | self.f = self.k = c 955 | self.g = -s 956 | self.j = s 957 | return self 958 | new_rotatex = classmethod(new_rotatex) 959 | 960 | def new_rotatey(cls, angle): 961 | self = cls() 962 | s = math.sin(angle) 963 | c = math.cos(angle) 964 | self.a = self.k = c 965 | self.c = s 966 | self.i = -s 967 | return self 968 | new_rotatey = classmethod(new_rotatey) 969 | 970 | def new_rotatez(cls, angle): 971 | self = cls() 972 | s = math.sin(angle) 973 | c = math.cos(angle) 974 | self.a = self.f = c 975 | self.b = -s 976 | self.e = s 977 | return self 978 | new_rotatez = classmethod(new_rotatez) 979 | 980 | def new_rotate_axis(cls, angle, axis): 981 | assert(isinstance(axis, Vector3)) 982 | vector = axis.normalized() 983 | x = vector.x 984 | y = vector.y 985 | z = vector.z 986 | 987 | self = cls() 988 | s = math.sin(angle) 989 | c = math.cos(angle) 990 | c1 = 1. - c 991 | 992 | # from the glRotate man page 993 | self.a = x * x * c1 + c 994 | self.b = x * y * c1 - z * s 995 | self.c = x * z * c1 + y * s 996 | self.e = y * x * c1 + z * s 997 | self.f = y * y * c1 + c 998 | self.g = y * z * c1 - x * s 999 | self.i = x * z * c1 - y * s 1000 | self.j = y * z * c1 + x * s 1001 | self.k = z * z * c1 + c 1002 | return self 1003 | new_rotate_axis = classmethod(new_rotate_axis) 1004 | 1005 | def new_rotate_euler(cls, heading, attitude, bank): 1006 | # from http://www.euclideanspace.com/ 1007 | ch = math.cos(heading) 1008 | sh = math.sin(heading) 1009 | ca = math.cos(attitude) 1010 | sa = math.sin(attitude) 1011 | cb = math.cos(bank) 1012 | sb = math.sin(bank) 1013 | 1014 | self = cls() 1015 | self.a = ch * ca 1016 | self.b = sh * sb - ch * sa * cb 1017 | self.c = ch * sa * sb + sh * cb 1018 | self.e = sa 1019 | self.f = ca * cb 1020 | self.g = -ca * sb 1021 | self.i = -sh * ca 1022 | self.j = sh * sa * cb + ch * sb 1023 | self.k = -sh * sa * sb + ch * cb 1024 | return self 1025 | new_rotate_euler = classmethod(new_rotate_euler) 1026 | 1027 | def new_perspective(cls, fov_y, aspect, near, far): 1028 | # from the gluPerspective man page 1029 | f = 1 / math.tan(fov_y / 2) 1030 | self = cls() 1031 | assert near != 0.0 and near != far 1032 | self.a = f / aspect 1033 | self.f = f 1034 | self.k = (far + near) / (near - far) 1035 | self.l = 2 * far * near / (near - far) 1036 | self.o = -1 1037 | self.p = 0 1038 | return self 1039 | new_perspective = classmethod(new_perspective) 1040 | 1041 | class Quaternion: 1042 | # All methods and naming conventions based off 1043 | # http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions 1044 | 1045 | # w is the real part, (x, y, z) are the imaginary parts 1046 | __slots__ = ['w', 'x', 'y', 'z'] 1047 | 1048 | def __init__(self): 1049 | self.identity() 1050 | 1051 | def __copy__(self): 1052 | Q = Quaternion() 1053 | Q.w = self.w 1054 | Q.x = self.x 1055 | Q.y = self.y 1056 | Q.z = self.z 1057 | 1058 | copy = __copy__ 1059 | 1060 | def __repr__(self): 1061 | return 'Quaternion(real=%.2f, imag=<%.2f, %.2f, %.2f>)' % \ 1062 | (self.w, self.x, self.y, self.z) 1063 | 1064 | def __mul__(self, other): 1065 | if isinstance(other, Quaternion): 1066 | Ax = self.x 1067 | Ay = self.y 1068 | Az = self.z 1069 | Aw = self.w 1070 | Bx = other.x 1071 | By = other.y 1072 | Bz = other.z 1073 | Bw = other.w 1074 | Q = Quaternion() 1075 | Q.x = Ax * Bw + Ay * Bz - Az * By + Aw * Bx 1076 | Q.y = -Ax * Bz + Ay * Bw + Az * Bx + Aw * By 1077 | Q.z = Ax * By - Ay * Bx + Az * Bw + Aw * Bz 1078 | Q.w = -Ax * Bx - Ay * By - Az * Bz + Aw * Bw 1079 | return Q 1080 | elif isinstance(other, Vector3): 1081 | w = self.w 1082 | x = self.x 1083 | y = self.y 1084 | z = self.z 1085 | Vx = other.x 1086 | Vy = other.y 1087 | Vz = other.z 1088 | return other.__class__(\ 1089 | w * w * Vx + 2 * y * w * Vz - 2 * z * w * Vy + \ 1090 | x * x * Vx + 2 * y * x * Vy + 2 * z * x * Vz - \ 1091 | z * z * Vx - y * y * Vx, 1092 | 2 * x * y * Vx + y * y * Vy + 2 * z * y * Vz + \ 1093 | 2 * w * z * Vx - z * z * Vy + w * w * Vy - \ 1094 | 2 * x * w * Vz - x * x * Vy, 1095 | 2 * x * z * Vx + 2 * y * z * Vy + \ 1096 | z * z * Vz - 2 * w * y * Vx - y * y * Vz + \ 1097 | 2 * w * x * Vy - x * x * Vz + w * w * Vz) 1098 | else: 1099 | other = other.copy() 1100 | other._apply_transform(self) 1101 | return other 1102 | 1103 | def __imul__(self, other): 1104 | assert isinstance(other, Quaternion) 1105 | Ax = self.x 1106 | Ay = self.y 1107 | Az = self.z 1108 | Aw = self.w 1109 | Bx = other.x 1110 | By = other.y 1111 | Bz = other.z 1112 | Bw = other.w 1113 | self.x = Ax * Bw + Ay * Bz - Az * By + Aw * Bx 1114 | self.y = -Ax * Bz + Ay * Bw + Az * Bx + Aw * By 1115 | self.z = Ax * By - Ay * Bx + Az * Bw + Aw * Bz 1116 | self.w = -Ax * Bx - Ay * By - Az * Bz + Aw * Bw 1117 | return self 1118 | 1119 | def __abs__(self): 1120 | return math.sqrt(self.w ** 2 + \ 1121 | self.x ** 2 + \ 1122 | self.y ** 2 + \ 1123 | self.z ** 2) 1124 | 1125 | magnitude = __abs__ 1126 | 1127 | def magnitude_squared(self): 1128 | return self.w ** 2 + \ 1129 | self.x ** 2 + \ 1130 | self.y ** 2 + \ 1131 | self.z ** 2 1132 | 1133 | def identity(self): 1134 | self.w = 1 1135 | self.x = 0 1136 | self.y = 0 1137 | self.z = 0 1138 | return self 1139 | 1140 | def rotate_axis(self, angle, axis): 1141 | self *= Quaternion.new_rotate_axis(angle, axis) 1142 | return self 1143 | 1144 | def rotate_euler(self, heading, attitude, bank): 1145 | self *= Quaternion.new_rotate_euler(heading, attitude, bank) 1146 | return self 1147 | 1148 | def conjugated(self): 1149 | Q = Quaternion() 1150 | Q.w = self.w 1151 | Q.x = -self.x 1152 | Q.y = -self.y 1153 | Q.z = -self.z 1154 | return Q 1155 | 1156 | def normalize(self): 1157 | d = self.magnitude() 1158 | if d != 0: 1159 | self.w /= d 1160 | self.x /= d 1161 | self.y /= d 1162 | self.z /= d 1163 | return self 1164 | 1165 | def normalized(self): 1166 | d = self.magnitude() 1167 | if d != 0: 1168 | Q = Quaternion() 1169 | Q.w /= d 1170 | Q.x /= d 1171 | Q.y /= d 1172 | Q.z /= d 1173 | return Q 1174 | else: 1175 | return self.copy() 1176 | 1177 | def get_angle_axis(self): 1178 | if self.w > 1: 1179 | self = self.normalized() 1180 | angle = 2 * math.acos(self.w) 1181 | s = math.sqrt(1 - self.w ** 2) 1182 | if s < 0.001: 1183 | return angle, Vector3(1, 0, 0) 1184 | else: 1185 | return angle, Vector3(self.x / s, self.y / s, self.z / s) 1186 | 1187 | def get_euler(self): 1188 | t = self.x * self.y + self.z * self.w 1189 | if t > 0.4999: 1190 | heading = 2 * math.atan2(self.x, self.w) 1191 | attitude = math.pi / 2 1192 | bank = 0 1193 | elif t < -0.4999: 1194 | heading = -2 * math.atan2(self.x, self.w) 1195 | attitude = -math.pi / 2 1196 | bank = 0 1197 | else: 1198 | sqx = self.x ** 2 1199 | sqy = self.y ** 2 1200 | sqz = self.z ** 2 1201 | heading = math.atan2(2 * self.y * self.w - 2 * self.x * self.z, 1202 | 1 - 2 * sqy - 2 * sqz) 1203 | attitude = math.asin(2 * t) 1204 | bank = math.atan2(2 * self.x * self.w - 2 * self.y * self.z, 1205 | 1 - 2 * sqx - 2 * sqz) 1206 | return heading, attitude, bank 1207 | 1208 | def get_matrix(self): 1209 | xx = self.x ** 2 1210 | xy = self.x * self.y 1211 | xz = self.x * self.z 1212 | xw = self.x * self.w 1213 | yy = self.y ** 2 1214 | yz = self.y * self.z 1215 | yw = self.y * self.w 1216 | zz = self.z ** 2 1217 | zw = self.z * self.w 1218 | M = Matrix4() 1219 | M.a = 1 - 2 * (yy + zz) 1220 | M.b = 2 * (xy - zw) 1221 | M.c = 2 * (xz + yw) 1222 | M.e = 2 * (xy + zw) 1223 | M.f = 1 - 2 * (xx + zz) 1224 | M.g = 2 * (yz - xw) 1225 | M.i = 2 * (xz - yw) 1226 | M.j = 2 * (yz + xw) 1227 | M.k = 1 - 2 * (xx + yy) 1228 | return M 1229 | 1230 | # Static constructors 1231 | def new_identity(cls): 1232 | return cls() 1233 | new_identity = classmethod(new_identity) 1234 | 1235 | def new_rotate_axis(cls, angle, axis): 1236 | assert(isinstance(axis, Vector3)) 1237 | axis = axis.normalized() 1238 | s = math.sin(angle / 2) 1239 | Q = cls() 1240 | Q.w = math.cos(angle / 2) 1241 | Q.x = axis.x * s 1242 | Q.y = axis.y * s 1243 | Q.z = axis.z * s 1244 | return Q 1245 | new_rotate_axis = classmethod(new_rotate_axis) 1246 | 1247 | def new_rotate_euler(cls, heading, attitude, bank): 1248 | Q = cls() 1249 | c1 = math.cos(heading / 2) 1250 | s1 = math.sin(heading / 2) 1251 | c2 = math.cos(attitude / 2) 1252 | s2 = math.sin(attitude / 2) 1253 | c3 = math.cos(bank / 2) 1254 | s3 = math.sin(bank / 2) 1255 | 1256 | Q.w = c1 * c2 * c3 - s1 * s2 * s3 1257 | Q.x = s1 * s2 * c3 + c1 * c2 * s3 1258 | Q.y = s1 * c2 * c3 + c1 * s2 * s3 1259 | Q.z = c1 * s2 * c3 - s1 * c2 * s3 1260 | return Q 1261 | new_rotate_euler = classmethod(new_rotate_euler) 1262 | 1263 | def new_interpolate(cls, q1, q2, t): 1264 | assert isinstance(q1, Quaternion) and isinstance(q2, Quaternion) 1265 | Q = cls() 1266 | 1267 | costheta = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z 1268 | theta = math.acos(costheta) 1269 | if abs(theta) < 0.01: 1270 | Q.w = q2.w 1271 | Q.x = q2.x 1272 | Q.y = q2.y 1273 | Q.z = q2.z 1274 | return Q 1275 | 1276 | sintheta = math.sqrt(1.0 - costheta * costheta) 1277 | if abs(sintheta) < 0.01: 1278 | Q.w = (q1.w + q2.w) * 0.5 1279 | Q.x = (q1.x + q2.x) * 0.5 1280 | Q.y = (q1.y + q2.y) * 0.5 1281 | Q.z = (q1.z + q2.z) * 0.5 1282 | return Q 1283 | 1284 | ratio1 = math.sin((1 - t) * theta) / sintheta 1285 | ratio2 = math.sin(t * theta) / sintheta 1286 | 1287 | Q.w = q1.w * ratio1 + q2.w * ratio2 1288 | Q.x = q1.x * ratio1 + q2.x * ratio2 1289 | Q.y = q1.y * ratio1 + q2.y * ratio2 1290 | Q.z = q1.z * ratio1 + q2.z * ratio2 1291 | return Q 1292 | new_interpolate = classmethod(new_interpolate) 1293 | 1294 | # Geometry 1295 | # Much maths thanks to Paul Bourke, http://astronomy.swin.edu.au/~pbourke 1296 | # --------------------------------------------------------------------------- 1297 | 1298 | class Geometry: 1299 | def _connect_unimplemented(self, other): 1300 | print('Cannot connect %s to %s' % (self.__class__, other.__class__)) 1301 | raise AttributeError 1302 | 1303 | def _intersect_unimplemented(self, other): 1304 | print('Cannot intersect %s and %s' % (self.__class__, other.__class__)) 1305 | raise AttributeError 1306 | 1307 | _intersect_point2 = _intersect_unimplemented 1308 | _intersect_line2 = _intersect_unimplemented 1309 | _intersect_circle = _intersect_unimplemented 1310 | _connect_point2 = _connect_unimplemented 1311 | _connect_line2 = _connect_unimplemented 1312 | _connect_circle = _connect_unimplemented 1313 | 1314 | _intersect_point3 = _intersect_unimplemented 1315 | _intersect_line3 = _intersect_unimplemented 1316 | _intersect_sphere = _intersect_unimplemented 1317 | _intersect_plane = _intersect_unimplemented 1318 | _connect_point3 = _connect_unimplemented 1319 | _connect_line3 = _connect_unimplemented 1320 | _connect_sphere = _connect_unimplemented 1321 | _connect_plane = _connect_unimplemented 1322 | 1323 | def intersect(self, other): 1324 | raise NotImplementedError 1325 | 1326 | def connect(self, other): 1327 | raise NotImplementedError 1328 | 1329 | def distance(self, other): 1330 | c = self.connect(other) 1331 | if c: 1332 | return c.length 1333 | return 0.0 1334 | 1335 | def _intersect_point2_circle(P, C): 1336 | return abs(P - C.c) <= C.r 1337 | 1338 | def _intersect_line2_line2(A, B): 1339 | d = B.v.y * A.v.x - B.v.x * A.v.y 1340 | if d == 0: 1341 | return None 1342 | 1343 | dy = A.p.y - B.p.y 1344 | dx = A.p.x - B.p.x 1345 | ua = (B.v.x * dy - B.v.y * dx) / d 1346 | if not A._u_in(ua): 1347 | return None 1348 | ub = (A.v.x * dy - A.v.y * dx) / d 1349 | if not B._u_in(ub): 1350 | return None 1351 | 1352 | return Point2(A.p.x + ua * A.v.x, 1353 | A.p.y + ua * A.v.y) 1354 | 1355 | def _intersect_line2_circle(L, C): 1356 | a = L.v.magnitude_squared() 1357 | b = 2 * (L.v.x * (L.p.x - C.c.x) + \ 1358 | L.v.y * (L.p.y - C.c.y)) 1359 | c = C.c.magnitude_squared() + \ 1360 | L.p.magnitude_squared() - \ 1361 | 2 * C.c.dot(L.p) - \ 1362 | C.r ** 2 1363 | det = b ** 2 - 4 * a * c 1364 | if det < 0: 1365 | return None 1366 | sq = math.sqrt(det) 1367 | u1 = (-b + sq) / (2 * a) 1368 | u2 = (-b - sq) / (2 * a) 1369 | if not L._u_in(u1): 1370 | u1 = max(min(u1, 1.0), 0.0) 1371 | if not L._u_in(u2): 1372 | u2 = max(min(u2, 1.0), 0.0) 1373 | return LineSegment2(Point2(L.p.x + u1 * L.v.x, 1374 | L.p.y + u1 * L.v.y), 1375 | Point2(L.p.x + u2 * L.v.x, 1376 | L.p.y + u2 * L.v.y)) 1377 | 1378 | def _connect_point2_line2(P, L): 1379 | d = L.v.magnitude_squared() 1380 | assert d != 0 1381 | u = ((P.x - L.p.x) * L.v.x + \ 1382 | (P.y - L.p.y) * L.v.y) / d 1383 | if not L._u_in(u): 1384 | u = max(min(u, 1.0), 0.0) 1385 | return LineSegment2(P, 1386 | Point2(L.p.x + u * L.v.x, 1387 | L.p.y + u * L.v.y)) 1388 | 1389 | def _connect_point2_circle(P, C): 1390 | v = P - C.c 1391 | v.normalize() 1392 | v *= C.r 1393 | return LineSegment2(P, Point2(C.c.x + v.x, C.c.y + v.y)) 1394 | 1395 | def _connect_line2_line2(A, B): 1396 | d = B.v.y * A.v.x - B.v.x * A.v.y 1397 | if d == 0: 1398 | # Parallel, connect an endpoint with a line 1399 | if isinstance(B, Ray2) or isinstance(B, LineSegment2): 1400 | p1, p2 = _connect_point2_line2(B.p, A) 1401 | return p2, p1 1402 | # No endpoint (or endpoint is on A), possibly choose arbitrary point 1403 | # on line. 1404 | return _connect_point2_line2(A.p, B) 1405 | 1406 | dy = A.p.y - B.p.y 1407 | dx = A.p.x - B.p.x 1408 | ua = (B.v.x * dy - B.v.y * dx) / d 1409 | if not A._u_in(ua): 1410 | ua = max(min(ua, 1.0), 0.0) 1411 | ub = (A.v.x * dy - A.v.y * dx) / d 1412 | if not B._u_in(ub): 1413 | ub = max(min(ub, 1.0), 0.0) 1414 | 1415 | return LineSegment2(Point2(A.p.x + ua * A.v.x, A.p.y + ua * A.v.y), 1416 | Point2(B.p.x + ub * B.v.x, B.p.y + ub * B.v.y)) 1417 | 1418 | def _connect_circle_line2(C, L): 1419 | d = L.v.magnitude_squared() 1420 | assert d != 0 1421 | u = ((C.c.x - L.p.x) * L.v.x + (C.c.y - L.p.y) * L.v.y) / d 1422 | if not L._u_in(u): 1423 | u = max(min(u, 1.0), 0.0) 1424 | point = Point2(L.p.x + u * L.v.x, L.p.y + u * L.v.y) 1425 | v = (point - C.c) 1426 | v.normalize() 1427 | v *= C.r 1428 | return LineSegment2(Point2(C.c.x + v.x, C.c.y + v.y), point) 1429 | 1430 | def _connect_circle_circle(A, B): 1431 | v = B.c - A.c 1432 | v.normalize() 1433 | return LineSegment2(Point2(A.c.x + v.x * A.r, A.c.y + v.y * A.r), 1434 | Point2(B.c.x - v.x * B.r, B.c.y - v.y * B.r)) 1435 | 1436 | 1437 | class Point2(Vector2, Geometry): 1438 | def __repr__(self): 1439 | return 'Point2(%.2f, %.2f)' % (self.x, self.y) 1440 | 1441 | def intersect(self, other): 1442 | return other._intersect_point2(self) 1443 | 1444 | def _intersect_circle(self, other): 1445 | return _intersect_point2_circle(self, other) 1446 | 1447 | def connect(self, other): 1448 | return other._connect_point2(self) 1449 | 1450 | def _connect_point2(self, other): 1451 | return LineSegment2(other, self) 1452 | 1453 | def _connect_line2(self, other): 1454 | c = _connect_point2_line2(self, other) 1455 | if c: 1456 | return c._swap() 1457 | 1458 | def _connect_circle(self, other): 1459 | c = _connect_point2_circle(self, other) 1460 | if c: 1461 | return c._swap() 1462 | 1463 | class Line2(Geometry): 1464 | __slots__ = ['p', 'v'] 1465 | 1466 | def __init__(self, *args): 1467 | if len(args) == 3: 1468 | assert isinstance(args[0], Point2) and \ 1469 | isinstance(args[1], Vector2) and \ 1470 | type(args[2]) == float 1471 | self.p = args[0].copy() 1472 | self.v = args[1] * args[2] / abs(args[1]) 1473 | elif len(args) == 2: 1474 | if isinstance(args[0], Point2) and isinstance(args[1], Point2): 1475 | self.p = args[0].copy() 1476 | self.v = args[1] - args[0] 1477 | elif isinstance(args[0], Point2) and isinstance(args[1], Vector2): 1478 | self.p = args[0].copy() 1479 | self.v = args[1].copy() 1480 | else: 1481 | raise AttributeError 1482 | elif len(args) == 1: 1483 | if isinstance(args[0], Line2): 1484 | self.p = args[0].p.copy() 1485 | self.v = args[0].v.copy() 1486 | else: 1487 | raise AttributeError 1488 | else: 1489 | raise AttributeError 1490 | 1491 | if not self.v: 1492 | print('Line has zero-length vector') 1493 | raise AttributeError 1494 | 1495 | def __copy__(self): 1496 | return self.__class__(self.p, self.v) 1497 | 1498 | copy = __copy__ 1499 | 1500 | def __repr__(self): 1501 | return 'Line2(<%.2f, %.2f> + u<%.2f, %.2f>)' % \ 1502 | (self.p.x, self.p.y, self.v.x, self.v.y) 1503 | 1504 | p1 = property(lambda self: self.p) 1505 | p2 = property(lambda self: Point2(self.p.x + self.v.x, 1506 | self.p.y + self.v.y)) 1507 | 1508 | def _apply_transform(self, t): 1509 | self.p = t * self.p 1510 | self.v = t * self.v 1511 | 1512 | def _u_in(self, u): 1513 | return True 1514 | 1515 | def intersect(self, other): 1516 | return other._intersect_line2(self) 1517 | 1518 | def _intersect_line2(self, other): 1519 | return _intersect_line2_line2(self, other) 1520 | 1521 | def _intersect_circle(self, other): 1522 | return _intersect_line2_circle(self, other) 1523 | 1524 | def connect(self, other): 1525 | return other._connect_line2(self) 1526 | 1527 | def _connect_point2(self, other): 1528 | return _connect_point2_line2(other, self) 1529 | 1530 | def _connect_line2(self, other): 1531 | return _connect_line2_line2(other, self) 1532 | 1533 | def _connect_circle(self, other): 1534 | return _connect_circle_line2(other, self) 1535 | 1536 | class Ray2(Line2): 1537 | def __repr__(self): 1538 | return 'Ray2(<%.2f, %.2f> + u<%.2f, %.2f>)' % \ 1539 | (self.p.x, self.p.y, self.v.x, self.v.y) 1540 | 1541 | def _u_in(self, u): 1542 | return u >= 0.0 1543 | 1544 | class LineSegment2(Line2): 1545 | def __repr__(self): 1546 | return 'LineSegment2(<%.2f, %.2f> to <%.2f, %.2f>)' % \ 1547 | (self.p.x, self.p.y, self.p.x + self.v.x, self.p.y + self.v.y) 1548 | 1549 | def _u_in(self, u): 1550 | return u >= 0.0 and u <= 1.0 1551 | 1552 | def __abs__(self): 1553 | return abs(self.v) 1554 | 1555 | def magnitude_squared(self): 1556 | return self.v.magnitude_squared() 1557 | 1558 | def _swap(self): 1559 | # used by connect methods to switch order of points 1560 | self.p = self.p2 1561 | self.v *= -1 1562 | return self 1563 | 1564 | length = property(lambda self: abs(self.v)) 1565 | 1566 | class Circle(Geometry): 1567 | __slots__ = ['c', 'r'] 1568 | 1569 | def __init__(self, center, radius): 1570 | assert isinstance(center, Vector2) and type(radius) == float 1571 | self.c = center.copy() 1572 | self.r = radius 1573 | 1574 | def __copy__(self): 1575 | return self.__class__(self.c, self.r) 1576 | 1577 | copy = __copy__ 1578 | 1579 | def __repr__(self): 1580 | return 'Circle(<%.2f, %.2f>, radius=%.2f)' % \ 1581 | (self.c.x, self.c.y, self.r) 1582 | 1583 | def _apply_transform(self, t): 1584 | self.c = t * self.c 1585 | 1586 | def intersect(self, other): 1587 | return other._intersect_circle(self) 1588 | 1589 | def _intersect_point2(self, other): 1590 | return _intersect_point2_circle(other, self) 1591 | 1592 | def _intersect_line2(self, other): 1593 | return _intersect_line2_circle(other, self) 1594 | 1595 | def connect(self, other): 1596 | return other._connect_circle(self) 1597 | 1598 | def _connect_point2(self, other): 1599 | return _connect_point2_circle(other, self) 1600 | 1601 | def _connect_line2(self, other): 1602 | c = _connect_circle_line2(self, other) 1603 | if c: 1604 | return c._swap() 1605 | 1606 | def _connect_circle(self, other): 1607 | return _connect_circle_circle(other, self) 1608 | 1609 | # 3D Geometry 1610 | # ------------------------------------------------------------------------- 1611 | 1612 | def _connect_point3_line3(P, L): 1613 | d = L.v.magnitude_squared() 1614 | assert d != 0 1615 | u = ((P.x - L.p.x) * L.v.x + \ 1616 | (P.y - L.p.y) * L.v.y + \ 1617 | (P.z - L.p.z) * L.v.z) / d 1618 | if not L._u_in(u): 1619 | u = max(min(u, 1.0), 0.0) 1620 | return LineSegment3(P, Point3(L.p.x + u * L.v.x, 1621 | L.p.y + u * L.v.y, 1622 | L.p.z + u * L.v.z)) 1623 | 1624 | def _connect_point3_sphere(P, S): 1625 | v = P - S.c 1626 | v.normalize() 1627 | v *= S.r 1628 | return LineSegment3(P, Point3(S.c.x + v.x, S.c.y + v.y, S.c.z + v.z)) 1629 | 1630 | def _connect_point3_plane(p, plane): 1631 | n = plane.n.normalized() 1632 | d = p.dot(plane.n) - plane.k 1633 | return LineSegment3(p, Point3(p.x - n.x * d, p.y - n.y * d, p.z - n.z * d)) 1634 | 1635 | def _connect_line3_line3(A, B): 1636 | assert A.v and B.v 1637 | p13 = A.p - B.p 1638 | d1343 = p13.dot(B.v) 1639 | d4321 = B.v.dot(A.v) 1640 | d1321 = p13.dot(A.v) 1641 | d4343 = B.v.magnitude_squared() 1642 | denom = A.v.magnitude_squared() * d4343 - d4321 ** 2 1643 | if denom == 0: 1644 | # Parallel, connect an endpoint with a line 1645 | if isinstance(B, Ray3) or isinstance(B, LineSegment3): 1646 | return _connect_point3_line3(B.p, A)._swap() 1647 | # No endpoint (or endpoint is on A), possibly choose arbitrary 1648 | # point on line. 1649 | return _connect_point3_line3(A.p, B) 1650 | 1651 | ua = (d1343 * d4321 - d1321 * d4343) / denom 1652 | if not A._u_in(ua): 1653 | ua = max(min(ua, 1.0), 0.0) 1654 | ub = (d1343 + d4321 * ua) / d4343 1655 | if not B._u_in(ub): 1656 | ub = max(min(ub, 1.0), 0.0) 1657 | return LineSegment3(Point3(A.p.x + ua * A.v.x, 1658 | A.p.y + ua * A.v.y, 1659 | A.p.z + ua * A.v.z), 1660 | Point3(B.p.x + ub * B.v.x, 1661 | B.p.y + ub * B.v.y, 1662 | B.p.z + ub * B.v.z)) 1663 | 1664 | def _connect_line3_plane(L, P): 1665 | d = P.n.dot(L.v) 1666 | if not d: 1667 | # Parallel, choose an endpoint 1668 | return _connect_point3_plane(L.p, P) 1669 | u = (P.k - P.n.dot(L.p)) / d 1670 | if not L._u_in(u): 1671 | # intersects out of range, choose nearest endpoint 1672 | u = max(min(u, 1.0), 0.0) 1673 | return _connect_point3_plane(Point3(L.p.x + u * L.v.x, 1674 | L.p.y + u * L.v.y, 1675 | L.p.z + u * L.v.z), P) 1676 | # Intersection 1677 | return None 1678 | 1679 | def _connect_sphere_line3(S, L): 1680 | d = L.v.magnitude_squared() 1681 | assert d != 0 1682 | u = ((S.c.x - L.p.x) * L.v.x + \ 1683 | (S.c.y - L.p.y) * L.v.y + \ 1684 | (S.c.z - L.p.z) * L.v.z) / d 1685 | if not L._u_in(u): 1686 | u = max(min(u, 1.0), 0.0) 1687 | point = Point3(L.p.x + u * L.v.x, L.p.y + u * L.v.y, L.p.z + u * L.v.z) 1688 | v = (point - S.c) 1689 | v.normalize() 1690 | v *= S.r 1691 | return LineSegment3(Point3(S.c.x + v.x, S.c.y + v.y, S.c.z + v.z), 1692 | point) 1693 | 1694 | def _connect_sphere_sphere(A, B): 1695 | v = B.c - A.c 1696 | v.normalize() 1697 | return LineSegment3(Point3(A.c.x + v.x * A.r, 1698 | A.c.y + v.y * A.r, 1699 | A.c.x + v.z * A.r), 1700 | Point3(B.c.x + v.x * B.r, 1701 | B.c.y + v.y * B.r, 1702 | B.c.x + v.z * B.r)) 1703 | 1704 | def _connect_sphere_plane(S, P): 1705 | c = _connect_point3_plane(S.c, P) 1706 | if not c: 1707 | return None 1708 | p2 = c.p2 1709 | v = p2 - S.c 1710 | v.normalize() 1711 | v *= S.r 1712 | return LineSegment3(Point3(S.c.x + v.x, S.c.y + v.y, S.c.z + v.z), 1713 | p2) 1714 | 1715 | def _connect_plane_plane(A, B): 1716 | if A.n.cross(B.n): 1717 | # Planes intersect 1718 | return None 1719 | else: 1720 | # Planes are parallel, connect to arbitrary point 1721 | return _connect_point3_plane(A._get_point(), B) 1722 | 1723 | def _intersect_point3_sphere(P, S): 1724 | return abs(P - S.c) <= S.r 1725 | 1726 | def _intersect_line3_sphere(L, S): 1727 | a = L.v.magnitude_squared() 1728 | b = 2 * (L.v.x * (L.p.x - S.c.x) + \ 1729 | L.v.y * (L.p.y - S.c.y) + \ 1730 | L.v.z * (L.p.z - S.c.z)) 1731 | c = S.c.magnitude_squared() + \ 1732 | L.p.magnitude_squared() - \ 1733 | 2 * S.c.dot(L.p) - \ 1734 | S.r ** 2 1735 | det = b ** 2 - 4 * a * c 1736 | if det < 0: 1737 | return None 1738 | sq = math.sqrt(det) 1739 | u1 = (-b + sq) / (2 * a) 1740 | u2 = (-b - sq) / (2 * a) 1741 | if not L._u_in(u1): 1742 | u1 = max(min(u1, 1.0), 0.0) 1743 | if not L._u_in(u2): 1744 | u2 = max(min(u2, 1.0), 0.0) 1745 | return LineSegment3(Point3(L.p.x + u1 * L.v.x, 1746 | L.p.y + u1 * L.v.y, 1747 | L.p.z + u1 * L.v.z), 1748 | Point3(L.p.x + u2 * L.v.x, 1749 | L.p.y + u2 * L.v.y, 1750 | L.p.z + u2 * L.v.z)) 1751 | 1752 | def _intersect_line3_plane(L, P): 1753 | d = P.n.dot(L.v) 1754 | if not d: 1755 | # Parallel 1756 | return None 1757 | u = (P.k - P.n.dot(L.p)) / d 1758 | if not L._u_in(u): 1759 | return None 1760 | return Point3(L.p.x + u * L.v.x, 1761 | L.p.y + u * L.v.y, 1762 | L.p.z + u * L.v.z) 1763 | 1764 | def _intersect_plane_plane(A, B): 1765 | n1_m = A.n.magnitude_squared() 1766 | n2_m = B.n.magnitude_squared() 1767 | n1d2 = A.n.dot(B.n) 1768 | det = n1_m * n2_m - n1d2 ** 2 1769 | if det == 0: 1770 | # Parallel 1771 | return None 1772 | c1 = (A.k * n2_m - B.k * n1d2) / det 1773 | c2 = (B.k * n1_m - A.k * n1d2) / det 1774 | return Line3(Point3(c1 * A.n.x + c2 * B.n.x, 1775 | c1 * A.n.y + c2 * B.n.y, 1776 | c1 * A.n.z + c2 * B.n.z), 1777 | A.n.cross(B.n)) 1778 | 1779 | class Point3(Vector3, Geometry): 1780 | def __repr__(self): 1781 | return 'Point3(%.2f, %.2f, %.2f)' % (self.x, self.y, self.z) 1782 | 1783 | def intersect(self, other): 1784 | return other._intersect_point3(self) 1785 | 1786 | def _intersect_sphere(self, other): 1787 | return _intersect_point3_sphere(self, other) 1788 | 1789 | def connect(self, other): 1790 | other._connect_point3(self) 1791 | 1792 | def _connect_point3(self, other): 1793 | if self != other: 1794 | return LineSegment3(other, self) 1795 | return None 1796 | 1797 | def _connect_line3(self, other): 1798 | c = _connect_point3_line3(self, other) 1799 | if c: 1800 | return c._swap() 1801 | 1802 | def _connect_sphere(self, other): 1803 | c = _connect_point3_sphere(self, other) 1804 | if c: 1805 | return c._swap() 1806 | 1807 | def _connect_plane(self, other): 1808 | c = _connect_point3_plane(self, other) 1809 | if c: 1810 | return c._swap() 1811 | 1812 | class Line3: 1813 | __slots__ = ['p', 'v'] 1814 | 1815 | def __init__(self, *args): 1816 | if len(args) == 3: 1817 | assert isinstance(args[0], Point3) and \ 1818 | isinstance(args[1], Vector3) and \ 1819 | type(args[2]) == float 1820 | self.p = args[0].copy() 1821 | self.v = args[1] * args[2] / abs(args[1]) 1822 | elif len(args) == 2: 1823 | if isinstance(args[0], Point3) and isinstance(args[1], Point3): 1824 | self.p = args[0].copy() 1825 | self.v = args[1] - args[0] 1826 | elif isinstance(args[0], Point3) and isinstance(args[1], Vector3): 1827 | self.p = args[0].copy() 1828 | self.v = args[1].copy() 1829 | else: 1830 | raise AttributeError 1831 | elif len(args) == 1: 1832 | if isinstance(args[0], Line3): 1833 | self.p = args[0].p.copy() 1834 | self.v = args[0].v.copy() 1835 | else: 1836 | raise AttributeError 1837 | else: 1838 | raise AttributeError 1839 | 1840 | # XXX This is annoying. 1841 | #if not self.v: 1842 | # raise AttributeError, 'Line has zero-length vector' 1843 | 1844 | def __copy__(self): 1845 | return self.__class__(self.p, self.v) 1846 | 1847 | copy = __copy__ 1848 | 1849 | def __repr__(self): 1850 | return 'Line3(<%.2f, %.2f, %.2f> + u<%.2f, %.2f, %.2f>)' % \ 1851 | (self.p.x, self.p.y, self.p.z, self.v.x, self.v.y, self.v.z) 1852 | 1853 | p1 = property(lambda self: self.p) 1854 | p2 = property(lambda self: Point3(self.p.x + self.v.x, 1855 | self.p.y + self.v.y, 1856 | self.p.z + self.v.z)) 1857 | 1858 | def _apply_transform(self, t): 1859 | self.p = t * self.p 1860 | self.v = t * self.v 1861 | 1862 | def _u_in(self, u): 1863 | return True 1864 | 1865 | def intersect(self, other): 1866 | return other._intersect_line3(self) 1867 | 1868 | def _intersect_sphere(self, other): 1869 | return _intersect_line3_sphere(self, other) 1870 | 1871 | def _intersect_plane(self, other): 1872 | return _intersect_line3_plane(self, other) 1873 | 1874 | def connect(self, other): 1875 | return other._connect_line3(self) 1876 | 1877 | def _connect_point3(self, other): 1878 | return _connect_point3_line3(other, self) 1879 | 1880 | def _connect_line3(self, other): 1881 | return _connect_line3_line3(other, self) 1882 | 1883 | def _connect_sphere(self, other): 1884 | return _connect_sphere_line3(other, self) 1885 | 1886 | def _connect_plane(self, other): 1887 | c = _connect_line3_plane(self, other) 1888 | if c: 1889 | return c 1890 | 1891 | class Ray3(Line3): 1892 | def __repr__(self): 1893 | return 'Ray3(<%.2f, %.2f, %.2f> + u<%.2f, %.2f, %.2f>)' % \ 1894 | (self.p.x, self.p.y, self.p.z, self.v.x, self.v.y, self.v.z) 1895 | 1896 | def _u_in(self, u): 1897 | return u >= 0.0 1898 | 1899 | class LineSegment3(Line3): 1900 | def __repr__(self): 1901 | return 'LineSegment3(<%.2f, %.2f, %.2f> to <%.2f, %.2f, %.2f>)' % \ 1902 | (self.p.x, self.p.y, self.p.z, 1903 | self.p.x + self.v.x, self.p.y + self.v.y, self.p.z + self.v.z) 1904 | 1905 | def _u_in(self, u): 1906 | return u >= 0.0 and u <= 1.0 1907 | 1908 | def __abs__(self): 1909 | return abs(self.v) 1910 | 1911 | def magnitude_squared(self): 1912 | return self.v.magnitude_squared() 1913 | 1914 | def _swap(self): 1915 | # used by connect methods to switch order of points 1916 | self.p = self.p2 1917 | self.v *= -1 1918 | return self 1919 | 1920 | length = property(lambda self: abs(self.v)) 1921 | 1922 | class Sphere: 1923 | __slots__ = ['c', 'r'] 1924 | 1925 | def __init__(self, center, radius): 1926 | assert isinstance(center, Vector3) and type(radius) == float 1927 | self.c = center.copy() 1928 | self.r = radius 1929 | 1930 | def __copy__(self): 1931 | return self.__class__(self.c, self.r) 1932 | 1933 | copy = __copy__ 1934 | 1935 | def __repr__(self): 1936 | return 'Sphere(<%.2f, %.2f, %.2f>, radius=%.2f)' % \ 1937 | (self.c.x, self.c.y, self.c.z, self.r) 1938 | 1939 | def _apply_transform(self, t): 1940 | self.c = t * self.c 1941 | 1942 | def intersect(self, other): 1943 | return other._intersect_sphere(self) 1944 | 1945 | def _intersect_point3(self, other): 1946 | return _intersect_point3_sphere(other, self) 1947 | 1948 | def _intersect_line3(self, other): 1949 | return _intersect_line3_sphere(other, self) 1950 | 1951 | def connect(self, other): 1952 | return other._connect_sphere(self) 1953 | 1954 | def _connect_point3(self, other): 1955 | return _connect_point3_sphere(other, self) 1956 | 1957 | def _connect_line3(self, other): 1958 | c = _connect_sphere_line3(self, other) 1959 | if c: 1960 | return c._swap() 1961 | 1962 | def _connect_sphere(self, other): 1963 | return _connect_sphere_sphere(other, self) 1964 | 1965 | def _connect_plane(self, other): 1966 | c = _connect_sphere_plane(self, other) 1967 | if c: 1968 | return c 1969 | 1970 | class Plane: 1971 | # n.p = k, where n is normal, p is point on plane, k is constant scalar 1972 | __slots__ = ['n', 'k'] 1973 | 1974 | def __init__(self, *args): 1975 | if len(args) == 3: 1976 | assert isinstance(args[0], Point3) and \ 1977 | isinstance(args[1], Point3) and \ 1978 | isinstance(args[2], Point3) 1979 | self.n = (args[1] - args[0]).cross(args[2] - args[0]) 1980 | self.n.normalize() 1981 | self.k = self.n.dot(args[0]) 1982 | elif len(args) == 2: 1983 | if isinstance(args[0], Point3) and isinstance(args[1], Vector3): 1984 | self.n = args[1].normalized() 1985 | self.k = self.n.dot(args[0]) 1986 | elif isinstance(args[0], Vector3) and type(args[1]) == float: 1987 | self.n = args[0].normalized() 1988 | self.k = args[1] 1989 | else: 1990 | raise AttributeError 1991 | 1992 | else: 1993 | raise AttributeError 1994 | 1995 | if not self.n: 1996 | print('Points on plane are colinear') 1997 | raise AttributeError 1998 | 1999 | def __copy__(self): 2000 | return self.__class__(self.n, self.k) 2001 | 2002 | copy = __copy__ 2003 | 2004 | def __repr__(self): 2005 | return 'Plane(<%.2f, %.2f, %.2f>.p = %.2f)' % \ 2006 | (self.n.x, self.n.y, self.n.z, self.k) 2007 | 2008 | def _get_point(self): 2009 | # Return an arbitrary point on the plane 2010 | if self.n.z: 2011 | return Point3(0., 0., self.k / self.n.z) 2012 | elif self.n.y: 2013 | return Point3(0., self.k / self.n.y, 0.) 2014 | else: 2015 | return Point3(self.k / self.n.x, 0., 0.) 2016 | 2017 | def _apply_transform(self, t): 2018 | p = t * self._get_point() 2019 | self.n = t * self.n 2020 | self.k = self.n.dot(p) 2021 | 2022 | def intersect(self, other): 2023 | return other._intersect_plane(self) 2024 | 2025 | def _intersect_line3(self, other): 2026 | return _intersect_line3_plane(other, self) 2027 | 2028 | def _intersect_plane(self, other): 2029 | return _intersect_plane_plane(self, other) 2030 | 2031 | def connect(self, other): 2032 | return other._connect_plane(self) 2033 | 2034 | def _connect_point3(self, other): 2035 | return _connect_point3_plane(other, self) 2036 | 2037 | def _connect_line3(self, other): 2038 | return _connect_line3_plane(other, self) 2039 | 2040 | def _connect_sphere(self, other): 2041 | return _connect_sphere_plane(other, self) 2042 | 2043 | def _connect_plane(self, other): 2044 | return _connect_plane_plane(other, self) 2045 | 2046 | --------------------------------------------------------------------------------