├── 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 |
--------------------------------------------------------------------------------