| 1 |
#!/usr/bin/env python |
|---|
| 2 |
##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 3 |
##~ License |
|---|
| 4 |
##~ |
|---|
| 5 |
##- The RuneBlade Foundation library is intended to ease some |
|---|
| 6 |
##- aspects of writing intricate Jabber, XML, and User Interface (wxPython, etc.) |
|---|
| 7 |
##- applications, while providing the flexibility to modularly change the |
|---|
| 8 |
##- architecture. Enjoy. |
|---|
| 9 |
##~ |
|---|
| 10 |
##~ Copyright (C) 2002 TechGame Networks, LLC. |
|---|
| 11 |
##~ |
|---|
| 12 |
##~ This library is free software; you can redistribute it and/or |
|---|
| 13 |
##~ modify it under the terms of the BSD style License as found in the |
|---|
| 14 |
##~ LICENSE file included with this distribution. |
|---|
| 15 |
##~ |
|---|
| 16 |
##~ TechGame Networks, LLC can be reached at: |
|---|
| 17 |
##~ 3578 E. Hartsel Drive #211 |
|---|
| 18 |
##~ Colorado Springs, Colorado, USA, 80920 |
|---|
| 19 |
##~ |
|---|
| 20 |
##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 21 |
|
|---|
| 22 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 23 |
#~ Imports |
|---|
| 24 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 25 |
|
|---|
| 26 |
import math |
|---|
| 27 |
import Numeric |
|---|
| 28 |
from Transformations import TransformPrimitive3dh |
|---|
| 29 |
|
|---|
| 30 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 31 |
#~ Definitions |
|---|
| 32 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 33 |
|
|---|
| 34 |
class ProjectionTransform(TransformPrimitive3dh): |
|---|
| 35 |
""" |
|---|
| 36 |
>>> proj = ProjectionTransform(-2, 3, 5,8, -9, -2) |
|---|
| 37 |
>>> proj.Width |
|---|
| 38 |
5.0 |
|---|
| 39 |
>>> proj.Height |
|---|
| 40 |
3.0 |
|---|
| 41 |
>>> proj.Deprojh |
|---|
| 42 |
7.0 |
|---|
| 43 |
>>> proj.Center |
|---|
| 44 |
(0.5, 6.5, -5.5) |
|---|
| 45 |
>>> proj.Dimensions |
|---|
| 46 |
(5.0, 3.0, 7.0) |
|---|
| 47 |
>>> proj.HCenter |
|---|
| 48 |
0.5 |
|---|
| 49 |
>>> proj.VCenter |
|---|
| 50 |
6.5 |
|---|
| 51 |
>>> proj.DCenter |
|---|
| 52 |
-5.5 |
|---|
| 53 |
""" |
|---|
| 54 |
def __init__(self, left=-1.0, right=1.0, bottom=-1.0, top=1.0, near=-1.0, far=1.0): |
|---|
| 55 |
self.Left = float(left) |
|---|
| 56 |
self.Right = float(right) |
|---|
| 57 |
self.Bottom = float(bottom) |
|---|
| 58 |
self.Top = float(top) |
|---|
| 59 |
self.Near = float(near) |
|---|
| 60 |
self.Far = float(far) |
|---|
| 61 |
|
|---|
| 62 |
def SetCorners(self, Lower, Higher): |
|---|
| 63 |
self.Left, self.Bottom, self.Near = Lower |
|---|
| 64 |
self.Right, self.Top, self.Far = Higher |
|---|
| 65 |
|
|---|
| 66 |
def GetWidth(self): |
|---|
| 67 |
return self.Right - self.Left |
|---|
| 68 |
def SetWidth(self, value): |
|---|
| 69 |
delta = (self.Width - value) * 0.5 |
|---|
| 70 |
self.Right -= delta |
|---|
| 71 |
self.Left += delta |
|---|
| 72 |
Width = property(GetWidth, SetWidth) |
|---|
| 73 |
def GetHCenter(self, mult=0.5): |
|---|
| 74 |
return (self.Left + self.Right) * mult |
|---|
| 75 |
def SetHCenter(self, value): |
|---|
| 76 |
hw = self.Width * 0.5 |
|---|
| 77 |
self.Right = value + hw |
|---|
| 78 |
self.Left = value - hw |
|---|
| 79 |
HCenter = property(GetHCenter, SetHCenter) |
|---|
| 80 |
|
|---|
| 81 |
def GetHeight(self): |
|---|
| 82 |
return self.Top - self.Bottom |
|---|
| 83 |
def SetHeight(self, value): |
|---|
| 84 |
delta = (self.Height - value) * 0.5 |
|---|
| 85 |
self.Top -= delta |
|---|
| 86 |
self.Bottom += delta |
|---|
| 87 |
Height = property(GetHeight, SetHeight) |
|---|
| 88 |
def GetVCenter(self, mult=0.5): |
|---|
| 89 |
return (self.Top + self.Bottom) * mult |
|---|
| 90 |
def SetVCenter(self, value): |
|---|
| 91 |
hw = self.Height * 0.5 |
|---|
| 92 |
self.Top = value + hw |
|---|
| 93 |
self.Bottom = value - hw |
|---|
| 94 |
VCenter = property(GetVCenter, SetVCenter) |
|---|
| 95 |
|
|---|
| 96 |
def GetDeprojh(self): |
|---|
| 97 |
return self.Far - self.Near |
|---|
| 98 |
def SetDeprojh(self, value): |
|---|
| 99 |
delta = (self.Deprojh - value) * 0.5 |
|---|
| 100 |
self.Far -= delta |
|---|
| 101 |
self.Near += delta |
|---|
| 102 |
Deprojh = property(GetDeprojh, SetDeprojh) |
|---|
| 103 |
def GetDCenter(self, mult=0.5): |
|---|
| 104 |
return (self.Far + self.Near) * mult |
|---|
| 105 |
def SetDCenter(self, value): |
|---|
| 106 |
hw = self.Deprojh * 0.5 |
|---|
| 107 |
self.Far = value + hw |
|---|
| 108 |
self.Near = value - hw |
|---|
| 109 |
DCenter = property(GetDCenter, SetDCenter) |
|---|
| 110 |
|
|---|
| 111 |
def GetCenter(self): |
|---|
| 112 |
return (self.HCenter, self.VCenter, self.DCenter) |
|---|
| 113 |
def SetCenter(self, value): |
|---|
| 114 |
self.HCenter, self.VCenter, self.DCenter = value |
|---|
| 115 |
Center = property(GetCenter, SetCenter) |
|---|
| 116 |
def GetDimensions(self): |
|---|
| 117 |
return (self.Width, self.Height, self.Deprojh) |
|---|
| 118 |
def SetDimensions(self, value): |
|---|
| 119 |
self.Width, self.Height, self.Deprojh = value |
|---|
| 120 |
Dimensions = property(GetDimensions, SetDimensions) |
|---|
| 121 |
|
|---|
| 122 |
def GetAspectRatio(self): |
|---|
| 123 |
return self.Height / self.Width |
|---|
| 124 |
def SetAspectRatio(self, value, byWidth=None): |
|---|
| 125 |
self.SetDimensionsAspectRatio(self.Width, self.Height, value) |
|---|
| 126 |
AspectRatio = property(GetAspectRatio, SetAspectRatio) |
|---|
| 127 |
|
|---|
| 128 |
def SetDimensionsAspectRatio(self, width, height, aspectYX=1., largest=True): |
|---|
| 129 |
width, height, aspectYX = map(float, (width, height, aspectYX)) |
|---|
| 130 |
if largest: # scale by larger dimension |
|---|
| 131 |
if height/width > aspectYX: # height is greater -- scale width |
|---|
| 132 |
width = height/aspectYX |
|---|
| 133 |
else: # width is greater -- scale height |
|---|
| 134 |
height = width*aspectYX |
|---|
| 135 |
else: # scale by smaller dimension |
|---|
| 136 |
if height/width < aspectYX: # width is greater -- scale height |
|---|
| 137 |
width = height/aspectYX |
|---|
| 138 |
else: # height is greater -- scale width |
|---|
| 139 |
height = width*aspectYX |
|---|
| 140 |
self.Width, self.Height = width, height |
|---|
| 141 |
return width, height |
|---|
| 142 |
|
|---|
| 143 |
def GetTuple(self): |
|---|
| 144 |
return self.Left, self.Right, self.Bottom, self.Top, self.Near, self.Far |
|---|
| 145 |
def SetTuple(self, value): |
|---|
| 146 |
self.Left, self.Right, self.Bottom, self.Top, self.Near, self.Far = value |
|---|
| 147 |
asTuple = property(GetTuple, SetTuple) |
|---|
| 148 |
|
|---|
| 149 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 150 |
|
|---|
| 151 |
class Orthographic(ProjectionTransform): |
|---|
| 152 |
""" |
|---|
| 153 |
>>> proj = Orthographic(-2, 3, 5,8, -9, -2); proj |
|---|
| 154 |
<Orthographic: l=-2.0, r=3.0, b=5.0, t=8.0, n=-9.0, f=-2.0 > |
|---|
| 155 |
>>> proj.asArray4x4() |
|---|
| 156 |
array([[ 0.40000001, 0. , 0. , 0.2 ], |
|---|
| 157 |
[ 0. , 0.66666669, 0. , 4.33333349], |
|---|
| 158 |
[ 0. , 0. , -0.2857143 , -1.57142854], |
|---|
| 159 |
[ 0. , 0. , 0. , 1. ]],'f') |
|---|
| 160 |
""" |
|---|
| 161 |
|
|---|
| 162 |
def asArray4x4(self): |
|---|
| 163 |
"""As defined by the OpenGL Red Bock""" |
|---|
| 164 |
width = self.Width |
|---|
| 165 |
height = self.Height |
|---|
| 166 |
deprojh = self.Deprojh |
|---|
| 167 |
xoff = self.GetHCenter(1.) / width |
|---|
| 168 |
yoff = self.GetVCenter(1.) / height |
|---|
| 169 |
zoff = self.GetDCenter(1.) / deprojh |
|---|
| 170 |
|
|---|
| 171 |
result = Numeric.asarray([ |
|---|
| 172 |
[2./width, 0., 0., xoff], |
|---|
| 173 |
[0., 2./height, 0., yoff], |
|---|
| 174 |
[0., 0., -2./deprojh, zoff], |
|---|
| 175 |
[0., 0., 0., 1.]], |
|---|
| 176 |
self.NumericType) |
|---|
| 177 |
return result |
|---|
| 178 |
|
|---|
| 179 |
def __repr__(self): |
|---|
| 180 |
return "<Orthographic: l=%s, r=%s, b=%s, t=%s, n=%s, f=%s >" % self.asTuple |
|---|
| 181 |
|
|---|
| 182 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 183 |
|
|---|
| 184 |
class Viewport(Orthographic): |
|---|
| 185 |
""" |
|---|
| 186 |
>>> proj = Viewport(0, 10, 0, 10, -1, 1); proj |
|---|
| 187 |
<Viewport: l=0.0, r=10.0, b=0.0, t=10.0, n=-1.0, f=1.0 > |
|---|
| 188 |
>>> proj.asArray4x4() |
|---|
| 189 |
array([[ 5., 0., 0., 5.], |
|---|
| 190 |
[ 0., 5., 0., 5.], |
|---|
| 191 |
[ 0., 0., 1., 0.], |
|---|
| 192 |
[ 0., 0., 0., 1.]]) |
|---|
| 193 |
>>> proj.asInverse4x4() |
|---|
| 194 |
array([[ 0.2, 0. , 0. , -1. ], |
|---|
| 195 |
[ 0. , 0.2, 0. , -1. ], |
|---|
| 196 |
[ 0. , 0. , 1. , 0. ], |
|---|
| 197 |
[ 0. , 0. , 0. , 1. ]]) |
|---|
| 198 |
>>> # Create "screen" coordinates" |
|---|
| 199 |
>>> v = Numeric.transpose(Numeric.asarray([(4.,4.,0.,1.),(6.,6.,0.,1.)])); v |
|---|
| 200 |
array([[ 4., 6.], |
|---|
| 201 |
[ 4., 6.], |
|---|
| 202 |
[ 0., 0.], |
|---|
| 203 |
[ 1., 1.]]) |
|---|
| 204 |
>>> # Convert to v to [-1,1] nominal coordinates |
|---|
| 205 |
>>> vn = Numeric.dot(proj.asInverse4x4(), v); vn |
|---|
| 206 |
array([[-0.2, 0.2], |
|---|
| 207 |
[-0.2, 0.2], |
|---|
| 208 |
[ 0. , 0. ], |
|---|
| 209 |
[ 1. , 1. ]]) |
|---|
| 210 |
>>> # Convert back to "screen" coordinates |
|---|
| 211 |
>>> Numeric.dot(proj.asArray4x4(), vn) |
|---|
| 212 |
array([[ 4., 6.], |
|---|
| 213 |
[ 4., 6.], |
|---|
| 214 |
[ 0., 0.], |
|---|
| 215 |
[ 1., 1.]]) |
|---|
| 216 |
""" |
|---|
| 217 |
|
|---|
| 218 |
def __init__(self, left=0., right=1., bottom=0., top=1., near=-1., far=1.): |
|---|
| 219 |
self.Left = float(left) |
|---|
| 220 |
self.Right = float(right) |
|---|
| 221 |
self.Bottom = float(bottom) |
|---|
| 222 |
self.Top = float(top) |
|---|
| 223 |
self.Near = float(near) |
|---|
| 224 |
self.Far = float(far) |
|---|
| 225 |
|
|---|
| 226 |
def asArray4x4(self): |
|---|
| 227 |
"""As defined by the OpenGL Red Bock""" |
|---|
| 228 |
halfwidth = 0.5 * self.Width |
|---|
| 229 |
halfheight = 0.5 * self.Height |
|---|
| 230 |
halfdeprojh = 0.5 * self.Deprojh |
|---|
| 231 |
xoff = self.GetHCenter() |
|---|
| 232 |
yoff = self.GetVCenter() |
|---|
| 233 |
zoff = self.GetDCenter() |
|---|
| 234 |
|
|---|
| 235 |
result = Numeric.asarray([ |
|---|
| 236 |
[halfwidth, 0, 0, xoff], |
|---|
| 237 |
[0, halfheight, 0, yoff], |
|---|
| 238 |
[0, 0, halfdeprojh, zoff], |
|---|
| 239 |
[0, 0, 0, 1]]) |
|---|
| 240 |
return result |
|---|
| 241 |
|
|---|
| 242 |
def __repr__(self): |
|---|
| 243 |
return "<Viewport: l=%s, r=%s, b=%s, t=%s, n=%s, f=%s >" % self.asTuple |
|---|
| 244 |
|
|---|
| 245 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 246 |
|
|---|
| 247 |
class Frustum(ProjectionTransform): |
|---|
| 248 |
""" |
|---|
| 249 |
>>> proj = Frustum(-2, 3, 5,8, -9, -2); proj |
|---|
| 250 |
<Frustm: l=-2.0, r=3.0, b=5.0, t=8.0, n=-9.0, f=-2.0 > |
|---|
| 251 |
>>> proj.asArray4x4() |
|---|
| 252 |
array([[-3.5999999 , 0. , 0.2 , 0. ], |
|---|
| 253 |
[ 0. , -6. , 4.33333349, 0. ], |
|---|
| 254 |
[ 0. , 0. , 1.57142854, -2.57142854], |
|---|
| 255 |
[ 0. , 0. , -1. , 0. ]],'f') |
|---|
| 256 |
""" |
|---|
| 257 |
|
|---|
| 258 |
def __init__(self, left=-1.0, right=1.0, bottom=-1.0, top=1.0, near=1., far=10.): |
|---|
| 259 |
ProjectionTransform.__init__(self, left, right, bottom, top, near, far) |
|---|
| 260 |
|
|---|
| 261 |
def asArray4x4(self): |
|---|
| 262 |
"""As defined by the OpenGL Red Bock""" |
|---|
| 263 |
width = self.Width |
|---|
| 264 |
height = self.Height |
|---|
| 265 |
deprojh = self.Deprojh |
|---|
| 266 |
xoff = self.GetHCenter(1.) / width |
|---|
| 267 |
yoff = self.GetVCenter(1.) / height |
|---|
| 268 |
zoff = self.GetDCenter(1.) / deprojh |
|---|
| 269 |
near2 = self.Near * 2. |
|---|
| 270 |
ugly = -self.Near * self.Far / deprojh |
|---|
| 271 |
|
|---|
| 272 |
result = Numeric.asarray([ |
|---|
| 273 |
[near2/width, 0., xoff, 0.], |
|---|
| 274 |
[0., near2/height, yoff, 0.], |
|---|
| 275 |
[0., 0., -zoff, ugly], |
|---|
| 276 |
[0., 0., -1., 0.]], |
|---|
| 277 |
self.NumericType) |
|---|
| 278 |
return result |
|---|
| 279 |
|
|---|
| 280 |
def __repr__(self): |
|---|
| 281 |
return "<Frustm: l=%s, r=%s, b=%s, t=%s, n=%s, f=%s >" % self.asTuple |
|---|
| 282 |
|
|---|
| 283 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 284 |
|
|---|
| 285 |
class Perspective(Frustum): |
|---|
| 286 |
""" |
|---|
| 287 |
>>> proj = Perspective(30., 1.234, 0.5, 23.2) |
|---|
| 288 |
>>> proj |
|---|
| 289 |
<Persepctive: ViewAngle=30.0, Aspect=1.234, n=0.5, f=23.2 > |
|---|
| 290 |
>>> proj.asArray4x4() |
|---|
| 291 |
array([[ 4.60535049, 0. , 0. , 0. ], |
|---|
| 292 |
[ 0. , 3.7320509 , 0. , 0. ], |
|---|
| 293 |
[ 0. , 0. , -1.04405284, -0.51101321], |
|---|
| 294 |
[ 0. , 0. , -1. , 0. ]],'f') |
|---|
| 295 |
>>> proj.AspectRatio |
|---|
| 296 |
1.2339999999999995 |
|---|
| 297 |
>>> proj.ViewAngle |
|---|
| 298 |
30.000000000000011 |
|---|
| 299 |
>>> proj.ViewRadians |
|---|
| 300 |
0.52359877559829904 |
|---|
| 301 |
""" |
|---|
| 302 |
|
|---|
| 303 |
def __init__(self, viewangle=45., aspectratio=1., near=1., far=10.): |
|---|
| 304 |
"""Derived from OpenGL Red Book""" |
|---|
| 305 |
Frustum.__init__(self,-1.,1.,-1.,1.,near,far) |
|---|
| 306 |
self.SetViewAngleAndRatio(math.radians(viewangle), float(aspectratio)) |
|---|
| 307 |
|
|---|
| 308 |
def __repr__(self): |
|---|
| 309 |
return "<Persepctive: ViewAngle=%s, Aspect=%s, n=%s, f=%s >" % (self.ViewAngle, self.AspectRatio, self.Near, self.Far) |
|---|
| 310 |
|
|---|
| 311 |
def GetViewAngle(self): |
|---|
| 312 |
return math.degrees(2.*math.atan(self.Height/(2.*self.Near))) |
|---|
| 313 |
def SetViewAngle(self, value): |
|---|
| 314 |
self.SetViewRadians(math.radians(value)) |
|---|
| 315 |
ViewAngle = property(GetViewAngle, SetViewAngle) |
|---|
| 316 |
|
|---|
| 317 |
def GetViewRadians(self): |
|---|
| 318 |
return 2.*math.atan(self.Height/(2.*self.Near)) |
|---|
| 319 |
def SetViewRadians(self, value): |
|---|
| 320 |
ar = self.AspectRatio |
|---|
| 321 |
self.SetViewAngleAndRatio(float(value), ar) |
|---|
| 322 |
ViewRadians = property(GetViewRadians, SetViewRadians) |
|---|
| 323 |
|
|---|
| 324 |
def SetViewAngleAndRatio(self, viewradians, aspectratio): |
|---|
| 325 |
self.Height = math.tan(0.5*viewradians) * 2. * self.Near |
|---|
| 326 |
self.Width = self.Height / aspectratio |
|---|
| 327 |
|
|---|
| 328 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 329 |
#~ Testing |
|---|
| 330 |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 331 |
|
|---|
| 332 |
if __name__=='__main__': |
|---|
| 333 |
print "Testing..." |
|---|
| 334 |
import doctest, Projections |
|---|
| 335 |
doctest.testmod(Projections) |
|---|
| 336 |
print "Test complete." |
|---|
| 337 |
|
|---|