diff --git a/CMakeLists.txt b/CMakeLists.txt index 8af58ba6..61384b2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,7 @@ IF(ENABLE_EXAMPLES AND QT_QTOPENGL_FOUND) ADD_SUBDIRECTORY(examples/Paging) ADD_SUBDIRECTORY(examples/OpenGL) ADD_SUBDIRECTORY(examples/SmoothLOD) + ADD_SUBDIRECTORY(examples/Python) SET(BUILD_EXAMPLES ON) ELSE() SET(BUILD_EXAMPLES OFF) diff --git a/examples/Python/CMakeLists.txt b/examples/Python/CMakeLists.txt new file mode 100644 index 00000000..1d101d6e --- /dev/null +++ b/examples/Python/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (c) 2010-2013 Matt Williams +# +# This software is provided 'as-is', without any express or implied +# warranty. In no event will the authors be held liable for any damages +# arising from the use of this software. +# +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it +# freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. If you use this software +# in a product, an acknowledgment in the product documentation would be +# appreciated but is not required. +# +# 2. Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# +# 3. This notice may not be removed or altered from any source +# distribution. + +PROJECT(PythonExample) + +SOURCE_GROUP("Sources" FILES PythonExample.py) + +configure_file(PythonExample.py PythonExample.py COPYONLY) diff --git a/examples/Python/PythonExample.py b/examples/Python/PythonExample.py new file mode 100755 index 00000000..2a688971 --- /dev/null +++ b/examples/Python/PythonExample.py @@ -0,0 +1,257 @@ +#! /usr/bin/env python + +################################################################################ +# Copyright (c) 2013 Matt Williams +# +# This software is provided 'as-is', without any express or implied +# warranty. In no event will the authors be held liable for any damages +# arising from the use of this software. +# +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it +# freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. If you use this software +# in a product, an acknowledgment in the product documentation would be +# appreciated but is not required. +# +# 2. Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# +# 3. This notice may not be removed or altered from any source +# distribution. +################################################################################ + +import sys +sys.path.append("../../library/bindings/") #This is just to point to the generated bindings + +import PolyVoxCore as pv + +#Create a 64x64x64 volume of integers +r = pv.Region(pv.Vector3Dint32_t(0,0,0), pv.Vector3Dint32_t(63,63,63)) +vol = pv.SimpleVolumeuint8(r) + +#Now fill the volume with our data (a sphere) +v3dVolCenter = pv.Vector3Dint32_t(vol.getWidth() / 2, vol.getHeight() / 2, vol.getDepth() / 2) +sphereRadius = 30 +#This three-level for loop iterates over every voxel in the volume +for z in range(vol.getDepth()): + for y in range(vol.getHeight()): + for x in range(vol.getWidth()): + #Store our current position as a vector... + v3dCurrentPos = pv.Vector3Dint32_t(x,y,z) + #And compute how far the current position is from the center of the volume + fDistToCenter = (v3dCurrentPos - v3dVolCenter).length() + + uVoxelValue = 0 + + #If the current voxel is less than 'radius' units from the center then we make it solid. + if(fDistToCenter <= sphereRadius): + #Our new voxel value + uVoxelValue = 255 + + #Write the voxel value into the volume + vol.setVoxelAt(x, y, z, uVoxelValue); + +#Create a mesh, pass it to the extractor and generate the mesh +mesh = pv.SurfaceMeshPositionMaterialNormal() +extractor = pv.CubicSurfaceExtractorWithNormalsSimpleVolumeuint8(vol, r, mesh) +extractor.execute() + +#That's all of the PolyVox generation done, now to convert the output to something OpenGL can read efficiently + +import numpy as np + +indices = np.array(mesh.getIndices(), dtype='uint32') #Throw in the vertex indices into an array +#The vertices and normals are placed in an interpolated array like [vvvnnn,vvvnnn,vvvnnn] +vertices = np.array([[vertex.getPosition().getX(), vertex.getPosition().getY(), vertex.getPosition().getZ(), + vertex.getNormal().getX(), vertex.getNormal().getY(), vertex.getNormal().getZ()] + for vertex in mesh.getVertices()], + dtype='f') + +#Now that we have our data, everything else here is just OpenGL + +import OpenGL + +from OpenGL.GL import shaders +from OpenGL.arrays import vbo +from OpenGL.GL import glClear, glEnable, glDepthFunc, GLuint, glEnableVertexAttribArray, glVertexAttribPointer, glDisableVertexAttribArray, \ + glDrawElements, glGetUniformLocation, glUniformMatrix4fv, glDepthMask, glDepthRange, glGetString, glBindAttribLocation, \ + GL_COLOR_BUFFER_BIT, GL_TRIANGLES, GL_DEPTH_TEST, GL_LEQUAL, GL_FLOAT, \ + GL_DEPTH_BUFFER_BIT, GL_ELEMENT_ARRAY_BUFFER, GL_UNSIGNED_INT, GL_STATIC_DRAW, \ + GL_FALSE, GL_TRUE, GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, GL_CULL_FACE, \ + GL_VENDOR, GL_RENDERER, GL_VERSION, GL_SHADING_LANGUAGE_VERSION +from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays, glBindVertexArray + +import pygame + +from math import sin, cos, tan, radians + +SCREEN_SIZE = (800, 800) + +def run(): + #Start OpenGL and ask it for an OpenGL context + pygame.init() + clock = pygame.time.Clock() + screen = pygame.display.set_mode(SCREEN_SIZE, pygame.HWSURFACE|pygame.OPENGL|pygame.DOUBLEBUF) + + #The first thing we do is print some OpenGL details and check that we have a good enough version + print "OpenGL Implementation Details:" + if glGetString(GL_VENDOR): + print "\tGL_VENDOR:", glGetString(GL_VENDOR) + if glGetString(GL_RENDERER): + print "\tGL_RENDERER:", glGetString(GL_RENDERER) + if glGetString(GL_VERSION): + print "\tGL_VERSION:", glGetString(GL_VERSION) + if glGetString(GL_SHADING_LANGUAGE_VERSION): + print "\tGL_SHADING_LANGUAGE_VERSION:", glGetString(GL_SHADING_LANGUAGE_VERSION) + + major_version = int(glGetString(GL_VERSION).split()[0].split('.')[0]) + minor_version = int(glGetString(GL_VERSION).split()[0].split('.')[1]) + if major_version < 3 or (major_version < 3 and minor_version < 0): + print "OpenGL version must be at least 3.0 (found {0})".format(glGetString(GL_VERSION).split()[0]) + + #Now onto the OpenGL initialisation + + #Set up depth culling + glEnable(GL_CULL_FACE) + glEnable(GL_DEPTH_TEST) + glDepthMask(GL_TRUE) + glDepthFunc(GL_LEQUAL) + glDepthRange(0.0, 1.0) + + #We create out shaders which do little more than set a flat colour for each face + + VERTEX_SHADER = shaders.compileShader(""" + #version 130 + + in vec4 position; + in vec4 normal; + + uniform mat4 cameraToClipMatrix; + uniform mat4 worldToCameraMatrix; + uniform mat4 modelToWorldMatrix; + + flat out float theColor; + + void main() + { + vec4 temp = modelToWorldMatrix * position; + temp = worldToCameraMatrix * temp; + gl_Position = cameraToClipMatrix * temp; + + theColor = clamp(abs(dot(normalize(normal.xyz), normalize(vec3(0.9,0.1,0.5)))), 0, 1); + } + """, GL_VERTEX_SHADER) + + FRAGMENT_SHADER = shaders.compileShader(""" + #version 130 + + flat in float theColor; + + out vec4 outputColor; + void main() + { + outputColor = vec4(1.0, 0.5, theColor, 1.0); + } + """, GL_FRAGMENT_SHADER) + + shader = shaders.compileProgram(VERTEX_SHADER, FRAGMENT_SHADER) + + #And then grab our attribute locations from it + glBindAttribLocation(shader, 0, "position") + glBindAttribLocation(shader, 1, "normal") + + #Create the Vertex Array Object to hold our volume mesh + vertexArrayObject = GLuint(0) + glGenVertexArrays(1, vertexArrayObject) + glBindVertexArray(vertexArrayObject) + + #Create the index buffer object + indexPositions = vbo.VBO(indices, target=GL_ELEMENT_ARRAY_BUFFER, usage=GL_STATIC_DRAW) + #Create the VBO + vertexPositions = vbo.VBO(vertices, usage=GL_STATIC_DRAW) + + #Bind our VBOs and set up our data layout specifications + with indexPositions, vertexPositions: + glEnableVertexAttribArray(0) + glVertexAttribPointer(0, 3, GL_FLOAT, False, 6*vertices.dtype.itemsize, vertexPositions+(0*vertices.dtype.itemsize)) + glEnableVertexAttribArray(1) + glVertexAttribPointer(1, 3, GL_FLOAT, False, 6*vertices.dtype.itemsize, vertexPositions+(3*vertices.dtype.itemsize)) + + glBindVertexArray(0) + glDisableVertexAttribArray(0) + + #Now grab out transformation martix locations + modelToWorldMatrixUnif = glGetUniformLocation(shader, "modelToWorldMatrix") + worldToCameraMatrixUnif = glGetUniformLocation(shader, "worldToCameraMatrix") + cameraToClipMatrixUnif = glGetUniformLocation(shader, "cameraToClipMatrix") + + modelToWorldMatrix = np.array([[1.0,0.0,0.0,-32.0],[0.0,1.0,0.0,-32.0],[0.0,0.0,1.0,-32.0],[0.0,0.0,0.0,1.0]], dtype='f') + worldToCameraMatrix = np.array([[1.0,0.0,0.0,0.0],[0.0,1.0,0.0,0.0],[0.0,0.0,1.0,-50.0],[0.0,0.0,0.0,1.0]], dtype='f') + cameraToClipMatrix = np.array([[0.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0]], dtype='f') + + #These next few lines just set up our camera frustum + fovDeg = 45.0 + frustumScale = 1.0 / tan(radians(fovDeg) / 2.0) + + zNear = 1.0 + zFar = 1000.0 + + cameraToClipMatrix[0][0] = frustumScale + cameraToClipMatrix[1][1] = frustumScale + cameraToClipMatrix[2][2] = (zFar + zNear) / (zNear - zFar) + cameraToClipMatrix[2][3] = -1.0 + cameraToClipMatrix[3][2] = (2 * zFar * zNear) / (zNear - zFar) + + #worldToCameraMatrix and cameraToClipMatrix don't change ever so just set them once here + with shader: + glUniformMatrix4fv(cameraToClipMatrixUnif, 1, GL_TRUE, cameraToClipMatrix) + glUniformMatrix4fv(worldToCameraMatrixUnif, 1, GL_TRUE, worldToCameraMatrix) + + #These are used to track the rotation of the volume + LastFrameMousePos = (0,0) + CurrentMousePos = (0,0) + xRotation = 0 + yRotation = 0 + + while True: + clock.tick() + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return + if event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE: + return + if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: + CurrentMousePos = event.pos + LastFrameMousePos = CurrentMousePos + if event.type == pygame.MOUSEMOTION and 1 in event.buttons: + CurrentMousePos = event.pos + diff = (CurrentMousePos[0] - LastFrameMousePos[0], CurrentMousePos[1] - LastFrameMousePos[1]) + xRotation += event.rel[0] + yRotation += event.rel[1] + LastFrameMousePos = CurrentMousePos + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + + #Perform the rotation of the mesh + moveToOrigin = np.array([[1.0,0.0,0.0,-32.0],[0.0,1.0,0.0,-32.0],[0.0,0.0,1.0,-32.0],[0.0,0.0,0.0,1.0]], dtype='f') + rotateAroundX = np.array([[1.0,0.0,0.0,0.0],[0.0,cos(radians(yRotation)),-sin(radians(yRotation)),0.0],[0.0,sin(radians(yRotation)),cos(radians(yRotation)),0.0],[0.0,0.0,0.0,1.0]], dtype='f') + rotateAroundY = np.array([[cos(radians(xRotation)),0.0,sin(radians(xRotation)),0.0],[0.0,1.0,0.0,0.0],[-sin(radians(xRotation)),0.0,cos(radians(xRotation)),0.0],[0.0,0.0,0.0,1.0]], dtype='f') + + modelToWorldMatrix = rotateAroundY.dot(rotateAroundX.dot(moveToOrigin)) + + with shader: + glUniformMatrix4fv(modelToWorldMatrixUnif, 1, GL_TRUE, modelToWorldMatrix) + glBindVertexArray(vertexArrayObject) + + glDrawElements(GL_TRIANGLES, len(indices), GL_UNSIGNED_INT, None) + + glBindVertexArray(0) + + # Show the screen + pygame.display.flip() + +run()