class VTKWidget(Widget): def __init__(self, **kwargs): super(VTKWidget, self).__init__(**kwargs) self.setupVTK() def updateVTK(self, *largs): self.fbo.ask_update() self.canvas.ask_update() def setupVTK(self): Clock.schedule_interval(self.updateVTK, 1 / 1.) with self.canvas: self.fbo = Fbo(size=(512, 512), clear_color=(.3, .3, .3, .8), push_viewport=True, with_depthbuffer=True) self.size = self.fbo.size Color(0, 0, 1) Rectangle(pos=self.pos, size=self.size, texture=self.fbo.texture) Callback(self.drawVTK, reset_context=True) def drawVTK(self, instr): glEnable(GL_DEPTH_TEST) glEnable(GL_CULL_FACE) VTKClock.tick() glViewport(0, 0, 512, 512) self.fbo.clear_buffer() #push GL state of Kivy glUseProgram(0) glPushAttrib(GL_ALL_ATTRIB_BITS) glMatrixMode(GL_PROJECTION) glPushMatrix() glMatrixMode(GL_MODELVIEW) glPushMatrix() glLoadIdentity() renWin.Render() #pop previous state of Kivy glMatrixMode(GL_MODELVIEW) glPopMatrix() glMatrixMode(GL_PROJECTION) glPopMatrix() glPopAttrib()
class VTKWidget(Widget): def __init__(self, **kwargs): super(VTKWidget,self).__init__(**kwargs) self.setupVTK() def updateVTK(self, *largs): self.fbo.ask_update() self.canvas.ask_update() def setupVTK(self): Clock.schedule_interval(self.updateVTK, 1 / 1.) with self.canvas: self.fbo = Fbo(size=(512,512), clear_color=(.3, .3, .3, .8), push_viewport=True, with_depthbuffer=True) self.size = self.fbo.size Color(0, 0, 1) Rectangle(pos=self.pos, size=self.size, texture=self.fbo.texture) Callback(self.drawVTK, reset_context=True) def drawVTK(self, instr): glEnable(GL_DEPTH_TEST) glEnable(GL_CULL_FACE) VTKClock.tick() glViewport(0,0,512,512) self.fbo.clear_buffer() #push GL state of Kivy glUseProgram(0) glPushAttrib(GL_ALL_ATTRIB_BITS) glMatrixMode(GL_PROJECTION) glPushMatrix() glMatrixMode(GL_MODELVIEW) glPushMatrix() glLoadIdentity() renWin.Render() #pop previous state of Kivy glMatrixMode(GL_MODELVIEW) glPopMatrix() glMatrixMode(GL_PROJECTION) glPopMatrix() glPopAttrib()
class Paintbrush(Widget): def __init__(self, **kwargs): super(Paintbrush, self).__init__(**kwargs) self.fbo = Fbo(size=(10, 10)) self.mesh = Mesh() self.points = [] self.vertices = [] self.indices = [] self.line_widths = [] self.cap_vertices_index = 0 self.cap_indices_index = 0 self.mask_lines = [] self.mask_alphas = [] self.canvas = RenderContext() self.canvas.shader.fs = mask_shader self.buffer_container = None self.rgb = (0, 1, 1) # We'll update our glsl variables in a clock Clock.schedule_interval(self.update_glsl, 0) # Maintain a window of history for autocorrelations self.ac_window = [] self.ac_position = 0 self.periodicity_factor = 1.0 def update_glsl(self, *largs): # This is needed for the default vertex shader. self.canvas['projection_mat'] = Window.render_context['projection_mat'] self.canvas['modelview_mat'] = Window.render_context['modelview_mat'] def on_size(self, instance, value): self.canvas.clear() with self.canvas: self.fbo = Fbo(size=value) self.mask_fbo = Fbo(size=(value[0] // 5, value[1] // 5), clear_color=(1, 1, 1, 1)) Color(*self.rgb, 0.9) BindTexture(texture=self.mask_fbo.texture, index=1) self.buffer_container = Rectangle(pos=self.pos, size=value, texture=self.fbo.texture) #Rectangle(pos=self.pos, size=value, texture=self.mask_fbo.texture) self.canvas['texture1'] = 1 with self.fbo: Color(1, 1, 1) self.mesh = Mesh(mode='triangle_strip') def on_pos(self, instance, value): if not self.buffer_container: return self.buffer_container.pos = value def build_line_segment(self, start, end, future, start_width=8.0, end_width=8.0): """Builds a line segment knowing the start and end, as well as one point in the future.""" start = np.array([start[0], start[1]]) end = np.array([end[0], end[1]]) future = np.array([future[0], future[1]]) length = np.linalg.norm(end - start) num_interpolants = max(int(length / DISTANCE_PER_POINT), 3) normal = (end - start) / length * start_width / 2.0 normal = np.array([-normal[1], normal[0]]) end_normal = (future - end) / max(np.linalg.norm(future - end), 0.1) * end_width / 2.0 end_normal = np.array([-end_normal[1], end_normal[0]]) delta_sign = None # if (self.last_normal is not None and # np.linalg.norm(normal - self.last_normal) > np.linalg.norm(normal + self.last_normal)): # self.last_normal *= -1 # Add points deviating in alternating directions around the actual path for i in range(num_interpolants): path_point = start + (i / num_interpolants) * (end - start) delta = normal + (i / num_interpolants) * (end_normal - normal) if delta_sign is None: delta_sign = 1 if len(self.points) > 3: second_last_vertex = np.array(self.vertices[-8:-6]) option_1 = path_point + delta option_2 = path_point - delta if (np.linalg.norm(option_2 - second_last_vertex) < np.linalg.norm(option_1 - second_last_vertex)): delta_sign *= -1 self.vertices.extend([*(path_point + delta * delta_sign), 0, 0]) self.indices.append(len(self.indices)) delta_sign *= -1 def add_cap(self, width): """Adds a round line cap to the end of the vertex/index list.""" self.cap_vertices_index = len(self.vertices) self.cap_indices_index = len(self.indices) if len(self.points) < 3: return # Extend the current line segment using a circular interpolation of line widths start = np.array([self.points[-1][0], self.points[-1][1]]) prev = np.array([self.points[-2][0], self.points[-2][1]]) end = start + (start - prev) / max(np.linalg.norm(start - prev), 0.001) * width / 2.0 length = np.linalg.norm(end - start) num_interpolants = max(int(length / DISTANCE_PER_POINT) * 2, 3) normal = (end - start) / length * width / 2.0 normal = np.array([-normal[1], normal[0]]) end_normal = np.zeros(2) delta_sign = None # Add points deviating in alternating directions around the actual path for i in range(num_interpolants): path_point = start + (i / (num_interpolants - 1)) * (end - start) circ_progress = 1 - np.sqrt(1 - (i / (num_interpolants - 1)) ** 2) delta = normal + circ_progress * (end_normal - normal) if delta_sign is None: delta_sign = 1 if len(self.points) > 3: second_last_vertex = np.array(self.vertices[-8:-6]) option_1 = path_point + delta option_2 = path_point - delta if (np.linalg.norm(option_2 - second_last_vertex) < np.linalg.norm(option_1 - second_last_vertex)): delta_sign *= -1 self.vertices.extend([*(path_point + delta * delta_sign), 0, 0]) self.indices.append(len(self.indices)) delta_sign *= -1 def remove_cap(self): """Removes a cap on the line.""" if self.cap_vertices_index > 0 and self.cap_vertices_index <= len(self.vertices): del self.vertices[self.cap_vertices_index:] del self.indices[self.cap_indices_index:] self.cap_vertices_index = 0 self.cap_indices_index = 0 def current_line_width(self, depth, window=5): """Computes the current line width of the previous `window` points.""" max_width = 120.0 min_width = 5.0 min_dist = 40.0 max_dist = 140.0 last_point = self.points[-1] old_point = self.points[max(0, len(self.points) - window)] if PAINTBRUSH_MODE == 0: dist = np.linalg.norm(np.array([last_point[0], last_point[1]]) - np.array([old_point[0], old_point[1]])) else: dist = 120.0 width = (max_dist - dist) * (max_width * 0.8 - min_width) / (max_dist - min_dist) if PAINTBRUSH_MODE != 0: depth_factor = 1 / (1 + np.exp(-(depth - 0.5) * 4)) width *= depth_factor if PAINTBRUSH_MODE == 2: width *= self.periodicity_factor return np.clip(width, min_width, max_width) def update_periodicity(self, point): """Computes a new autocorrelation magnitude by adding the given point.""" self.ac_window.append(point) if len(self.ac_window) > AUTOCORRELATION_WINDOW: del self.ac_window[0] self.ac_position += 1 if self.ac_position % 8 == 0 and len(self.ac_window) == AUTOCORRELATION_WINDOW: ac_window = np.array(self.ac_window) x_fft = np.abs(np.fft.rfft(ac_window[:,0] * np.hanning(AUTOCORRELATION_WINDOW))) y_fft = np.abs(np.fft.rfft(ac_window[:,1] * np.hanning(AUTOCORRELATION_WINDOW))) x_fft = x_fft[4:20] / np.mean(x_fft[4:20]) y_fft = y_fft[4:20] / np.mean(y_fft[4:20]) # if self.ac_position > 200: # plt.figure() # plt.subplot(121) # plt.plot(ac_window[:,0], ac_window[:,1]) # plt.subplot(122) # plt.plot(x_fft, label='x') # plt.plot(y_fft, label='y') # plt.show() self.periodicity_factor = ((max(1.0, np.max(x_fft) / 4.0) * max(1.0, np.max(y_fft) / 4.0)) - 1) ** 2 + 1 def add_point(self, point, depth=None, width=None, alpha=None): """ point: a point in window space to add to the paintbrush trajectory (x, y). depth: a 0-1 value indicating the depth into the screen of the current point. alpha: a manual 0-1 alpha level for this point. Returns the current line width. """ point = (point[0] - self.pos[0], point[1] - self.pos[1]) self.points.append(point) # Build a segment of line line_width = 0 if len(self.points) > 2: self.update_periodicity(point) line_width = self.current_line_width(depth) if depth is not None else width old_line_width = (sum(self.line_widths) / len(self.line_widths) if self.line_widths else line_width) self.line_widths.append(line_width) if len(self.line_widths) > LINE_WIDTH_WINDOW: self.line_widths.pop(0) if width is None: line_width = sum(self.line_widths) / len(self.line_widths) # Clamp the amount by which the line width can change - results in # smoother lines # line_width = old_line_width + np.clip(line_width - old_line_width, -2.0, 2.0) self.remove_cap() self.build_line_segment(*self.points[-3:], old_line_width, line_width) self.add_cap(line_width) # Update mask if len(self.points) % MASK_INTERVAL == 0 and len(self.points) > MASK_INTERVAL: self.mask_lines.append(Line(points=(self.points[-MASK_INTERVAL - 1][0] / 5, self.points[-MASK_INTERVAL - 1][1] /5 , self.points[-1][0] / 5, self.points[-1][1] / 5), width=(line_width + 8.0) / 10)) if alpha is not None: self.mask_alphas.append(alpha) with self.mask_fbo: self.mask_fbo.clear() self.mask_fbo.clear_buffer() if len(self.mask_alphas) == len(self.mask_lines): white_values = self.mask_alphas else: white_values = 1 / (1 + np.exp(-((np.arange(len(self.mask_lines)) - len(self.mask_lines)) / FADE_FACTOR + 3))) white_values = white_values * (1 - BASE_FADE) + BASE_FADE for i, (white, line) in enumerate(zip(white_values, self.mask_lines)): Color(white, white, white, 1) self.mask_fbo.add(line) # if len(self.points) % 100 == 20: # plt.figure() # plt.plot(self.vertices[::4], self.vertices[1::4]) # plt.plot(self.vertices[::4], self.vertices[1::4], 'b.') # plt.plot([x[0] for x in self.points], [x[1] for x in self.points], 'ro') # plt.plot([x[0] for x in self.points], [x[1] for x in self.points], 'r-') # plt.show() # self.vertices.extend([point[0], point[1], 0, 0]) # if len(self.points) > 1: # self.indices.extend([len(self.points) - 2, len(self.points) - 1]) self.mesh.vertices = self.vertices self.mesh.indices = self.indices return line_width def clear(self): self.points = [] self.vertices = [] self.indices = [] self.mesh.vertices = [] self.mesh.indices = [] self.periodicity_factor = 1.0 self.ac_window = [] self.ac_position = 0 with self.fbo: self.fbo.clear_buffer() self.mask_lines = [] self.mask_colors = [] with self.mask_fbo: self.mask_fbo.clear() self.mask_fbo.clear_buffer() self.on_size(self, self.size)
class PandaView(Widget): cam_pos = ListProperty([0, 0, 0]) cam_lookat = ListProperty([0, 0, 0]) def __init__(self, **kwargs): super(PandaView, self).__init__(**kwargs) self.setup_panda() def load_model(self, filename): return self.msb.load_model(filename) def update_panda(self, *largs): self.fbo.ask_update() self.canvas.ask_update() def setup_panda(self, *largs): self.msb = ModelShowbase() self.msb.camLens.setFov(52.0) self.msb.camLens.setNearFar(1.0, 10000.0) with self.canvas: self.fbo = Fbo(size=self.size, clear_color=(.3, .3, .3, .2)) Color(1, 1, 1) self.viewport = Rectangle(pos=self.pos, size=self.size, texture=self.fbo.texture) Callback(self.draw_panda, reset_context=True) Clock.schedule_interval(self.update_panda, 1 / 60.) def draw_panda(self, instr): self.fbo.clear_buffer() glEnable(GL_DEPTH_TEST) glEnable(GL_CULL_FACE) self.msb.taskMgr.step() self.msb.camera.setPos(Point3(*self.cam_pos)) self.msb.camera.lookAt(Point3(*self.cam_lookat), Vec3(0, 0, 1)) def toggle_show_collision_boxes(self): ''' Debug visualization: Toggle drawing of collision nodes. Off by default. ''' # See http://www.panda3d.org/apiref.php?page=NodePath matches = self.msb.render.findAllMatches('**/+CollisionNode') if self._show_collision_boxes: matches.hide() else: matches.show() self._show_collision_boxes = not self._show_collision_boxes return self._show_collision_boxes def set_show_collision_boxes(self, value): if self._show_collision_boxes and value: return if not self._show_collision_boxes and not value: return self.toggle_show_collision_boxes() def to_panda_window(self, x, y): ''' Convert Kivy window coordinates to panda window coordinates. The panda window coordinate system's origin is in the middle of the window, with the x-axis going positively to the right and the y axis pointing positively up, where coordinates in all directions are normalized to a float of maximum value 1. (i.e., top right corner is (1., 1.)). ''' w, h = self.size w2 = w / 2. h2 = h / 2. x = (x - w2) / w2 y = (y - h2) / h2 return x, y def on_size(self, *args): self.fbo.size = self.size self.viewport.size = self.size props = WindowProperties() props.setSize(LVector2i(*self.size)) self.msb.win.requestProperties(props)
class PandaView(Widget): cam_pos = ListProperty([0, 0, 0]) cam_lookat = ListProperty([0, 0, 0]) def __init__(self, **kwargs): super(PandaView, self).__init__(**kwargs) self.setup_panda() def load_model(self, filename): return self.msb.load_model(filename) def update_panda(self, *largs): self.fbo.ask_update() self.canvas.ask_update() def setup_panda(self, *largs): self.msb = ModelShowbase() self.msb.camLens.setFov(52.0) self.msb.camLens.setNearFar(1.0, 10000.0) with self.canvas: self.fbo = Fbo(size=self.size, clear_color=(0.3, 0.3, 0.3, 0.2)) Color(1, 1, 1) self.viewport = Rectangle(pos=self.pos, size=self.size, texture=self.fbo.texture) Callback(self.draw_panda, reset_context=True) Clock.schedule_interval(self.update_panda, 1 / 60.0) def draw_panda(self, instr): self.fbo.clear_buffer() glEnable(GL_DEPTH_TEST) glEnable(GL_CULL_FACE) self.msb.taskMgr.step() self.msb.camera.setPos(Point3(*self.cam_pos)) self.msb.camera.lookAt(Point3(*self.cam_lookat), Vec3(0, 0, 1)) def toggle_show_collision_boxes(self): """ Debug visualization: Toggle drawing of collision nodes. Off by default. """ # See http://www.panda3d.org/apiref.php?page=NodePath matches = self.msb.render.findAllMatches("**/+CollisionNode") if self._show_collision_boxes: matches.hide() else: matches.show() self._show_collision_boxes = not self._show_collision_boxes return self._show_collision_boxes def set_show_collision_boxes(self, value): if self._show_collision_boxes and value: return if not self._show_collision_boxes and not value: return self.toggle_show_collision_boxes() def to_panda_window(self, x, y): """ Convert Kivy window coordinates to panda window coordinates. The panda window coordinate system's origin is in the middle of the window, with the x-axis going positively to the right and the y axis pointing positively up, where coordinates in all directions are normalized to a float of maximum value 1. (i.e., top right corner is (1., 1.)). """ w, h = self.size w2 = w / 2.0 h2 = h / 2.0 x = (x - w2) / w2 y = (y - h2) / h2 return x, y def on_size(self, *args): self.fbo.size = self.size self.viewport.size = self.size props = WindowProperties() props.setSize(LVector2i(*self.size)) self.msb.win.requestProperties(props)
class EffectWidget(FloatLayout): fs = StringProperty(None) # Texture of the final Fbo texture = ObjectProperty(None) # Rectangle clearing Fbo fbo_rectangle = ObjectProperty(None) # List of effect strings effects = ListProperty([]) # One extra Fbo for each effect fbo_list = ListProperty([]) effect_mask = None motion_effect = None def __init__(self, **kwargs): # Make sure opengl context exists EventLoop.ensure_window() self.mask_effect = kwargs.get("mask_effect", None) self.motion_effect = kwargs.get("motion_effect", None) self.canvas = RenderContext(use_parent_projection=True, use_parent_modelview=True) self.size = C_SIZE with self.canvas: #self._viewport = Rectangle(size=(800,600), pos=self.pos) self.fbo = Fbo(size=C_SIZE, with_depthbuffer=True, compute_normal_mat=True, clear_color=(0.3, 0.3, 0.7, 1)) with self.fbo.before: #Rectangle(size=(800, 600)) PushMatrix() self.fbo_translation = Translate(-self.x, -self.y, 0) BindTexture(texture=self.mask_effect.texture, index=4) BindTexture(texture=self.motion_effect.texture, index=5) with self.fbo: Color(0, 0, 0) BindTexture(texture=self.mask_effect.texture, index=4) BindTexture(texture=self.motion_effect.texture, index=5) self.fbo_rectangle = Rectangle(size=C_SIZE) self._instructions = InstructionGroup() with self.fbo.after: PopMatrix() self.cbs = Callback(self.reset_gl_context) super(EffectWidget, self).__init__() self.size = C_SIZE Clock.schedule_interval(self.update_glsl, 0) self._instructions.add(Callback(self.setup_gl_context)) self.refresh_fbo_setup() Clock.schedule_interval(self.update_fbos, 0) def on_pos(self, *args): self.fbo_translation.x = -self.x self.fbo_translation.y = -self.y def on_size(self, instance, value): self.fbo.size = C_SIZE self.fbo_rectangle.size = C_SIZE self.refresh_fbo_setup() #self._viewport.texture = self.fbo.texture #self._viewport.size = value def setup_gl_context(self, *args): glEnable(GL_DEPTH_TEST) self.fbo.clear_buffer() #for fbo in self.fbo_list: # fbo.clear_buffer() def reset_gl_context(self, *args): glDisable(GL_DEPTH_TEST) def update_glsl(self, *largs): time = Clock.get_boottime() resolution = [float(size) for size in C_SIZE] self.canvas['time'] = time self.canvas['resolution'] = resolution self.canvas['texture4'] = 4 self.canvas['texture5'] = 5 for fbo in self.fbo_list: fbo['time'] = time fbo['resolution'] = resolution fbo['texture4'] = 4 fbo['texture5'] = 5 def on_effects(self, *args): self.refresh_fbo_setup() def update_fbos(self, *args): for fbo in self.fbo_list: fbo.ask_update() def refresh_fbo_setup(self, *args): # Add/remove fbos until there is one per effect while len(self.fbo_list) < len(self.effects): with self.canvas: new_fbo = EffectFbo(size=C_SIZE) with new_fbo: Color(1, 1, 1, 1) new_fbo.texture_rectangle = Rectangle(size=C_SIZE) new_fbo.texture_rectangle.size = C_SIZE self.fbo_list.append(new_fbo) while len(self.fbo_list) > len(self.effects): old_fbo = self.fbo_list.pop() self.canvas.remove(old_fbo) # Do resizing etc. self.fbo.size = C_SIZE self.fbo_rectangle.size = C_SIZE for i in range(len(self.fbo_list)): self.fbo_list[i].size = C_SIZE self.fbo_list[i].texture_rectangle.size = C_SIZE # If there are no effects, just draw our main fbo if len(self.fbo_list) == 0: self.texture = self.fbo.texture return for i in range(1, len(self.fbo_list)): fbo = self.fbo_list[i] fbo.texture_rectangle.texture = self.fbo_list[i - 1].texture for effect, fbo in zip(self.effects, self.fbo_list): fbo.set_fs(shader_header + shader_uniforms + effect + shader_footer_effect) self.fbo_list[0].texture_rectangle.texture = self.fbo.texture self.texture = self.fbo_list[-1].texture def on_fs(self, instance, value): # set the fragment shader to our source code shader = self.canvas.shader old_value = shader.fs shader.fs = value if not shader.success: shader.fs = old_value raise Exception('failed') def add_widget(self, widget): # Add the widget to our Fbo instead of the normal canvas c = self.canvas self.canvas = self.fbo super(EffectWidget, self).add_widget(widget) #self._instructions.add(widget.canvas) self.canvas = c def remove_widget(self, widget): # Remove the widget from our Fbo instead of the normal canvas c = self.canvas self.canvas = self.fbo super(EffectWidget, self).remove_widget(widget) self.canvas = c def clear_widgets(self, children=None): # Clear widgets from our Fbo instead of the normal canvas c = self.canvas self.canvas = self.fbo super(EffectWidget, self).clear_widgets(children) self.canvas = c
class EffectWidget(FloatLayout): fs = StringProperty(None) # Texture of the final Fbo texture = ObjectProperty(None) # Rectangle clearing Fbo fbo_rectangle = ObjectProperty(None) # List of effect strings effects = ListProperty([]) # One extra Fbo for each effect fbo_list = ListProperty([]) effect_mask = None motion_effect = None def __init__(self, **kwargs): # Make sure opengl context exists EventLoop.ensure_window() self.mask_effect = kwargs.get("mask_effect", None) self.motion_effect = kwargs.get("motion_effect", None) self.canvas = RenderContext(use_parent_projection=True, use_parent_modelview=True) self.size = C_SIZE with self.canvas: #self._viewport = Rectangle(size=(800,600), pos=self.pos) self.fbo = Fbo(size=C_SIZE, with_depthbuffer=True, compute_normal_mat=True, clear_color=(0.3, 0.3, 0.7, 1)) with self.fbo.before: #Rectangle(size=(800, 600)) PushMatrix() self.fbo_translation = Translate(-self.x, -self.y, 0) BindTexture(texture=self.mask_effect.texture, index=4) BindTexture(texture=self.motion_effect.texture, index=5) with self.fbo: Color(0, 0, 0) BindTexture(texture=self.mask_effect.texture, index=4) BindTexture(texture=self.motion_effect.texture, index=5) self.fbo_rectangle = Rectangle(size=C_SIZE) self._instructions = InstructionGroup() with self.fbo.after: PopMatrix() self.cbs = Callback(self.reset_gl_context) super(EffectWidget, self).__init__(**kwargs) self.size = C_SIZE Clock.schedule_interval(self.update_glsl, 0) self._instructions.add(Callback(self.setup_gl_context)) self.refresh_fbo_setup() Clock.schedule_interval(self.update_fbos, 0) def on_pos(self, *args): self.fbo_translation.x = -self.x self.fbo_translation.y = -self.y def on_size(self, instance, value): self.fbo.size = C_SIZE self.fbo_rectangle.size = C_SIZE self.refresh_fbo_setup() #self._viewport.texture = self.fbo.texture #self._viewport.size = value def setup_gl_context(self, *args): glEnable(GL_DEPTH_TEST) self.fbo.clear_buffer() #for fbo in self.fbo_list: # fbo.clear_buffer() def reset_gl_context(self, *args): glDisable(GL_DEPTH_TEST) def update_glsl(self, *largs): time = Clock.get_boottime() resolution = [float(size) for size in C_SIZE] self.canvas['time'] = time self.canvas['resolution'] = resolution self.canvas['texture4'] = 4 self.canvas['texture5'] = 5 for fbo in self.fbo_list: fbo['time'] = time fbo['resolution'] = resolution fbo['texture4'] = 4 fbo['texture5'] = 5 def on_effects(self, *args): self.refresh_fbo_setup() def update_fbos(self, *args): for fbo in self.fbo_list: fbo.ask_update() def refresh_fbo_setup(self, *args): # Add/remove fbos until there is one per effect while len(self.fbo_list) < len(self.effects): with self.canvas: new_fbo = EffectFbo(size=C_SIZE) with new_fbo: Color(1, 1, 1, 1) new_fbo.texture_rectangle = Rectangle( size=C_SIZE) new_fbo.texture_rectangle.size = C_SIZE self.fbo_list.append(new_fbo) while len(self.fbo_list) > len(self.effects): old_fbo = self.fbo_list.pop() self.canvas.remove(old_fbo) # Do resizing etc. self.fbo.size = C_SIZE self.fbo_rectangle.size = C_SIZE for i in range(len(self.fbo_list)): self.fbo_list[i].size = C_SIZE self.fbo_list[i].texture_rectangle.size = C_SIZE # If there are no effects, just draw our main fbo if len(self.fbo_list) == 0: self.texture = self.fbo.texture return for i in range(1, len(self.fbo_list)): fbo = self.fbo_list[i] fbo.texture_rectangle.texture = self.fbo_list[i - 1].texture for effect, fbo in zip(self.effects, self.fbo_list): fbo.set_fs(shader_header + shader_uniforms + effect + shader_footer_effect) self.fbo_list[0].texture_rectangle.texture = self.fbo.texture self.texture = self.fbo_list[-1].texture def on_fs(self, instance, value): # set the fragment shader to our source code shader = self.canvas.shader old_value = shader.fs shader.fs = value if not shader.success: shader.fs = old_value raise Exception('failed') def add_widget(self, widget): # Add the widget to our Fbo instead of the normal canvas c = self.canvas self.canvas = self.fbo super(EffectWidget, self).add_widget(widget) #self._instructions.add(widget.canvas) self.canvas = c def remove_widget(self, widget): # Remove the widget from our Fbo instead of the normal canvas c = self.canvas self.canvas = self.fbo super(EffectWidget, self).remove_widget(widget) self.canvas = c def clear_widgets(self, children=None): # Clear widgets from our Fbo instead of the normal canvas c = self.canvas self.canvas = self.fbo super(EffectWidget, self).clear_widgets(children) self.canvas = c
class PandaView(Widget): cam_pos = ListProperty([0, 0, 0]) cam_lookat = ListProperty([0, 0, 0]) node = ObjectProperty(None) angle = NumericProperty(0) radius = NumericProperty(0) obj_z = NumericProperty(0) sources = ListProperty([]) nodes = ListProperty([]) obj_pos = ListProperty([]) def __init__(self, **kwargs): super(PandaView, self).__init__(**kwargs) self.setup_panda() trigger = Clock.create_trigger(self.update_obj_pos) self.bind( radius=trigger, obj_z=trigger, ) def on_sources(self, instance, values): self.node.remove_all_children() return self.load_models(values) def load_models(self, filenames): models = [] for i, path in enumerate(filenames): model = self.msb.load_model(path) model.reparent_to(self.node) models.append(model) self.nodes = models return models def update_panda(self, *largs): self.canvas.ask_update() def setup_panda(self, *largs): self.msb = msb = ModelShowbase() msb.camLens.setFov(52.0) msb.camLens.setNearFar(1.0, 10000.0) self.node = Node(node=msb.render.attachNewNode('PandaView')) with self.canvas: self.fbo = Fbo(size=self.size) self.viewport = Rectangle( pos=self.pos, size=self.size, ) Callback(self.draw_panda, reset_context=True) Clock.schedule_interval(self.update_panda, 1 / 60.) def draw_panda(self, instr): self.fbo.clear_buffer() glEnable(GL_DEPTH_TEST) glEnable(GL_CULL_FACE) self.msb.taskMgr.step() self.msb.camera.setPos(Point3(*self.cam_pos)) self.msb.camera.lookAt(Point3(*self.cam_lookat), Vec3(0, 0, 1)) def toggle_show_collision_boxes(self): ''' Debug visualization: Toggle drawing of collision nodes. Off by default. ''' # See http://www.panda3d.org/apiref.php?page=NodePath matches = self.msb.render.findAllMatches('**/+CollisionNode') if self._show_collision_boxes: matches.hide() else: matches.show() self._show_collision_boxes = not self._show_collision_boxes return self._show_collision_boxes def set_show_collision_boxes(self, value): if self._show_collision_boxes and value: return if not self._show_collision_boxes and not value: return self.toggle_show_collision_boxes() def to_panda_window(self, x, y): ''' Convert Kivy window coordinates to panda window coordinates. The panda window coordinate system's origin is in the middle of the window, with the x-axis going positively to the right and the y axis pointing positively up, where coordinates in all directions are normalized to a float of maximum value 1. (i.e., top right corner is (1., 1.)). ''' w, h = self.size w2 = w / 2. h2 = h / 2. x = (x - w2) / w2 y = (y - h2) / h2 return x, y def on_size(self, instance, value): self.fbo.size = value self.viewport.size = value def on_pos(self, instance, value): self.viewport.pos = value def on_obj_pos(self, instance, values): for index, pos in enumerate(values): self.nodes[index].pos = pos def on_angle(self, instance, angle): self.node.h = angle * -360. + 180 for node in self.nodes: node.h = angle * -360. def update_obj_pos(self, *args): self.obj_pos = ( ( sin(-2. / 3 * pi) * self.radius, cos(-2. / 3 * pi) * self.radius, self.obj_z, ), ( 0, self.radius, self.obj_z, ), ( sin(2. / 3 * pi) * self.radius, cos(2. / 3 * pi) * self.radius, self.obj_z, ), )