summaryrefslogtreecommitdiffstats
path: root/libdimension-python/dimension.pyx
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@gmail.com>2011-06-13 00:16:27 -0600
committerTavian Barnes <tavianator@gmail.com>2011-06-13 00:17:16 -0600
commit9b758508df283a533a4cfc605545a35f77bc9d5f (patch)
treedd7e471d07a32bff8702aed3624a084c9c209ccf /libdimension-python/dimension.pyx
parent7acd8ea6673b7a90ed4041408ccf1b024b8a007a (diff)
downloaddimension-9b758508df283a533a4cfc605545a35f77bc9d5f.tar.xz
Use Cython for the Python module.
Diffstat (limited to 'libdimension-python/dimension.pyx')
-rw-r--r--libdimension-python/dimension.pyx910
1 files changed, 910 insertions, 0 deletions
diff --git a/libdimension-python/dimension.pyx b/libdimension-python/dimension.pyx
new file mode 100644
index 0000000..9a5fd80
--- /dev/null
+++ b/libdimension-python/dimension.pyx
@@ -0,0 +1,910 @@
+#########################################################################
+# Copyright (C) 2011 Tavian Barnes <tavianator@tavianator.com> #
+# #
+# This file is part of The Dimension Python Module. #
+# #
+# The Dimension Python Module is free software; you can redistribute it #
+# and/or modify it under the terms of the GNU General Public License as #
+# published by the Free Software Foundation; either version 3 of the #
+# License, or (at your option) any later version. #
+# #
+# The Dimension Python Module is distributed in the hope that it will #
+# be useful, but WITHOUT ANY WARRANTY; without even the implied #
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See #
+# the GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+#########################################################################
+
+import os
+
+###########
+# Globals #
+###########
+
+# Make warnings fatal
+def dieOnWarnings(alwaysDie):
+ dmnsn_die_on_warnings(alwaysDie)
+
+############
+# Geometry #
+############
+
+cdef class Vector:
+ cdef dmnsn_vector _v
+
+ def __init__(self, *args, **kwargs):
+ if len(args) == 1:
+ if (isinstance(args[0], Vector)):
+ self._v = (<Vector>args[0])._v
+ elif (isinstance(args[0], tuple)):
+ self._realInit(*args[0])
+ elif (args[0] == 0):
+ self._v = dmnsn_zero
+ else:
+ raise TypeError, 'expected a tuple or 0'
+ else:
+ self._realInit(*args, **kwargs)
+
+ def _realInit(self, double x, double y, double z):
+ self._v = dmnsn_new_vector(x, y, z)
+
+ property x:
+ def __get__(self):
+ return self._v.x
+ property y:
+ def __get__(self):
+ return self._v.y
+ property z:
+ def __get__(self):
+ return self._v.z
+
+ def __pos__(self):
+ return self
+ def __neg__(self):
+ return _rawVector(dmnsn_vector_negate(self._v))
+ def __nonzero__(self):
+ return dmnsn_vector_norm(self._v) >= dmnsn_epsilon
+
+ def __add__(lhs, rhs):
+ return _rawVector(dmnsn_vector_add(Vector(lhs)._v, Vector(rhs)._v))
+ def __sub__(lhs, rhs):
+ return _rawVector(dmnsn_vector_sub(Vector(lhs)._v, Vector(rhs)._v))
+ def __mul__(lhs, rhs):
+ if (isinstance(lhs, Vector)):
+ return _rawVector(dmnsn_vector_mul(rhs, (<Vector>lhs)._v))
+ else:
+ return _rawVector(dmnsn_vector_mul(lhs, (<Vector>rhs)._v))
+ def __truediv__(Vector lhs not None, double rhs):
+ return _rawVector(dmnsn_vector_div(lhs._v, rhs))
+
+ def __richcmp__(lhs, rhs, int op):
+ equal = (Vector(lhs) - Vector(rhs)).norm() < dmnsn_epsilon
+ if (op == 2): # ==
+ return equal
+ elif (op == 3): # !=
+ return not equal
+ else:
+ return NotImplemented
+
+ def norm(self):
+ return dmnsn_vector_norm(self._v)
+ def normalized(self):
+ return _rawVector(dmnsn_vector_normalized(self._v))
+
+ def __repr__(self):
+ return 'dimension.Vector(%r, %r, %r)' % (self.x, self.y, self.z)
+
+ def __str__(self):
+ return '<%s, %s, %s>' % (self.x, self.y, self.z)
+
+cdef _rawVector(dmnsn_vector v):
+ cdef Vector self = Vector.__new__(Vector)
+ self._v = v
+ return self
+
+def cross(Vector lhs not None, Vector rhs not None):
+ return _rawVector(dmnsn_vector_cross(lhs._v, rhs._v))
+def dot(Vector lhs not None, Vector rhs not None):
+ return dmnsn_vector_dot(lhs._v, rhs._v)
+def proj(Vector u not None, Vector d not None):
+ return _rawVector(dmnsn_vector_proj(u._v, d._v))
+
+X = _rawVector(dmnsn_x)
+Y = _rawVector(dmnsn_y)
+Z = _rawVector(dmnsn_z)
+
+cdef class Matrix:
+ cdef dmnsn_matrix _m
+
+ def __init__(self,
+ double a1, double a2, double a3, double a4,
+ double b1, double b2, double b3, double b4,
+ double c1, double c2, double c3, double c4):
+ self._m = dmnsn_new_matrix(a1, a2, a3, a4,
+ b1, b2, b3, b4,
+ c1, c2, c3, c4)
+
+ def __nonzero__(self):
+ cdef double sum = 0.0
+ for i in range(3):
+ for j in range(4):
+ sum += self._m.n[i][j]
+ return sqrt(sum) >= dmnsn_epsilon
+
+ def __mul__(Matrix lhs not None, rhs):
+ if (isinstance(rhs, Matrix)):
+ return _rawMatrix(dmnsn_matrix_mul(lhs._m, (<Matrix>rhs)._m))
+ else:
+ return _rawVector(dmnsn_transform_vector(lhs._m, (<Vector>rhs)._v))
+
+ def __richcmp__(Matrix lhs not None, Matrix rhs not None, int op):
+ cdef double sum = 0.0
+ for i in range(3):
+ for j in range(4):
+ diff = lhs._m.n[i][j] - rhs._m.n[i][j]
+ sum += diff*diff
+ equal = sqrt(sum) < dmnsn_epsilon
+
+ if (op == 2): # ==
+ return equal
+ elif (op == 3): # !=
+ return not equal
+ else:
+ return NotImplemented
+
+ cpdef Matrix inverse(self):
+ return _rawMatrix(dmnsn_matrix_inverse(self._m));
+
+ def __repr__(self):
+ return \
+ 'dimension.Matrix(%r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r, %r)' % \
+ (self._m.n[0][0], self._m.n[0][1], self._m.n[0][2], self._m.n[0][3],
+ self._m.n[1][0], self._m.n[1][1], self._m.n[1][2], self._m.n[1][3],
+ self._m.n[2][0], self._m.n[2][1], self._m.n[2][2], self._m.n[2][3])
+
+ def __str__(self):
+ return \
+ '\n[%s\t%s\t%s\t%s]' \
+ '\n[%s\t%s\t%s\t%s]' \
+ '\n[%s\t%s\t%s\t%s]' \
+ '\n[%s\t%s\t%s\t%s]' %\
+ (self._m.n[0][0], self._m.n[0][1], self._m.n[0][2], self._m.n[0][3],
+ self._m.n[1][0], self._m.n[1][1], self._m.n[1][2], self._m.n[1][3],
+ self._m.n[2][0], self._m.n[2][1], self._m.n[2][2], self._m.n[2][3],
+ 0.0, 0.0, 0.0, 1.0)
+
+cdef Matrix _rawMatrix(dmnsn_matrix m):
+ cdef Matrix self = Matrix.__new__(Matrix)
+ self._m = m
+ return self
+
+def scale(*args, **kwargs):
+ return _rawMatrix(dmnsn_scale_matrix(Vector(*args, **kwargs)._v))
+def translate(*args, **kwargs):
+ return _rawMatrix(dmnsn_translation_matrix(Vector(*args, **kwargs)._v))
+def rotate(*args, **kwargs):
+ cdef Vector rad = dmnsn_radians(1.0)*Vector(*args, **kwargs)
+ return _rawMatrix(dmnsn_rotation_matrix(rad._v))
+def _rawRotate(*args, **kwargs):
+ return _rawMatrix(dmnsn_rotation_matrix(Vector(*args, **kwargs)._v))
+
+##########
+# Colors #
+##########
+
+cdef class Color:
+ cdef dmnsn_color _c
+ cdef dmnsn_color _sRGB
+
+ def __init__(self, *args, **kwargs):
+ if len(args) == 1:
+ if (isinstance(args[0], Color)):
+ self._sRGB = (<Color>args[0])._sRGB
+ elif (isinstance(args[0], tuple)):
+ self._realInit(*args[0])
+ else:
+ self._sRGB = dmnsn_color_mul(args[0], dmnsn_white)
+ else:
+ self._realInit(*args, **kwargs)
+
+ self._c = dmnsn_color_from_sRGB(self._sRGB)
+
+ def _realInit(self, double red, double green, double blue,
+ double trans = 0.0, double filter = 0.0):
+ self._sRGB = dmnsn_new_color5(red, green, blue, trans, filter)
+
+ property red:
+ def __get__(self):
+ return self._sRGB.R
+ property green:
+ def __get__(self):
+ return self._sRGB.G
+ property blue:
+ def __get__(self):
+ return self._sRGB.B
+ property trans:
+ def __get__(self):
+ return self._sRGB.trans
+ property filter:
+ def __get__(self):
+ return self._sRGB.filter
+
+ def __nonzero__(self):
+ return not dmnsn_color_is_black(self._c)
+
+ def __add__(lhs, rhs):
+ return _rawsRGBColor(dmnsn_color_add(Color(lhs)._sRGB, Color(rhs)._sRGB))
+ def __mul__(lhs, rhs):
+ if (isinstance(lhs, Color)):
+ return _rawsRGBColor(dmnsn_color_mul(rhs, (<Color>lhs)._sRGB))
+ else:
+ return _rawsRGBColor(dmnsn_color_mul(lhs, (<Color>rhs)._sRGB))
+
+ def __richcmp__(lhs, rhs, int op):
+ cdef clhs = Color(lhs)
+ cdef crhs = Color(rhs)
+
+ cdef double rdiff = clhs.red - crhs.red
+ cdef double gdiff = clhs.green - crhs.green
+ cdef double bdiff = clhs.blue - crhs.blue
+ cdef double tdiff = clhs.trans - crhs.trans
+ cdef double fdiff = clhs.filter - crhs.filter
+ cdef double sum = rdiff*rdiff + gdiff*gdiff + bdiff*bdiff \
+ + tdiff*tdiff + fdiff*fdiff
+ equal = sqrt(sum) < dmnsn_epsilon
+ if (op == 2): # ==
+ return equal
+ elif (op == 3): # !=
+ return not equal
+ else:
+ return NotImplemented
+
+ def __repr__(self):
+ return 'dimension.Color(%r, %r, %r, %r, %r)' % \
+ (self.red, self.green, self.blue, self.trans, self.filter)
+
+ def __str__(self):
+ if (self.trans >= dmnsn_epsilon):
+ return '<red = %s, green = %s, blue = %s, trans = %s, filter = %s>' % \
+ (self.red, self.green, self.blue, self.trans, self.filter)
+ else:
+ return '<red = %s, green = %s, blue = %s>' % \
+ (self.red, self.green, self.blue)
+
+cdef _rawsRGBColor(dmnsn_color sRGB):
+ cdef Color self = Color.__new__(Color)
+ self._sRGB = sRGB
+ self._c = dmnsn_color_from_sRGB(sRGB)
+ return self
+
+cdef _rawColor(dmnsn_color c):
+ cdef Color self = Color.__new__(Color)
+ self._c = c
+ self._sRGB = dmnsn_color_to_sRGB(c)
+ return self
+
+Black = _rawColor(dmnsn_black)
+White = _rawColor(dmnsn_white)
+Clear = _rawColor(dmnsn_clear)
+Red = _rawColor(dmnsn_red)
+Green = _rawColor(dmnsn_green)
+Blue = _rawColor(dmnsn_blue)
+Magenta = _rawColor(dmnsn_magenta)
+Orange = _rawColor(dmnsn_orange)
+Yellow = _rawColor(dmnsn_yellow)
+Cyan = _rawColor(dmnsn_cyan)
+
+############
+# Canvases #
+############
+
+cdef class Canvas:
+ cdef dmnsn_canvas *_canvas
+
+ def __cinit__(self, size_t width, size_t height):
+ self._canvas = dmnsn_new_canvas(width, height)
+
+ def __dealloc__(self):
+ dmnsn_delete_canvas(self._canvas)
+
+ property width:
+ def __get__(self):
+ return self._canvas.width
+ property height:
+ def __get__(self):
+ return self._canvas.height
+
+ def optimizePNG(self):
+ if dmnsn_png_optimize_canvas(self._canvas) != 0:
+ raise OSError(errno, os.strerror(errno))
+
+ def optimizeGL(self):
+ if dmnsn_gl_optimize_canvas(self._canvas) != 0:
+ raise OSError(errno, os.strerror(errno))
+
+ def clear(self, c):
+ dmnsn_clear_canvas(self._canvas, Color(c)._c)
+
+ def writePNG(self, str path not None):
+ bpath = path.encode('UTF-8')
+ cdef char *cpath = bpath
+ cdef FILE *file = fopen(cpath, "wb")
+ if file == NULL:
+ raise OSError(errno, os.strerror(errno))
+
+ if dmnsn_png_write_canvas(self._canvas, file) != 0:
+ raise OSError(errno, os.strerror(errno))
+
+ def drawGL(self):
+ if dmnsn_gl_write_canvas(self._canvas) != 0:
+ raise OSError(errno, os.strerror(errno))
+
+############
+# Patterns #
+############
+
+cdef class Pattern:
+ cdef dmnsn_pattern *_pattern
+
+ def __cinit__(self):
+ self._pattern = NULL
+
+ def __dealloc__(self):
+ dmnsn_delete_pattern(self._pattern)
+
+ def transform(self, Matrix trans not None):
+ if self._pattern == NULL:
+ raise TypeError('attempt to transform base Pattern')
+
+ self._pattern.trans = dmnsn_matrix_mul(trans._m, self._pattern.trans)
+ return self
+
+cdef class Checker(Pattern):
+ def __init__(self):
+ self._pattern = dmnsn_new_checker_pattern()
+ Pattern.__init__(self)
+
+cdef class Gradient(Pattern):
+ def __init__(self, orientation):
+ self._pattern = dmnsn_new_gradient_pattern(Vector(orientation)._v)
+ Pattern.__init__(self)
+
+############
+# Pigments #
+############
+
+cdef class Pigment:
+ cdef dmnsn_pigment *_pigment
+
+ def __cinit__(self):
+ self._pigment = NULL
+
+ def __init__(self, arg = None):
+ if arg is not None:
+ if (isinstance(arg, Pigment)):
+ self._pigment = (<Pigment>arg)._pigment
+ DMNSN_INCREF(self._pigment)
+ else:
+ self._pigment = dmnsn_new_solid_pigment(Color(arg)._c)
+
+ def __dealloc__(self):
+ dmnsn_delete_pigment(self._pigment)
+
+ def transform(self, Matrix trans not None):
+ if self._pigment == NULL:
+ raise TypeError('attempt to transform base Pigment')
+
+ self._pigment.trans = dmnsn_matrix_mul(trans._m, self._pigment.trans)
+ return self
+
+cdef class ColorMap(Pigment):
+ def __init__(self, Pattern pattern not None, map, bool sRGB not None = True):
+ cdef dmnsn_map *color_map = dmnsn_new_color_map()
+ if hasattr(map, 'items'):
+ for i, color in map.items():
+ dmnsn_add_map_entry(color_map, i, &Color(color)._c)
+ else:
+ for i, color in enumerate(map):
+ dmnsn_add_map_entry(color_map, i/len(map), &Color(color)._c)
+
+ cdef dmnsn_pigment_map_flags flags
+ if sRGB:
+ flags = DMNSN_PIGMENT_MAP_SRGB
+ else:
+ flags = DMNSN_PIGMENT_MAP_REGULAR
+
+ DMNSN_INCREF(pattern._pattern)
+ self._pigment = dmnsn_new_color_map_pigment(pattern._pattern, color_map,
+ flags)
+ Pigment.__init__(self)
+
+cdef class PigmentMap(Pigment):
+ def __init__(self, Pattern pattern not None, map, bool sRGB not None = True):
+ cdef dmnsn_map *pigment_map = dmnsn_new_pigment_map()
+ cdef dmnsn_pigment *realPigment
+ if hasattr(map, 'items'):
+ for i, pigment in map.items():
+ pigment = Pigment(pigment)
+ realPigment = (<Pigment>pigment)._pigment
+ DMNSN_INCREF(realPigment)
+ dmnsn_add_map_entry(pigment_map, i, &realPigment)
+ else:
+ for i, pigment in enumerate(map):
+ pigment = Pigment(pigment)
+ realPigment = (<Pigment>pigment)._pigment
+ DMNSN_INCREF(realPigment)
+ dmnsn_add_map_entry(pigment_map, i/len(map), &realPigment)
+
+ cdef dmnsn_pigment_map_flags flags
+ if sRGB:
+ flags = DMNSN_PIGMENT_MAP_SRGB
+ else:
+ flags = DMNSN_PIGMENT_MAP_REGULAR
+
+ DMNSN_INCREF(pattern._pattern)
+ self._pigment = dmnsn_new_pigment_map_pigment(pattern._pattern, pigment_map,
+ flags)
+ Pigment.__init__(self)
+
+############
+# Finishes #
+############
+
+cdef class Finish:
+ cdef dmnsn_finish _finish
+
+ def __cinit__(self):
+ self._finish = dmnsn_new_finish()
+
+ def __dealloc__(self):
+ dmnsn_delete_finish(self._finish)
+
+ def __add__(Finish lhs not None, Finish rhs not None):
+ cdef Finish ret = Finish()
+
+ if lhs._finish.ambient != NULL and rhs._finish.ambient != NULL:
+ raise ValueError('both Finishes provide an ambient contribution')
+ elif lhs._finish.ambient != NULL:
+ ret._finish.ambient = lhs._finish.ambient
+ DMNSN_INCREF(ret._finish.ambient)
+ elif rhs._finish.ambient != NULL:
+ ret._finish.ambient = rhs._finish.ambient
+ DMNSN_INCREF(ret._finish.ambient)
+
+ if lhs._finish.diffuse != NULL and rhs._finish.diffuse != NULL:
+ raise ValueError('both Finishes provide a diffuse contribution')
+ elif lhs._finish.diffuse != NULL:
+ ret._finish.diffuse = lhs._finish.diffuse
+ DMNSN_INCREF(ret._finish.diffuse)
+ elif rhs._finish.diffuse != NULL:
+ ret._finish.diffuse = rhs._finish.diffuse
+ DMNSN_INCREF(ret._finish.diffuse)
+
+ if lhs._finish.specular != NULL and rhs._finish.specular != NULL:
+ raise ValueError('both Finishes provide a specular contribution')
+ elif lhs._finish.specular != NULL:
+ ret._finish.specular = lhs._finish.specular
+ DMNSN_INCREF(ret._finish.specular)
+ elif rhs._finish.specular != NULL:
+ ret._finish.specular = rhs._finish.specular
+ DMNSN_INCREF(ret._finish.specular)
+
+ if lhs._finish.reflection != NULL and rhs._finish.reflection != NULL:
+ raise ValueError('both Finishes provide a reflection contribution')
+ elif lhs._finish.reflection != NULL:
+ ret._finish.reflection = lhs._finish.reflection
+ DMNSN_INCREF(ret._finish.reflection)
+ elif rhs._finish.reflection != NULL:
+ ret._finish.reflection = rhs._finish.reflection
+ DMNSN_INCREF(ret._finish.reflection)
+
+ return ret
+
+cdef class Ambient(Finish):
+ def __init__(self, color):
+ self._finish.ambient = dmnsn_new_basic_ambient(Color(color)._c)
+
+cdef class Diffuse(Finish):
+ def __init__(self, double diffuse):
+ cdef dmnsn_color gray = dmnsn_color_mul(diffuse, dmnsn_white)
+ diffuse = dmnsn_color_intensity(dmnsn_color_from_sRGB(gray))
+ self._finish.diffuse = dmnsn_new_lambertian(diffuse)
+
+cdef class Phong(Finish):
+ def __init__(self, double strength, double size = 40.0):
+ self._finish.specular = dmnsn_new_phong(strength, size)
+
+cdef class Reflection(Finish):
+ def __init__(self, min, max = None, double falloff = 1.0):
+ if max is None:
+ max = min
+ self._finish.reflection = dmnsn_new_basic_reflection(Color(min)._c,
+ Color(max)._c,
+ falloff)
+
+############
+# Textures #
+############
+
+cdef class Texture:
+ cdef dmnsn_texture *_texture
+
+ def __init__(self, pigment = None, Finish finish = None):
+ self._texture = dmnsn_new_texture()
+
+ cdef Pigment realPigment
+ if pigment is not None:
+ realPigment = Pigment(pigment)
+ self._texture.pigment = realPigment._pigment
+ DMNSN_INCREF(self._texture.pigment)
+
+ if finish is not None:
+ self._texture.finish = finish._finish
+ if self._texture.finish.ambient != NULL:
+ DMNSN_INCREF(self._texture.finish.ambient)
+ if self._texture.finish.diffuse != NULL:
+ DMNSN_INCREF(self._texture.finish.diffuse)
+ if self._texture.finish.specular != NULL:
+ DMNSN_INCREF(self._texture.finish.specular)
+ if self._texture.finish.reflection != NULL:
+ DMNSN_INCREF(self._texture.finish.reflection)
+
+ def __dealloc__(self):
+ dmnsn_delete_texture(self._texture)
+
+#############
+# Interiors #
+#############
+
+cdef class Interior:
+ cdef dmnsn_interior *_interior
+
+ def __init__(self, double ior = 1.0):
+ self._interior = dmnsn_new_interior()
+ self._interior.ior = ior
+
+ def __dealloc__(self):
+ dmnsn_delete_interior(self._interior)
+
+###########
+# Objects #
+###########
+
+cdef class Object:
+ cdef dmnsn_object *_object
+
+ def __cinit__(self):
+ self._object = NULL
+
+ def __init__(self, Texture texture = None, Interior interior = None):
+ if texture is not None:
+ self._object.texture = texture._texture
+ DMNSN_INCREF(self._object.texture)
+ if interior is not None:
+ self._object.interior = interior._interior
+ DMNSN_INCREF(self._object.interior)
+
+ def __dealloc__(self):
+ dmnsn_delete_object(self._object)
+
+ def transform(self, Matrix trans not None):
+ if self._object == NULL:
+ raise TypeError('attempt to transform base Object')
+
+ self._object.trans = dmnsn_matrix_mul(trans._m, self._object.trans)
+ return self
+
+ # Transform an object without affecting the texture
+ cdef _intrinsicTransform(self, Matrix trans):
+ self._object.trans = dmnsn_matrix_mul(self._object.trans, trans._m)
+ if self._object.texture != NULL:
+ self._object.texture.trans = dmnsn_matrix_mul(self._object.texture.trans,
+ trans.inverse()._m)
+
+cdef class Plane(Object):
+ def __init__(self, normal, double distance, *args, **kwargs):
+ self._object = dmnsn_new_plane(Vector(normal)._v)
+ Object.__init__(self, *args, **kwargs)
+
+ self._intrinsicTransform(translate(distance*Vector(normal)))
+
+cdef class Sphere(Object):
+ def __init__(self, center, double radius, *args, **kwargs):
+ self._object = dmnsn_new_sphere()
+ Object.__init__(self, *args, **kwargs)
+
+ cdef Matrix trans = translate(Vector(center))
+ trans *= scale(radius, radius, radius)
+ self._intrinsicTransform(trans)
+
+cdef class Box(Object):
+ def __init__(self, min, max, *args, **kwargs):
+ self._object = dmnsn_new_cube()
+ Object.__init__(self, *args, **kwargs)
+
+ min = Vector(min)
+ max = Vector(max)
+ cdef Matrix trans = translate((max + min)/2)
+ trans *= scale((max - min)/2)
+ self._intrinsicTransform(trans)
+
+cdef class Cone(Object):
+ def __init__(self, bottom, double bottomRadius, top, double topRadius,
+ bool open not None = False, *args, **kwargs):
+ self._object = dmnsn_new_cone(bottomRadius, topRadius, open)
+ Object.__init__(self, *args, **kwargs)
+
+ # Lift the cone to start at the origin, then scale, rotate, and translate
+ # properly
+
+ cdef Vector dir = Vector(top) - Vector(bottom)
+
+ cdef Matrix trans = translate(Y)
+ trans = scale(1.0, dir.norm()/2, 1.0)*trans
+
+ cdef double thetaX = dmnsn_vector_axis_angle(dmnsn_y, dir._v, dmnsn_x)
+ cdef double thetaZ = dmnsn_vector_axis_angle(dmnsn_y, dir._v, dmnsn_z)
+ trans = _rawRotate(thetaX*X)*_rawRotate(thetaZ*Z)*trans
+
+ trans = translate(bottom)*trans
+
+ self._intrinsicTransform(trans)
+
+cdef class Cylinder(Cone):
+ def __init__(self, bottom, top, double radius, bool open not None = False):
+ Cone.__init__(self,
+ bottom = bottom, bottomRadius = radius,
+ top = top, topRadius = radius,
+ open = open)
+
+cdef class Torus(Object):
+ def __init__(self, double majorRadius, double minorRadius, *args, **kwargs):
+ self._object = dmnsn_new_torus(majorRadius, minorRadius)
+ Object.__init__(self, *args, **kwargs)
+
+cdef class Union(Object):
+ def __init__(self, objects, *args, **kwargs):
+ if len(objects) < 2:
+ raise TypeError('expected a list of two or more Objects')
+
+ cdef dmnsn_array *array = dmnsn_new_array(sizeof(dmnsn_object *))
+ cdef dmnsn_object *o
+
+ try:
+ for obj in objects:
+ o = (<Object?>obj)._object
+ DMNSN_INCREF(o)
+ dmnsn_array_push(array, &o)
+
+ self._object = dmnsn_new_csg_union(array)
+ finally:
+ dmnsn_delete_array(array)
+
+ Object.__init__(self, *args, **kwargs)
+
+cdef class Intersection(Object):
+ def __init__(self, objects, *args, **kwargs):
+ if len(objects) < 2:
+ raise TypeError('expected a list of two or more Objects')
+
+ cdef dmnsn_object *o
+
+ for obj in objects:
+ if self._object == NULL:
+ self._object = (<Object?>obj)._object
+ DMNSN_INCREF(self._object)
+ else:
+ o = (<Object?>obj)._object
+ DMNSN_INCREF(o)
+ self._object = dmnsn_new_csg_intersection(self._object, o)
+
+ Object.__init__(self, *args, **kwargs)
+
+cdef class Difference(Object):
+ def __init__(self, objects, *args, **kwargs):
+ if len(objects) < 2:
+ raise TypeError('expected a list of two or more Objects')
+
+ cdef dmnsn_object *o
+
+ for obj in objects:
+ if self._object == NULL:
+ self._object = (<Object?>obj)._object
+ DMNSN_INCREF(self._object)
+ else:
+ o = (<Object?>obj)._object
+ DMNSN_INCREF(o)
+ self._object = dmnsn_new_csg_difference(self._object, o)
+
+ Object.__init__(self, *args, **kwargs)
+
+cdef class Merge(Object):
+ def __init__(self, objects, *args, **kwargs):
+ if len(objects) < 2:
+ raise TypeError('expected a list of two or more Objects')
+
+ cdef dmnsn_object *o
+
+ for obj in objects:
+ if self._object == NULL:
+ self._object = (<Object?>obj)._object
+ DMNSN_INCREF(self._object)
+ else:
+ o = (<Object?>obj)._object
+ DMNSN_INCREF(o)
+ self._object = dmnsn_new_csg_merge(self._object, o)
+
+ Object.__init__(self, *args, **kwargs)
+
+##########
+# Lights #
+##########
+
+cdef class Light:
+ cdef dmnsn_light *_light
+
+ def __dealloc__(self):
+ dmnsn_delete_light(self._light)
+
+cdef class PointLight(Light):
+ def __init__(self, location, color):
+ self._light = dmnsn_new_point_light(Vector(location)._v, Color(color)._c)
+ Light.__init__(self)
+
+###########
+# Cameras #
+###########
+
+cdef class Camera:
+ cdef dmnsn_camera *_camera
+
+ def __cinit__(self):
+ self._camera = NULL
+
+ def __dealloc__(self):
+ dmnsn_delete_camera(self._camera)
+
+ def transform(self, Matrix trans not None):
+ if self._camera == NULL:
+ raise TypeError('attempt to transform base Camera')
+
+ self._camera.trans = dmnsn_matrix_mul(trans._m, self._camera.trans)
+ return self
+
+cdef class PerspectiveCamera(Camera):
+ def __init__(self, location = -Z, lookAt = 0, sky = Y,
+ angle = dmnsn_degrees(atan(0.5))):
+ self._camera = dmnsn_new_perspective_camera()
+ Camera.__init__(self)
+
+ # Apply the field of view angle
+ self.transform(scale(2*tan(dmnsn_radians(angle))*(X + Y) + Z))
+
+ cdef Vector dir = Vector(lookAt) - Vector(location)
+ cdef Vector vsky = Vector(sky)
+
+ # Line up the top of the viewport with the sky vector
+ cdef double thetaSkyX = dmnsn_vector_axis_angle(dmnsn_y, vsky._v, dmnsn_x)
+ cdef double thetaSkyZ = dmnsn_vector_axis_angle(dmnsn_y, vsky._v, dmnsn_z)
+ cdef Matrix alignSky = _rawRotate(thetaSkyX*X)*_rawRotate(thetaSkyZ*Z)
+ self.transform(alignSky)
+ cdef Vector right = alignSky*X
+ cdef Vector forward = alignSky*Z
+
+ # Line up the look at point with lookAt
+ cdef double thetaLookAtSky = dmnsn_vector_axis_angle(
+ forward._v, dir._v, vsky._v
+ )
+ cdef double thetaLookAtRight = dmnsn_vector_axis_angle(
+ forward._v, dir._v, right._v
+ )
+ self.transform(_rawRotate(thetaLookAtSky*vsky))
+ self.transform(_rawRotate(thetaLookAtRight*right))
+
+ # Move the camera into position
+ self.transform(translate(Vector(location)))
+
+###############
+# Sky Spheres #
+###############
+
+cdef class SkySphere:
+ cdef dmnsn_sky_sphere *_skySphere
+
+ def __init__(self, pigments):
+ self._skySphere = dmnsn_new_sky_sphere()
+
+ cdef Pigment realPigment
+ for pigment in pigments:
+ realPigment = Pigment(pigment)
+ DMNSN_INCREF(realPigment._pigment)
+ dmnsn_array_push(self._skySphere.pigments, &realPigment._pigment)
+
+ def __dealloc__(self):
+ dmnsn_delete_sky_sphere(self._skySphere)
+
+ def transform(self, Matrix trans not None):
+ self._skySphere.trans = dmnsn_matrix_mul(trans._m, self._skySphere.trans)
+ return self
+
+##########
+# Scenes #
+##########
+
+cdef class Scene:
+ cdef dmnsn_scene *_scene
+
+ def __init__(self, Canvas canvas not None, objects, lights,
+ Camera camera not None):
+ self._scene = dmnsn_new_scene()
+
+ self._scene.canvas = canvas._canvas
+ DMNSN_INCREF(self._scene.canvas)
+
+ cdef dmnsn_object *o
+ for obj in objects:
+ o = (<Object?>obj)._object
+ DMNSN_INCREF(o)
+ dmnsn_array_push(self._scene.objects, &o)
+
+ cdef dmnsn_light *l
+ for light in lights:
+ l = (<Light?>light)._light
+ DMNSN_INCREF(l)
+ dmnsn_array_push(self._scene.lights, &l)
+
+ # Account for image dimensions in the camera
+ camera._camera.trans = dmnsn_matrix_mul(
+ camera._camera.trans,
+ dmnsn_scale_matrix(dmnsn_new_vector(canvas.width/canvas.height, 1.0, 1.0))
+ )
+ self._scene.camera = camera._camera
+ DMNSN_INCREF(self._scene.camera)
+
+ property defaultTexture:
+ def __set__(self, Texture texture not None):
+ dmnsn_delete_texture(self._scene.default_texture)
+ self._scene.default_texture = texture._texture
+ DMNSN_INCREF(self._scene.default_texture)
+
+ property background:
+ def __get__(self):
+ return _rawColor(self._scene.background)
+ def __set__(self, color):
+ self._scene.background = Color(color)._c
+
+ property skySphere:
+ def __set__(self, SkySphere skySphere not None):
+ dmnsn_delete_sky_sphere(self._scene.sky_sphere)
+ self._scene.sky_sphere = skySphere._skySphere
+ DMNSN_INCREF(self._scene.sky_sphere)
+
+ property adcBailout:
+ def __get__(self):
+ return self._scene.adc_bailout
+ def __set__(self, double bailout):
+ self._scene.adc_bailout = bailout
+
+ property recursionLimit:
+ def __get__(self):
+ return self._scene.reclimit
+ def __set__(self, level):
+ self._scene.reclimit = level
+
+ property nThreads:
+ def __get__(self):
+ return self._scene.nthreads
+ def __set__(self, n):
+ self._scene.nthreads = n
+
+ def raytrace(self):
+ # Ensure the default texture is complete
+ cdef Texture default = Texture(Black)
+ dmnsn_texture_cascade(default._texture, &self._scene.default_texture)
+
+ dmnsn_raytrace_scene(self._scene)
+
+ def __dealloc__(self):
+ dmnsn_delete_scene(self._scene)