def splatting(node, first, second, stencil, scale=None, offset=None): """Apply a texture splatting to the provided NodePath. """ # Apply the first texture. ts1 = TextureStage("stage-first") ts1.setSort(0) ts1.setMode(TextureStage.MReplace) ts1.setSavedResult(True) node.setTexture(ts1, first) # Apply the second texture. ts2 = TextureStage("stage-second") ts2.setSort(1) ts2.setMode(TextureStage.MReplace) node.setTexture(ts2, second) # Apply the stencil. ts3 = TextureStage("stage-stencil") ts3.setSort(2) ts3.setCombineRgb(TextureStage.CMInterpolate, TextureStage.CSPrevious, TextureStage.COSrcColor, TextureStage.CSLastSavedResult, TextureStage.COSrcColor, TextureStage.CSTexture, TextureStage.COSrcColor) node.setTexture(ts3, stencil) if scale: node.setTexScale(ts3, scale, scale) if offset is not None: node.setTexOffset(ts1, *offset) node.setTexOffset(ts2, *offset) node.setTexOffset(ts3, *offset)
def apply_splatted_textures(self, tile: NodePath, first_tex, second_tex, stencil_tex): # first = self.load("textures/sand_tex_1.png") # second = self.load("textures/grass_tex_1.png") # third = self.load("textures/water_tex_1.png") # stencil = self.load("textures/stencil_tex_1.png") # stencil_2 = self.load("textures/stencil_tex_2.png") # # normal = self.load("textures/sea-normal.jpg") # normal = self.loader.load_texture("textures/layingrock-n.jpg") # Apply the first texture. ts1 = TextureStage("stage-first") ts1.setSort(0) ts1.setMode(TextureStage.MReplace) ts1.setSavedResult(True) tile.setTexture(ts1, first_tex) # Apply the second texture. ts2 = TextureStage("stage-second") ts2.setSort(1) ts2.setMode(TextureStage.MReplace) tile.setTexture(ts2, second_tex) ts3 = TextureStage("stage-stencil") ts3.setSort(2) ts3.setCombineRgb(TextureStage.CMInterpolate, TextureStage.CSPrevious, TextureStage.COSrcColor, TextureStage.CSLastSavedResult, TextureStage.COSrcColor, TextureStage.CSTexture, TextureStage.COSrcColor) ts3.setSavedResult(True) tile.setTexture(ts3, stencil_tex)
def __create_terrain(self): terrain = GeoMipTerrain("Terrain") terrain.setHeightfield(self.__texture_path(self.__scene.get("scene", "heightmap"))) terrain.getRoot().reparentTo(self.render) terrain.generate() terrain.getRoot().setSx(1000.0 / 512) terrain.getRoot().setSy(1000.0 / 512) terrain.getRoot().setSz(74) terrain.getRoot().setPos(-500, -500, 0) black = self.loader.loadTexture(self.__texture_path(self.__scene.get("terrain", "black"))) black.setMinfilter(Texture.FTLinearMipmapNearest) ts = TextureStage("stage-first") ts.setSort(0) ts.setMode(TextureStage.MReplace) ts.setSavedResult(True) terrain.getRoot().setTexture(ts, black) terrain.getRoot().setTexScale(ts, 250, 250) white = self.loader.loadTexture(self.__texture_path(self.__scene.get("terrain", "white"))) white.setMinfilter(Texture.FTLinearMipmapNearest) ts = TextureStage("stage-second") ts.setSort(1) ts.setMode(TextureStage.MReplace) terrain.getRoot().setTexture(ts, white) terrain.getRoot().setTexScale(ts, 250, 250) stencil = self.loader.loadTexture(self.__texture_path(self.__scene.get("scene", "stencil"))) ts = TextureStage("stage-stencil") ts.setSort(2) ts.setCombineRgb(TextureStage.CMInterpolate, TextureStage.CSPrevious, TextureStage.COSrcColor, TextureStage.CSLastSavedResult, TextureStage.COSrcColor, TextureStage.CSTexture, TextureStage.COSrcColor) terrain.getRoot().setTexture(ts, stencil) ts = TextureStage("stage-vertexcolour") ts.setSort(3) ts.setCombineRgb(TextureStage.CMModulate, TextureStage.CSPrevious, TextureStage.COSrcColor, TextureStage.CSPrimaryColor, TextureStage.COSrcColor) terrain.getRoot().setTexture(ts, "final")
def __create_terrain(self): terrain = GeoMipTerrain("Terrain") terrain.setHeightfield(self.__texture_path(self.__scene.get("scene", "heightmap"))) terrain.getRoot().reparentTo(self.render) terrain.generate() terrain.getRoot().setSx(1000.0 / 512) terrain.getRoot().setSy(1000.0 / 512) terrain.getRoot().setSz(74) terrain.getRoot().setPos(-500, -500, 0) black = self.loader.loadTexture(self.__texture_path(self.__scene.get("terrain", "black"))) black.setMinfilter(Texture.FTLinearMipmapNearest) ts = TextureStage("stage-first") ts.setSort(0) ts.setMode(TextureStage.MReplace) ts.setSavedResult(True) terrain.getRoot().setTexture(ts, black) terrain.getRoot().setTexScale(ts, 250, 250) white = self.loader.loadTexture(self.__texture_path(self.__scene.get("terrain", "white"))) white.setMinfilter(Texture.FTLinearMipmapNearest) ts = TextureStage("stage-second") ts.setSort(1) ts.setMode(TextureStage.MReplace) terrain.getRoot().setTexture(ts, white) terrain.getRoot().setTexScale(ts, 250, 250) stencil = self.loader.loadTexture(self.__texture_path(self.__scene.get("scene", "stencil"))) ts = TextureStage("stage-stencil") ts.setSort(2) ts.setCombineRgb(TextureStage.CMInterpolate, TextureStage.CSPrevious, TextureStage.COSrcColor, TextureStage.CSLastSavedResult, TextureStage.COSrcColor, TextureStage.CSTexture, TextureStage.COSrcColor) terrain.getRoot().setTexture(ts, stencil) ts = TextureStage("stage-vertexcolour") ts.setSort(3) ts.setCombineRgb(TextureStage.CMModulate, TextureStage.CSPrevious, TextureStage.COSrcColor, TextureStage.CSPrimaryColor, TextureStage.COSrcColor) terrain.getRoot().setTexture(ts, "final")
class CombineStages(ShowBase): """Trying to draw a mask stage over a stim stage and combine them (modulate) Note debug variable is 1 to print vars, 2 to print vars and display mask from matplotlib""" def __init__(self, texture_array, mask_position, mask_scale, debug): super().__init__() self.debug = debug if self.debug >= 1: import matplotlib.pyplot as plt texture_size = 1024 self.mask_scale = mask_scale self.texture_array = texture_array self.mask_position_ndc = mask_position self.mask_position_uv = (self.ndc2uv(self.mask_position_ndc[0]), self.ndc2uv(self.mask_position_ndc[1])) #CREATE MASK (zeros on left, 255s on right) self.right_mask = 255*np.ones((texture_size,texture_size), dtype=np.uint8) #should set to 0/1 not 255? self.right_mask[:, texture_size//2: ] = 0 self.right_mask[texture_size//2 - 400:texture_size//2-300, -40:] = 120 #gray notch in RHS of zeros self.right_mask[texture_size//2 - 50:texture_size//2+50, texture_size//2: texture_size//2+80] = 180 #light notch in LHS of zeros if self.debug >= 2: plt.imshow(self.right_mask, cmap = 'gray') plt.show() #CREATE TEXTURE STAGES #Grating texture self.grating_texture = Texture("Grating") #T_unsigned_byte self.grating_texture.setup2dTexture(texture_size, texture_size, Texture.T_unsigned_byte, Texture.F_luminance) self.grating_texture.setRamImage(self.texture_array) self.left_texture_stage = TextureStage('grating') #Mask self.right_mask_texture = Texture("right_mask") self.right_mask_texture.setup2dTexture(texture_size, texture_size, Texture.T_unsigned_byte, Texture.F_luminance) self.right_mask_texture.setRamImage(self.right_mask) self.right_mask_stage = TextureStage('right_mask') #Multiply the texture stages together self.right_mask_stage.setCombineRgb(TextureStage.CMModulate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor) #CREATE SCENE GRAPH cm = CardMaker('card') cm.setFrameFullscreenQuad() self.left_card = self.aspect2d.attachNewNode(cm.generate()) self.left_card.setTexture(self.left_texture_stage, self.grating_texture) self.left_card.setTexture(self.right_mask_stage, self.right_mask_texture) #CREATE TASKS TO MOVE MASK, AND PRINT OUT INFO ABOUT MASK ShowBaseGlobal.base.taskMgr.add(self.update) if self.debug >= 1: display_period = 2 #seconds ShowBaseGlobal.base.taskMgr.doMethodLater(display_period, self.print_updates, 'mask_display') #SCREEN TEXT FOR REFERENCE #Text mark the origin with green o self.title = OnscreenText("o", style = 1, fg = (1,1,0,1), bg = (0,1,0,0.8), pos = (0,0), scale = 0.05) #Text mark the desired location with white x self.title = OnscreenText("x", style = 1, fg = (1,1,1,1), bg = (0,0,0,0.5), pos = self.mask_position_ndc, scale = 0.08) def update(self, task): update_transform = self.trs_transform(task.time) self.left_card.setTexTransform(self.right_mask_stage, update_transform) return task.cont def print_updates(self, task): self.print_ts_info() return task.again #to keep going every whatever seconds def trs_transform(self, elapsed_time): """ trs = translate rotate scale: transform for mask stage """ pos = 0.5 + self.mask_position_uv[0], 0.5 + self.mask_position_uv[1] center_shift = TransformState.make_pos2d((-pos[0], -pos[1])) scale = TransformState.make_scale2d(1/self.mask_scale) rotate = TransformState.make_rotate2d(np.mod(elapsed_time*40, 360)) translate = TransformState.make_pos2d((0.5, 0.5)) return translate.compose(rotate.compose(scale.compose(center_shift))) def print_ts_info(self): print("\noffset: ", self.left_card.getTexOffset(self.right_mask_stage)) print("rot: ", self.left_card.getTexRotate(self.right_mask_stage)) #print("position: ", self.left_card.getTexPos(self.right_mask_stage)) #z is always 0 return def ndc2uv(self, val): return 0.5*val def uv2ndc(self, val): return 2*val
class BinocularDrift(ShowBase): """ Show binocular drifting textures forever. Takes in texture array and other parameters, and shows texture drifting indefinitely. Texture array can be grayscale or rgb, uint8 or uint16. Usage: BinocularDrift(texture_array, stim_angles = (0, 0), mask_angle = 0, position = (0,0), velocity = 0.1, band_radius = 3, window_size = 512, texture_size = 512, bgcolor = (0,0,0,1)) Note(s): angles are (left_texture_angle, right_texture_angle): > is cw, < 0 cc2 Velocity is in NDC, so 1.0 is the entire window width (i.e., super-fast). Make texture_size a power of 2: this makes the GPU happier. position is x,y in NDC (from [-1 1]), so (.5, .5) will be in top right quadrant of window. band_radius is just the half-width of the band in the middle. It can be 0. The texture array can be 2d (gray) or NxNx3 (rgb) """ def __init__(self, texture_array, stim_angles = (0, 0), mask_angle = 0, position = (0, 0), velocity = 0, band_radius = 3, window_size = 512, texture_size = 512, bgcolor = (0, 0, 0, 1)): super().__init__() self.mask_position_ndc = position self.mask_position_uv = (ndc2uv(self.mask_position_ndc[0]), ndc2uv(self.mask_position_ndc[1])) self.scale = np.sqrt(8) self.texture_array = texture_array self.texture_dtype = type(self.texture_array.flat[0]) self.ndims = self.texture_array.ndim self.left_texture_angle = stim_angles[0] self.right_texture_angle = stim_angles[1] self.velocity = velocity self.mask_angle = mask_angle #this will change fairly frequently #Set window title and size self.window_properties = WindowProperties() self.window_properties.setSize(window_size, window_size) self.window_properties.setTitle("BinocularStatic") ShowBaseGlobal.base.win.requestProperties(self.window_properties) #base is a panda3d global #CREATE MASKS (right mask for left tex, left mask for right tex) self.right_mask = 255*np.ones((texture_size,texture_size), dtype=np.uint8) self.right_mask[:, texture_size//2 - band_radius :] = 0 self.left_mask = 255*np.ones((texture_size,texture_size), dtype=np.uint8) self.left_mask[:, : texture_size//2 + band_radius] = 0 if False: #set to True to debug import matplotlib.pyplot as plt plt.imshow(self.left_mask, cmap = 'gray') plt.show() #CREATE TEXTURE STAGES #Grating texture self.grating_texture = Texture("Grating") #T_unsigned_byte self.grating_texture.setup2dTexture(texture_size, texture_size, Texture.T_unsigned_byte, Texture.F_luminance) self.grating_texture.setRamImage(self.texture_array) #TEXTURE STAGES FOR RIGHT CARD self.right_texture_stage = TextureStage('right_texture') #Mask self.left_mask_texture = Texture("left_mask") self.left_mask_texture.setup2dTexture(texture_size, texture_size, Texture.T_unsigned_byte, Texture.F_luminance) self.left_mask_texture.setRamImage(self.left_mask) self.left_mask_stage = TextureStage('left_mask') #Multiply the texture stages together self.left_mask_stage.setCombineRgb(TextureStage.CMModulate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor) #CREATE CARDS/SCENEGRAPH cm = CardMaker('stimcard') cm.setFrameFullscreenQuad() self.right_card = self.aspect2d.attachNewNode(cm.generate()) self.setBackgroundColor((0,0,0,1)) #set above self.right_card.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.M_add)) #SET TEXTURE STAGES self.right_card.setTexture(self.right_texture_stage, self.grating_texture) self.right_card.setTexture(self.left_mask_stage, self.left_mask_texture) #SET static transforms #Left texture mask self.mask_transform = self.trs_transform() self.right_card.setTexTransform(self.left_mask_stage, self.mask_transform) #Right texture self.right_card.setTexScale(self.right_texture_stage, 1/self.scale) self.right_card.setTexRotate(self.right_texture_stage, self.right_texture_angle) #Set dynamic transforms if self.velocity != 0: self.taskMgr.add(self.texture_update, "moveTextureTask") self.title = OnscreenText("x", style = 1, fg = (1,1,1,1), bg = (0,0,0,.8), pos = self.mask_position_ndc, scale = 0.05) #Procedure to move the camera def texture_update(self, task): #Move texture in whatever direction right_tex_position = task.time*self.velocity self.right_card.setTexPos(self.right_texture_stage, right_tex_position, 0, 0) return task.cont def trs_transform(self): """ trs = translate rotate scale: transform for mask stage """ pos = 0.5 + self.mask_position_uv[0], 0.5 + self.mask_position_uv[1] center_shift = TransformState.make_pos2d((-pos[0], -pos[1])) scale = TransformState.make_scale2d(1/self.scale) rotate = TransformState.make_rotate2d(self.mask_angle) translate = TransformState.make_pos2d((0.5, 0.5)) return translate.compose(rotate.compose(scale.compose(center_shift)))
class Water(AssetBase): def __init__(self, name, size=10000, resolution=1024): """Arguments: size -- Edge length of the water square. resolution -- Texture size of the rendered reflection buffer. """ # Uncomment to see the output of the refclection buffer. base.bufferViewer.toggleEnable() AssetBase.__init__(self) self.name = name self.cm = CardMaker("water surface") self.cm.setFrame(-0.5 * size, 0.5 * size, -0.5 * size, 0.5 * size) self.cm.setHasUvs(True) self.node = NodePath(self.cm.generate()) self.node.setP(self.node, -90) self.node.flattenLight() self.node.hide(BitMask32.bit(1)) # self.node.setTwoSided(True) self.node.setShaderOff() # size of one texture tile in meters self.tex_size = 100.0 diffuse = TexturePool.loadTexture("textures/water.diffuse.png") diffuse.setWrapU(Texture.WMRepeat) diffuse.setWrapV(Texture.WMRepeat) diffuse.setMinfilter(Texture.FTLinearMipmapLinear) diffuse.setMagfilter(Texture.FTLinearMipmapLinear) self.diffuse_stage = TextureStage("diffuse") self.diffuse_stage.setSort(2) self.node.setTexture(self.diffuse_stage, diffuse) self.node.setTexScale(self.diffuse_stage, size / self.tex_size, size / self.tex_size) # Reflection camera renders to 'buffer' which is projected onto the # water surface. buffer = base.win.makeTextureBuffer("water reflection", resolution, resolution) buffer.setClearColor(Vec4(0, 0, 0, 1)) self.refl_cam = base.makeCamera(buffer) self.refl_cam.reparentTo(self.node) self.refl_cam.node().setCameraMask(BitMask32.bit(1)) self.refl_cam.node().getLens().setFov(base.camLens.getFov()) self.refl_cam.node().getLens().setNearFar(1, 100000) plane = PlaneNode("water culling plane", Plane(Vec3(0, 0, 1), Point3(0, 0, 0))) cfa = CullFaceAttrib.makeReverse() cpa = ClipPlaneAttrib.make(PlaneNode.CEVisible, plane) rs = RenderState.make(cfa, cpa) self.refl_cam.node().setInitialState(rs) reflection = buffer.getTexture() reflection.setMinfilter(Texture.FTLinear) reflection.setMagfilter(Texture.FTLinear) self.refl_stage = TextureStage("reflection") self.refl_stage.setSort(1) self.node.projectTexture(self.refl_stage, reflection, base.cam) self.node.setTexture(self.refl_stage, reflection) # Blend between diffuse and reflection. self.diffuse_stage.setColor(VBase4(1, 1, 1, 0.2)) # opacity of 20% self.diffuse_stage.setCombineRgb( TextureStage.CMInterpolate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor, TextureStage.CSConstant, TextureStage.COSrcAlpha, ) self.addTask(self.update, name="water update", sort=1, taskChain="world") def update(self, task): """Updates position of the reflection camera and the water plane.""" mc = base.cam.getMat(render) # mf = Plane(Vec3(0, 0, 1), Point3(0, 0, 0)).getReflectionMat() mf = Mat4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1) self.refl_cam.setMat(mc * mf) self.node.setX(camera.getX(render)) self.node.setY(camera.getY(render)) self.node.setTexOffset(self.diffuse_stage, self.node.getX() / self.tex_size, self.node.getY() / self.tex_size) return task.cont def destroy(self): self.removeAllTasks() self.node.removeNode() self.refl_cam.removeNode()
class InputControlStim(ShowBase): """ Generic input-controll stimulus class: takes in list of texture classes, and stimulus parameters. Stimulus shown, in real-time, depends on events produced by utils.Monitor() class. Inputs: Positional tex_classes: m-element list of texture classes stim_params: m-element list of dictionaries: each contains parameters (e.g., velocity) Keyword initial_tex_ind (0): index for first stim to show window_size (512): size of the panda3d window (pixels) window_name ('InputControlStim'): title of window in gui profile_on (False): will show actual fps, profiler, and little x at center if True fps (30): controls frame rate of display save_path (None): if set to a file path, will save data about stimuli, and time they are delivered """ def __init__(self, tex_classes, stim_params, initial_tex_ind=0, window_size=512, window_name="InputControlStim", profile_on=False, fps=30, save_path=None): super().__init__() self.current_tex_num = initial_tex_ind self.previous_tex_num = None self.tex_classes = tex_classes self.stim_params = stim_params self.window_size = window_size self.stimulus_initialized = False # for setting up first stim (don't clear cards they don't exist) self.fps = fps self.profile_on = profile_on self.save_path = save_path if self.save_path: self.filestream = utils.save_initialize(save_path, tex_classes, stim_params) else: self.filestream = None self.scale = np.sqrt( 8) #so it can handle arbitrary rotations and shifts self.window_name = window_name #Window properties self.window_props = WindowProperties() self.window_props.setSize(self.window_size, self.window_size) self.set_title(self.window_name) # Set frame rate ShowBaseGlobal.globalClock.setMode(ClockObject.MLimited) ShowBaseGlobal.globalClock.setFrameRate(self.fps) #Set up profiling if desired if self.profile_on: PStatClient.connect() # this will only work if pstats is running ShowBaseGlobal.base.setFrameRateMeter(True) #Show frame rate #Set initial texture(s) self.set_stimulus(str(self.current_tex_num)) # Set up event handlers (accept) and tasks (taskMgr) for dynamics self.accept('stim0', self.set_stimulus, ['0']) self.accept('stim1', self.set_stimulus, ['1']) self.accept('stim2', self.set_stimulus, ['2']) # Wrinkle: should we set this here or there? self.taskMgr.add(self.move_textures, "move textures") def set_tasks(self): if self.current_stim_params['stim_type'] == 'b': self.taskMgr.add(self.textures_update, "move_both") #Move textures def move_textures(self, task): if self.current_stim_params['stim_type'] == 'b': left_tex_position = -task.time * self.current_stim_params[ 'velocities'][0] #negative b/c texture stage right_tex_position = -task.time * self.current_stim_params[ 'velocities'][1] try: self.left_card.setTexPos(self.left_texture_stage, left_tex_position, 0, 0) self.right_card.setTexPos(self.right_texture_stage, right_tex_position, 0, 0) except Exception as e: logger.error(e) elif self.current_stim_params['stim_type'] == 's': if self.current_stim_params['velocity'] == 0: pass else: new_position = -task.time * self.current_stim_params['velocity'] # Sometimes setting position fails when the texture stage isn't fully set try: self.card.setTexPos(self.texture_stage, new_position, 0, 0) #u, v, w except Exception as e: logger.error(e) return task.cont @property def texture_size(self): return self.tex_classes[self.current_tex_num].texture_size @property def current_stim_params(self): """ Parameters of current texture (e.g., velocity, stim_type) """ return self.stim_params[self.current_tex_num] def create_cards(self): """ Create cards: these are panda3d objects that are required for displaying textures. You can't just have a disembodied texture. In pandastim (at least for now) we are only showing 2d projections of textures, so we use cards. """ cardmaker = CardMaker("stimcard") cardmaker.setFrameFullscreenQuad() #Binocular cards if self.current_stim_params['stim_type'] == 'b': self.setBackgroundColor( (0, 0, 0, 1)) # without this the cards will appear washed out self.left_card = self.aspect2d.attachNewNode(cardmaker.generate()) self.left_card.setAttrib( ColorBlendAttrib.make( ColorBlendAttrib.M_add)) # otherwise only right card shows self.right_card = self.aspect2d.attachNewNode(cardmaker.generate()) self.right_card.setAttrib( ColorBlendAttrib.make(ColorBlendAttrib.M_add)) if self.profile_on: self.center_indicator = OnscreenText( "x", style=1, fg=(1, 1, 1, 1), bg=(0, 0, 0, .8), pos=self.current_stim_params['position'], scale=0.05) # Tex card elif self.current_stim_params['stim_type'] == 's': self.card = self.aspect2d.attachNewNode(cardmaker.generate()) self.card.setColor((1, 1, 1, 1)) #? self.card.setScale(self.scale) return def create_texture_stages(self): """ Create the texture stages: these are basically textures that you can apply to cards (sometimes mulitple textures at the same time -- is useful with masks). For more on texture stages: https://docs.panda3d.org/1.10/python/programming/texturing/multitexture-introduction """ #Binocular cards if self.current_stim_params['stim_type'] == 'b': #TEXTURE STAGES FOR LEFT CARD # Texture itself self.left_texture_stage = TextureStage('left_texture_stage') # Mask self.left_mask = Texture("left_mask_texture") self.left_mask.setup2dTexture(self.texture_size, self.texture_size, Texture.T_unsigned_byte, Texture.F_luminance) self.left_mask_stage = TextureStage('left_mask_array') #TEXTURE STAGES FOR RIGHT CARD self.right_texture_stage = TextureStage('right_texture_stage') #Mask self.right_mask = Texture("right_mask_texture") self.right_mask.setup2dTexture(self.texture_size, self.texture_size, Texture.T_unsigned_byte, Texture.F_luminance) self.right_mask_stage = TextureStage('right_mask_stage') # Tex card elif self.current_stim_params['stim_type'] == 's': self.texture_stage = TextureStage("texture_stage") return def set_stimulus(self, data): """ Uses events from zmq to set the stimulus value. """ logger.debug("\tset_stimulus(%s)", data) if not self.stimulus_initialized: # If this is first stim, then toggle initialization to on, and # do not clear previous texture (there is no previous texture). self.stimulus_initialized = True self.data_previous = data elif data == self.data_previous: return else: self.data_previous = data self.clear_cards() #clear the textures before adding new ones # This assumes data streaming is string numbers 0, 1, etc. self.current_tex_num = int(data) # Set new texture stages/cards etc self.tex = self.tex_classes[self.current_tex_num] logger.debug("\t%d: %s", self.current_tex_num, self.tex) self.create_texture_stages() self.create_cards() self.set_texture_stages() self.set_transforms() #Save stim to file (put this last as you want to set transforms quickly) if self.filestream: self.filestream.write(f"{str(datetime.now())}\t{data}\n") self.filestream.flush() return def clear_cards(self): """ Clear cards when new stimulus: stim-class sensitive """ if self.current_stim_params['stim_type'] == 'b': self.left_card.detachNode() self.right_card.detachNode() if self.profile_on: self.center_indicator.detachNode() elif self.current_stim_params['stim_type'] == 's': self.card.detachNode() return def set_transforms(self): """ Set up the transforms to apply to textures/cards (e.g., rotations/scales) This is different from the framewise movement handled by the task manager """ if self.current_stim_params['stim_type'] == 'b': #masks self.mask_transform = self.trs_transform() self.left_card.setTexTransform(self.left_mask_stage, self.mask_transform) self.right_card.setTexTransform(self.right_mask_stage, self.mask_transform) #Left texture self.left_card.setTexScale(self.left_texture_stage, 1 / self.scale) self.left_card.setTexRotate(self.left_texture_stage, self.current_stim_params['angles'][0]) #Right texture self.right_card.setTexScale(self.right_texture_stage, 1 / self.scale) self.right_card.setTexRotate(self.right_texture_stage, self.current_stim_params['angles'][1]) if self.current_stim_params['stim_type'] == 's': self.card.setTexRotate(self.texture_stage, self.current_stim_params['angle']) return def set_texture_stages(self): """ Add texture stages to cards """ if self.current_stim_params['stim_type'] == 'b': self.mask_position_uv = ( utils.card2uv(self.current_stim_params['position'][0]), utils.card2uv(self.current_stim_params['position'][1])) #CREATE MASK ARRAYS self.left_mask_array = 255 * np.ones( (self.texture_size, self.texture_size), dtype=np.uint8) self.left_mask_array[:, self.texture_size // 2 - self.current_stim_params['strip_width'] // 2:] = 0 self.right_mask_array = 255 * np.ones( (self.texture_size, self.texture_size), dtype=np.uint8) self.right_mask_array[:, :self.texture_size // 2 + self.current_stim_params['strip_width'] // 2] = 0 #ADD TEXTURE STAGES TO CARDS self.left_mask.setRamImage(self.left_mask_array) self.left_card.setTexture(self.left_texture_stage, self.tex.texture) self.left_card.setTexture(self.left_mask_stage, self.left_mask) #Multiply the texture stages together self.left_mask_stage.setCombineRgb(TextureStage.CMModulate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor) self.right_mask.setRamImage(self.right_mask_array) self.right_card.setTexture(self.right_texture_stage, self.tex.texture) self.right_card.setTexture(self.right_mask_stage, self.right_mask) #Multiply the texture stages together self.right_mask_stage.setCombineRgb(TextureStage.CMModulate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor) elif self.current_stim_params['stim_type'] == 's': self.card.setTexture(self.texture_stage, self.tex.texture) return def trs_transform(self): """ trs = translate-rotate-scale transform for mask stage panda3d developer rdb contributed to this code """ pos = 0.5 + self.mask_position_uv[0], 0.5 + self.mask_position_uv[1] center_shift = TransformState.make_pos2d((-pos[0], -pos[1])) scale = TransformState.make_scale2d(1 / self.scale) rotate = TransformState.make_rotate2d( self.current_stim_params['strip_angle']) translate = TransformState.make_pos2d((0.5, 0.5)) return translate.compose(rotate.compose(scale.compose(center_shift))) def set_title(self, title): self.window_props.setTitle(title) ShowBaseGlobal.base.win.requestProperties( self.window_props) #base is a panda3d global
class BinocularMoving(ShowBase): """ Show binocular drifting textures forever. Takes in texture object and other parameters, and shows texture drifting indefinitely. Usage: BinocularDrift(texture_object, stim_angles = (0, 0), strip_angle = 0, position = (0,0), velocities = (0,0), strip_width = 2, window_size = 512, window_name = 'FunStim', profile_on = False) Note(s): - angles are (left_texture_angle, right_texture_angle): >0 is cw, <0 ccw - Make texture_size a power of 2: this makes the GPU happier. - position is x,y in card-based coordinates (from [-1 1]), so (.5, .5) will be in middle of top right quadrant - Velocity is in card-based units, so 1.0 is the entire window width (i.e., super-fast). - strip_width is just the width of the strip down the middle. It can be 0. Even is better. - The texture array can be 2d (gray) or NxNx3 (rgb) with unit8 or uint16 elements. """ def __init__(self, tex, stim_angles=(0, 0), strip_angle=0, position=(0, 0), velocities=(0, 0), strip_width=4, fps=30, window_size=None, window_name='BinocularDrift', profile_on=False): super().__init__() self.tex = tex if window_size == None: self.window_size = tex.texture_size else: self.window_size = window_size self.mask_position_card = position self.mask_position_uv = (utils.card2uv(self.mask_position_card[0]), utils.card2uv(self.mask_position_card[1])) self.scale = np.sqrt( 8) #so it can handle arbitrary rotations and shifts self.left_texture_angle = stim_angles[0] self.right_texture_angle = stim_angles[1] self.left_velocity = velocities[0] self.right_velocity = velocities[1] self.strip_angle = strip_angle #this will change fairly frequently self.fps = fps self.window_name = window_name self.profile_on = profile_on #Set window title and size self.window_properties = WindowProperties() self.window_properties.setSize(self.window_size, self.window_size) self.window_properties.setTitle(self.window_name) ShowBaseGlobal.base.win.requestProperties( self.window_properties) #base is a panda3d global #CREATE MASK ARRAYS self.left_mask_array = 255 * np.ones( (self.tex.texture_size, self.tex.texture_size), dtype=np.uint8) self.left_mask_array[:, self.tex.texture_size // 2 - strip_width // 2:] = 0 self.right_mask_array = 255 * np.ones( (self.tex.texture_size, self.tex.texture_size), dtype=np.uint8) self.right_mask_array[:, :self.tex.texture_size // 2 + strip_width // 2] = 0 #TEXTURE STAGES FOR LEFT CARD self.left_texture_stage = TextureStage('left_texture_stage') #Mask self.left_mask = Texture("left_mask_texture") self.left_mask.setup2dTexture(self.tex.texture_size, self.tex.texture_size, Texture.T_unsigned_byte, Texture.F_luminance) self.left_mask.setRamImage(self.left_mask_array) self.left_mask_stage = TextureStage('left_mask_array') #Multiply the texture stages together self.left_mask_stage.setCombineRgb(TextureStage.CMModulate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor) #TEXTURE STAGES FOR RIGHT CARD self.right_texture_stage = TextureStage('right_texture_stage') #Mask self.right_mask = Texture("right_mask_texture") self.right_mask.setup2dTexture(self.tex.texture_size, self.tex.texture_size, Texture.T_unsigned_byte, Texture.F_luminance) self.right_mask.setRamImage(self.right_mask_array) self.right_mask_stage = TextureStage('right_mask_stage') #Multiply the texture stages together self.right_mask_stage.setCombineRgb(TextureStage.CMModulate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor) #CREATE CARDS/SCENEGRAPH cm = CardMaker('stimcard') cm.setFrameFullscreenQuad() #self.setBackgroundColor((0,0,0,1)) self.left_card = self.aspect2d.attachNewNode(cm.generate()) self.right_card = self.aspect2d.attachNewNode(cm.generate()) self.left_card.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.M_add)) self.right_card.setAttrib(ColorBlendAttrib.make( ColorBlendAttrib.M_add)) #ADD TEXTURE STAGES TO CARDS self.left_card.setTexture(self.left_texture_stage, self.tex.texture) self.left_card.setTexture(self.left_mask_stage, self.left_mask) self.right_card.setTexture(self.right_texture_stage, self.tex.texture) self.right_card.setTexture(self.right_mask_stage, self.right_mask) self.setBackgroundColor( (0, 0, 0, 1)) # without this the cards will appear washed out #TRANSFORMS #Masks self.mask_transform = self.trs_transform() self.left_card.setTexTransform(self.left_mask_stage, self.mask_transform) self.right_card.setTexTransform(self.right_mask_stage, self.mask_transform) #Left texture self.left_card.setTexScale(self.left_texture_stage, 1 / self.scale) self.left_card.setTexRotate(self.left_texture_stage, self.left_texture_angle) #Right texture self.right_card.setTexScale(self.right_texture_stage, 1 / self.scale) self.right_card.setTexRotate(self.right_texture_stage, self.right_texture_angle) #Set dynamic transforms if self.left_velocity != 0 and self.right_velocity != 0: self.taskMgr.add(self.textures_update, "move_both") elif self.left_velocity != 0 and self.right_velocity == 0: self.taskMgr.add(self.left_texture_update, "move_left") elif self.left_velocity == 0 and self.right_velocity != 0: self.taskMgr.add(self.right_texture_update, "move_right") # Set frame rate ShowBaseGlobal.globalClock.setMode(ClockObject.MLimited) ShowBaseGlobal.globalClock.setFrameRate( self.fps) #can lock this at whatever #Set up profiling if desired if profile_on: PStatClient.connect() # this will only work if pstats is running ShowBaseGlobal.base.setFrameRateMeter(True) #Show frame rate # Following will show a small x at the center self.title = OnscreenText("x", style=1, fg=(1, 1, 1, 1), bg=(0, 0, 0, .8), pos=self.mask_position_card, scale=0.05) #Move both textures def textures_update(self, task): left_tex_position = -task.time * self.left_velocity #negative b/c texture stage right_tex_position = -task.time * self.right_velocity self.left_card.setTexPos(self.left_texture_stage, left_tex_position, 0, 0) self.right_card.setTexPos(self.right_texture_stage, right_tex_position, 0, 0) return task.cont def left_texture_update(self, task): left_tex_position = -task.time * self.left_velocity #negative b/c texture stage self.left_card.setTexPos(self.left_texture_stage, left_tex_position, 0, 0) return task.cont def right_texture_update(self, task): right_tex_position = -task.time * self.right_velocity self.right_card.setTexPos(self.right_texture_stage, right_tex_position, 0, 0) return task.cont def trs_transform(self): """ trs = translate rotate scale transform for mask stage rdb contributed to this code """ pos = 0.5 + self.mask_position_uv[0], 0.5 + self.mask_position_uv[1] center_shift = TransformState.make_pos2d((-pos[0], -pos[1])) scale = TransformState.make_scale2d(1 / self.scale) rotate = TransformState.make_rotate2d(self.strip_angle) translate = TransformState.make_pos2d((0.5, 0.5)) return translate.compose(rotate.compose(scale.compose(center_shift)))
class InputControlParams(ShowBase): """ Input signal sends in x, y, theta values for binocular stimuli to control those parameters of the stim in real time. Need to expand to single texture. Usage: InputControlParams(texture_object, stim_angles = (0, 0), strip_angle = 0, position = (0,0), velocities = (0,0), strip_width = 2, window_size = 512, window_name = 'FunStim', profile_on = False) Note(s): - angles are relative to strip angle - position is x,y in card-based coordinates (from [-1 1]), so (.5, .5) will be in middle of top right quadrant - Velocity is in same direction as angle, and units of window size (so 1 is super-fast) - strip_width is just the width of the strip down the middle. Can be 0. """ def __init__(self, tex, stim_angles=(0, 0), initial_angle=0, initial_position=(0, 0), velocities=(0, 0), strip_width=4, fps=30, window_size=None, window_name='position control', profile_on=False, save_path=None): super().__init__() self.render.setAntialias(AntialiasAttrib.MMultisample) self.aspect2d.prepareScene( ShowBaseGlobal.base.win.getGsg()) # pre-loads world self.tex = tex if window_size == None: self.window_size = tex.texture_size else: self.window_size = window_size self.mask_position_card = initial_position self.strip_width = strip_width self.scale = np.sqrt( 8) #so it can handle arbitrary rotations and shifts self.strip_angle = initial_angle #this will change fairly frequently self.stim_angles = stim_angles self.left_texture_angle = self.stim_angles[ 0] + self.strip_angle #make this a property self.right_texture_angle = self.stim_angles[1] + self.strip_angle self.left_velocity = velocities[0] self.right_velocity = velocities[1] self.fps = fps self.window_name = window_name self.profile_on = profile_on print(save_path) self.save_path = save_path if self.save_path: initial_params = { 'angles': stim_angles, 'initial_angle': self.strip_angle, 'velocities': velocities, 'strip_width': self.strip_width, 'initial_position': initial_position } print(tex, initial_params) self.filestream = utils.save_initialize(self.save_path, [tex], [initial_params]) print(self.filestream) else: self.filestream = None #Set window title and size self.window_properties = WindowProperties() self.window_properties.setSize(self.window_size, self.window_size) self.window_properties.setTitle(self.window_name) ShowBaseGlobal.base.win.requestProperties( self.window_properties) #base is a panda3d global # Set frame rate ShowBaseGlobal.globalClock.setMode(ClockObject.MLimited) ShowBaseGlobal.globalClock.setFrameRate( self.fps) #can lock this at whatever #CREATE MASK ARRAYS self.left_mask_array = 255 * np.ones( (self.tex.texture_size, self.tex.texture_size), dtype=np.uint8) self.left_mask_array[:, self.tex.texture_size // 2 - self.strip_width // 2:] = 0 self.right_mask_array = 255 * np.ones( (self.tex.texture_size, self.tex.texture_size), dtype=np.uint8) self.right_mask_array[:, :self.tex.texture_size // 2 + self.strip_width // 2] = 0 #TEXTURE STAGES FOR LEFT CARD self.left_texture_stage = TextureStage('left_texture_stage') #Mask self.left_mask = Texture("left_mask_texture") self.left_mask.setup2dTexture(self.tex.texture_size, self.tex.texture_size, Texture.T_unsigned_byte, Texture.F_luminance) self.left_mask.setRamImage(self.left_mask_array) self.left_mask_stage = TextureStage('left_mask_array') #Multiply the texture stages together self.left_mask_stage.setCombineRgb(TextureStage.CMModulate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor) #TEXTURE STAGES FOR RIGHT CARD self.right_texture_stage = TextureStage('right_texture_stage') #Mask self.right_mask = Texture("right_mask_texture") self.right_mask.setup2dTexture(self.tex.texture_size, self.tex.texture_size, Texture.T_unsigned_byte, Texture.F_luminance) self.right_mask.setRamImage(self.right_mask_array) self.right_mask_stage = TextureStage('right_mask_stage') #Multiply the texture stages together self.right_mask_stage.setCombineRgb(TextureStage.CMModulate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor) #CREATE CARDS/SCENEGRAPH cm = CardMaker('stimcard') cm.setFrameFullscreenQuad() #self.setBackgroundColor((0,0,0,1)) self.left_card = self.aspect2d.attachNewNode(cm.generate()) self.right_card = self.aspect2d.attachNewNode(cm.generate()) self.left_card.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.M_add)) self.right_card.setAttrib(ColorBlendAttrib.make( ColorBlendAttrib.M_add)) #ADD TEXTURE STAGES TO CARDS self.left_card.setTexture(self.left_texture_stage, self.tex.texture) self.left_card.setTexture(self.left_mask_stage, self.left_mask) self.right_card.setTexture(self.right_texture_stage, self.tex.texture) self.right_card.setTexture(self.right_mask_stage, self.right_mask) self.setBackgroundColor( (0, 0, 0, 1)) # without this the cards will appear washed out #self.left_card.setAntialias(AntialiasAttrib.MMultisample) #self.right_card.setAntialias(AntialiasAttrib.MMultisample) #TRANSFORMS # Masks self.mask_transform = self.trs_transform() self.left_card.setTexTransform(self.left_mask_stage, self.mask_transform) self.right_card.setTexTransform(self.right_mask_stage, self.mask_transform) # Textures # Left self.left_card.setTexScale(self.left_texture_stage, 1 / self.scale) self.left_card.setTexRotate(self.left_texture_stage, self.left_texture_angle) # Right self.right_card.setTexScale(self.right_texture_stage, 1 / self.scale) self.right_card.setTexRotate(self.right_texture_stage, self.right_texture_angle) #Set task manager(s) for textures if self.left_velocity != 0 and self.right_velocity != 0: self.taskMgr.add(self.textures_update, "move_both") elif self.left_velocity != 0 and self.right_velocity == 0: self.taskMgr.add(self.left_texture_update, "move_left") elif self.left_velocity == 0 and self.right_velocity != 0: self.taskMgr.add(self.right_texture_update, "move_right") # Event handler to process the messages self.accept("stim", self.process_stim, []) #Set up profiling if desired if profile_on: PStatClient.connect() # this will only work if pstats is running ShowBaseGlobal.base.setFrameRateMeter(True) #Show frame rate @property def mask_position_uv(self): return (utils.card2uv(self.mask_position_card[0]), utils.card2uv(self.mask_position_card[1])) def process_stim(self, x, y, theta): """ Event handler method for processing message about current x,y, theta """ # If new values are same as previous, return to caller. Otherwise, reset if (self.strip_angle, self.mask_position_card) == (theta, (x, y)): return else: self.strip_angle = theta self.right_texture_angle = self.stim_angles[1] + self.strip_angle self.left_texture_angle = self.stim_angles[0] + self.strip_angle #print(self.strip_angle, self.left_texture_angle, self.right_texture_angle) self.mask_position_card = (x, y) self.mask_transform = self.trs_transform() self.left_card.setTexTransform(self.left_mask_stage, self.mask_transform) self.right_card.setTexTransform(self.right_mask_stage, self.mask_transform) self.left_card.setTexRotate(self.left_texture_stage, self.left_texture_angle) self.right_card.setTexRotate(self.right_texture_stage, self.right_texture_angle) if self.filestream: self.filestream.write( f"{str(datetime.now())}\t{x}\t{y}\t{theta}\n") self.filestream.flush() return #Move both textures def textures_update(self, task): left_tex_position = -task.time * self.left_velocity #negative b/c texture stage right_tex_position = -task.time * self.right_velocity self.left_card.setTexPos(self.left_texture_stage, left_tex_position, 0, 0) self.right_card.setTexPos(self.right_texture_stage, right_tex_position, 0, 0) return task.cont def left_texture_update(self, task): left_tex_position = -task.time * self.left_velocity #negative b/c texture stage self.left_card.setTexPos(self.left_texture_stage, left_tex_position, 0, 0) return task.cont def right_texture_update(self, task): right_tex_position = -task.time * self.right_velocity self.right_card.setTexPos(self.right_texture_stage, right_tex_position, 0, 0) return task.cont def trs_transform(self): """ trs = translate rotate scale transform for mask stage rdb contributed to this code """ #print(self.strip_angle) pos = 0.5 + self.mask_position_uv[0], 0.5 + self.mask_position_uv[1] center_shift = TransformState.make_pos2d((-pos[0], -pos[1])) scale = TransformState.make_scale2d(1 / self.scale) rotate = TransformState.make_rotate2d(self.strip_angle) translate = TransformState.make_pos2d((0.5, 0.5)) return translate.compose(rotate.compose(scale.compose(center_shift)))
class TerrainManager(DirectObject.DirectObject): def __init__(self): self.accept("mouse1", self.lclick) self.waterType = 2 self.water = None self.citycolors = {0: VBase3D(1, 1, 1)} self.accept('generateRegion', self.generateWorld) self.accept('regenerateRegion', self.regenerateWorld) self.accept("regionView_normal", self.setSurfaceTextures) self.accept("regionView_owners", self.setOwnerTextures) self.accept("regionView_foundNew", self.regionViewFound) self.accept("updateRegion", self.updateRegion) self.accept("enterCityView", self.enterCity) # View: 0, region, # cityid self.view = 0 self.ownerview = False def lclick(self): cell = picker.getMouseCell() print "Cell:", cell blockCoords = self.terrain.getBlockFromPos(cell[0], cell[1]) block = self.terrain.getBlockNodePath(blockCoords[0], blockCoords[1]) print "Block coords:", blockCoords print "NodePath:", block print "Elevation:", self.terrain.getElevation(cell[0], cell[1]) if not self.view: messenger.send("clickForCity", [cell]) def switchWater(self): print "Switch Water" self.waterType += 1 if self.waterType > 2: self.waterType = 0 self.generateWater(self.waterType) def generateWorld(self, heightmap, tiles, cities, container): self.heightmap = heightmap self.terrain = PagedGeoMipTerrain("surface") #self.terrain = GeoMipTerrain("surface") self.terrain.setHeightfield(self.heightmap) #self.terrain.setFocalPoint(base.camera) self.terrain.setBruteforce(True) self.terrain.setBlockSize(64) self.terrain.generate() root = self.terrain.getRoot() root.reparentTo(render) #root.setSz(100) self.terrain.setSz(100) messenger.send('makePickable', [root]) if self.heightmap.getXSize() > self.heightmap.getYSize(): self.size = self.heightmap.getXSize() - 1 else: self.size = self.heightmap.getYSize() - 1 self.xsize = self.heightmap.getXSize() - 1 self.ysize = self.heightmap.getYSize() - 1 # Set multi texture # Source http://www.panda3d.org/phpbb2/viewtopic.php?t=4536 self.generateSurfaceTextures() self.generateWaterMap() self.generateOwnerTexture(tiles, cities) #self.terrain.makeTextureMap() colormap = PNMImage(heightmap.getXSize() - 1, heightmap.getYSize() - 1) colormap.addAlpha() slopemap = self.terrain.makeSlopeImage() for x in range(0, colormap.getXSize()): for y in range(0, colormap.getYSize()): # Else if statements used to make sure one channel is used per pixel # Also for some optimization # Snow. We do things funky here as alpha will be 1 already. if heightmap.getGrayVal(x, y) < 200: colormap.setAlpha(x, y, 0) else: colormap.setAlpha(x, y, 1) # Beach. Estimations from http://www.simtropolis.com/omnibus/index.cfm/Main.SimCity_4.Custom_Content.Custom_Terrains_and_Using_USGS_Data if heightmap.getGrayVal(x, y) < 62: colormap.setBlue(x, y, 1) # Rock elif slopemap.getGrayVal(x, y) > 170: colormap.setRed(x, y, 1) else: colormap.setGreen(x, y, 1) self.colorTexture = Texture() self.colorTexture.load(colormap) self.colorTS = TextureStage('color') self.colorTS.setSort(0) self.colorTS.setPriority(1) self.setSurfaceTextures() self.generateWater(2) taskMgr.add(self.updateTerrain, "updateTerrain") print "Done with terrain generation" messenger.send("finishedTerrainGen", [[self.xsize, self.ysize]]) self.terrain.getRoot().analyze() self.accept("h", self.switchWater) def regenerateWorld(self): '''Regenerates world, often upon city exit.''' self.terrain.generate() root = self.terrain.getRoot() root.reparentTo(render) self.terrain.setSz(100) messenger.send('makePickable', [root]) # Set multi texture # Source http://www.panda3d.org/phpbb2/viewtopic.php?t=4536 #self.generateSurfaceTextures() self.generateWaterMap() self.setSurfaceTextures() self.generateWater(2) print "Done with terrain regeneration" messenger.send("finishedTerrainGen", [[self.xsize, self.ysize]]) def generateWaterMap(self): ''' Iterate through every pix of color map. This will be very slow so until faster method is developed, use sparingly getXSize returns pixels length starting with 1, subtract 1 for obvious reasons We also slip in checking for the water card size, which should only change when the color map does ''' print "GenerateWaterMap" self.waterXMin, self.waterXMax, self.waterYMin, self.waterYMax = -1, 0, -1, 0 for x in range(0, self.heightmap.getXSize() - 1): for y in range(0, self.heightmap.getYSize() - 1): # Else if statements used to make sure one channel is used per pixel # Also for some optimization # Snow. We do things funky here as alpha will be 1 already. if self.heightmap.getGrayVal(x, y) < 62: # Water card dimensions here # Y axis flipped from texture space to world space if self.waterXMin == -1 or x < self.waterXMin: self.waterXMin = x if not self.waterYMax: self.waterYMax = y if y < self.waterYMax: self.waterYMax = y if x > self.waterXMax: self.waterXMax = x if y > self.waterYMin: self.waterYMin = y # Transform y coords self.waterYMin = self.size - 64 - self.waterYMin self.waterYMax = self.size - 64 - self.waterYMax def generateOwnerTexture(self, tiles, cities): '''Generates a simple colored texture to be applied to the city info region overlay. Due to different coordinate systems (terrain org bottom left, texture top left) some conversions are needed, Also creates and sends a citylabels dict for the region view ''' self.citymap = PNMImage(self.xsize, self.ysize) citylabels = cities scratch = {} # Setup for city labels for ident in cities: scratch[ident] = [] # conversion for y axis ycon = [] s = self.ysize - 1 for y in range(self.ysize): ycon.append(s) s -= 1 for ident, city in cities.items(): if ident not in self.citycolors: self.citycolors[ident] = VBase3D(random.random(), random.random(), random.random()) for tile in tiles: self.citymap.setXel(tile.coords[0], ycon[tile.coords[1]], self.citycolors[tile.cityid]) # Scratch for labeling if tile.cityid: scratch[tile.cityid].append((tile.coords[0], tile.coords[1])) for ident, values in scratch.items(): xsum = 0 ysum = 0 n = 0 for coords in values: xsum += coords[0] ysum += coords[1] n += 1 xavg = xsum / n yavg = ysum / n print "Elevation:", self.terrain.getElevation(xavg, yavg) z = self.terrain.getElevation(xavg, yavg) * 100 citylabels[ident]["position"] = (xavg, yavg, z + 15) print "Citylabels:", citylabels messenger.send("updateCityLabels", [citylabels, self.terrain]) def generateSurfaceTextures(self): # Textureize self.grassTexture = loader.loadTexture("Textures/grass.png") self.grassTS = TextureStage('grass') self.grassTS.setSort(1) self.rockTexture = loader.loadTexture("Textures/rock.jpg") self.rockTS = TextureStage('rock') self.rockTS.setSort(2) self.rockTS.setCombineRgb(TextureStage.CMAdd, TextureStage.CSLastSavedResult, TextureStage.COSrcColor, TextureStage.CSTexture, TextureStage.COSrcColor) self.sandTexture = loader.loadTexture("Textures/sand.jpg") self.sandTS = TextureStage('sand') self.sandTS.setSort(3) self.sandTS.setPriority(5) self.snowTexture = loader.loadTexture("Textures/ice.png") self.snowTS = TextureStage('snow') self.snowTS.setSort(4) self.snowTS.setPriority(0) # Grid for city placement and guide and stuff self.gridTexture = loader.loadTexture("Textures/grid.png") self.gridTexture.setWrapU(Texture.WMRepeat) self.gridTexture.setWrapV(Texture.WMRepeat) self.gridTS = TextureStage('grid') self.gridTS.setSort(5) self.gridTS.setPriority(10) def enterCity(self, ident, city, position, tiles): '''Identifies which terrain blocks city belogs to and disables those that are not A lot of uneeded for loops in here. Will need to find a better way later.''' #root = self.terrain.getRoot() children = [] for terrain in self.terrain.terrains: root = terrain.getRoot() children += root.getChildren() keepBlocks = [] # Reset water dimentions self.waterXMin = 0 self.waterXMax = 0 self.waterYMin = 0 self.waterYMax = 0 for tile in tiles: blockCoords = self.terrain.getBlockFromPos(tile.coords[0], tile.coords[1]) block = self.terrain.getBlockNodePath(blockCoords[0], blockCoords[1]) if block not in keepBlocks: keepBlocks.append(block) if self.heightmap.getGrayVal(tile.coords[0], self.size - tile.coords[1]) < 62: # Water card dimensions here # Y axis flipped from texture space to world space if not self.waterXMin: self.waterXMin = tile.coords[0] if not self.waterYMin: self.waterYMin = tile.coords[1] if tile.coords[1] > self.waterYMax: self.waterYMax = tile.coords[1] if tile.coords[0] > self.waterXMax: self.waterXMax = tile.coords[0] if tile.coords[0] < self.waterXMin: self.waterXMin = tile.coords[0] if tile.coords[1] < self.waterYMin: self.waterYMin = tile.coords[1] for child in children: if child not in keepBlocks: child.detachNode() self.view = ident self.generateWater(2) def newTerrainOverlay(self, task): root = self.terrain.getRoot() position = picker.getMouseCell() if position: # Check to make sure we do not go out of bounds if position[0] < 32: position = (32, position[1]) elif position[0] > self.xsize - 32: position = (self.xsize - 32, position[1]) if position[1] < 32: position = (position[0], 32) elif position[1] > self.ysize - 32: position = (position[0], self.size - 32) root.setTexOffset(self.tileTS, -(position[0] - 32) / 64, -(position[1] - 32) / 64) return task.cont def regionViewFound(self): '''Gui for founding a new city!''' self.setOwnerTextures() root = self.terrain.getRoot() task = taskMgr.add(self.newTerrainOverlay, "newTerrainOverlay") tileTexture = loader.loadTexture("Textures/tile.png") tileTexture.setWrapU(Texture.WMClamp) tileTexture.setWrapV(Texture.WMClamp) self.tileTS = TextureStage('tile') self.tileTS.setSort(6) self.tileTS.setMode(TextureStage.MDecal) #self.tileTS.setColor(Vec4(1,0,1,1)) root.setTexture(self.tileTS, tileTexture) root.setTexScale(self.tileTS, self.terrain.xchunks, self.terrain.ychunks) self.acceptOnce("mouse1", self.regionViewFound2) self.acceptOnce("escape", self.cancelRegionViewFound) def regionViewFound2(self): '''Grabs cell location for founding. The texture coordinate is used as the mouse may enter an out of bounds area. ''' root = self.terrain.getRoot() root_position = root.getTexOffset(self.tileTS) # We offset the position of the texture, so we will now put the origin of the new city not on mouse cursor but the "bottom left" of it. Just need to add 32 to get other edge position = [ int(abs(root_position[0] * 64)), int(abs(root_position[1] * 64)) ] self.cancelRegionViewFound() messenger.send("found_city_name", [position]) def cancelRegionViewFound(self): taskMgr.remove("newTerrainOverlay") root = self.terrain.getRoot() root.clearTexture(self.tileTS) # Restore original mouse function self.accept("mouse1", self.lclick) messenger.send("showRegionGUI") def setSurfaceTextures(self): self.ownerview = False root = self.terrain.getRoot() root.clearTexture() #self.terrain.setTextureMap() root.setTexture(self.colorTS, self.colorTexture) root.setTexture(self.grassTS, self.grassTexture) root.setTexScale(self.grassTS, self.size / 8, self.size / 8) root.setTexture(self.rockTS, self.rockTexture) root.setTexScale(self.rockTS, self.size / 8, self.size / 8) root.setTexture(self.sandTS, self.sandTexture) root.setTexScale(self.sandTS, self.size / 8, self.size / 8) root.setTexture(self.snowTS, self.snowTexture) root.setTexScale(self.snowTS, self.size / 8, self.size / 8) root.setTexture(self.gridTS, self.gridTexture) root.setTexScale(self.gridTS, self.xsize, self.ysize) root.setShaderInput('size', self.xsize, self.ysize, self.size, self.size) root.setShader(loader.loadShader('Shaders/terraintexture.sha')) def setOwnerTextures(self): self.ownerview = True root = self.terrain.getRoot() root.clearShader() root.clearTexture() cityTexture = Texture() cityTexture.load(self.citymap) cityTS = TextureStage('citymap') cityTS.setSort(0) root.setTexture(self.gridTS, self.gridTexture) root.setTexScale(self.gridTS, self.terrain.xchunks, self.terrain.ychunks) root.setTexture(cityTS, cityTexture, 1) def updateRegion(self, heightmap, tiles, cities): self.generateOwnerTexture(tiles, cities) if self.ownerview: self.setOwnerTextures() def updateTerrain(self, task): '''Updates terrain and water''' self.terrain.update() # Water if self.waterType is 2: pos = base.camera.getPos() render.setShaderInput('time', task.time) mc = base.camera.getMat() self.water.changeCameraPos(pos, mc) self.water.changeCameraPos(pos, mc) #print "Render diagnostics" #render.analyze() #base.cTrav.showCollisions(render) return task.cont def generateWater(self, style): print "Generate Water:", self.waterXMin, self.waterXMax, self.waterYMin, self.waterYMax '''Generates water style 0: blue card style 1: reflective card style 2: reflective card with shaders ''' self.waterHeight = 22.0 if self.water: self.water.removeNode() if style is 0: cm = CardMaker("water") #cm.setFrame(-1, 1, -1, 1) cm.setFrame(self.waterXMin, self.waterXMax, self.waterYMin, self.waterYMax) cm.setColor(0, 0, 1, 0.9) self.water = render.attachNewNode(cm.generate()) if self.waterYMax > self.waterXMax: size = self.waterYMax else: size = self.waterXMax self.water.lookAt(0, 0, -1) self.water.setZ(self.waterHeight) messenger.send('makePickable', [self.water]) elif style is 1: # From Prosoft's super awesome terrain demo cm = CardMaker("water") #cm.setFrame(-1, 1, -1, 1) cm.setFrame(self.waterXMin, self.waterXMax, self.waterYMin, self.waterYMax) self.water = render.attachNewNode(cm.generate()) if self.waterYMax > self.waterXMax: size = self.waterYMax else: size = self.waterXMax #self.water.setScale(size) self.water.lookAt(0, 0, -1) self.water.setZ(self.waterHeight) self.water.setShaderOff(1) self.water.setLightOff(1) self.water.setAlphaScale(0.5) self.water.setTransparency(TransparencyAttrib.MAlpha) wbuffer = base.win.makeTextureBuffer("water", 512, 512) wbuffer.setClearColorActive(True) wbuffer.setClearColor(base.win.getClearColor()) self.wcamera = base.makeCamera(wbuffer) self.wcamera.reparentTo(render) self.wcamera.node().setLens(base.camLens) self.wcamera.node().setCameraMask(BitMask32.bit(1)) self.water.hide(BitMask32.bit(1)) wtexture = wbuffer.getTexture() wtexture.setWrapU(Texture.WMClamp) wtexture.setWrapV(Texture.WMClamp) wtexture.setMinfilter(Texture.FTLinearMipmapLinear) self.wplane = Plane(Vec3(0, 0, 1), Point3(0, 0, self.water.getZ())) wplanenp = render.attachNewNode(PlaneNode("water", self.wplane)) tmpnp = NodePath("StateInitializer") tmpnp.setClipPlane(wplanenp) tmpnp.setAttrib(CullFaceAttrib.makeReverse()) self.wcamera.node().setInitialState(tmpnp.getState()) self.water.projectTexture(TextureStage("reflection"), wtexture, self.wcamera) messenger.send('makePickable', [self.water]) elif style is 2: # From Clcheung just as super awesome demomaster self.water_level = Vec4(0.0, 0.0, self.waterHeight, 1.0) self.water = water.WaterNode(self.waterXMin, self.waterYMin, self.waterXMax, self.waterYMax, self.water_level.getZ()) self.water.setStandardControl() self.water.changeParams(None) wl = self.water_level wl.setZ(wl.getZ() - 0.05) #root.setShaderInput('waterlevel', self.water_level) render.setShaderInput('time', 0) messenger.send('makePickable', [self.water.waterNP])
class Water(AssetBase): def __init__(self, name, size=10000, resolution=1024): """Arguments: size -- Edge length of the water square. resolution -- Texture size of the rendered reflection buffer. """ # Uncomment to see the output of the refclection buffer. base.bufferViewer.toggleEnable() AssetBase.__init__(self) self.name = name self.cm = CardMaker("water surface") self.cm.setFrame(-0.5 * size, 0.5 * size, -0.5 * size, 0.5 * size) self.cm.setHasUvs(True) self.node = NodePath(self.cm.generate()) self.node.setP(self.node, -90) self.node.flattenLight() self.node.hide(BitMask32.bit(1)) #self.node.setTwoSided(True) self.node.setShaderOff() # size of one texture tile in meters self.tex_size = 100.0 diffuse = TexturePool.loadTexture("textures/water.diffuse.png") diffuse.setWrapU(Texture.WMRepeat) diffuse.setWrapV(Texture.WMRepeat) diffuse.setMinfilter(Texture.FTLinearMipmapLinear) diffuse.setMagfilter(Texture.FTLinearMipmapLinear) self.diffuse_stage = TextureStage("diffuse") self.diffuse_stage.setSort(2) self.node.setTexture(self.diffuse_stage, diffuse) self.node.setTexScale(self.diffuse_stage, size / self.tex_size, size / self.tex_size) # Reflection camera renders to 'buffer' which is projected onto the # water surface. buffer = base.win.makeTextureBuffer("water reflection", resolution, resolution) buffer.setClearColor(Vec4(0, 0, 0, 1)) self.refl_cam = base.makeCamera(buffer) self.refl_cam.reparentTo(self.node) self.refl_cam.node().setCameraMask(BitMask32.bit(1)) self.refl_cam.node().getLens().setFov(base.camLens.getFov()) self.refl_cam.node().getLens().setNearFar(1, 100000) plane = PlaneNode("water culling plane", Plane(Vec3(0, 0, 1), Point3(0, 0, 0))) cfa = CullFaceAttrib.makeReverse() cpa = ClipPlaneAttrib.make(PlaneNode.CEVisible, plane) rs = RenderState.make(cfa, cpa) self.refl_cam.node().setInitialState(rs) reflection = buffer.getTexture() reflection.setMinfilter(Texture.FTLinear) reflection.setMagfilter(Texture.FTLinear) self.refl_stage = TextureStage("reflection") self.refl_stage.setSort(1) self.node.projectTexture(self.refl_stage, reflection, base.cam) self.node.setTexture(self.refl_stage, reflection) # Blend between diffuse and reflection. self.diffuse_stage.setColor(VBase4(1, 1, 1, 0.2)) # opacity of 20% self.diffuse_stage.setCombineRgb( TextureStage.CMInterpolate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor, TextureStage.CSConstant, TextureStage.COSrcAlpha) self.addTask(self.update, name="water update", sort=1, taskChain="world") def update(self, task): """Updates position of the reflection camera and the water plane.""" mc = base.cam.getMat(render) #mf = Plane(Vec3(0, 0, 1), Point3(0, 0, 0)).getReflectionMat() mf = Mat4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1) self.refl_cam.setMat(mc * mf) self.node.setX(camera.getX(render)) self.node.setY(camera.getY(render)) self.node.setTexOffset(self.diffuse_stage, self.node.getX() / self.tex_size, self.node.getY() / self.tex_size) return task.cont def destroy(self): self.removeAllTasks() self.node.removeNode() self.refl_cam.removeNode()
def setupHeightmap(self, name): # Automatically generate a heightmap mesh from a monochrome image. self.hmHeight = 120 hmPath = "../maps/map" + name + "/map" + name + "-h.png" imPath = "../maps/map" + name + "/map" + name + "-i.png" smPath = "../maps/map" + name + "/map" + name + "-s.png" scmPath = "../maps/map" + name + "/map" + name + "-sc.png" print(hmPath) print(imPath) print(smPath) print(scmPath) hmImg = PNMImage(Filename(hmPath)) hmShape = BulletHeightfieldShape(hmImg, self.hmHeight, ZUp) hmNode = BulletRigidBodyNode('Terrain') hmNode.addShape(hmShape) hmNode.setMass(0) self.hmNP = render.attachNewNode(hmNode) self.worldBullet.attachRigidBody(hmNode) self.hmOffset = hmImg.getXSize() / 2.0 - 0.5 self.hmTerrain = GeoMipTerrain('gmTerrain') self.hmTerrain.setHeightfield(hmImg) # Optimizations and fixes self.hmTerrain.setBruteforce( True) # I don't think this is actually needed. self.hmTerrain.setMinLevel(3) # THIS is what triangulates the terrain. self.hmTerrain.setBlockSize( 128) # This does a pretty good job of raising FPS. # Level-of-detail (not yet working) # self.hmTerrain.setNear(40) # self.hmTerrain.setFar(200) self.hmTerrain.generate() self.hmTerrainNP = self.hmTerrain.getRoot() self.hmTerrainNP.setSz(self.hmHeight) self.hmTerrainNP.setPos(-self.hmOffset, -self.hmOffset, -self.hmHeight / 2.0) self.hmTerrainNP.flattenStrong( ) # This only reduces the number of nodes; nothing to do with polys. self.hmTerrainNP.analyze() # Here begins the scenery mapping treeModel = loader.loadModel("../res/models/tree_1.egg") rockModel = loader.loadModel("../res/models/rock_1.egg") rock2Model = loader.loadModel("../res/models/rock_2.egg") rock3Model = loader.loadModel("../res/models/rock_3.egg") # caveModel = loader.loadModel("../res/models/cave_new.egg") # planeFrontModel = loader.loadModel("../res/models/plane_front.egg") # planeWingModel = loader.loadModel("../res/models/plane_wing.egg") texpk = loader.loadTexture(scmPath).peek() # GameObject nodepath for flattening self.objNP = render.attachNewNode("gameObjects") self.treeNP = self.objNP.attachNewNode("goTrees") self.rockNP = self.objNP.attachNewNode("goRocks") self.rock2NP = self.objNP.attachNewNode("goRocks2") self.rock3NP = self.objNP.attachNewNode("goRocks3") # self.caveNP = self.objNP.attachNewNode("goCave") # self.planeFrontNP = self.objNP.attachNewNode("goPlaneFront") # self.planeWingNP = self.objNP.attachNewNode("goPlaneWing") for i in range(0, texpk.getXSize()): for j in range(0, texpk.getYSize()): color = VBase4(0, 0, 0, 0) texpk.lookup(color, float(i) / texpk.getXSize(), float(j) / texpk.getYSize()) if (int(color.getX() * 255.0) == 255.0): newTree = self.treeNP.attachNewNode("treeNode") treeModel.instanceTo(newTree) newTree.setPos( i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) # newTree.setScale(randint(0,4)) newTree.setScale(2) if (int(color.getX() * 255.0) == 128): newRock = self.rockNP.attachNewNode("newRock") newRock.setPos( i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) rockModel.instanceTo(newRock) if (int(color.getX() * 255.0) == 77): newRock2 = self.rock2NP.attachNewNode("newRock2") newRock2.setPos( i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) rock2Model.instanceTo(newRock2) if (int(color.getX() * 255.0) == 102): newRock3 = self.rock3NP.attachNewNode("newRock3") newRock3.setPos( i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) rock3Model.instanceTo(newRock3) # if(int(color.getX() * 255.0) == 64): # newCave = self.caveNP.attachNewNode("newCave") # newCave.setPos(i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) # newCave.setScale(5) # newCave.setP(180) # caveModel.instanceTo(newCave) # if(int(color.getX() * 255.0) == 191): # newPlaneFront = self.planeFrontNP.attachNewNode("newPlaneFront") # newPlaneFront.setPos(i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) # newPlaneFront.setScale(6) # planeFrontModel.instanceTo(newPlaneFront) # if(int(color.getX() * 255.0) == 179): # newPlaneWing = self.planeWingNP.attachNewNode("newPlaneWing") # newPlaneWing.setPos(i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) # newPlaneWing.setScale(6) # newPlaneWing.setH(250) # newPlaneWing.setR(180) # newPlaneWing.setP(135) # planeWingModel.instanceTo(newPlaneWing) self.snowflakes = [] for i in xrange(0, self.snowflakeCount): print("Call " + str(i)) sf = SMCollect(self.worldBullet, self.worldObj, self.snowflakePositions[i]) self.snowflakes.append(sf) # render.flattenStrong() self.hmTerrainNP.reparentTo(render) # Here begins the attribute mapping ts = TextureStage("stage-alpha") ts.setSort(0) ts.setPriority(1) ts.setMode(TextureStage.MReplace) ts.setSavedResult(True) self.hmTerrainNP.setTexture(ts, loader.loadTexture(imPath, smPath)) ts = TextureStage("stage-stone") ts.setSort(1) ts.setPriority(1) ts.setMode(TextureStage.MReplace) self.hmTerrainNP.setTexture( ts, loader.loadTexture("../res/textures/stone_tex.png")) self.hmTerrainNP.setTexScale(ts, 32, 32) ts = TextureStage("stage-ice") ts.setSort(2) ts.setPriority(1) ts.setCombineRgb(TextureStage.CMInterpolate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor, TextureStage.CSLastSavedResult, TextureStage.COSrcColor) self.hmTerrainNP.setTexture( ts, loader.loadTexture("../res/textures/ice_tex.png")) self.hmTerrainNP.setTexScale(ts, 32, 32) ts = TextureStage("stage-snow") ts.setSort(3) ts.setPriority(0) ts.setCombineRgb(TextureStage.CMInterpolate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor, TextureStage.CSLastSavedResult, TextureStage.COSrcAlpha) self.hmTerrainNP.setTexture( ts, loader.loadTexture("../res/textures/snow_tex_1.png")) self.hmTerrainNP.setTexScale(ts, 32, 32) # print(self.snowflakes) return hmNode
def setupHeightmap(self, name): # Automatically generate a heightmap mesh from a monochrome image. self.hmHeight = 120 hmPath = "../maps/map" + name + "/map" + name + "-h.png" imPath = "../maps/map" + name + "/map" + name + "-i.png" smPath = "../maps/map" + name + "/map" + name + "-s.png" scmPath = "../maps/map" + name + "/map" + name + "-sc.png" print(hmPath) print(imPath) print(smPath) print(scmPath) hmImg = PNMImage(Filename(hmPath)) hmShape = BulletHeightfieldShape(hmImg, self.hmHeight, ZUp) hmNode = BulletRigidBodyNode('Terrain') hmNode.addShape(hmShape) hmNode.setMass(0) self.hmNP = render.attachNewNode(hmNode) self.worldBullet.attachRigidBody(hmNode) self.hmOffset = hmImg.getXSize() / 2.0 - 0.5 self.hmTerrain = GeoMipTerrain('gmTerrain') self.hmTerrain.setHeightfield(hmImg) # Optimizations and fixes self.hmTerrain.setBruteforce(True) # I don't think this is actually needed. self.hmTerrain.setMinLevel(3) # THIS is what triangulates the terrain. self.hmTerrain.setBlockSize(128) # This does a pretty good job of raising FPS. # Level-of-detail (not yet working) # self.hmTerrain.setNear(40) # self.hmTerrain.setFar(200) self.hmTerrain.generate() self.hmTerrainNP = self.hmTerrain.getRoot() self.hmTerrainNP.setSz(self.hmHeight) self.hmTerrainNP.setPos(-self.hmOffset, -self.hmOffset, -self.hmHeight / 2.0) self.hmTerrainNP.flattenStrong() # This only reduces the number of nodes; nothing to do with polys. self.hmTerrainNP.analyze() # Here begins the scenery mapping treeModel = loader.loadModel("../res/models/tree_1.egg") rockModel = loader.loadModel("../res/models/rock_1.egg") rock2Model = loader.loadModel("../res/models/rock_2.egg") rock3Model = loader.loadModel("../res/models/rock_3.egg") # caveModel = loader.loadModel("../res/models/cave_new.egg") # planeFrontModel = loader.loadModel("../res/models/plane_front.egg") # planeWingModel = loader.loadModel("../res/models/plane_wing.egg") texpk = loader.loadTexture(scmPath).peek() # GameObject nodepath for flattening self.objNP = render.attachNewNode("gameObjects") self.treeNP = self.objNP.attachNewNode("goTrees") self.rockNP = self.objNP.attachNewNode("goRocks") self.rock2NP = self.objNP.attachNewNode("goRocks2") self.rock3NP = self.objNP.attachNewNode("goRocks3") # self.caveNP = self.objNP.attachNewNode("goCave") # self.planeFrontNP = self.objNP.attachNewNode("goPlaneFront") # self.planeWingNP = self.objNP.attachNewNode("goPlaneWing") for i in range(0, texpk.getXSize()): for j in range(0, texpk.getYSize()): color = VBase4(0, 0, 0, 0) texpk.lookup(color, float(i) / texpk.getXSize(), float(j) / texpk.getYSize()) if(int(color.getX() * 255.0) == 255.0): newTree = self.treeNP.attachNewNode("treeNode") treeModel.instanceTo(newTree) newTree.setPos(i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) # newTree.setScale(randint(0,4)) newTree.setScale(2) if(int(color.getX() * 255.0) == 128): newRock = self.rockNP.attachNewNode("newRock") newRock.setPos(i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) rockModel.instanceTo(newRock) if(int(color.getX() * 255.0) == 77): newRock2 = self.rock2NP.attachNewNode("newRock2") newRock2.setPos(i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) rock2Model.instanceTo(newRock2) if(int(color.getX() * 255.0) == 102): newRock3 = self.rock3NP.attachNewNode("newRock3") newRock3.setPos(i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) rock3Model.instanceTo(newRock3) # if(int(color.getX() * 255.0) == 64): # newCave = self.caveNP.attachNewNode("newCave") # newCave.setPos(i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) # newCave.setScale(5) # newCave.setP(180) # caveModel.instanceTo(newCave) # if(int(color.getX() * 255.0) == 191): # newPlaneFront = self.planeFrontNP.attachNewNode("newPlaneFront") # newPlaneFront.setPos(i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) # newPlaneFront.setScale(6) # planeFrontModel.instanceTo(newPlaneFront) # if(int(color.getX() * 255.0) == 179): # newPlaneWing = self.planeWingNP.attachNewNode("newPlaneWing") # newPlaneWing.setPos(i - texpk.getXSize() / 2, j - texpk.getYSize() / 2, self.hmTerrain.get_elevation(i, j) * self.hmHeight - self.hmHeight / 2) # newPlaneWing.setScale(6) # newPlaneWing.setH(250) # newPlaneWing.setR(180) # newPlaneWing.setP(135) # planeWingModel.instanceTo(newPlaneWing) self.snowflakes = [] for i in xrange(0, self.snowflakeCount): print("Call " + str(i)) sf = SMCollect(self.worldBullet, self.worldObj, self.snowflakePositions[i]) self.snowflakes.append(sf) # render.flattenStrong() self.hmTerrainNP.reparentTo(render) # Here begins the attribute mapping ts = TextureStage("stage-alpha") ts.setSort(0) ts.setPriority(1) ts.setMode(TextureStage.MReplace) ts.setSavedResult(True) self.hmTerrainNP.setTexture(ts, loader.loadTexture(imPath, smPath)) ts = TextureStage("stage-stone") ts.setSort(1) ts.setPriority(1) ts.setMode(TextureStage.MReplace) self.hmTerrainNP.setTexture(ts, loader.loadTexture("../res/textures/stone_tex.png")) self.hmTerrainNP.setTexScale(ts, 32, 32) ts = TextureStage("stage-ice") ts.setSort(2) ts.setPriority(1) ts.setCombineRgb(TextureStage.CMInterpolate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor, TextureStage.CSLastSavedResult, TextureStage.COSrcColor) self.hmTerrainNP.setTexture(ts, loader.loadTexture("../res/textures/ice_tex.png")) self.hmTerrainNP.setTexScale(ts, 32, 32) ts = TextureStage("stage-snow") ts.setSort(3) ts.setPriority(0) ts.setCombineRgb(TextureStage.CMInterpolate, TextureStage.CSTexture, TextureStage.COSrcColor, TextureStage.CSPrevious, TextureStage.COSrcColor, TextureStage.CSLastSavedResult, TextureStage.COSrcAlpha) self.hmTerrainNP.setTexture(ts, loader.loadTexture("../res/textures/snow_tex_1.png")) self.hmTerrainNP.setTexScale(ts, 32, 32) # print(self.snowflakes) return hmNode
class TerrainManager(DirectObject.DirectObject): def __init__(self): self.accept("mouse1", self.lclick) self.waterType = 2 self.water = None self.citycolors = {0: VBase3D(1, 1, 1)} self.accept('generateRegion', self.generateWorld) self.accept('regenerateRegion', self.regenerateWorld) self.accept("regionView_normal", self.setSurfaceTextures) self.accept("regionView_owners", self.setOwnerTextures) self.accept("regionView_foundNew", self.regionViewFound) self.accept("updateRegion", self.updateRegion) self.accept("enterCityView", self.enterCity) # View: 0, region, # cityid self.view = 0 self.ownerview = False def lclick(self): cell = picker.getMouseCell() print "Cell:", cell blockCoords = self.terrain.getBlockFromPos(cell[0], cell[1]) block = self.terrain.getBlockNodePath(blockCoords[0], blockCoords[1]) print "Block coords:", blockCoords print "NodePath:", block print "Elevation:", self.terrain.getElevation(cell[0], cell[1]) if not self.view: messenger.send("clickForCity", [cell]) def switchWater(self): print "Switch Water" self.waterType += 1 if self.waterType > 2: self.waterType = 0 self.generateWater(self.waterType) def generateWorld(self, heightmap, tiles, cities, container): self.heightmap = heightmap self.terrain = PagedGeoMipTerrain("surface") #self.terrain = GeoMipTerrain("surface") self.terrain.setHeightfield(self.heightmap) #self.terrain.setFocalPoint(base.camera) self.terrain.setBruteforce(True) self.terrain.setBlockSize(64) self.terrain.generate() root = self.terrain.getRoot() root.reparentTo(render) #root.setSz(100) self.terrain.setSz(100) messenger.send('makePickable', [root]) if self.heightmap.getXSize() > self.heightmap.getYSize(): self.size = self.heightmap.getXSize()-1 else: self.size = self.heightmap.getYSize()-1 self.xsize = self.heightmap.getXSize()-1 self.ysize = self.heightmap.getYSize()-1 # Set multi texture # Source http://www.panda3d.org/phpbb2/viewtopic.php?t=4536 self.generateSurfaceTextures() self.generateWaterMap() self.generateOwnerTexture(tiles, cities) #self.terrain.makeTextureMap() colormap = PNMImage(heightmap.getXSize()-1, heightmap.getYSize()-1) colormap.addAlpha() slopemap = self.terrain.makeSlopeImage() for x in range(0, colormap.getXSize()): for y in range(0, colormap.getYSize()): # Else if statements used to make sure one channel is used per pixel # Also for some optimization # Snow. We do things funky here as alpha will be 1 already. if heightmap.getGrayVal(x, y) < 200: colormap.setAlpha(x, y, 0) else: colormap.setAlpha(x, y, 1) # Beach. Estimations from http://www.simtropolis.com/omnibus/index.cfm/Main.SimCity_4.Custom_Content.Custom_Terrains_and_Using_USGS_Data if heightmap.getGrayVal(x,y) < 62: colormap.setBlue(x, y, 1) # Rock elif slopemap.getGrayVal(x, y) > 170: colormap.setRed(x, y, 1) else: colormap.setGreen(x, y, 1) self.colorTexture = Texture() self.colorTexture.load(colormap) self.colorTS = TextureStage('color') self.colorTS.setSort(0) self.colorTS.setPriority(1) self.setSurfaceTextures() self.generateWater(2) taskMgr.add(self.updateTerrain, "updateTerrain") print "Done with terrain generation" messenger.send("finishedTerrainGen", [[self.xsize, self.ysize]]) self.terrain.getRoot().analyze() self.accept("h", self.switchWater) def regenerateWorld(self): '''Regenerates world, often upon city exit.''' self.terrain.generate() root = self.terrain.getRoot() root.reparentTo(render) self.terrain.setSz(100) messenger.send('makePickable', [root]) # Set multi texture # Source http://www.panda3d.org/phpbb2/viewtopic.php?t=4536 #self.generateSurfaceTextures() self.generateWaterMap() self.setSurfaceTextures() self.generateWater(2) print "Done with terrain regeneration" messenger.send("finishedTerrainGen", [[self.xsize, self.ysize]]) def generateWaterMap(self): ''' Iterate through every pix of color map. This will be very slow so until faster method is developed, use sparingly getXSize returns pixels length starting with 1, subtract 1 for obvious reasons We also slip in checking for the water card size, which should only change when the color map does ''' print "GenerateWaterMap" self.waterXMin, self.waterXMax, self.waterYMin, self.waterYMax = -1,0,-1,0 for x in range(0, self.heightmap.getXSize()-1): for y in range(0, self.heightmap.getYSize()-1): # Else if statements used to make sure one channel is used per pixel # Also for some optimization # Snow. We do things funky here as alpha will be 1 already. if self.heightmap.getGrayVal(x,y) < 62: # Water card dimensions here # Y axis flipped from texture space to world space if self.waterXMin == -1 or x < self.waterXMin: self.waterXMin = x if not self.waterYMax: self.waterYMax = y if y < self.waterYMax: self.waterYMax = y if x > self.waterXMax: self.waterXMax = x if y > self.waterYMin: self.waterYMin = y # Transform y coords self.waterYMin = self.size-64 - self.waterYMin self.waterYMax = self.size-64 - self.waterYMax def generateOwnerTexture(self, tiles, cities): '''Generates a simple colored texture to be applied to the city info region overlay. Due to different coordinate systems (terrain org bottom left, texture top left) some conversions are needed, Also creates and sends a citylabels dict for the region view ''' self.citymap = PNMImage(self.xsize, self.ysize) citylabels = cities scratch = {} # Setup for city labels for ident in cities: scratch[ident] = [] # conversion for y axis ycon = [] s = self.ysize - 1 for y in range(self.ysize): ycon.append(s) s -= 1 for ident, city in cities.items(): if ident not in self.citycolors: self.citycolors[ident] = VBase3D(random.random(), random.random(), random.random()) for tile in tiles: self.citymap.setXel(tile.coords[0], ycon[tile.coords[1]], self.citycolors[tile.cityid]) # Scratch for labeling if tile.cityid: scratch[tile.cityid].append((tile.coords[0], tile.coords[1])) for ident, values in scratch.items(): xsum = 0 ysum = 0 n = 0 for coords in values: xsum += coords[0] ysum += coords[1] n += 1 xavg = xsum/n yavg = ysum/n print "Elevation:", self.terrain.getElevation(xavg, yavg) z = self.terrain.getElevation(xavg, yavg)*100 citylabels[ident]["position"] = (xavg, yavg, z+15) print "Citylabels:", citylabels messenger.send("updateCityLabels", [citylabels, self.terrain]) def generateSurfaceTextures(self): # Textureize self.grassTexture = loader.loadTexture("Textures/grass.png") self.grassTS = TextureStage('grass') self.grassTS.setSort(1) self.rockTexture = loader.loadTexture("Textures/rock.jpg") self.rockTS = TextureStage('rock') self.rockTS.setSort(2) self.rockTS.setCombineRgb(TextureStage.CMAdd, TextureStage.CSLastSavedResult, TextureStage.COSrcColor, TextureStage.CSTexture, TextureStage.COSrcColor) self.sandTexture = loader.loadTexture("Textures/sand.jpg") self.sandTS = TextureStage('sand') self.sandTS.setSort(3) self.sandTS.setPriority(5) self.snowTexture = loader.loadTexture("Textures/ice.png") self.snowTS = TextureStage('snow') self.snowTS.setSort(4) self.snowTS.setPriority(0) # Grid for city placement and guide and stuff self.gridTexture = loader.loadTexture("Textures/grid.png") self.gridTexture.setWrapU(Texture.WMRepeat) self.gridTexture.setWrapV(Texture.WMRepeat) self.gridTS = TextureStage('grid') self.gridTS.setSort(5) self.gridTS.setPriority(10) def enterCity(self, ident, city, position, tiles): '''Identifies which terrain blocks city belogs to and disables those that are not A lot of uneeded for loops in here. Will need to find a better way later.''' #root = self.terrain.getRoot() children = [] for terrain in self.terrain.terrains: root = terrain.getRoot() children += root.getChildren() keepBlocks = [] # Reset water dimentions self.waterXMin = 0 self.waterXMax = 0 self.waterYMin = 0 self.waterYMax = 0 for tile in tiles: blockCoords = self.terrain.getBlockFromPos(tile.coords[0], tile.coords[1]) block = self.terrain.getBlockNodePath(blockCoords[0], blockCoords[1]) if block not in keepBlocks: keepBlocks.append(block) if self.heightmap.getGrayVal(tile.coords[0], self.size-tile.coords[1]) < 62: # Water card dimensions here # Y axis flipped from texture space to world space if not self.waterXMin: self.waterXMin = tile.coords[0] if not self.waterYMin: self.waterYMin = tile.coords[1] if tile.coords[1] > self.waterYMax: self.waterYMax = tile.coords[1] if tile.coords[0] > self.waterXMax: self.waterXMax = tile.coords[0] if tile.coords[0] < self.waterXMin: self.waterXMin = tile.coords[0] if tile.coords[1] < self.waterYMin: self.waterYMin = tile.coords[1] for child in children: if child not in keepBlocks: child.detachNode() self.view = ident self.generateWater(2) def newTerrainOverlay(self, task): root = self.terrain.getRoot() position = picker.getMouseCell() if position: # Check to make sure we do not go out of bounds if position[0] < 32: position = (32, position[1]) elif position[0] > self.xsize-32: position = (self.xsize-32, position[1]) if position[1] < 32: position = (position[0], 32) elif position [1] > self.ysize-32: position = (position[0], self.size-32) root.setTexOffset(self.tileTS, -(position[0]-32)/64, -(position[1]-32)/64) return task.cont def regionViewFound(self): '''Gui for founding a new city!''' self.setOwnerTextures() root = self.terrain.getRoot() task = taskMgr.add(self.newTerrainOverlay, "newTerrainOverlay") tileTexture = loader.loadTexture("Textures/tile.png") tileTexture.setWrapU(Texture.WMClamp) tileTexture.setWrapV(Texture.WMClamp) self.tileTS = TextureStage('tile') self.tileTS.setSort(6) self.tileTS.setMode(TextureStage.MDecal) #self.tileTS.setColor(Vec4(1,0,1,1)) root.setTexture(self.tileTS, tileTexture) root.setTexScale(self.tileTS, self.terrain.xchunks, self.terrain.ychunks) self.acceptOnce("mouse1", self.regionViewFound2) self.acceptOnce("escape", self.cancelRegionViewFound) def regionViewFound2(self): '''Grabs cell location for founding. The texture coordinate is used as the mouse may enter an out of bounds area. ''' root = self.terrain.getRoot() root_position = root.getTexOffset(self.tileTS) # We offset the position of the texture, so we will now put the origin of the new city not on mouse cursor but the "bottom left" of it. Just need to add 32 to get other edge position = [int(abs(root_position[0]*64)), int(abs(root_position[1]*64))] self.cancelRegionViewFound() messenger.send("found_city_name", [position]) def cancelRegionViewFound(self): taskMgr.remove("newTerrainOverlay") root = self.terrain.getRoot() root.clearTexture(self.tileTS) # Restore original mouse function self.accept("mouse1", self.lclick) messenger.send("showRegionGUI") def setSurfaceTextures(self): self.ownerview = False root = self.terrain.getRoot() root.clearTexture() #self.terrain.setTextureMap() root.setTexture( self.colorTS, self.colorTexture ) root.setTexture( self.grassTS, self.grassTexture ) root.setTexScale(self.grassTS, self.size/8, self.size/8) root.setTexture( self.rockTS, self.rockTexture ) root.setTexScale(self.rockTS, self.size/8, self.size/8) root.setTexture( self.sandTS, self.sandTexture) root.setTexScale(self.sandTS, self.size/8, self.size/8) root.setTexture( self.snowTS, self.snowTexture ) root.setTexScale(self.snowTS, self.size/8, self.size/8) root.setTexture( self.gridTS, self.gridTexture ) root.setTexScale(self.gridTS, self.xsize, self.ysize) root.setShaderInput('size', self.xsize, self.ysize, self.size, self.size) root.setShader(loader.loadShader('Shaders/terraintexture.sha')) def setOwnerTextures(self): self.ownerview = True root = self.terrain.getRoot() root.clearShader() root.clearTexture() cityTexture = Texture() cityTexture.load(self.citymap) cityTS = TextureStage('citymap') cityTS.setSort(0) root.setTexture( self.gridTS, self.gridTexture ) root.setTexScale(self.gridTS, self.terrain.xchunks, self.terrain.ychunks) root.setTexture(cityTS, cityTexture, 1) def updateRegion(self, heightmap, tiles, cities): self.generateOwnerTexture(tiles, cities) if self.ownerview: self.setOwnerTextures() def updateTerrain(self, task): '''Updates terrain and water''' self.terrain.update() # Water if self.waterType is 2: pos = base.camera.getPos() render.setShaderInput('time', task.time) mc = base.camera.getMat( ) self.water.changeCameraPos(pos,mc) self.water.changeCameraPos(pos,mc) #print "Render diagnostics" #render.analyze() #base.cTrav.showCollisions(render) return task.cont def generateWater(self, style): print "Generate Water:", self.waterXMin, self.waterXMax, self.waterYMin, self.waterYMax '''Generates water style 0: blue card style 1: reflective card style 2: reflective card with shaders ''' self.waterHeight = 22.0 if self.water: self.water.removeNode() if style is 0: cm = CardMaker("water") #cm.setFrame(-1, 1, -1, 1) cm.setFrame(self.waterXMin, self.waterXMax, self.waterYMin, self.waterYMax) cm.setColor(0, 0, 1, 0.9) self.water = render.attachNewNode(cm.generate()) if self.waterYMax > self.waterXMax: size = self.waterYMax else: size = self.waterXMax self.water.lookAt(0, 0, -1) self.water.setZ(self.waterHeight) messenger.send('makePickable', [self.water]) elif style is 1: # From Prosoft's super awesome terrain demo cm = CardMaker("water") #cm.setFrame(-1, 1, -1, 1) cm.setFrame(self.waterXMin, self.waterXMax, self.waterYMin, self.waterYMax) self.water = render.attachNewNode(cm.generate()) if self.waterYMax > self.waterXMax: size = self.waterYMax else: size = self.waterXMax #self.water.setScale(size) self.water.lookAt(0, 0, -1) self.water.setZ(self.waterHeight) self.water.setShaderOff(1) self.water.setLightOff(1) self.water.setAlphaScale(0.5) self.water.setTransparency(TransparencyAttrib.MAlpha) wbuffer = base.win.makeTextureBuffer("water", 512, 512) wbuffer.setClearColorActive(True) wbuffer.setClearColor(base.win.getClearColor()) self.wcamera = base.makeCamera(wbuffer) self.wcamera.reparentTo(render) self.wcamera.node().setLens(base.camLens) self.wcamera.node().setCameraMask(BitMask32.bit(1)) self.water.hide(BitMask32.bit(1)) wtexture = wbuffer.getTexture() wtexture.setWrapU(Texture.WMClamp) wtexture.setWrapV(Texture.WMClamp) wtexture.setMinfilter(Texture.FTLinearMipmapLinear) self.wplane = Plane(Vec3(0, 0, 1), Point3(0, 0, self.water.getZ())) wplanenp = render.attachNewNode(PlaneNode("water", self.wplane)) tmpnp = NodePath("StateInitializer") tmpnp.setClipPlane(wplanenp) tmpnp.setAttrib(CullFaceAttrib.makeReverse()) self.wcamera.node().setInitialState(tmpnp.getState()) self.water.projectTexture(TextureStage("reflection"), wtexture, self.wcamera) messenger.send('makePickable', [self.water]) elif style is 2: # From Clcheung just as super awesome demomaster self.water_level = Vec4(0.0, 0.0, self.waterHeight, 1.0) self.water = water.WaterNode(self.waterXMin, self.waterYMin, self.waterXMax, self.waterYMax, self.water_level.getZ()) self.water.setStandardControl() self.water.changeParams(None) wl=self.water_level wl.setZ(wl.getZ()-0.05) #root.setShaderInput('waterlevel', self.water_level) render.setShaderInput('time', 0) messenger.send('makePickable', [self.water.waterNP])
class LeftDrift(ShowBase): """ Show drifting textures forever to left eye. """ def __init__(self, texture_array, texture_angle = 0, mask_angle = 0, position = (0, 0), velocity = 0, window_size = 512, texture_size = 512, bgcolor = (0, 0, 0, 1)): super().__init__() self.texture_array = texture_array self.texture_dtype = type(self.texture_array.flat[0]) self.left_angle = texture_angle self.velocity = velocity self.mask_angle = mask_angle #this will change fairly frequently #Set window title and size self.window_properties = WindowProperties() self.window_properties.setSize(window_size, window_size) self.window_properties.setTitle("LeftDrift") ShowBaseGlobal.base.win.requestProperties(self.window_properties) #base is a panda3d global #CREATE MASKS (right mask for left stim, and vice-versa) self.right_mask = np.ones((texture_size,texture_size), dtype=np.uint8) #was 255* this but for modulate self.right_mask[:, texture_size//2: ] = 0 #texture_size//2:] = 0 #print(self.right_mask) #CREATE TEXTURE STAGES #Stimuli self.grating_texture = Texture("Grating") #T_unsigned_byte self.grating_texture.setup2dTexture(texture_size, texture_size, Texture.T_unsigned_byte, Texture.F_luminance) self.grating_texture.setRamImage(self.texture_array) self.left_texture_stage = TextureStage('grating') #Mask to restrict stimulus to LHS self.right_mask_texture = Texture("right_mask") self.right_mask_texture.setup2dTexture(texture_size, texture_size, Texture.T_unsigned_byte, Texture.F_luminance) self.right_mask_texture.setRamImage(self.right_mask) self.right_mask_stage = TextureStage('right_mask') #CREATE CARDS/SCENEGRAPH cm = CardMaker('left_stim_card') cm.setFrameFullscreenQuad() self.left_texture_card = self.aspect2d.attachNewNode(cm.generate()) self.right_mask_card = self.aspect2d.attachNewNode(cm.generate()) #SET TEXTURE STAGES self.left_texture_card.setTexture(self.left_texture_stage, self.grating_texture) self.right_mask_card.setTexture(self.right_mask_stage, self.right_mask_texture) #BASIC TRANSFORMS to set up angles and position #TEXTURE self.left_texture_card.setScale(np.sqrt(8)) #so it can handle arbitrary rotations and shifts self.left_texture_card.setR(self.left_angle) #MASK #self.right_card.setPos(position[0], 0, position[1]) self.right_mask_card.setScale(np.sqrt(8)) self.right_mask_card.setR(self.mask_angle) self.right_mask_card.setPos(position[0], 0, position[1] ) # ndc2uv(position[0]), ndc2uv(position[1])) self.right_mask_card.setTransparency(TransparencyAttrib.MAlpha) #enable transparency #Combine the two cards (take product of mask and texture) operand = TextureStage.COSrcColor source0 = self.right_mask_stage source1 = self.left_texture_stage self.right_mask_stage.setCombineRgb(TextureStage.CMModulate, source0, operand, source1, operand) #self.left_texture_card.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.M_add)) #self.left_texture_card.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.M_add)) #This puts a marker where the stimulus should end self.title = OnscreenText("x", style = 1, fg = (1,1,1,1), bg = bgcolor, pos = (position[0], position[1]), scale = 0.02) #Add texture move procedure to the task manager, if needed if self.velocity != 0: self.taskMgr.add(self.moveTextureTask, "moveTextureTask") #Procedure to move the texture def moveTextureTask(self, task): new_position = -task.time*self.velocity #not sure why negative self.left_texture_card.setTexPos(self.left_texture_stage, new_position, 0, 0) #u, v, w return Task.cont #as long as this is returned, the taskMgr will continue to call it