(For more resources related to this topic, see here.)
We may be able to create models and objects within our 3D space, as well as generate backgrounds, but we may also want to create a more interesting environment within which to place them.
3D terrain maps provide an elegant way to define very complex landscapes. The terrain is defined using a grayscale image to set the elevation of the land. The following example shows how we can define our own landscape and simulate flying over it, or even walk on its surface:
A 3D landscape generated from a terrain map
You will need to place the Map.png file in the pi3d/textures directory of the Pi3D library. Alternatively, you can use one of the elevation maps already present—replace the reference to Map.png for another one of the elevation maps, such as testislands.jpg.
How to do it…
Create the following 3dWorld.py script:
#!/usr/bin/python3 from __future__ import absolute_import, division from __future__ import print_function, unicode_literals """ An example of generating a 3D environment using a elevation map """ from math import sin, cos, radians import demo import pi3d DISPLAY = pi3d.Display.create(x=50, y=50) #capture mouse and key presses inputs=pi3d.InputEvents() def limit(value,min,max): if (value < min): value = min elif (value > max): value = max return value def main(): CAMERA = pi3d.Camera.instance() tex = pi3d.Texture("textures/grass.jpg") flatsh = pi3d.Shader("uv_flat") # Create elevation map mapwidth,mapdepth,mapheight=200.0,200.0,50.0 mymap = pi3d.ElevationMap("textures/Map.png", width=mapwidth, depth=mapdepth, height=mapheight, divx=128, divy=128, ntiles=20) mymap.set_draw_details(flatsh, [tex], 1.0, 1.0) rot = 0.0 # rotation of camera tilt = 0.0 # tilt of camera height = 20 viewhight = 4 sky = 200 xm,ym,zm = 0.0,height,0.0 onGround = False # main display loop while DISPLAY.loop_running() and not \ inputs.key_state("KEY_ESC"): inputs.do_input_events() #Note:Some mice devices will be located on #get_mouse_movement(1) or (2) etc. mx,my,mv,mh,md=inputs.get_mouse_movement() rot -= (mx)*0.2 tilt -= (my)*0.2 CAMERA.reset() CAMERA.rotate(-tilt, rot, 0) CAMERA.position((xm,ym,zm)) mymap.draw() if inputs.key_state("KEY_W"): xm -= sin(radians(rot)) zm += cos(radians(rot)) elif inputs.key_state("KEY_S"): xm += sin(radians(rot)) zm -= cos(radians(rot)) elif inputs.key_state("KEY_R"): ym += 2 onGround = False elif inputs.key_state("KEY_T"): ym -= 2 ym-=0.1 #Float down! #Limit the movement xm=limit(xm,-(mapwidth/2),mapwidth/2) zm=limit(zm,-(mapdepth/2),mapdepth/2) if ym >= sky: ym = sky #Check onGround ground = mymap.calcHeight(xm, zm) + viewhight if (onGround == True) or (ym <= ground): ym = mymap.calcHeight(xm, zm) + viewhight onGround = True try: main() finally: inputs.release() DISPLAY.destroy() print("Closed Everything. END") #End
How it works…
Once we have defined the display, camera, textures, and shaders that we are going to use, we can define the ElevationMap object.
It works by assigning a height to the terrain image based on the pixel value of selected points of the image. For example, a single line of an image will provide a slice of the ElevationMap object and a row of elevation points on the 3D surface.
Mapping the map.png pixel shade to the terrain height
We create an ElevationMap object by providing the filename of the image we will use for the gradient information (textures/Map.png), and we also create the dimensions of the map (width, depth, and height—which is how high the white spaces will be compared to the black spaces).
The light parts of the map will create high points and the dark ones will create low points.
The Map.png texture provides an example terrain map, which is converted into a three-dimensional surface.
We also specify divx and divy, which determines how much detail of the terrain map is used (how many points from the terrain map are used to create the elevation surface). Finally, ntiles specifies that the texture used will be scaled to fit 20 times across the surface.
Within the main DISPLAY.loop_running() section, we will control the camera, draw ElevationMap, respond to inputs, and limit movements in our space.
As before, we use an InputEvents object to capture mouse movements and translate them to control the camera. We will also use inputs.key_state() to determine if W, S, R, and T have been pressed, which allow us to move forward, backwards, as well as rise up and down.
To ensure that we do not fall through the ElevationMap object when we move over it, we can use mymap.calcHeight() to provide us with the height of the terrain at a specific location (x,y,z). We can either follow the ground by ensuring the camera is set to equal this, or fly through the air by just ensuring that we never go below it. When we detect that we are on the ground, we ensure that we remain on the ground until we press R to rise again.
In this article, we created a 3D world by covering how to define landscapes, the use of elevation maps, and the script required to respond to particular inputs and control movements in a space.