From 10716020afdf42fb83a69188fc3d72042e5ad00a Mon Sep 17 00:00:00 2001 From: Rnhmjoj Date: Tue, 20 May 2014 17:02:35 +0200 Subject: [PATCH] Add main files --- LICENSE | 21 --- README.md | 13 +- screenshot.png | Bin 0 -> 47182 bytes terrain.py | 148 +++++++++++++++++++ trackball_camera.py | 338 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 496 insertions(+), 24 deletions(-) delete mode 100644 LICENSE create mode 100644 screenshot.png create mode 100644 terrain.py create mode 100644 trackball_camera.py diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f8fa6a8..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Michele Guerini Rocco - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index beaf156..f857429 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ -terrain -======= +# Terrain -Simple terrain generator +## Simple terrain generator +![Screenshot](screenshot.png) +## Info +A simple application which uses the diamond square algorithm to generate a terrain and renders it in OpenGL. + +### License +Dual licensed under the MIT and GPL licenses: +http://www.opensource.org/licenses/mit-license.php +http://www.gnu.org/licenses/gpl.html diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ffe6f3f27217b86dc32a67379c0787478a59bbfc GIT binary patch literal 47182 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

ydA_$9_=M4@Ob(WSkf55EGPk;VlOonF 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 obB 1: + for y in numpy.arange(half, self.size - 1, i): + for x in numpy.arange(half, self.size - 1, i): + self._square(x, y, half, scale / 2 * random.random()) + for y in numpy.arange(0, self.size - 1, half): + for x in numpy.arange((y + half) % i, self.size - 1, i): + self._diamond(x, y, half, scale / 2 * random.random()) + i /= 2 + half = i / 2 + scale = high * half * 2 + + +class Window(pyglet.window.Window): + def __init__(self, *args, **kwargs): + self.terrain = None + self.camera = kwargs['camera'] + del kwargs['camera'] + super().__init__(*args, **kwargs) + + # Initialize OpenGL + glEnable(GL_DEPTH_TEST) + glDisable(GL_CULL_FACE) + + def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): + """Move camera/zoom on mouse drag""" + if buttons & pyglet.window.mouse.LEFT: + self.camera.mouse_roll( + self._norm(x, self.width), + self._norm(y, self.height)) + elif buttons & pyglet.window.mouse.RIGHT: + self.camera.mouse_zoom( + self._norm(x * 2, self.width), + self._norm(y * 2, self.height)) + + def on_mouse_press(self, x, y, button, modifiers): + """Move camera/zoom on mouse drag""" + if button == pyglet.window.mouse.LEFT: + self.camera.mouse_roll( + self._norm(x, self.width), + self._norm(y, self.height), + False) + elif button == pyglet.window.mouse.RIGHT: + self.camera.mouse_zoom( + self._norm(x * 2, self.width), + self._norm(y * 2, self.height), + False) + + def on_resize(self, width, height): + """Adjust drawing after window is resized""" + self.width = width + self.height = height + glViewport(0, 0, self.width, self.height) + self.on_show() + + def on_show(self): + """Set OpenGl config""" + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + gluPerspective(40, self.width / self.height, 1, 400) + self.camera.update_modelview() + + def on_draw(self): + """Draw the current frame""" + if self.terrain is None: + return + self.clear() + map, size = self.terrain.map, self.terrain.size + + glPushMatrix() + glTranslatef(-size / 2, 0, -size / 2) + + # Draw terrain + for x in numpy.arange(size - 1): + for y in numpy.arange(size - 1): + glBegin(GL_TRIANGLE_STRIP) + glColor3f(map[x][y] / 20, map[x][y] / 20, map[x][y] / 20) + glVertex3f(x, map[x][y], y) + glVertex3f(x + 1, map[x + 1][y], y) + glVertex3f(x, map[x][y + 1], y + 1) + glVertex3f(x + 1, map[x + 1][y + 1], y + 1) + glEnd() + glPopMatrix() + + # Draw axis + glBegin(GL_LINES) + glColor3f(1, 0, 0) + glVertex3f(-size, 0, 0) + glVertex3f(size, 0, 0) + glColor3f(0, 1, 0) + glVertex3f(0, -size, 0) + glVertex3f(0, size, 0) + glColor3f(0, 0, 1) + glVertex3f(0, 0, -size) + glVertex3f(0, 0, size) + glEnd() + + def _norm(self, x, max_x): + """given x within [0,max_x], scale to a range [-1,1]""" + return (2 * x - float(max_x)) / float(max_x) + + def draw(self, terrain): + """Render the height map""" + self.terrain = terrain + + +def main(): + terrain = Terrain(5) + camera = TrackballCamera(150) + window = Window(caption="Terrain", resizable=True, + width=800, height=600, camera=camera) + terrain.generate(0.7) + window.draw(terrain) + pyglet.app.run() + +if __name__ == '__main__': + main() diff --git a/trackball_camera.py b/trackball_camera.py new file mode 100644 index 0000000..c09068a --- /dev/null +++ b/trackball_camera.py @@ -0,0 +1,338 @@ +"""trackball_camera.py - An OpenGL Trackball Camera Class for Pyglet + +by Roger Allen, July 2008 +roger@rogerandwendy.com + +A class for simple-minded 3d example apps. + +Usage: + +Initialize with a radius from the center/focus point: + + tbcam = TrackballCamera(5.0) + +After adjusting your projection matrix, set the modelview matrix. + + tbcam.update_modelview() + +On each primary mouse click, scale the x & y to [-1,1] and call: + + tbcam.mouse_roll(x,y,False) + +On each primary mouse drag, scale the x & y to [-1,1] and call: + + tbcam.mouse_roll(x,y) + +Mouse movements adjust the modelview projection matrix directly. + +""" + +__version__ = "1.0" + +# Code derived from the GLUT trackball.c, but now quite different and +# customized for pyglet. +# +# I simply wanted an easy-to-use trackball camera for quick-n-dirty +# opengl programs that I'd like to write. Finding none, I grabbed +# the trackball.c code & started hacking. +# +# Originally implemented by Gavin Bell, lots of ideas from Thant Tessman +# and the August '88 issue of Siggraph's "Computer Graphics," pp. 121-129. +# and David M. Ciemiewicz, Mark Grossman, Henry Moreton, and Paul Haeberli +# +# Note: See the following for more information on quaternions: +# +# - Shoemake, K., Animating rotation with quaternion curves, Computer +# Graphics 19, No 3 (Proc. SIGGRAPH'85), 245-254, 1985. +# - Pletinckx, D., Quaternion calculus as a basic tool in computer +# graphics, The Visual Computer 5, 2-13, 1989. +# +# Gavin Bell's code had this copyright notice: +# (c) Copyright 1993, 1994, Silicon Graphics, Inc. +# ALL RIGHTS RESERVED +# Permission to use, copy, modify, and distribute this software for +# any purpose and without fee is hereby granted, provided that the above +# copyright notice appear in all copies and that both the copyright notice +# and this permission notice appear in supporting documentation, and that +# the name of Silicon Graphics, Inc. not be used in advertising +# or publicity pertaining to distribution of the software without specific, +# written prior permission. +# +# THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS" +# AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE, +# INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR +# FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON +# GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT, +# SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY +# KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, +# LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF +# THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN +# ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE +# POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE. +# +# US Government Users Restricted Rights +# Use, duplication, or disclosure by the Government is subject to +# restrictions set forth in FAR 52.227.19(c)(2) or subparagraph +# (c)(1)(ii) of the Rights in Technical Data and Computer Software +# clause at DFARS 252.227-7013 and/or in similar or successor +# clauses in the FAR or the DOD or NASA FAR Supplement. +# Unpublished-- rights reserved under the copyright laws of the +# United States. Contractor/manufacturer is Silicon Graphics, +# Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311. + +import math +import copy +from pyglet.gl import * + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# a little vector library that is misused in odd ways below. +def v3add(src1, src2): + return [ src1[0] + src2[0], + src1[1] + src2[1], + src1[2] + src2[2] ] + +def v3sub(src1, src2): + return [ src1[0] - src2[0], + src1[1] - src2[1], + src1[2] - src2[2] ] + +def v3scale(v, scale): + return [ v[0] * scale, + v[1] * scale, + v[2] * scale ] + +def v3dot(v1, v2): + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2] + +def v3cross(v1, v2): + return [ (v1[1] * v2[2]) - (v1[2] * v2[1]), + (v1[2] * v2[0]) - (v1[0] * v2[2]), + (v1[0] * v2[1]) - (v1[1] * v2[0]) ] + +def v3length(v): + return math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) + +def v3normalize(v): + try: + tmp = v3scale(v,1.0/v3length(v)) + return tmp + except ZeroDivisionError: + return v + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Some quaternion routines +def q_add(q1, q2): + """Given two quaternions, add them together to get a third quaternion. + Adding quaternions to get a compound rotation is analagous to adding + translations to get a compound translation. When incrementally + adding rotations, the first argument here should be the new rotation. + """ + t1 = v3scale(q1,q2[3]) + t2 = v3scale(q2,q1[3]) + t3 = v3cross(q2,q1) + tf = v3add(t1,t2) + tf = v3add(t3,tf) + tf.append( q1[3] * q2[3] - v3dot(q1,q2) ) + return tf + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def q_from_axis_angle(a, phi): + # a is a 3-vector, q is a 4-vector + """Computes a quaternion based on an axis (defined by the given vector) + and an angle about which to rotate. The angle is expressed in radians. + """ + q = v3normalize(a) + q = v3scale(q, math.sin(phi/2.0)) + q.append(math.cos(phi/2.0)) + return q + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def q_normalize(q): + """Return a normalized quaternion""" + mag = (q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]) + if mag != 0: + for i in range(4): + q[i] /= mag; + return q + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def q_matrix(q): + """return the rotation matrix based on q""" + m = [0.0]*16 + m[0*4+0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]) + m[0*4+1] = 2.0 * (q[0] * q[1] - q[2] * q[3]) + m[0*4+2] = 2.0 * (q[2] * q[0] + q[1] * q[3]) + m[0*4+3] = 0.0 + + m[1*4+0] = 2.0 * (q[0] * q[1] + q[2] * q[3]) + m[1*4+1] = 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]) + m[1*4+2] = 2.0 * (q[1] * q[2] - q[0] * q[3]) + m[1*4+3] = 0.0 + + m[2*4+0] = 2.0 * (q[2] * q[0] - q[1] * q[3]) + m[2*4+1] = 2.0 * (q[1] * q[2] + q[0] * q[3]) + m[2*4+2] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]) + m[2*4+3] = 0.0 + + m[3*4+0] = 0.0 + m[3*4+1] = 0.0 + m[3*4+2] = 0.0 + m[3*4+3] = 1.0 + return m + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +def project_z(r, x, y): + """Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet + if we are away from the center of the sphere. + """ + d = math.sqrt(x*x + y*y) + if (d < r * 0.70710678118654752440): # Inside sphere + z = math.sqrt(r*r - d*d) + else: # On hyperbola + t = r / 1.41421356237309504880 + z = t*t / d + return z + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Trackball Camera Class +# +class TrackballCamera: + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def __init__(self, radius=1.0): + """ initialize the camera, giving a radius from the focal point for + the camera eye. Update focal point & up via the update_modelview call. + """ + # the quaternion storing the rotation + self.rot_quat = [0,0,0,1] + # the last mouse update + self.last_x = None + self.last_y = None + # camera vars + self.cam_eye = [0.,0.,radius] + self.cam_focus = [0.,0.,0.] + self.cam_up = [0.,1.,0.] + # in add_quat routine, renormalize "sometimes" + self.RENORMCOUNT = 97 + self.count = 0 + # Trackballsize should really be based on the distance from the center of + # rotation to the point on the object underneath the mouse. That + # point would then track the mouse as closely as possible. This is a + # simple example, though, so that is left as an Exercise for the + # Programmer. + self.TRACKBALLSIZE = 0.8 + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def mouse_roll(self, norm_mouse_x, norm_mouse_y, dragging=True): + """When you click or drag the primary mouse button, scale the mouse + x & y to the range [-1.0,1.0] and call this routine to roll the trackball + and update the modelview matrix. + + The initial click should set dragging to False. + """ + if dragging: + norm_mouse_quat = self._rotate(norm_mouse_x, norm_mouse_y) + self.rot_quat = q_add(norm_mouse_quat,self.rot_quat) + self.count += 1 + if (self.count > self.RENORMCOUNT): + self.rot_quat = q_normalize(self.rot_quat) + self.count = 0 + self.update_modelview() + self.last_x = norm_mouse_x + self.last_y = norm_mouse_y + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def mouse_zoom(self, norm_mouse_x, norm_mouse_y, dragging=True): + """When you click or drag a secondary mouse button, scale the mouse + x & y to the range [-1.0,1.0] and call this routine to change the + trackball's camera radius and update the modelview matrix. + + The initial click should set dragging to False. + """ + if self.last_x: + dx = norm_mouse_x - self.last_x + dy = norm_mouse_y - self.last_y + norm_mouse_r_delta = 20.0*math.sqrt(dx*dx+dy*dy) + if dy > 0.0: + norm_mouse_r_delta = -norm_mouse_r_delta + if dragging: + self.cam_eye[2] = self.cam_eye[2] + norm_mouse_r_delta + if self.cam_eye[2] < 1.0: + self.cam_eye[2] == 1.0 + self.update_modelview() + self.last_x = norm_mouse_x + self.last_y = norm_mouse_y + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def update_modelview(self,cam_radius=None,cam_focus=None,cam_up=None): + """Given a radius for the trackball camera, a focus-point 3-vector, + another 3-vector the points 'up' combined with the current + orientation of the trackball, update the GL_MODELVIEW matrix. + """ + if cam_radius: + self.cam_eye[2] = cam_radius + if cam_focus: + self.cam_focus = cam_focus + if cam_up: + self.cam_up = cam_up + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + gluLookAt( + self.cam_eye[0],self.cam_eye[1],self.cam_eye[2], + self.cam_focus[0],self.cam_focus[1],self.cam_focus[2], + self.cam_up[0],self.cam_up[1],self.cam_up[2] + ) + # rotate this view by the current orientation + m = self._matrix() + mm = (GLfloat * len(m))(*m) # FIXME there is prob a better way... + glMultMatrixf(mm) + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def _matrix(self): + """return the rotation matrix for the trackball""" + return q_matrix(self.rot_quat) + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def _rotate(self, norm_mouse_x, norm_mouse_y): + """Pass the x and y coordinates of the last and current positions of + the mouse, scaled so they are in the range [-1.0,1.0]. + + Simulate a track-ball. Project the points onto the virtual + trackball, then figure out the axis of rotation, which is the cross + product of LAST NEW and O LAST (O is the center of the ball, 0,0,0) + Note: This is a deformed trackball-- is a trackball in the center, + but is deformed into a hyperbolic sheet of rotation away from the + center. This particular function was chosen after trying out + several variations. + """ + # handle special case + if (self.last_x == norm_mouse_x and self.last_y == norm_mouse_y): + # Zero rotation + return [ 0.0, 0.0, 0.0, 1.0] + + # + # First, figure out z-coordinates for projection of P1 and P2 to + # deformed sphere + # + last = [self.last_x, self.last_y, project_z(self.TRACKBALLSIZE,self.last_x,self.last_y)] + new = [norm_mouse_x, norm_mouse_y, project_z(self.TRACKBALLSIZE,norm_mouse_x,norm_mouse_y)] + + # + # Now, we want the cross product of LAST and NEW + # aka the axis of rotation + # + a = v3cross(new,last) + + # + # Figure out how much to rotate around that axis (phi) + # + d = v3sub(last,new) + t = v3length(d) / (2.0*self.TRACKBALLSIZE) + # Avoid problems with out-of-control values... + if (t > 1.0): t = 1.0 + if (t < -1.0): t = -1.0 + phi = 2.0 * math.asin(t) + + return q_from_axis_angle(a,phi) +