class ShadowCaster(OffScreenTexture): """For creating a depth-of-field blurring effect on selected objects""" def __init__(self, position, light, scale=10.0): """ calls Texture.__init__ but doesn't need to set file name as texture generated from the framebuffer """ super(ShadowCaster, self).__init__("shadow_caster") self.LIGHT_CAM = Camera(is_3d=False, scale=scale) l_p = light.lightpos l_len = (l_p[0]**2 + l_p[1]**2 + l_p[2]**2)**0.5 self.OFFSET = [200.0 * i / l_len for i in l_p] self.LIGHT_CAM.position([position[i] - o for i, o in enumerate(self.OFFSET)]) self.tilt, self.rot = self.LIGHT_CAM.point_at(position) self.cast_shader = Shader("shadowcast") def move_light(self, position): self.LIGHT_CAM.reset() self.LIGHT_CAM.rotate(self.tilt, self.rot, 0) self.LIGHT_CAM.position([position[i] - o for i, o in enumerate(self.OFFSET)]) def start_cast(self, position=None): if position is not None: self.move_light(position) super(ShadowCaster, self)._start() def cast_shadow(self, shape): shape.draw(shader=self.cast_shader, light_camera=self.LIGHT_CAM) def end_cast(self): super(ShadowCaster, self)._end() def draw_shadow(self): self.emap.draw(shader=self.dshader) def draw_tree(self, tree, shader): tree.draw(shader, [self])
class ShadowCaster(OffScreenTexture): """For creating a depth-of-field blurring effect on selected objects""" def __init__(self, position, light, scale=10.0, ix=None, iy=None): """ calls Texture.__init__ but doesn't need to set file name as texture generated from the framebuffer """ super(ShadowCaster, self).__init__("shadow_caster") self.LIGHT_CAM = Camera(is_3d=False, scale=scale) l_p = light.lightpos l_len = (l_p[0]**2 + l_p[1]**2 + l_p[2]**2)**0.5 self.OFFSET = [200.0 * i / l_len for i in l_p] self.OFFSET[0] = -self.OFFSET[0] self.OFFSET[2] = -self.OFFSET[2] self.LIGHT_CAM.position( [position[i] - o for i, o in enumerate(self.OFFSET)]) self.tilt, self.rot = self.LIGHT_CAM.point_at(position) self.cast_shader = Shader("shadowcast") def move_light(self, position): self.LIGHT_CAM.reset() self.LIGHT_CAM.rotate(self.tilt, self.rot, 0) self.LIGHT_CAM.position( [position[i] - o for i, o in enumerate(self.OFFSET)]) def start_cast(self, position=None): if position is not None: self.move_light(position) super(ShadowCaster, self)._start() def cast_shadow(self, shape): shape.draw(shader=self.cast_shader, light_camera=self.LIGHT_CAM) def end_cast(self): super(ShadowCaster, self)._end() def draw_shadow(self): self.emap.draw(shader=self.dshader) def draw_tree(self, tree, shader): tree.draw(shader, [self])
class StereoCam(object): """For creating an apparatus with two sprites to hold left and right eye views. This Class is used to hold the 3D Camera which should be used to draw the 3D objects. It also holds a 2D Camera for drawing the Sprites""" def __init__(self, shader="uv_flat", mipmap=False, separation=0.4, interlace=0): """ calls Texture.__init__ but doesn't need to set file name as texture generated from the framebuffer. Keyword Arguments: *shader* to use when drawing sprite, defaults to post_base, a simple 3x3 convolution that does basic edge detection. Can be copied to project directory and modified as required. *mipmap* can be set to True with slight cost to speed, or use fxaa shader *separation* distance between the two camera positions - how wide apart the eye views are. *interlace* if interlace > 0 then the images are not taken with glScissor and must be drawn with a special interlacing shader. """ # load shader if interlace <= 0: self.shader = Shader(shader) else: self.shader = Shader( vshader_source=""" precision mediump float; attribute vec3 vertex; attribute vec2 texcoord; uniform mat4 modelviewmatrix[2]; varying vec2 texcoordout; void main(void) { texcoordout = texcoord; gl_Position = modelviewmatrix[1] * vec4(vertex,1.0); } """, fshader_source=""" precision mediump float; uniform sampler2D tex0; uniform sampler2D tex1; varying vec2 texcoordout; void main(void) {{ vec4 texc0 = texture2D(tex0, texcoordout); vec4 texc1 = texture2D(tex1, texcoordout); vec2 coord = vec2(gl_FragCoord); gl_FragColor = mix(texc0, texc1, step(0.5, fract(coord.x / {:f}))); }} """.format( interlace * 2.0 ), ) # self.shader = Shader("2d_flat") self.camera_3d = Camera() self.forMtrx = np.identity(4, dtype="float32") # initially not rotated self.position = [0.0, 0.0, 0.0] self.camera_2d = Camera(is_3d=False) self.offs = separation / 2.0 self.interlace = interlace self.textures = [] self.sprites = [] self.tex_list = [] for i in range(2): self.textures.append(OffScreenTexture(name="stereo")) ix, iy = self.textures[i].ix, self.textures[i].iy # two sprites full width but moved so that they are centred on the # left and right edges. The offset values then move the uv mapping # so the image is on the right of the left sprite and left of the # right sprite self.sprites.append(Sprite(z=20.0, w=ix, h=iy, flip=True)) if interlace <= 0: self.sprites[i].positionX(-ix / 2.0 + i * ix) self.sprites[i].set_offset((i * 0.5 - 0.25, 0.0)) else: self.sprites[i].set_2d_size(w=ix, h=iy) self.textures[i].blend = True self.textures[i].mipmap = mipmap self.tex_list.append(self.textures[i]) opengles.glColorMask(1, 1, 1, 1) def move_camera(self, position, rot, tilt, roll=0.0, absolute=True): """ Arguments: *position* array [x,y,z] *rot, tilt, roll* rotations about y, x, z axis (yes it's not entirely logical for position to be an array and orientation three values but it's too late to change!) *absolute* if set to False then the rotations are treated as relative to the rotated frame of reference i.e. as if signals from VR headset 3 axis gyro. """ self.camera_3d.rotate(tilt, rot, roll) self.camera_3d.position(position) self.camera_3d.absolute = absolute def start_capture(self, side): """ after calling this method all object.draw()s will rendered to this texture and not appear on the display. *side* Either 0 or 1 to determine stereoscopic view """ self.camera_3d.reset() offs = -self.offs if side == 0 else self.offs self.camera_3d.offset([offs, 0.0, 0.0]) # self.camera_3d.mtrx = np.dot(self.forMtrx, self.camera_3d.mtrx) # self.camera_3d.position(self.position) tex = self.textures[side] tex._start() if self.interlace <= 0: xx = tex.ix / 4.0 # draw the middle only - half width yy = 0 ww = tex.ix / 2.0 hh = tex.iy opengles.glEnable(GL_SCISSOR_TEST) opengles.glScissor( ctypes.c_int(int(xx)), ctypes.c_int(int(yy)), ctypes.c_int(int(ww)), ctypes.c_int(int(hh)) ) def end_capture(self, side): """ stop capturing to texture and resume normal rendering to default """ self.textures[side]._end() if self.interlace <= 0: opengles.glDisable(GL_SCISSOR_TEST) def draw(self): """ draw the shape using the saved texture """ if self.interlace <= 0: for i in range(2): self.sprites[i].draw(self.shader, [self.tex_list[i]], 0.0, 0.0, self.camera_2d) else: self.sprites[0].draw(self.shader, self.tex_list, 0.0, 0.0, self.camera_2d) def get_direction(self): return self.camera_3d.get_direction()
omx, omy = mymouse.position() while DISPLAY.loop_running(): # mouse movement checking here to get bat movment values mx, my = mymouse.position() dx = (mx - omx) * 0.04 omx = mx if ((xm >= (-1 * maphalf) and dx < 0) or (xm <= maphalf and dx > 0)): xm += dx dy = (my - omy) * 0.02 omy = my if ((ym >= (0) and dy < 0) or (ym <= mapheight and dy > 0)): ym += dy if not (dy == 0.0 and dx == 0.0): camera.reset() camera.position((xm, 2 + ym, -maphalf - 2.5)) #monster movement drx = sx - rx if abs(drx) > max_speed: drx = drx / abs(drx) * max_speed dry = sy - ry if abs(dry) > max_speed: dry = dry / abs(dry) * max_speed rx += drx ry += dry monster.position(rx, ry, maphalf) dsy -= gravity sx += dsx sy += dsy
class StereoCam(object): """For creating an apparatus with two sprites to hold left and right eye views. This Class is used to hold the 3D Camera which should be used to draw the 3D objects. It also holds a 2D Camera for drawing the Sprites""" def __init__(self, shader="uv_flat", mipmap=False, separation=0.4, interlace=0): """ calls Texture.__init__ but doesn't need to set file name as texture generated from the framebuffer. Keyword Arguments: *shader* to use when drawing sprite, defaults to uv_flat. *mipmap* can be set to True with slight cost to speed, or use fxaa shader *separation* distance between the two camera positions - how wide apart the eye views are. *interlace* if interlace > 0 then the images are not taken with glScissor and must be drawn with a special interlacing shader. """ # load shader if interlace <= 0: # i.e. default side by side behaviour self.shader = Shader.create(shader) else: self.shader = Shader(vshader_source = """ precision mediump float; attribute vec3 vertex; attribute vec2 texcoord; uniform mat4 modelviewmatrix[2]; varying vec2 texcoordout; void main(void) { texcoordout = texcoord; gl_Position = modelviewmatrix[1] * vec4(vertex,1.0); } """, fshader_source = """ precision mediump float; uniform sampler2D tex0; uniform sampler2D tex1; varying vec2 texcoordout; void main(void) {{ vec4 texc0 = texture2D(tex0, texcoordout); vec4 texc1 = texture2D(tex1, texcoordout); vec2 coord = vec2(gl_FragCoord); gl_FragColor = mix(texc0, texc1, step(0.5, fract(coord.x / {:f}))); }} """.format(interlace * 2.0)) self.camera_3d = Camera() # create 3d cam first so it becomes default instance self.forMtrx = np.identity(4, dtype='float32') # initially not rotated self.position = [0.0, 0.0, 0.0] self.camera_2d = Camera(is_3d=False) self.offs = separation / 2.0 self.interlace = interlace self.textures = [] self.sprites = [] self.tex_list = [] for i in range(2): self.textures.append(OffScreenTexture(name="stereo")) ix, iy = self.textures[i].ix, self.textures[i].iy #two sprites full width but moved so that they are centred on the #left and right edges. The offset values then move the uv mapping #so the image is on the right of the left sprite and left of the #right sprite self.sprites.append(Sprite(z=20.0, w=ix, h=iy, flip=True)) if interlace <= 0: self.sprites[i].positionX(-ix/2.0 + i*ix) self.sprites[i].set_offset((i * 0.5 - 0.25, 0.0)) else: self.sprites[i].set_2d_size(w=ix, h=iy) self.textures[i].blend = True self.textures[i].mipmap = mipmap self.tex_list.append(self.textures[i]) opengles.glColorMask(1, 1, 1, 1) def move_camera(self, position, rot, tilt, roll=0.0, absolute=True): ''' Arguments: *position* array [x,y,z] *rot, tilt, roll* rotations about y, x, z axis (yes it's not entirely logical for position to be an array and orientation three values but it's too late to change!) *absolute* if set to False then the rotations are treated as relative to the rotated frame of reference i.e. as if signals from VR headset 3 axis gyro. ''' self.camera_3d.rotate(tilt, rot, roll) self.camera_3d.position(position) self.camera_3d.absolute = absolute def start_capture(self, side): """ after calling this method all object.draw()s will rendered to this texture and not appear on the display. *side* Either 0 or 1 to determine stereoscopic view """ self.camera_3d.reset() offs = -self.offs if side == 0 else self.offs self.camera_3d.offset([offs, 0.0, 0.0]) #self.camera_3d.mtrx = np.dot(self.forMtrx, self.camera_3d.mtrx) #self.camera_3d.position(self.position) tex = self.textures[side] tex._start() if self.interlace <= 0: xx = tex.ix / 4.0 # draw the middle only - half width yy = 0 ww = tex.ix / 2.0 hh = tex.iy opengles.glEnable(GL_SCISSOR_TEST) opengles.glScissor(GLint(int(xx)), GLint(int(yy)), GLsizei(int(ww)), GLsizei(int(hh))) def end_capture(self, side): """ stop capturing to texture and resume normal rendering to default """ self.textures[side]._end() if self.interlace <= 0: opengles.glDisable(GL_SCISSOR_TEST) def draw(self): """ draw the shape using the saved texture """ if self.interlace <= 0: for i in range(2): self.sprites[i].draw(self.shader, [self.tex_list[i].color], 0.0, 0.0, self.camera_2d) else: self.sprites[0].draw(self.shader, [t.color for t in self.tex_list], 0.0, 0.0, self.camera_2d) def get_direction(self): return self.camera_3d.get_direction()
class ShadowCaster(Texture): """For creating a depth-of-field blurring effect on selected objects""" def __init__(self, emap, light): """ calls Texture.__init__ but doesn't need to set file name as texture generated from the framebuffer """ super(ShadowCaster, self).__init__("shadow_caster") from pi3d.Display import Display self.ix, self.iy = Display.INSTANCE.width, Display.INSTANCE.height self.im = Image.new("RGBA",(self.ix, self.iy)) self.image = self.im.convert("RGBA").tostring('raw', "RGBA") self.alpha = True self.blend = False self._tex = ctypes.c_int() self.framebuffer = (ctypes.c_int * 1)() opengles.glGenFramebuffers(1, self.framebuffer) self.depthbuffer = (ctypes.c_int * 1)() opengles.glGenRenderbuffers(1, self.depthbuffer) # load shader for casting shadows and camera self.cshader = Shader("shaders/uv_flat") self.mshader = Shader("shaders/mat_flat") # keep copy of ElevationMap self.emap = emap self.emap.set_material((0.0, 0.0, 0.0)) # hide bits below ground #TODO doesn't cope with z light positions self.eye = [-500*i for i in light.lightpos] # good distance away if self.eye[1] <= 0: # must have +ve y self.eye[1] = 500.0 if abs(self.eye[0]) > abs(self.eye[2]): #x val is bigger than z val #change scale so map just fits on screen if self.eye[0] < 0: su, sv = 1.0, 1.0 else: su, sv = -1.0, -1.0 self.scaleu = float(self.iy) / self.emap.width self.scalev = float(self.ix)/ self.emap.depth self.eye[2] = 0 self.scaleu = self.scaleu / self.eye[1] * float(self.eye[0]**2 + self.eye[1]**2)**0.5 self.emap.unif[50] = 1.0 #orientation flag self.emap.unif[53] = -3.0 * su / self.emap.width * self.eye[0] / float(self.eye[1]) #height adjustment else: #change scale so map just fits on screen if self.eye[2] < 0: su, sv = 1.0, -1.0 else: su, sv = -1.0, 1.0 self.scaleu = float(self.iy) / self.emap.depth self.scalev = float(self.ix)/ self.emap.width self.eye[0] = 0 self.scaleu = self.scaleu / self.eye[1] * float(self.eye[2]**2 + self.eye[1]**2)**0.5 self.emap.unif[50] = 0.0 self.emap.unif[53] = -3.0 * su / self.emap.width * self.eye[2] / float(self.eye[1]) if abs(self.scaleu) > abs(self.scalev): self.scale = 3.0 * self.scalev # multiplication factor to reduce pixeliness else: self.scale = 3.0 * self.scaleu self.scaleu = su * self.scale / self.scaleu # reused later in end_cast self.scalev = sv * self.scale / self.scalev self.camera0 = Camera() # default instance created as normal, just in case! self.camera = Camera(is_3d=False, eye=self.eye, scale=self.scale) # load shader for drawing map with shadows self.dshader = Shader("shaders/shadowcast") def _load_disk(self): """ have to override this """ def start_cast(self, location=(0.0, 0.0, 0.0)): """ after calling this method all object.draw()s will rendered to this texture and not appear on the display. If you want blurred edges you will have to capture the rendering of an object and its background then re-draw them using the blur() method. Large objects will obviously take a while to draw and re-draw """ opengles.glBindFramebuffer(GL_FRAMEBUFFER, self.framebuffer) opengles.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self._tex.value, 0) #thanks to PeterO c.o. RPi forum for pointing out missing depth attchmnt opengles.glBindRenderbuffer(GL_RENDERBUFFER, self.depthbuffer) opengles.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, self.ix, self.iy) opengles.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, self.depthbuffer) opengles.glClearColor(ctypes.c_float(0.0), ctypes.c_float(0.0), ctypes.c_float(0.0), ctypes.c_float(1.0)) opengles.glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT) opengles.glEnable(GL_TEXTURE_2D) opengles.glActiveTexture(0) self.camera.reset(is_3d=False, scale=self.scale) self.camera.position((location[0], 0, location[2])) self.location = location def end_cast(self): """ stop capturing to texture and resume normal rendering to default """ #draw the actual map self.emap.draw(shader=self.mshader, camera=self.camera) opengles.glBindTexture(GL_TEXTURE_2D, 0) opengles.glBindFramebuffer(GL_FRAMEBUFFER, 0) # set third texture to this ShadowCaster texture texs = self.emap.buf[0].textures if len(texs) == 2: texs.append(self) else: texs[2] = self # change background back to blue opengles.glClearColor(ctypes.c_float(0.4), ctypes.c_float(0.8), ctypes.c_float(0.8), ctypes.c_float(1.0)) # work out left, top, right, bottom for shader self.emap.unif[48] = 0.5 * (1.0 + self.scaleu) # left [16][0] self.emap.unif[49] = 0.5 * (1.0 + self.scalev) # top [16][1] self.emap.unif[51] = 1.0 - self.emap.unif[48] # right [17][0] self.emap.unif[52] = 1.0 - self.emap.unif[49] # bottom [17][1] du = float(self.location[0] / self.emap.width) dv = float(self.location[2] / self.emap.depth) self.emap.unif[48] -= self.scaleu * (du if self.emap.unif[50] == 1.0 else dv) self.emap.unif[49] += self.scalev * (dv if self.emap.unif[50] == 1.0 else du) self.emap.unif[51] -= self.scaleu * (du if self.emap.unif[50] == 1.0 else dv) self.emap.unif[52] += self.scalev * (dv if self.emap.unif[50] == 1.0 else du) def add_shadow(self, shape): shape.draw(shader=self.cshader, camera=self.camera) def draw_shadow(self): self.emap.draw(shader=self.dshader) def delete_buffers(self): opengles.glDeleteFramebuffers(1, self.framebuffer) opengles.glDeleteRenderbuffers(1, self.depthbuffer)
class ShadowCaster(OffScreenTexture): """For creating a depth-of-field blurring effect on selected objects""" def __init__(self, emap, light): """ calls Texture.__init__ but doesn't need to set file name as texture generated from the framebuffer """ super(ShadowCaster, self).__init__("shadow_caster") # load shader for casting shadows and camera self.cshader = Shader("uv_flat") self.mshader = Shader("mat_flat") # keep copy of ElevationMap self.emap = emap self.emap.set_material((0.0, 0.0, 0.0)) # hide bits below ground # TODO doesn't cope with z light positions self.eye = [-500 * i for i in light.lightpos] # good distance away if self.eye[1] <= 0: # must have +ve y self.eye[1] = 500.0 if abs(self.eye[0]) > abs(self.eye[2]): # x val is bigger than z val # change scale so map just fits on screen if self.eye[0] < 0: su, sv = 1.0, 1.0 else: su, sv = -1.0, -1.0 self.scaleu = float(self.iy) / self.emap.width self.scalev = float(self.ix) / self.emap.depth self.eye[2] = 0 self.scaleu = self.scaleu / self.eye[1] * float(self.eye[0] ** 2 + self.eye[1] ** 2) ** 0.5 self.emap.unif[50] = 1.0 # orientation flag self.emap.unif[53] = -3.0 * su / self.emap.width * self.eye[0] / float(self.eye[1]) # height adjustment else: # change scale so map just fits on screen if self.eye[2] < 0: su, sv = 1.0, -1.0 else: su, sv = -1.0, 1.0 self.scaleu = float(self.iy) / self.emap.depth self.scalev = float(self.ix) / self.emap.width self.eye[0] = 0 self.scaleu = self.scaleu / self.eye[1] * float(self.eye[2] ** 2 + self.eye[1] ** 2) ** 0.5 self.emap.unif[50] = 0.0 self.emap.unif[53] = -3.0 * su / self.emap.width * self.eye[2] / float(self.eye[1]) if abs(self.scaleu) > abs(self.scalev): self.scale = 3.0 * self.scalev # multiplication factor to reduce pixeliness else: self.scale = 3.0 * self.scaleu self.scaleu = su * self.scale / self.scaleu # reused later in end_cast self.scalev = sv * self.scale / self.scalev self.camera0 = Camera() # default instance created as normal, just in case! self.camera = Camera(is_3d=False, eye=self.eye, scale=self.scale) # load shader for drawing map with shadows self.dshader = Shader("shadowcast") def start_cast(self, location=(0.0, 0.0, 0.0)): """ after calling this method all object.draw()s will rendered to this texture and not appear on the display. If you want blurred edges you will have to capture the rendering of an object and its background then re-draw them using the blur() method. Large objects will obviously take a while to draw and re-draw """ opengles.glClearColor(ctypes.c_float(0.0), ctypes.c_float(0.0), ctypes.c_float(0.0), ctypes.c_float(1.0)) super(ShadowCaster, self)._start() self.camera.reset(is_3d=False, scale=self.scale) self.camera.position((location[0], 0, location[2])) self.location = location def end_cast(self): """ stop capturing to texture and resume normal rendering to default """ # draw the actual map self.emap.draw(shader=self.mshader, camera=self.camera) super(ShadowCaster, self)._end() # set third texture to this ShadowCaster texture texs = self.emap.buf[0].textures if len(texs) == 2: texs.append(self) else: texs[2] = self # change background back to blue opengles.glClearColor(ctypes.c_float(0.4), ctypes.c_float(0.8), ctypes.c_float(0.8), ctypes.c_float(1.0)) # work out left, top, right, bottom for shader self.emap.unif[48] = 0.5 * (1.0 + self.scaleu) # left [16][0] self.emap.unif[49] = 0.5 * (1.0 + self.scalev) # top [16][1] self.emap.unif[51] = 1.0 - self.emap.unif[48] # right [17][0] self.emap.unif[52] = 1.0 - self.emap.unif[49] # bottom [17][1] du = float(self.location[0] / self.emap.width) dv = float(self.location[2] / self.emap.depth) self.emap.unif[48] -= self.scaleu * (du if self.emap.unif[50] == 1.0 else dv) self.emap.unif[49] += self.scalev * (dv if self.emap.unif[50] == 1.0 else du) self.emap.unif[51] -= self.scaleu * (du if self.emap.unif[50] == 1.0 else dv) self.emap.unif[52] += self.scalev * (dv if self.emap.unif[50] == 1.0 else du) def add_shadow(self, shape): shape.draw(shader=self.cshader, camera=self.camera) def draw_shadow(self): self.emap.draw(shader=self.dshader)
class StereoCam(object): """For creating an apparatus with two sprites to hold left and right eye views. This Class is used to hold the 3D Camera which should be used to draw the 3D objects. It also holds a 2D Camera for drawing the Sprites""" def __init__(self, shader="uv_flat", mipmap=False, separation=0.4): """ calls Texture.__init__ but doesn't need to set file name as texture generated from the framebuffer. Keyword Arguments: *shader* to use when drawing sprite, defaults to post_base, a simple 3x3 convolution that does basic edge detection. Can be copied to project directory and modified as required. *mipmap* can be set to True with slight cost to speed, or use fxaa shader *separation* distance between the two camera positions - how wide apart the eye views are. """ # load shader self.shader = Shader(shader) self.camera_3d = Camera() self.camera_2d = Camera(is_3d=False) self.offs = separation / 2.0 self.textures = [] self.sprites = [] self.tex_list = [] for i in range(2): self.textures.append(OffScreenTexture(name="bin")) ix, iy = self.textures[i].ix, self.textures[i].iy #two sprites full width but moved so that they are centred on the #left and right edges. The offset values then move the uv mapping #so the image is on the right of the left sprite and left of the #right sprite self.sprites.append(Sprite(z=20.0, x=-ix/2.0 + i*ix, w=ix, h=iy, flip=True)) self.sprites[i].set_offset((i * 0.5 - 0.25, 0.0)) self.textures[i].alpha = False self.textures[i].blend = True self.textures[i].mipmap = mipmap self.tex_list.append([self.textures[i]]) def move_camera(self, position, rot, tilt): self.camera_3d.reset() self.camera_3d.rotate(tilt, rot, 0) self.camera_3d.position(position) def start_capture(self, side): """ after calling this method all object.draw()s will rendered to this texture and not appear on the display. *side* Either 0 or 1 to determine stereoscopic view """ offs = -self.offs if side == 0 else self.offs self.camera_3d.position((self.camera_3d.mtrx[2,3] * offs, 0, -self.camera_3d.mtrx[0,3] * offs)) tex = self.textures[side] tex._start() xx = tex.ix / 4.0 # draw the middle only - half width yy = 0 ww = tex.ix / 2.0 hh = tex.iy opengles.glEnable(GL_SCISSOR_TEST) opengles.glScissor(ctypes.c_int(int(xx)), ctypes.c_int(int(yy)), ctypes.c_int(int(ww)), ctypes.c_int(int(hh))) def end_capture(self, side): """ stop capturing to texture and resume normal rendering to default """ self.textures[side]._end() opengles.glDisable(GL_SCISSOR_TEST) def draw(self): """ draw the shape using the saved texture """ for i in range(2): self.sprites[i].draw(self.shader, self.tex_list[i], 0.0, 0.0, self.camera_2d)
class StereoCam(object): """For creating an apparatus with two sprites to hold left and right eye views. This Class is used to hold the 3D Camera which should be used to draw the 3D objects. It also holds a 2D Camera for drawing the Sprites""" def __init__(self, shader="uv_flat", mipmap=False, separation=0.4, interlace=0): """ calls Texture.__init__ but doesn't need to set file name as texture generated from the framebuffer. Keyword Arguments: *shader* to use when drawing sprite, defaults to post_base, a simple 3x3 convolution that does basic edge detection. Can be copied to project directory and modified as required. *mipmap* can be set to True with slight cost to speed, or use fxaa shader *separation* distance between the two camera positions - how wide apart the eye views are. *interlace* if interlace > 0 then the images are not taken with glScissor and must be drawn with a special interlacing shader. """ # load shader if interlace <= 0: self.shader = Shader(shader) else: self.shader = Shader(vshader_source = """ precision mediump float; attribute vec3 vertex; attribute vec2 texcoord; uniform mat4 modelviewmatrix[2]; varying vec2 texcoordout; void main(void) { texcoordout = texcoord; gl_Position = modelviewmatrix[1] * vec4(vertex,1.0); } """, fshader_source = """ precision mediump float; uniform sampler2D tex0; uniform sampler2D tex1; varying vec2 texcoordout; void main(void) {{ vec4 texc0 = texture2D(tex0, texcoordout); vec4 texc1 = texture2D(tex1, texcoordout); vec2 coord = vec2(gl_FragCoord); gl_FragColor = mix(texc0, texc1, step(0.5, fract(coord.x / {:f}))); }} """.format(interlace * 2.0)) #self.shader = Shader("2d_flat") self.camera_3d = Camera() self.camera_2d = Camera(is_3d=False) self.offs = separation / 2.0 self.interlace = interlace self.textures = [] self.sprites = [] self.tex_list = [] for i in range(2): self.textures.append(OffScreenTexture(name="stereo")) ix, iy = self.textures[i].ix, self.textures[i].iy #two sprites full width but moved so that they are centred on the #left and right edges. The offset values then move the uv mapping #so the image is on the right of the left sprite and left of the #right sprite self.sprites.append(Sprite(z=20.0, w=ix, h=iy, flip=True)) if interlace <= 0: self.sprites[i].positionX(-ix/2.0 + i*ix) self.sprites[i].set_offset((i * 0.5 - 0.25, 0.0)) else: self.sprites[i].set_2d_size(w=ix, h=iy) self.textures[i].blend = True self.textures[i].mipmap = mipmap self.tex_list.append(self.textures[i]) def move_camera(self, position, rot, tilt): self.camera_3d.reset() self.camera_3d.rotate(tilt, rot, 0) self.camera_3d.position(position) def start_capture(self, side): """ after calling this method all object.draw()s will rendered to this texture and not appear on the display. *side* Either 0 or 1 to determine stereoscopic view """ offs = -self.offs if side == 0 else self.offs self.camera_3d.position((self.camera_3d.mtrx[2,3] * offs, 0, -self.camera_3d.mtrx[0,3] * offs)) tex = self.textures[side] tex._start() if self.interlace <= 0: xx = tex.ix / 4.0 # draw the middle only - half width yy = 0 ww = tex.ix / 2.0 hh = tex.iy opengles.glEnable(GL_SCISSOR_TEST) opengles.glScissor(ctypes.c_int(int(xx)), ctypes.c_int(int(yy)), ctypes.c_int(int(ww)), ctypes.c_int(int(hh))) def end_capture(self, side): """ stop capturing to texture and resume normal rendering to default """ self.textures[side]._end() if self.interlace <= 0: opengles.glDisable(GL_SCISSOR_TEST) def draw(self): """ draw the shape using the saved texture """ if self.interlace <= 0: for i in range(2): self.sprites[i].draw(self.shader, [self.tex_list[i]], 0.0, 0.0, self.camera_2d) else: self.sprites[0].draw(self.shader, self.tex_list, 0.0, 0.0, self.camera_2d)
omx, omy = mymouse.position() while DISPLAY.loop_running(): # mouse movement checking here to get bat movment values mx, my = mymouse.position() dx = (mx-omx)*0.04 omx=mx if ((xm >= (-1*maphalf) and dx < 0) or (xm <= maphalf and dx > 0)): xm += dx dy = (my-omy)*0.02 omy = my if ((ym >= (0) and dy < 0) or (ym <= mapheight and dy > 0)): ym += dy if not (dy == 0.0 and dx == 0.0): camera.reset() camera.position((xm, 2 + ym, -maphalf - 2.5)) #monster movement drx = sx - rx if abs(drx) > max_speed: drx = drx/abs(drx) * max_speed dry = sy - ry if abs(dry) > max_speed: dry = dry/abs(dry) * max_speed rx += drx ry += dry monster.position(rx, ry, maphalf) dsy -= gravity sx += dsx sy += dsy
class ShadowCaster(OffScreenTexture): """For creating a depth-of-field blurring effect on selected objects""" def __init__(self, emap, light): """ calls Texture.__init__ but doesn't need to set file name as texture generated from the framebuffer """ super(ShadowCaster, self).__init__("shadow_caster") # load shader for casting shadows and camera self.cshader = Shader("uv_flat") self.mshader = Shader("mat_flat") # keep copy of ElevationMap self.emap = emap self.emap.set_material((0.0, 0.0, 0.0)) # hide bits below ground #TODO doesn't cope with z light positions self.eye = [-500.0 * i for i in light.lightpos] # good distance away if self.eye[1] <= 0: # must have +ve y self.eye[1] = 500.0 if abs(self.eye[0]) > abs(self.eye[2]): #x val is bigger than z val #change scale so map just fits on screen if self.eye[0] < 0: su, sv = 1.0, 1.0 else: su, sv = -1.0, -1.0 self.scaleu = float(self.iy) / self.emap.width self.scalev = float(self.ix)/ self.emap.depth self.eye[2] = 0 self.scaleu = self.scaleu / self.eye[1] * (self.eye[0]**2 + self.eye[1]**2)**0.5 self.emap.unif[50] = 1.0 #orientation flag self.emap.unif[53] = -3.0 * su / self.emap.width * self.eye[0] / self.eye[1] #height adjustment else: #change scale so map just fits on screen if self.eye[2] < 0: su, sv = 1.0, -1.0 else: su, sv = -1.0, 1.0 self.scaleu = float(self.iy) / self.emap.depth self.scalev = float(self.ix)/ self.emap.width self.eye[0] = 0 self.scaleu = self.scaleu / self.eye[1] * (self.eye[2]**2 + self.eye[1]**2)**0.5 self.emap.unif[50] = 0.0 self.emap.unif[53] = -3.0 * su / self.emap.width * self.eye[2] / self.eye[1] if abs(self.scaleu) > abs(self.scalev): self.scale = 3.0 * self.scalev # multiplication factor to reduce pixeliness else: self.scale = 3.0 * self.scaleu self.scaleu = su * self.scale / self.scaleu # reused later in end_cast self.scalev = sv * self.scale / self.scalev self.camera0 = Camera() # default instance created as normal, just in case! self.camera = Camera(is_3d=False, eye=self.eye, scale=self.scale) # load shader for drawing map with shadows self.dshader = Shader("shadowcast") def start_cast(self, location=(0.0, 0.0, 0.0)): """ after calling this method all object.draw()s will rendered to this texture and not appear on the display. If you want blurred edges you will have to capture the rendering of an object and its background then re-draw them using the blur() method. Large objects will obviously take a while to draw and re-draw """ opengles.glClearColor(ctypes.c_float(0.0), ctypes.c_float(0.0), ctypes.c_float(0.0), ctypes.c_float(1.0)) super(ShadowCaster, self)._start() self.camera.reset(is_3d=False, scale=self.scale) self.camera.position((location[0], 0, location[2])) self.location = location def end_cast(self): """ stop capturing to texture and resume normal rendering to default """ #draw the actual map self.emap.draw(shader=self.mshader, camera=self.camera) super(ShadowCaster, self)._end() # set third texture to this ShadowCaster texture texs = self.emap.buf[0].textures if len(texs) == 2: texs.append(self) else: texs[2] = self # change background back to blue opengles.glClearColor(ctypes.c_float(0.4), ctypes.c_float(0.8), ctypes.c_float(0.8), ctypes.c_float(1.0)) # work out left, top, right, bottom for shader self.emap.unif[48] = 0.5 * (1.0 + self.scaleu) # left [16][0] self.emap.unif[49] = 0.5 * (1.0 + self.scalev) # top [16][1] self.emap.unif[51] = 1.0 - self.emap.unif[48] # right [17][0] self.emap.unif[52] = 1.0 - self.emap.unif[49] # bottom [17][1] du = float(self.location[0] / self.emap.width) dv = float(self.location[2] / self.emap.depth) self.emap.unif[48] -= self.scaleu * (du if self.emap.unif[50] == 1.0 else dv) self.emap.unif[49] += self.scalev * (dv if self.emap.unif[50] == 1.0 else du) self.emap.unif[51] -= self.scaleu * (du if self.emap.unif[50] == 1.0 else dv) self.emap.unif[52] += self.scalev * (dv if self.emap.unif[50] == 1.0 else du) def add_shadow(self, shape): shape.draw(shader=self.cshader, camera=self.camera) def draw_shadow(self): self.emap.draw(shader=self.dshader)