3
# euclid graphics maths module
5
# Copyright (c) 2006 Alex Holkner
6
# Alex.Holkner@mail.google.com
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.
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
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
22
'''euclid graphics maths module
24
Documentation and tests are included in the file "euclid.txt", or online
25
at http://code.google.com/p/pyeuclid
28
__docformat__ = 'restructuredtext'
30
__revision__ = '$Revision$'
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__.
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).
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
52
# Requires class to derive from object.
53
if _enable_swizzle_set:
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__'])
63
return type.__new__(cls, name, bases + (object,), dct)
65
if '__slots__' in dct:
67
return types.ClassType.__new__(types.ClassType, name, bases, dct)
70
def _create_getstate(cls, slots):
71
def __getstate__(self):
74
d[slot] = getattr(self, slot)
79
def _create_setstate(cls, slots):
80
def __setstate__(self, state):
81
for name, value in state.items():
82
setattr(self, name, value)
85
__metaclass__ = _EuclidMetaclass
88
__slots__ = ['x', 'y']
90
def __init__(self, x=0, y=0):
95
return self.__class__(self.x, self.y)
100
return 'Vector2(%.2f, %.2f)' % (self.x, self.y)
102
def __eq__(self, other):
103
if isinstance(other, Vector2):
104
return self.x == other.x and \
107
assert hasattr(other, '__len__') and len(other) == 2
108
return self.x == other[0] and \
111
def __neq__(self, other):
112
return not self.__eq__(other)
114
def __nonzero__(self):
115
return self.x != 0 or self.y != 0
120
def __getitem__(self, key):
121
return (self.x, self.y)[key]
123
def __setitem__(self, key, value):
129
return iter((self.x, self.y))
131
def __getattr__(self, name):
133
return tuple([(self.x, self.y)['xy'.index(c)] \
136
raise AttributeError, name
138
if _enable_swizzle_set:
139
# This has detrimental performance on ordinary setattr as well
141
def __setattr__(self, name, value):
143
object.__setattr__(self, name, value)
147
for c, v in map(None, name, value):
151
raise AttributeError, name
153
def __add__(self, other):
154
if isinstance(other, Vector2):
155
# Vector + Vector -> Vector
156
# Vector + Point -> Point
157
# Point + Point -> Vector
158
if self.__class__ is other.__class__:
162
return _class(self.x + other.x,
165
assert hasattr(other, '__len__') and len(other) == 2
166
return Vector2(self.x + other[0],
170
def __iadd__(self, other):
171
if isinstance(other, Vector2):
179
def __sub__(self, other):
180
if isinstance(other, Vector2):
181
# Vector - Vector -> Vector
182
# Vector - Point -> Point
183
# Point - Point -> Vector
184
if self.__class__ is other.__class__:
188
return _class(self.x - other.x,
191
assert hasattr(other, '__len__') and len(other) == 2
192
return Vector2(self.x - other[0],
196
def __rsub__(self, other):
197
if isinstance(other, Vector2):
198
return Vector2(other.x - self.x,
201
assert hasattr(other, '__len__') and len(other) == 2
202
return Vector2(other.x - self[0],
205
def __mul__(self, other):
206
assert type(other) in (int, long, float)
207
return Vector2(self.x * other,
212
def __imul__(self, other):
213
assert type(other) in (int, long, float)
218
def __div__(self, other):
219
assert type(other) in (int, long, float)
220
return Vector2(operator.div(self.x, other),
221
operator.div(self.y, other))
224
def __rdiv__(self, other):
225
assert type(other) in (int, long, float)
226
return Vector2(operator.div(other, self.x),
227
operator.div(other, self.y))
229
def __floordiv__(self, other):
230
assert type(other) in (int, long, float)
231
return Vector2(operator.floordiv(self.x, other),
232
operator.floordiv(self.y, other))
235
def __rfloordiv__(self, other):
236
assert type(other) in (int, long, float)
237
return Vector2(operator.floordiv(other, self.x),
238
operator.floordiv(other, self.y))
240
def __truediv__(self, other):
241
assert type(other) in (int, long, float)
242
return Vector2(operator.truediv(self.x, other),
243
operator.truediv(self.y, other))
246
def __rtruediv__(self, other):
247
assert type(other) in (int, long, float)
248
return Vector2(operator.truediv(other, self.x),
249
operator.truediv(other, self.y))
252
return Vector2(-self.x,
258
return math.sqrt(self.x ** 2 + \
263
def magnitude_squared(self):
264
return self.x ** 2 + \
274
def normalized(self):
277
return Vector2(self.x / d,
281
def dot(self, other):
282
assert isinstance(other, Vector2)
283
return self.x * other.x + \
287
return Vector2(self.y, -self.x)
289
def reflect(self, normal):
290
# assume normal is normalized
291
assert isinstance(normal, Vector2)
292
d = 2 * (self.x * normal.x + self.y * normal.y)
293
return Vector2(self.x - d * normal.x,
294
self.y - d * normal.y)
297
__slots__ = ['x', 'y', 'z']
299
def __init__(self, x=0, y=0, z=0):
305
return self.__class__(self.x, self.y, self.z)
310
return 'Vector3(%.2f, %.2f, %.2f)' % (self.x,
314
def __eq__(self, other):
315
if isinstance(other, Vector3):
316
return self.x == other.x and \
317
self.y == other.y and \
320
assert hasattr(other, '__len__') and len(other) == 3
321
return self.x == other[0] and \
322
self.y == other[1] and \
325
def __neq__(self, other):
326
return not self.__eq__(other)
328
def __nonzero__(self):
329
return self.x != 0 or self.y != 0 or self.z != 0
334
def __getitem__(self, key):
335
return (self.x, self.y, self.z)[key]
337
def __setitem__(self, key, value):
338
l = [self.x, self.y, self.z]
340
self.x, self.y, self.z = l
343
return iter((self.x, self.y, self.z))
345
def __getattr__(self, name):
347
return tuple([(self.x, self.y, self.z)['xyz'.index(c)] \
350
raise AttributeError, name
352
if _enable_swizzle_set:
353
# This has detrimental performance on ordinary setattr as well
355
def __setattr__(self, name, value):
357
object.__setattr__(self, name, value)
360
l = [self.x, self.y, self.z]
361
for c, v in map(None, name, value):
362
l['xyz'.index(c)] = v
363
self.x, self.y, self.z = l
365
raise AttributeError, name
368
def __add__(self, other):
369
if isinstance(other, Vector3):
370
# Vector + Vector -> Vector
371
# Vector + Point -> Point
372
# Point + Point -> Vector
373
if self.__class__ is other.__class__:
377
return _class(self.x + other.x,
381
assert hasattr(other, '__len__') and len(other) == 3
382
return Vector3(self.x + other[0],
387
def __iadd__(self, other):
388
if isinstance(other, Vector3):
398
def __sub__(self, other):
399
if isinstance(other, Vector3):
400
# Vector - Vector -> Vector
401
# Vector - Point -> Point
402
# Point - Point -> Vector
403
if self.__class__ is other.__class__:
407
return Vector3(self.x - other.x,
411
assert hasattr(other, '__len__') and len(other) == 3
412
return Vector3(self.x - other[0],
417
def __rsub__(self, other):
418
if isinstance(other, Vector3):
419
return Vector3(other.x - self.x,
423
assert hasattr(other, '__len__') and len(other) == 3
424
return Vector3(other.x - self[0],
428
def __mul__(self, other):
429
if isinstance(other, Vector3):
430
# TODO component-wise mul/div in-place and on Vector2; docs.
431
if self.__class__ is Point3 or other.__class__ is Point3:
435
return _class(self.x * other.x,
439
assert type(other) in (int, long, float)
440
return Vector3(self.x * other,
446
def __imul__(self, other):
447
assert type(other) in (int, long, float)
453
def __div__(self, other):
454
assert type(other) in (int, long, float)
455
return Vector3(operator.div(self.x, other),
456
operator.div(self.y, other),
457
operator.div(self.z, other))
460
def __rdiv__(self, other):
461
assert type(other) in (int, long, float)
462
return Vector3(operator.div(other, self.x),
463
operator.div(other, self.y),
464
operator.div(other, self.z))
466
def __floordiv__(self, other):
467
assert type(other) in (int, long, float)
468
return Vector3(operator.floordiv(self.x, other),
469
operator.floordiv(self.y, other),
470
operator.floordiv(self.z, other))
473
def __rfloordiv__(self, other):
474
assert type(other) in (int, long, float)
475
return Vector3(operator.floordiv(other, self.x),
476
operator.floordiv(other, self.y),
477
operator.floordiv(other, self.z))
479
def __truediv__(self, other):
480
assert type(other) in (int, long, float)
481
return Vector3(operator.truediv(self.x, other),
482
operator.truediv(self.y, other),
483
operator.truediv(self.z, other))
486
def __rtruediv__(self, other):
487
assert type(other) in (int, long, float)
488
return Vector3(operator.truediv(other, self.x),
489
operator.truediv(other, self.y),
490
operator.truediv(other, self.z))
493
return Vector3(-self.x,
500
return math.sqrt(self.x ** 2 + \
506
def magnitude_squared(self):
507
return self.x ** 2 + \
519
def normalized(self):
522
return Vector3(self.x / d,
527
def dot(self, other):
528
assert isinstance(other, Vector3)
529
return self.x * other.x + \
533
def cross(self, other):
534
assert isinstance(other, Vector3)
535
return Vector3(self.y * other.z - self.z * other.y,
536
-self.x * other.z + self.z * other.x,
537
self.x * other.y - self.y * other.x)
539
def reflect(self, normal):
540
# assume normal is normalized
541
assert isinstance(normal, Vector3)
542
d = 2 * (self.x * normal.x + self.y * normal.y + self.z * normal.z)
543
return Vector3(self.x - d * normal.x,
544
self.y - d * normal.y,
545
self.z - d * normal.z)
552
__slots__ = list('abcefgijk')
572
return ('Matrix3([% 8.2f % 8.2f % 8.2f\n' \
573
' % 8.2f % 8.2f % 8.2f\n' \
574
' % 8.2f % 8.2f % 8.2f])') \
575
% (self.a, self.b, self.c,
576
self.e, self.f, self.g,
577
self.i, self.j, self.k)
579
def __getitem__(self, key):
580
return [self.a, self.e, self.i,
581
self.b, self.f, self.j,
582
self.c, self.g, self.k][key]
584
def __setitem__(self, key, value):
587
(self.a, self.e, self.i,
588
self.b, self.f, self.j,
589
self.c, self.g, self.k) = L
591
def __mul__(self, other):
592
if isinstance(other, Matrix3):
593
# Caching repeatedly accessed attributes in local variables
594
# apparently increases performance by 20%. Attrib: Will McGugan.
614
C.a = Aa * Ba + Ab * Be + Ac * Bi
615
C.b = Aa * Bb + Ab * Bf + Ac * Bj
616
C.c = Aa * Bc + Ab * Bg + Ac * Bk
617
C.e = Ae * Ba + Af * Be + Ag * Bi
618
C.f = Ae * Bb + Af * Bf + Ag * Bj
619
C.g = Ae * Bc + Af * Bg + Ag * Bk
620
C.i = Ai * Ba + Aj * Be + Ak * Bi
621
C.j = Ai * Bb + Aj * Bf + Ak * Bj
622
C.k = Ai * Bc + Aj * Bg + Ak * Bk
624
elif isinstance(other, Point2):
628
P.x = A.a * B.x + A.b * B.y + A.c
629
P.y = A.e * B.x + A.f * B.y + A.g
631
elif isinstance(other, Vector2):
635
V.x = A.a * B.x + A.b * B.y
636
V.y = A.e * B.x + A.f * B.y
640
other._apply_transform(self)
643
def __imul__(self, other):
644
assert isinstance(other, Matrix3)
645
# Cache attributes in local vars (see Matrix3.__mul__).
664
self.a = Aa * Ba + Ab * Be + Ac * Bi
665
self.b = Aa * Bb + Ab * Bf + Ac * Bj
666
self.c = Aa * Bc + Ab * Bg + Ac * Bk
667
self.e = Ae * Ba + Af * Be + Ag * Bi
668
self.f = Ae * Bb + Af * Bf + Ag * Bj
669
self.g = Ae * Bc + Af * Bg + Ag * Bk
670
self.i = Ai * Ba + Aj * Be + Ak * Bi
671
self.j = Ai * Bb + Aj * Bf + Ak * Bj
672
self.k = Ai * Bc + Aj * Bg + Ak * Bk
676
self.a = self.f = self.k = 1.
677
self.b = self.c = self.e = self.g = self.i = self.j = 0
680
def scale(self, x, y):
681
self *= Matrix3.new_scale(x, y)
684
def translate(self, x, y):
685
self *= Matrix3.new_translate(x, y)
688
def rotate(self, angle):
689
self *= Matrix3.new_rotate(angle)
692
# Static constructors
693
def new_identity(cls):
696
new_identity = classmethod(new_identity)
698
def new_scale(cls, x, y):
703
new_scale = classmethod(new_scale)
705
def new_translate(cls, x, y):
710
new_translate = classmethod(new_translate)
712
def new_rotate(cls, angle):
720
new_rotate = classmethod(new_rotate)
728
__slots__ = list('abcdefghijklmnop')
757
return ('Matrix4([% 8.2f % 8.2f % 8.2f % 8.2f\n' \
758
' % 8.2f % 8.2f % 8.2f % 8.2f\n' \
759
' % 8.2f % 8.2f % 8.2f % 8.2f\n' \
760
' % 8.2f % 8.2f % 8.2f % 8.2f])') \
761
% (self.a, self.b, self.c, self.d,
762
self.e, self.f, self.g, self.h,
763
self.i, self.j, self.k, self.l,
764
self.m, self.n, self.o, self.p)
766
def __getitem__(self, key):
767
return [self.a, self.e, self.i, self.m,
768
self.b, self.f, self.j, self.n,
769
self.c, self.g, self.k, self.o,
770
self.d, self.h, self.l, self.p][key]
772
def __setitem__(self, key, value):
775
(self.a, self.e, self.i, self.m,
776
self.b, self.f, self.j, self.n,
777
self.c, self.g, self.k, self.o,
778
self.d, self.h, self.l, self.p) = L
780
def __mul__(self, other):
781
if isinstance(other, Matrix4):
782
# Cache attributes in local vars (see Matrix3.__mul__).
816
C.a = Aa * Ba + Ab * Be + Ac * Bi + Ad * Bm
817
C.b = Aa * Bb + Ab * Bf + Ac * Bj + Ad * Bn
818
C.c = Aa * Bc + Ab * Bg + Ac * Bk + Ad * Bo
819
C.d = Aa * Bd + Ab * Bh + Ac * Bl + Ad * Bp
820
C.e = Ae * Ba + Af * Be + Ag * Bi + Ah * Bm
821
C.f = Ae * Bb + Af * Bf + Ag * Bj + Ah * Bn
822
C.g = Ae * Bc + Af * Bg + Ag * Bk + Ah * Bo
823
C.h = Ae * Bd + Af * Bh + Ag * Bl + Ah * Bp
824
C.i = Ai * Ba + Aj * Be + Ak * Bi + Al * Bm
825
C.j = Ai * Bb + Aj * Bf + Ak * Bj + Al * Bn
826
C.k = Ai * Bc + Aj * Bg + Ak * Bk + Al * Bo
827
C.l = Ai * Bd + Aj * Bh + Ak * Bl + Al * Bp
828
C.m = Am * Ba + An * Be + Ao * Bi + Ap * Bm
829
C.n = Am * Bb + An * Bf + Ao * Bj + Ap * Bn
830
C.o = Am * Bc + An * Bg + Ao * Bk + Ap * Bo
831
C.p = Am * Bd + An * Bh + Ao * Bl + Ap * Bp
833
elif isinstance(other, Point3):
837
P.x = A.a * B.x + A.b * B.y + A.c * B.z + A.d
838
P.y = A.e * B.x + A.f * B.y + A.g * B.z + A.h
839
P.z = A.i * B.x + A.j * B.y + A.k * B.z + A.l
841
elif isinstance(other, Vector3):
845
V.x = A.a * B.x + A.b * B.y + A.c * B.z
846
V.y = A.e * B.x + A.f * B.y + A.g * B.z
847
V.z = A.i * B.x + A.j * B.y + A.k * B.z
851
other._apply_transform(self)
854
def __imul__(self, other):
855
assert isinstance(other, Matrix4)
856
# Cache attributes in local vars (see Matrix3.__mul__).
889
self.a = Aa * Ba + Ab * Be + Ac * Bi + Ad * Bm
890
self.b = Aa * Bb + Ab * Bf + Ac * Bj + Ad * Bn
891
self.c = Aa * Bc + Ab * Bg + Ac * Bk + Ad * Bo
892
self.d = Aa * Bd + Ab * Bh + Ac * Bl + Ad * Bp
893
self.e = Ae * Ba + Af * Be + Ag * Bi + Ah * Bm
894
self.f = Ae * Bb + Af * Bf + Ag * Bj + Ah * Bn
895
self.g = Ae * Bc + Af * Bg + Ag * Bk + Ah * Bo
896
self.h = Ae * Bd + Af * Bh + Ag * Bl + Ah * Bp
897
self.i = Ai * Ba + Aj * Be + Ak * Bi + Al * Bm
898
self.j = Ai * Bb + Aj * Bf + Ak * Bj + Al * Bn
899
self.k = Ai * Bc + Aj * Bg + Ak * Bk + Al * Bo
900
self.l = Ai * Bd + Aj * Bh + Ak * Bl + Al * Bp
901
self.m = Am * Ba + An * Be + Ao * Bi + Ap * Bm
902
self.n = Am * Bb + An * Bf + Ao * Bj + Ap * Bn
903
self.o = Am * Bc + An * Bg + Ao * Bk + Ap * Bo
904
self.p = Am * Bd + An * Bh + Ao * Bl + Ap * Bp
907
def transform(self, other):
911
P.x = A.a * B.x + A.b * B.y + A.c * B.z + A.d
912
P.y = A.e * B.x + A.f * B.y + A.g * B.z + A.h
913
P.z = A.i * B.x + A.j * B.y + A.k * B.z + A.l
914
w = A.m * B.x + A.n * B.y + A.o * B.z + A.p
922
self.a = self.f = self.k = self.p = 1.
923
self.b = self.c = self.d = self.e = self.g = self.h = \
924
self.i = self.j = self.l = self.m = self.n = self.o = 0
927
def scale(self, x, y, z):
928
self *= Matrix4.new_scale(x, y, z)
931
def translate(self, x, y, z):
932
self *= Matrix4.new_translate(x, y, z)
935
def rotatex(self, angle):
936
self *= Matrix4.new_rotatex(angle)
939
def rotatey(self, angle):
940
self *= Matrix4.new_rotatey(angle)
943
def rotatez(self, angle):
944
self *= Matrix4.new_rotatez(angle)
947
def rotate_axis(self, angle, axis):
948
self *= Matrix4.new_rotate_axis(angle, axis)
951
def rotate_euler(self, heading, attitude, bank):
952
self *= Matrix4.new_rotate_euler(heading, attitude, bank)
955
def rotate_triple_axis(self, x, y, z):
956
self *= Matrix4.new_rotate_triple_axis(x, y, z)
960
(self.a, self.e, self.i, self.m,
961
self.b, self.f, self.j, self.n,
962
self.c, self.g, self.k, self.o,
963
self.d, self.h, self.l, self.p) = \
964
(self.a, self.b, self.c, self.d,
965
self.e, self.f, self.g, self.h,
966
self.i, self.j, self.k, self.l,
967
self.m, self.n, self.o, self.p)
969
def transposed(self):
974
# Static constructors
975
def new(cls, *values):
979
new = classmethod(new)
981
def new_identity(cls):
984
new_identity = classmethod(new_identity)
986
def new_scale(cls, x, y, z):
992
new_scale = classmethod(new_scale)
994
def new_translate(cls, x, y, z):
1000
new_translate = classmethod(new_translate)
1002
def new_rotatex(cls, angle):
1010
new_rotatex = classmethod(new_rotatex)
1012
def new_rotatey(cls, angle):
1020
new_rotatey = classmethod(new_rotatey)
1022
def new_rotatez(cls, angle):
1030
new_rotatez = classmethod(new_rotatez)
1032
def new_rotate_axis(cls, angle, axis):
1033
assert(isinstance(axis, Vector3))
1034
vector = axis.normalized()
1044
# from the glRotate man page
1045
self.a = x * x * c1 + c
1046
self.b = x * y * c1 - z * s
1047
self.c = x * z * c1 + y * s
1048
self.e = y * x * c1 + z * s
1049
self.f = y * y * c1 + c
1050
self.g = y * z * c1 - x * s
1051
self.i = x * z * c1 - y * s
1052
self.j = y * z * c1 + x * s
1053
self.k = z * z * c1 + c
1055
new_rotate_axis = classmethod(new_rotate_axis)
1057
def new_rotate_euler(cls, heading, attitude, bank):
1058
# from http://www.euclideanspace.com/
1059
ch = math.cos(heading)
1060
sh = math.sin(heading)
1061
ca = math.cos(attitude)
1062
sa = math.sin(attitude)
1068
self.b = sh * sb - ch * sa * cb
1069
self.c = ch * sa * sb + sh * cb
1074
self.j = sh * sa * cb + ch * sb
1075
self.k = -sh * sa * sb + ch * cb
1077
new_rotate_euler = classmethod(new_rotate_euler)
1079
def new_rotate_triple_axis(cls, x, y, z):
1082
m.a, m.b, m.c = x.x, y.x, z.x
1083
m.e, m.f, m.g = x.y, y.y, z.y
1084
m.i, m.j, m.k = x.z, y.z, z.z
1087
new_rotate_triple_axis = classmethod(new_rotate_triple_axis)
1089
def new_look_at(cls, eye, at, up):
1090
z = (eye - at).normalized()
1091
x = up.cross(z).normalized()
1094
m = cls.new_rotate_triple_axis(x, y, z)
1095
m.d, m.h, m.l = eye.x, eye.y, eye.z
1097
new_look_at = classmethod(new_look_at)
1099
def new_perspective(cls, fov_y, aspect, near, far):
1100
# from the gluPerspective man page
1101
f = 1 / math.tan(fov_y / 2)
1103
assert near != 0.0 and near != far
1106
self.k = (far + near) / (near - far)
1107
self.l = 2 * far * near / (near - far)
1111
new_perspective = classmethod(new_perspective)
1113
def determinant(self):
1114
return ((self.a * self.f - self.e * self.b)
1115
* (self.k * self.p - self.o * self.l)
1116
- (self.a * self.j - self.i * self.b)
1117
* (self.g * self.p - self.o * self.h)
1118
+ (self.a * self.n - self.m * self.b)
1119
* (self.g * self.l - self.k * self.h)
1120
+ (self.e * self.j - self.i * self.f)
1121
* (self.c * self.p - self.o * self.d)
1122
- (self.e * self.n - self.m * self.f)
1123
* (self.c * self.l - self.k * self.d)
1124
+ (self.i * self.n - self.m * self.j)
1125
* (self.c * self.h - self.g * self.d))
1129
d = self.determinant();
1132
# No inverse, return identity
1137
tmp.a = d * (self.f * (self.k * self.p - self.o * self.l) + self.j * (self.o * self.h - self.g * self.p) + self.n * (self.g * self.l - self.k * self.h));
1138
tmp.e = d * (self.g * (self.i * self.p - self.m * self.l) + self.k * (self.m * self.h - self.e * self.p) + self.o * (self.e * self.l - self.i * self.h));
1139
tmp.i = d * (self.h * (self.i * self.n - self.m * self.j) + self.l * (self.m * self.f - self.e * self.n) + self.p * (self.e * self.j - self.i * self.f));
1140
tmp.m = d * (self.e * (self.n * self.k - self.j * self.o) + self.i * (self.f * self.o - self.n * self.g) + self.m * (self.j * self.g - self.f * self.k));
1142
tmp.b = d * (self.j * (self.c * self.p - self.o * self.d) + self.n * (self.k * self.d - self.c * self.l) + self.b * (self.o * self.l - self.k * self.p));
1143
tmp.f = d * (self.k * (self.a * self.p - self.m * self.d) + self.o * (self.i * self.d - self.a * self.l) + self.c * (self.m * self.l - self.i * self.p));
1144
tmp.j = d * (self.l * (self.a * self.n - self.m * self.b) + self.p * (self.i * self.b - self.a * self.j) + self.d * (self.m * self.j - self.i * self.n));
1145
tmp.n = d * (self.i * (self.n * self.c - self.b * self.o) + self.m * (self.b * self.k - self.j * self.c) + self.a * (self.j * self.o - self.n * self.k));
1147
tmp.c = d * (self.n * (self.c * self.h - self.g * self.d) + self.b * (self.g * self.p - self.o * self.h) + self.f * (self.o * self.d - self.c * self.p));
1148
tmp.g = d * (self.o * (self.a * self.h - self.e * self.d) + self.c * (self.e * self.p - self.m * self.h) + self.g * (self.m * self.d - self.a * self.p));
1149
tmp.k = d * (self.p * (self.a * self.f - self.e * self.b) + self.d * (self.e * self.n - self.m * self.f) + self.h * (self.m * self.b - self.a * self.n));
1150
tmp.o = d * (self.m * (self.f * self.c - self.b * self.g) + self.a * (self.n * self.g - self.f * self.o) + self.e * (self.b * self.o - self.n * self.c));
1152
tmp.d = d * (self.b * (self.k * self.h - self.g * self.l) + self.f * (self.c * self.l - self.k * self.d) + self.j * (self.g * self.d - self.c * self.h));
1153
tmp.h = d * (self.c * (self.i * self.h - self.e * self.l) + self.g * (self.a * self.l - self.i * self.d) + self.k * (self.e * self.d - self.a * self.h));
1154
tmp.l = d * (self.d * (self.i * self.f - self.e * self.j) + self.h * (self.a * self.j - self.i * self.b) + self.l * (self.e * self.b - self.a * self.f));
1155
tmp.p = d * (self.a * (self.f * self.k - self.j * self.g) + self.e * (self.j * self.c - self.b * self.k) + self.i * (self.b * self.g - self.f * self.c));
1161
# All methods and naming conventions based off
1162
# http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions
1164
# w is the real part, (x, y, z) are the imaginary parts
1165
__slots__ = ['w', 'x', 'y', 'z']
1167
def __init__(self, w=1, x=0, y=0, z=0):
1184
return 'Quaternion(real=%.2f, imag=<%.2f, %.2f, %.2f>)' % \
1185
(self.w, self.x, self.y, self.z)
1187
def __mul__(self, other):
1188
if isinstance(other, Quaternion):
1198
Q.x = Ax * Bw + Ay * Bz - Az * By + Aw * Bx
1199
Q.y = -Ax * Bz + Ay * Bw + Az * Bx + Aw * By
1200
Q.z = Ax * By - Ay * Bx + Az * Bw + Aw * Bz
1201
Q.w = -Ax * Bx - Ay * By - Az * Bz + Aw * Bw
1203
elif isinstance(other, Vector3):
1211
return other.__class__(\
1212
w * w * Vx + 2 * y * w * Vz - 2 * z * w * Vy + \
1213
x * x * Vx + 2 * y * x * Vy + 2 * z * x * Vz - \
1214
z * z * Vx - y * y * Vx,
1215
2 * x * y * Vx + y * y * Vy + 2 * z * y * Vz + \
1216
2 * w * z * Vx - z * z * Vy + w * w * Vy - \
1217
2 * x * w * Vz - x * x * Vy,
1218
2 * x * z * Vx + 2 * y * z * Vy + \
1219
z * z * Vz - 2 * w * y * Vx - y * y * Vz + \
1220
2 * w * x * Vy - x * x * Vz + w * w * Vz)
1222
other = other.copy()
1223
other._apply_transform(self)
1226
def __imul__(self, other):
1227
assert isinstance(other, Quaternion)
1236
self.x = Ax * Bw + Ay * Bz - Az * By + Aw * Bx
1237
self.y = -Ax * Bz + Ay * Bw + Az * Bx + Aw * By
1238
self.z = Ax * By - Ay * Bx + Az * Bw + Aw * Bz
1239
self.w = -Ax * Bx - Ay * By - Az * Bz + Aw * Bw
1243
return math.sqrt(self.w ** 2 + \
1250
def magnitude_squared(self):
1251
return self.w ** 2 + \
1263
def rotate_axis(self, angle, axis):
1264
self *= Quaternion.new_rotate_axis(angle, axis)
1267
def rotate_euler(self, heading, attitude, bank):
1268
self *= Quaternion.new_rotate_euler(heading, attitude, bank)
1271
def rotate_matrix(self, m):
1272
self *= Quaternion.new_rotate_matrix(m)
1275
def conjugated(self):
1283
def normalize(self):
1284
d = self.magnitude()
1292
def normalized(self):
1293
d = self.magnitude()
1304
def get_angle_axis(self):
1306
self = self.normalized()
1307
angle = 2 * math.acos(self.w)
1308
s = math.sqrt(1 - self.w ** 2)
1310
return angle, Vector3(1, 0, 0)
1312
return angle, Vector3(self.x / s, self.y / s, self.z / s)
1314
def get_euler(self):
1315
t = self.x * self.y + self.z * self.w
1317
heading = 2 * math.atan2(self.x, self.w)
1318
attitude = math.pi / 2
1321
heading = -2 * math.atan2(self.x, self.w)
1322
attitude = -math.pi / 2
1328
heading = math.atan2(2 * self.y * self.w - 2 * self.x * self.z,
1329
1 - 2 * sqy - 2 * sqz)
1330
attitude = math.asin(2 * t)
1331
bank = math.atan2(2 * self.x * self.w - 2 * self.y * self.z,
1332
1 - 2 * sqx - 2 * sqz)
1333
return heading, attitude, bank
1335
def get_matrix(self):
1337
xy = self.x * self.y
1338
xz = self.x * self.z
1339
xw = self.x * self.w
1341
yz = self.y * self.z
1342
yw = self.y * self.w
1344
zw = self.z * self.w
1346
M.a = 1 - 2 * (yy + zz)
1350
M.f = 1 - 2 * (xx + zz)
1354
M.k = 1 - 2 * (xx + yy)
1357
# Static constructors
1358
def new_identity(cls):
1360
new_identity = classmethod(new_identity)
1362
def new_rotate_axis(cls, angle, axis):
1363
assert(isinstance(axis, Vector3))
1364
axis = axis.normalized()
1365
s = math.sin(angle / 2)
1367
Q.w = math.cos(angle / 2)
1372
new_rotate_axis = classmethod(new_rotate_axis)
1374
def new_rotate_euler(cls, heading, attitude, bank):
1376
c1 = math.cos(heading / 2)
1377
s1 = math.sin(heading / 2)
1378
c2 = math.cos(attitude / 2)
1379
s2 = math.sin(attitude / 2)
1380
c3 = math.cos(bank / 2)
1381
s3 = math.sin(bank / 2)
1383
Q.w = c1 * c2 * c3 - s1 * s2 * s3
1384
Q.x = s1 * s2 * c3 + c1 * c2 * s3
1385
Q.y = s1 * c2 * c3 + c1 * s2 * s3
1386
Q.z = c1 * s2 * c3 - s1 * c2 * s3
1388
new_rotate_euler = classmethod(new_rotate_euler)
1390
def new_rotate_matrix(cls, m):
1391
if m[0*4 + 0] + m[1*4 + 1] + m[2*4 + 2] > 0.00000001:
1392
t = m[0*4 + 0] + m[1*4 + 1] + m[2*4 + 2] + 1.0
1393
s = 0.5/math.sqrt(t)
1397
(m[1*4 + 2] - m[2*4 + 1])*s,
1398
(m[2*4 + 0] - m[0*4 + 2])*s,
1399
(m[0*4 + 1] - m[1*4 + 0])*s
1402
elif m[0*4 + 0] > m[1*4 + 1] and m[0*4 + 0] > m[2*4 + 2]:
1403
t = m[0*4 + 0] - m[1*4 + 1] - m[2*4 + 2] + 1.0
1404
s = 0.5/math.sqrt(t)
1407
(m[1*4 + 2] - m[2*4 + 1])*s,
1409
(m[0*4 + 1] + m[1*4 + 0])*s,
1410
(m[2*4 + 0] + m[0*4 + 2])*s
1413
elif m[1*4 + 1] > m[2*4 + 2]:
1414
t = -m[0*4 + 0] + m[1*4 + 1] - m[2*4 + 2] + 1.0
1415
s = 0.5/math.sqrt(t)
1418
(m[2*4 + 0] - m[0*4 + 2])*s,
1419
(m[0*4 + 1] + m[1*4 + 0])*s,
1421
(m[1*4 + 2] + m[2*4 + 1])*s
1425
t = -m[0*4 + 0] - m[1*4 + 1] + m[2*4 + 2] + 1.0
1426
s = 0.5/math.sqrt(t)
1429
(m[0*4 + 1] - m[1*4 + 0])*s,
1430
(m[2*4 + 0] + m[0*4 + 2])*s,
1431
(m[1*4 + 2] + m[2*4 + 1])*s,
1434
new_rotate_matrix = classmethod(new_rotate_matrix)
1436
def new_interpolate(cls, q1, q2, t):
1437
assert isinstance(q1, Quaternion) and isinstance(q2, Quaternion)
1440
costheta = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z
1442
costheta = -costheta
1443
q1 = q1.conjugated()
1447
theta = math.acos(costheta)
1448
if abs(theta) < 0.01:
1455
sintheta = math.sqrt(1.0 - costheta * costheta)
1456
if abs(sintheta) < 0.01:
1457
Q.w = (q1.w + q2.w) * 0.5
1458
Q.x = (q1.x + q2.x) * 0.5
1459
Q.y = (q1.y + q2.y) * 0.5
1460
Q.z = (q1.z + q2.z) * 0.5
1463
ratio1 = math.sin((1 - t) * theta) / sintheta
1464
ratio2 = math.sin(t * theta) / sintheta
1466
Q.w = q1.w * ratio1 + q2.w * ratio2
1467
Q.x = q1.x * ratio1 + q2.x * ratio2
1468
Q.y = q1.y * ratio1 + q2.y * ratio2
1469
Q.z = q1.z * ratio1 + q2.z * ratio2
1471
new_interpolate = classmethod(new_interpolate)
1474
# Much maths thanks to Paul Bourke, http://astronomy.swin.edu.au/~pbourke
1475
# ---------------------------------------------------------------------------
1478
def _connect_unimplemented(self, other):
1479
raise AttributeError, 'Cannot connect %s to %s' % \
1480
(self.__class__, other.__class__)
1482
def _intersect_unimplemented(self, other):
1483
raise AttributeError, 'Cannot intersect %s and %s' % \
1484
(self.__class__, other.__class__)
1486
_intersect_point2 = _intersect_unimplemented
1487
_intersect_line2 = _intersect_unimplemented
1488
_intersect_circle = _intersect_unimplemented
1489
_connect_point2 = _connect_unimplemented
1490
_connect_line2 = _connect_unimplemented
1491
_connect_circle = _connect_unimplemented
1493
_intersect_point3 = _intersect_unimplemented
1494
_intersect_line3 = _intersect_unimplemented
1495
_intersect_sphere = _intersect_unimplemented
1496
_intersect_plane = _intersect_unimplemented
1497
_connect_point3 = _connect_unimplemented
1498
_connect_line3 = _connect_unimplemented
1499
_connect_sphere = _connect_unimplemented
1500
_connect_plane = _connect_unimplemented
1502
def intersect(self, other):
1503
raise NotImplementedError
1505
def connect(self, other):
1506
raise NotImplementedError
1508
def distance(self, other):
1509
c = self.connect(other)
1514
def _intersect_point2_circle(P, C):
1515
return abs(P - C.c) <= C.r
1517
def _intersect_line2_line2(A, B):
1518
d = B.v.y * A.v.x - B.v.x * A.v.y
1524
ua = (B.v.x * dy - B.v.y * dx) / d
1527
ub = (A.v.x * dy - A.v.y * dx) / d
1531
return Point2(A.p.x + ua * A.v.x,
1534
def _intersect_line2_circle(L, C):
1535
a = L.v.magnitude_squared()
1536
b = 2 * (L.v.x * (L.p.x - C.c.x) + \
1537
L.v.y * (L.p.y - C.c.y))
1538
c = C.c.magnitude_squared() + \
1539
L.p.magnitude_squared() - \
1540
2 * C.c.dot(L.p) - \
1542
det = b ** 2 - 4 * a * c
1546
u1 = (-b + sq) / (2 * a)
1547
u2 = (-b - sq) / (2 * a)
1549
u1 = max(min(u1, 1.0), 0.0)
1551
u2 = max(min(u2, 1.0), 0.0)
1555
return Point2(L.p.x + u1 * L.v.x,
1558
return LineSegment2(Point2(L.p.x + u1 * L.v.x,
1559
L.p.y + u1 * L.v.y),
1560
Point2(L.p.x + u2 * L.v.x,
1561
L.p.y + u2 * L.v.y))
1563
def _connect_point2_line2(P, L):
1564
d = L.v.magnitude_squared()
1566
u = ((P.x - L.p.x) * L.v.x + \
1567
(P.y - L.p.y) * L.v.y) / d
1569
u = max(min(u, 1.0), 0.0)
1570
return LineSegment2(P,
1571
Point2(L.p.x + u * L.v.x,
1574
def _connect_point2_circle(P, C):
1578
return LineSegment2(P, Point2(C.c.x + v.x, C.c.y + v.y))
1580
def _connect_line2_line2(A, B):
1581
d = B.v.y * A.v.x - B.v.x * A.v.y
1583
# Parallel, connect an endpoint with a line
1584
if isinstance(B, Ray2) or isinstance(B, LineSegment2):
1585
p1, p2 = _connect_point2_line2(B.p, A)
1587
# No endpoint (or endpoint is on A), possibly choose arbitrary point
1589
return _connect_point2_line2(A.p, B)
1593
ua = (B.v.x * dy - B.v.y * dx) / d
1595
ua = max(min(ua, 1.0), 0.0)
1596
ub = (A.v.x * dy - A.v.y * dx) / d
1598
ub = max(min(ub, 1.0), 0.0)
1600
return LineSegment2(Point2(A.p.x + ua * A.v.x, A.p.y + ua * A.v.y),
1601
Point2(B.p.x + ub * B.v.x, B.p.y + ub * B.v.y))
1603
def _connect_circle_line2(C, L):
1604
d = L.v.magnitude_squared()
1606
u = ((C.c.x - L.p.x) * L.v.x + (C.c.y - L.p.y) * L.v.y) / d
1608
u = max(min(u, 1.0), 0.0)
1609
point = Point2(L.p.x + u * L.v.x, L.p.y + u * L.v.y)
1613
return LineSegment2(Point2(C.c.x + v.x, C.c.y + v.y), point)
1615
def _connect_circle_circle(A, B):
1618
return LineSegment2(Point2(A.c.x + v.x * A.r, A.c.y + v.y * A.r),
1619
Point2(B.c.x - v.x * B.r, B.c.y - v.y * B.r))
1622
class Point2(Vector2, Geometry):
1624
return 'Point2(%.2f, %.2f)' % (self.x, self.y)
1626
def intersect(self, other):
1627
return other._intersect_point2(self)
1629
def _intersect_circle(self, other):
1630
return _intersect_point2_circle(self, other)
1632
def connect(self, other):
1633
return other._connect_point2(self)
1635
def _connect_point2(self, other):
1636
return LineSegment2(other, self)
1638
def _connect_line2(self, other):
1639
c = _connect_point2_line2(self, other)
1643
def _connect_circle(self, other):
1644
c = _connect_point2_circle(self, other)
1648
class Line2(Geometry):
1649
__slots__ = ['p', 'v']
1651
def __init__(self, *args):
1653
assert isinstance(args[0], Point2) and \
1654
isinstance(args[1], Vector2) and \
1655
type(args[2]) == float
1656
self.p = args[0].copy()
1657
self.v = args[1] * args[2] / abs(args[1])
1658
elif len(args) == 2:
1659
if isinstance(args[0], Point2) and isinstance(args[1], Point2):
1660
self.p = args[0].copy()
1661
self.v = args[1] - args[0]
1662
elif isinstance(args[0], Point2) and isinstance(args[1], Vector2):
1663
self.p = args[0].copy()
1664
self.v = args[1].copy()
1666
raise AttributeError, '%r' % (args,)
1667
elif len(args) == 1:
1668
if isinstance(args[0], Line2):
1669
self.p = args[0].p.copy()
1670
self.v = args[0].v.copy()
1672
raise AttributeError, '%r' % (args,)
1674
raise AttributeError, '%r' % (args,)
1677
raise AttributeError, 'Line has zero-length vector'
1680
return self.__class__(self.p, self.v)
1685
return 'Line2(<%.2f, %.2f> + u<%.2f, %.2f>)' % \
1686
(self.p.x, self.p.y, self.v.x, self.v.y)
1688
p1 = property(lambda self: self.p)
1689
p2 = property(lambda self: Point2(self.p.x + self.v.x,
1690
self.p.y + self.v.y))
1692
def _apply_transform(self, t):
1699
def intersect(self, other):
1700
return other._intersect_line2(self)
1702
def _intersect_line2(self, other):
1703
return _intersect_line2_line2(self, other)
1705
def _intersect_circle(self, other):
1706
return _intersect_line2_circle(self, other)
1708
def connect(self, other):
1709
return other._connect_line2(self)
1711
def _connect_point2(self, other):
1712
return _connect_point2_line2(other, self)
1714
def _connect_line2(self, other):
1715
return _connect_line2_line2(other, self)
1717
def _connect_circle(self, other):
1718
return _connect_circle_line2(other, self)
1722
return 'Ray2(<%.2f, %.2f> + u<%.2f, %.2f>)' % \
1723
(self.p.x, self.p.y, self.v.x, self.v.y)
1728
class LineSegment2(Line2):
1730
return 'LineSegment2(<%.2f, %.2f> to <%.2f, %.2f>)' % \
1731
(self.p.x, self.p.y, self.p.x + self.v.x, self.p.y + self.v.y)
1734
return u >= 0.0 and u <= 1.0
1739
def magnitude_squared(self):
1740
return self.v.magnitude_squared()
1743
# used by connect methods to switch order of points
1748
length = property(lambda self: abs(self.v))
1750
class Circle(Geometry):
1751
__slots__ = ['c', 'r']
1753
def __init__(self, center, radius):
1754
assert isinstance(center, Vector2) and type(radius) == float
1755
self.c = center.copy()
1759
return self.__class__(self.c, self.r)
1764
return 'Circle(<%.2f, %.2f>, radius=%.2f)' % \
1765
(self.c.x, self.c.y, self.r)
1767
def _apply_transform(self, t):
1770
def intersect(self, other):
1771
return other._intersect_circle(self)
1773
def _intersect_point2(self, other):
1774
return _intersect_point2_circle(other, self)
1776
def _intersect_line2(self, other):
1777
return _intersect_line2_circle(other, self)
1779
def connect(self, other):
1780
return other._connect_circle(self)
1782
def _connect_point2(self, other):
1783
return _connect_point2_circle(other, self)
1785
def _connect_line2(self, other):
1786
c = _connect_circle_line2(self, other)
1790
def _connect_circle(self, other):
1791
return _connect_circle_circle(other, self)
1794
# -------------------------------------------------------------------------
1796
def _connect_point3_line3(P, L):
1797
d = L.v.magnitude_squared()
1799
u = ((P.x - L.p.x) * L.v.x + \
1800
(P.y - L.p.y) * L.v.y + \
1801
(P.z - L.p.z) * L.v.z) / d
1803
u = max(min(u, 1.0), 0.0)
1804
return LineSegment3(P, Point3(L.p.x + u * L.v.x,
1808
def _connect_point3_sphere(P, S):
1812
return LineSegment3(P, Point3(S.c.x + v.x, S.c.y + v.y, S.c.z + v.z))
1814
def _connect_point3_plane(p, plane):
1815
n = plane.n.normalized()
1816
d = p.dot(plane.n) - plane.k
1817
return LineSegment3(p, Point3(p.x - n.x * d, p.y - n.y * d, p.z - n.z * d))
1819
def _connect_line3_line3(A, B):
1822
d1343 = p13.dot(B.v)
1823
d4321 = B.v.dot(A.v)
1824
d1321 = p13.dot(A.v)
1825
d4343 = B.v.magnitude_squared()
1826
denom = A.v.magnitude_squared() * d4343 - d4321 ** 2
1828
# Parallel, connect an endpoint with a line
1829
if isinstance(B, Ray3) or isinstance(B, LineSegment3):
1830
return _connect_point3_line3(B.p, A)._swap()
1831
# No endpoint (or endpoint is on A), possibly choose arbitrary
1833
return _connect_point3_line3(A.p, B)
1835
ua = (d1343 * d4321 - d1321 * d4343) / denom
1837
ua = max(min(ua, 1.0), 0.0)
1838
ub = (d1343 + d4321 * ua) / d4343
1840
ub = max(min(ub, 1.0), 0.0)
1841
return LineSegment3(Point3(A.p.x + ua * A.v.x,
1843
A.p.z + ua * A.v.z),
1844
Point3(B.p.x + ub * B.v.x,
1846
B.p.z + ub * B.v.z))
1848
def _connect_line3_plane(L, P):
1851
# Parallel, choose an endpoint
1852
return _connect_point3_plane(L.p, P)
1853
u = (P.k - P.n.dot(L.p)) / d
1855
# intersects out of range, choose nearest endpoint
1856
u = max(min(u, 1.0), 0.0)
1857
return _connect_point3_plane(Point3(L.p.x + u * L.v.x,
1859
L.p.z + u * L.v.z), P)
1863
def _connect_sphere_line3(S, L):
1864
d = L.v.magnitude_squared()
1866
u = ((S.c.x - L.p.x) * L.v.x + \
1867
(S.c.y - L.p.y) * L.v.y + \
1868
(S.c.z - L.p.z) * L.v.z) / d
1870
u = max(min(u, 1.0), 0.0)
1871
point = Point3(L.p.x + u * L.v.x, L.p.y + u * L.v.y, L.p.z + u * L.v.z)
1875
return LineSegment3(Point3(S.c.x + v.x, S.c.y + v.y, S.c.z + v.z),
1878
def _connect_sphere_sphere(A, B):
1881
return LineSegment3(Point3(A.c.x + v.x * A.r,
1884
Point3(B.c.x + v.x * B.r,
1888
def _connect_sphere_plane(S, P):
1889
c = _connect_point3_plane(S.c, P)
1896
return LineSegment3(Point3(S.c.x + v.x, S.c.y + v.y, S.c.z + v.z),
1899
def _connect_plane_plane(A, B):
1904
# Planes are parallel, connect to arbitrary point
1905
return _connect_point3_plane(A._get_point(), B)
1907
def _intersect_point3_sphere(P, S):
1908
return abs(P - S.c) <= S.r
1910
def _intersect_line3_sphere(L, S):
1911
a = L.v.magnitude_squared()
1912
b = 2 * (L.v.x * (L.p.x - S.c.x) + \
1913
L.v.y * (L.p.y - S.c.y) + \
1914
L.v.z * (L.p.z - S.c.z))
1915
c = S.c.magnitude_squared() + \
1916
L.p.magnitude_squared() - \
1917
2 * S.c.dot(L.p) - \
1919
det = b ** 2 - 4 * a * c
1923
u1 = (-b + sq) / (2 * a)
1924
u2 = (-b - sq) / (2 * a)
1926
u1 = max(min(u1, 1.0), 0.0)
1928
u2 = max(min(u2, 1.0), 0.0)
1929
return LineSegment3(Point3(L.p.x + u1 * L.v.x,
1931
L.p.z + u1 * L.v.z),
1932
Point3(L.p.x + u2 * L.v.x,
1934
L.p.z + u2 * L.v.z))
1936
def _intersect_line3_plane(L, P):
1941
u = (P.k - P.n.dot(L.p)) / d
1944
return Point3(L.p.x + u * L.v.x,
1948
def _intersect_plane_plane(A, B):
1949
n1_m = A.n.magnitude_squared()
1950
n2_m = B.n.magnitude_squared()
1952
det = n1_m * n2_m - n1d2 ** 2
1956
c1 = (A.k * n2_m - B.k * n1d2) / det
1957
c2 = (B.k * n1_m - A.k * n1d2) / det
1958
return Line3(Point3(c1 * A.n.x + c2 * B.n.x,
1959
c1 * A.n.y + c2 * B.n.y,
1960
c1 * A.n.z + c2 * B.n.z),
1963
class Point3(Vector3, Geometry):
1965
return 'Point3(%.2f, %.2f, %.2f)' % (self.x, self.y, self.z)
1967
def intersect(self, other):
1968
return other._intersect_point3(self)
1970
def _intersect_sphere(self, other):
1971
return _intersect_point3_sphere(self, other)
1973
def connect(self, other):
1974
return other._connect_point3(self)
1976
def _connect_point3(self, other):
1978
return LineSegment3(other, self)
1981
def _connect_line3(self, other):
1982
c = _connect_point3_line3(self, other)
1986
def _connect_sphere(self, other):
1987
c = _connect_point3_sphere(self, other)
1991
def _connect_plane(self, other):
1992
c = _connect_point3_plane(self, other)
1997
__slots__ = ['p', 'v']
1999
def __init__(self, *args):
2001
assert isinstance(args[0], Point3) and \
2002
isinstance(args[1], Vector3) and \
2003
type(args[2]) == float
2004
self.p = args[0].copy()
2005
self.v = args[1] * args[2] / abs(args[1])
2006
elif len(args) == 2:
2007
if isinstance(args[0], Point3) and isinstance(args[1], Point3):
2008
self.p = args[0].copy()
2009
self.v = args[1] - args[0]
2010
elif isinstance(args[0], Point3) and isinstance(args[1], Vector3):
2011
self.p = args[0].copy()
2012
self.v = args[1].copy()
2014
raise AttributeError, '%r' % (args,)
2015
elif len(args) == 1:
2016
if isinstance(args[0], Line3):
2017
self.p = args[0].p.copy()
2018
self.v = args[0].v.copy()
2020
raise AttributeError, '%r' % (args,)
2022
raise AttributeError, '%r' % (args,)
2024
# XXX This is annoying.
2026
# raise AttributeError, 'Line has zero-length vector'
2029
return self.__class__(self.p, self.v)
2034
return 'Line3(<%.2f, %.2f, %.2f> + u<%.2f, %.2f, %.2f>)' % \
2035
(self.p.x, self.p.y, self.p.z, self.v.x, self.v.y, self.v.z)
2037
p1 = property(lambda self: self.p)
2038
p2 = property(lambda self: Point3(self.p.x + self.v.x,
2039
self.p.y + self.v.y,
2040
self.p.z + self.v.z))
2042
def _apply_transform(self, t):
2049
def intersect(self, other):
2050
return other._intersect_line3(self)
2052
def _intersect_sphere(self, other):
2053
return _intersect_line3_sphere(self, other)
2055
def _intersect_plane(self, other):
2056
return _intersect_line3_plane(self, other)
2058
def connect(self, other):
2059
return other._connect_line3(self)
2061
def _connect_point3(self, other):
2062
return _connect_point3_line3(other, self)
2064
def _connect_line3(self, other):
2065
return _connect_line3_line3(other, self)
2067
def _connect_sphere(self, other):
2068
return _connect_sphere_line3(other, self)
2070
def _connect_plane(self, other):
2071
c = _connect_line3_plane(self, other)
2077
return 'Ray3(<%.2f, %.2f, %.2f> + u<%.2f, %.2f, %.2f>)' % \
2078
(self.p.x, self.p.y, self.p.z, self.v.x, self.v.y, self.v.z)
2083
class LineSegment3(Line3):
2085
return 'LineSegment3(<%.2f, %.2f, %.2f> to <%.2f, %.2f, %.2f>)' % \
2086
(self.p.x, self.p.y, self.p.z,
2087
self.p.x + self.v.x, self.p.y + self.v.y, self.p.z + self.v.z)
2090
return u >= 0.0 and u <= 1.0
2095
def magnitude_squared(self):
2096
return self.v.magnitude_squared()
2099
# used by connect methods to switch order of points
2104
length = property(lambda self: abs(self.v))
2107
__slots__ = ['c', 'r']
2109
def __init__(self, center, radius):
2110
assert isinstance(center, Vector3) and type(radius) == float
2111
self.c = center.copy()
2115
return self.__class__(self.c, self.r)
2120
return 'Sphere(<%.2f, %.2f, %.2f>, radius=%.2f)' % \
2121
(self.c.x, self.c.y, self.c.z, self.r)
2123
def _apply_transform(self, t):
2126
def intersect(self, other):
2127
return other._intersect_sphere(self)
2129
def _intersect_point3(self, other):
2130
return _intersect_point3_sphere(other, self)
2132
def _intersect_line3(self, other):
2133
return _intersect_line3_sphere(other, self)
2135
def connect(self, other):
2136
return other._connect_sphere(self)
2138
def _connect_point3(self, other):
2139
return _connect_point3_sphere(other, self)
2141
def _connect_line3(self, other):
2142
c = _connect_sphere_line3(self, other)
2146
def _connect_sphere(self, other):
2147
return _connect_sphere_sphere(other, self)
2149
def _connect_plane(self, other):
2150
c = _connect_sphere_plane(self, other)
2155
# n.p = k, where n is normal, p is point on plane, k is constant scalar
2156
__slots__ = ['n', 'k']
2158
def __init__(self, *args):
2160
assert isinstance(args[0], Point3) and \
2161
isinstance(args[1], Point3) and \
2162
isinstance(args[2], Point3)
2163
self.n = (args[1] - args[0]).cross(args[2] - args[0])
2165
self.k = self.n.dot(args[0])
2166
elif len(args) == 2:
2167
if isinstance(args[0], Point3) and isinstance(args[1], Vector3):
2168
self.n = args[1].normalized()
2169
self.k = self.n.dot(args[0])
2170
elif isinstance(args[0], Vector3) and type(args[1]) == float:
2171
self.n = args[0].normalized()
2174
raise AttributeError, '%r' % (args,)
2177
raise AttributeError, '%r' % (args,)
2180
raise AttributeError, 'Points on plane are colinear'
2183
return self.__class__(self.n, self.k)
2188
return 'Plane(<%.2f, %.2f, %.2f>.p = %.2f)' % \
2189
(self.n.x, self.n.y, self.n.z, self.k)
2191
def _get_point(self):
2192
# Return an arbitrary point on the plane
2194
return Point3(0., 0., self.k / self.n.z)
2196
return Point3(0., self.k / self.n.y, 0.)
2198
return Point3(self.k / self.n.x, 0., 0.)
2200
def _apply_transform(self, t):
2201
p = t * self._get_point()
2203
self.k = self.n.dot(p)
2205
def intersect(self, other):
2206
return other._intersect_plane(self)
2208
def _intersect_line3(self, other):
2209
return _intersect_line3_plane(other, self)
2211
def _intersect_plane(self, other):
2212
return _intersect_plane_plane(self, other)
2214
def connect(self, other):
2215
return other._connect_plane(self)
2217
def _connect_point3(self, other):
2218
return _connect_point3_plane(other, self)
2220
def _connect_line3(self, other):
2221
return _connect_line3_plane(other, self)
2223
def _connect_sphere(self, other):
2224
return _connect_sphere_plane(other, self)
2226
def _connect_plane(self, other):
2227
return _connect_plane_plane(other, self)