class Simulation(Process): """Simulation job.""" def __init__(self, task, params, info_lock, save=False): self.task = task self.params = params self.info_lock = info_lock self.proclabel = None self.scene = None self.bbase = None self.debug_np = None self.cache = None self.floor = None self.cpo = None self.posquat_sz = 7 self.save = save self.start_time = None self.end_time = None self.sim_time = 0 super(Simulation, self).__init__() @contextmanager def _sim_context(self, pcpos): """Sets up the cpo.""" tags = ("shape", ) # Disconnect pcpos from tree. parents = [] for pcpo in pcpos: parents.append(pcpo.getParent()) NodePathCollection(pcpos).wrtReparentTo(self.scene) self.scene.init_tree(tags=tags) # Add the pcpos to the Bullet world. self.bbase.attach(pcpos) yield # Remove the pcpos from the Bullet world. self.bbase.remove(pcpos) self.scene.destroy_tree(tags=tags) # Reassemble tree. for pcpo, parent in zip(pcpos, parents): pcpo.wrtReparentTo(parent) def _add_noise(self, cpos, pcpos, noises): """Adds geometry noise.""" if (noises == 0).all(): return for cpo, noise in zip(cpos, noises): pos = cpo.getPos(self.scene) pos += Point3(*noise) cpo.setPos(self.scene, pos) # Repel. with self._sim_context(pcpos): self.bbase.repel(50) def _prepare_resources(self): """Set up all of the nodes and physics resources.""" # Set up scene. self.scene = SSO("scene") # Physics. self.bbase = BulletBase() self.bbase.init() self.bbase.gravity = self.params["physics"]["gravity"] self.bbase.sim_par = { "size": self.params['simulation']["step_size"], "n_subs": self.params['simulation']['n_substeps'], "size_sub": self.params['simulation']["substep_size"], } def _prepare_scene(self, cpo_path, floor_path, rec_names): """Sets up the current scene.""" def get_name(cpo): return cpo.getName() floor = load_cpo(floor_path) PSOStyler().apply(floor, "floor") floor.reparentTo(self.scene) cpo = load_cpo(cpo_path) cpo.reparentTo(self.scene) self.cache = self.scene.store_tree() self.floor = floor self.cpo = cpo self.scene.init_tree(tags=()) pcpos = self.scene.descendants(type_=PSO) for pcpo in pcpos: pcpo.setCollideMask(BitMask32.allOn()) pcpo.node().setDeactivationEnabled(False) cpos_rec = self._order_cpos(cpo.descendants(type_=PSO, names=rec_names)) return pcpos, cpos_rec def _clean_scene(self): """Clean up the current scene.""" try: self.scene.destroy_tree() except AttributeError: pass try: self.cpo.destroy_tree() except AttributeError: pass self.cache = None self.scene = None self.cpo = None def _clean_resources(self): """Clean up all resources.""" self._clean_scene() try: self.scene.destroy_tree() except AttributeError: pass self.bbase.destroy() def _order_cpos(self, cpos_rec0): names0 = list(self.task["bodies"]) names1 = [cpo.getName() for cpo in cpos_rec0] if names0 != names1: # Names don't match. if sorted(names0) == sorted(names1): # If they're just out of order, fix them. cpos_rec = [cpos_rec0[names1.index(n)] for n in names0] else: # Otherwise. raise CpoMismatchError( "Task bodies and cpos_rec mismatch:\nT: %s\nC: %s" % (", ".join(names0), ", ".join(names1))) else: cpos_rec = cpos_rec0 return cpos_rec def _set_masses(self, kappa, cpos): blocktypes = get_blocktypes(self.cpo) style = get_style(path(self.task["cpo_path"]).dirname().name) styler = PSOStyler() for cpo, blocktype in zip(cpos, blocktypes): styler.apply(cpo, style, blocktype=blocktype, kappa=kappa) def _simulate(self, data, noise, force, kappa, pcpos, rec_cpos, rec_ints): # Get simulation parameters step_size = self.bbase.sim_par["size"] n_substeps = self.bbase.sim_par["n_subs"] # Store pre-noise states read(data[0], rec_cpos) # Add position noise self._add_noise(rec_cpos, pcpos, noise) # Store post-noise states read(data[1], rec_cpos) # Update masses self._set_masses(kappa, rec_cpos) # Set up force function force_dur = self.params['physics']['force_duration'] force_pcpos = [x.node() for x in rec_cpos] force_vecpos = get_force(force['dir'], force['mag']) condition_time = 0. with self._sim_context(pcpos): # Iterative over record intervals. for i, interval in enumerate(rec_ints, start=2): # Step size and number of substeps for this # interval size = interval * step_size n_subs = interval * n_substeps # Set force with appropriate duration. dur = min(size, force_dur - condition_time) if dur > 0.: tforce = (force_pcpos, force_vecpos, dur) else: tforce = None # Simulate physics for this recording interval. self.bbase.step(size, n_subs, force=tforce) condition_time += size # Store the cpos' states. read(data[i], rec_cpos) # Sanity check, to make sure the blocks are all above # the floor if (data[i][..., 2] < 0).any(): mp.util.info("Object z-positions are negative!") self.cache.restore() return condition_time def simulate_all(self): ## Assorted parameters. icpo = self.task["icpo"] conditions = self.task['conditions'] ## Set up the cpo. pcpos, record_cpos = self._prepare_scene(path(self.task["cpo_path"]), path(self.task["floor_path"]), self.task['bodies']) # Determine recording intervals record_intervals = self.task['record_intervals'] # Allocate data storage. alldata = np.zeros(self.task['shape']) # enumerate over the parameters of each condition for icond, cond in enumerate(conditions): (iS, S), (iP, P), (iK, K), (isamp, samp) = cond data = alldata[icond] # shape of noises is (n_sigmas, n_samples, n_objs) noise = self.params['noises'][iS, icpo, isamp] # shape of forces is (n_forces, n_samples, n_objs) force = self.params['forces'][iP, icpo, isamp] self.sim_time += self._simulate(data, noise, force, float(K), pcpos, record_cpos, record_intervals) if self.save: # Write data to file. data_path = path(self.task["data_path"]) if not data_path.dirname().exists(): data_path.dirname().makedirs() np.save(data_path, alldata) # Mark simulation as complete. self.task["complete"] = True self._clean_scene() def print_info(self): self.info_lock.acquire() n_conditions = len(self.task['conditions']) dt = self.end_time - self.start_time avg = timedelta(seconds=(dt.total_seconds() / float(n_conditions))) speedup = 100 * self.sim_time / dt.total_seconds() mp.util.info("-" * 60) mp.util.info("Total sim. time : %s" % str(timedelta(seconds=self.sim_time))) mp.util.info("Total real time : %s" % str(dt)) mp.util.info("Avg. per condition: %s" % str(avg)) mp.util.info("Num conditions : %d" % n_conditions) mp.util.info("Speedup is %.1f%%" % speedup) mp.util.info("-" * 60) sys.stdout.flush() self.info_lock.release() def run(self): """Run one simulation.""" self._prepare_resources() try: self.start_time = datetime.now() self.simulate_all() except KeyboardInterrupt: mp.util.debug("Keyboard interrupt!") sys.exit(100) except Exception as err: mp.util.debug("Error: %s" % err) raise else: self.end_time = datetime.now() # print out information about the task in a thread-safe # manner (so information isn't interleaved across tasks) self.print_info() finally: # Clean simulation resources. self._clean_resources()
class Viewer(ShowBase, object): """ Viewer for SSOs.""" def __init__(self): ShowBase.__init__(self) resize_window = ConfigVariableBool('viewer-resize-window', '#t') if resize_window.getValue(): self.win_size = (800, 800) # Black background self.win.setClearColor((0.0, 0.0, 0.0, 1.0)) # Set up lights. self.lights = NodePath("lights") # Spotlight. Casts shadows. slight = Spotlight("slight") slight.setScene(self.render) slight.setShadowCaster(True, 2**11, 2**11) # Set shadow mask, so we can exclude objects from casting shadows self.shadow_mask = BitMask32.bit(2) slight.setCameraMask(self.shadow_mask) slight.setColor((1.2, 1.2, 1.2, 1.)) slight.getLens().setFov(45) slight.getLens().setNearFar(1, 100) slnp = self.lights.attachNewNode(slight) slnp.setPos((6, 8, 20)) slnp.lookAt(0, 0, 0) self.render.setLight(slnp) # Ambient light. alight = AmbientLight("alight") a = 0.75 alight.setColor((a, a, a, 1.0)) #alight.setColor((0.8, 0.8, 0.8, 1.0)) alnp = self.lights.attachNewNode(alight) self.render.setLight(alnp) self.lights.reparentTo(self.render) # Set auto shading for shadows use_shaders = ConfigVariableBool('viewer-use-shaders', '#t') if use_shaders.getValue(): self.render.setShaderAuto() # Set antialiasing on self.render.setAntialias(AntialiasAttrib.MAuto) # Camera self.camera_rot = self.render.attachNewNode("camera_rot") self.cameras = self.camera_rot.attachNewNode("cameras") self.cameras.setPos(14, 32, 9.) self.look_at = self.render.attachNewNode("look_at") self.look_at.setPos(Point3(2, 0, 1)) self.cameras.lookAt(self.look_at) self.camera.reparentTo(self.cameras) # Adjust the camera's lens lens = PerspectiveLens() self.camLens = lens self.camLens.setNearFar(0.01, 1000.0) setlens = ConfigVariableBool('viewer-set-cam-lens', '#t') if setlens: self.cam.node().setLens(self.camLens) # # Initialize / set variables self.sso = None self.ssos = [] self.cache = None self.scene = SSO("scene") self.scene.reparentTo(self.render) # Key callbacks. self.accept("shift-control-escape", self.exit) self.accept("escape", self.exit) self.accept("0", self.reset_sso) self.accept("arrow_left", self.prev) self.accept("arrow_right", self.next) self.accept("page_down", self.prev, [100]) self.accept("page_up", self.next, [100]) self.accept("f1", self.toggle_debug) self.accept("o", self.physics_once, extraArgs=[1. / 10]) self.accept("i", self.physics_once, extraArgs=[1. / 10000]) # Remove existing keyboard tasks. self.mandatory_events = ("window-event", "async_loader_0", "render-texture-targets-changed", "shift-control-escape") # Task list: name: (key, args) events = { "physics": ("p", ), "repel": ("t", ), "bump": ("f", ), "rotate": ("r", 20), "rotate90": ("h", ), "ss_task": ("s", ), "ssa_task": ("w", ), "bp": ("b", ) } # Add events for key, val in events.iteritems(): call = [key] + list(val[1:]) self.accept(val[0], self.toggle_task, call) # These are the key events that we will never ignore self.permanent_events = self.getAllAccepting() # These are the key events that we will never ignore self.permanent_tasks = [ task.getName() for task in self.taskMgr.getAllTasks() ] self.start_time = -1 self.old_elapsed = 0 @property def win_size(self): """ Returns window size.""" props = WindowProperties(self.win.getProperties()) return props.getXSize(), props.getYSize() @win_size.setter def win_size(self, wh): """ Sets window size.""" props = WindowProperties(self.win.getProperties()) props.setSize(*wh) self.size = wh self.win.requestProperties(props) def toggle_fullscreen(self): """ Toggles fullscreen mode.""" props = WindowProperties(self.win.getProperties()) if props.getFullscreen(): props.setSize(*self.size) props.setFullscreen(False) else: w = self.pipe.getDisplayWidth() h = self.pipe.getDisplayHeight() props.setSize(w, h) props.setFullscreen(True) self.win.requestProperties(props) def _get_screen_size(self): winx = self.win.getXSize() winy = self.win.getYSize() return winx, winy def _convert_coordinate(self, P0): """ Convert 3 coordinates to 2d projection, and 2d coordinates to 3d extrusion.""" P0 = array(P0) proj_mat = get_projection_mat(self.cam) if P0.size == 2: # 2d to 3d. line = extrude(P0, proj_mat) normal = array((0., 0., 1.)) P = plane_intersection(line, array(self.origin), normal) else: # 3d to 2d. P = project(P0, proj_mat) return P def _get_screen_mouse_location(self): """ Gets mouse location in screen coordinates.""" md = self.win.getPointer(0) s2d = array((md.getX(), md.getY())) return s2d def _set_screen_mouse_location(self, s2d): """ Sets mouse location in screen coordinates.""" self.win.movePointer(0, *s2d.astype("i")) def _get_cursor_location(self): """ Return cursor's 2D or 3D location.""" # Mouse's screen coordinates x = self.mouseWatcherNode.getMouseX() y = self.mouseWatcherNode.getMouseY() return self._convert_coordinate((x, y)) def _set_cursor_location(self, p2d): """ Sets cursor location in window coords [-1, 1].""" s2d = ((p2d * array( (1, -1)) + 1.) / 2. * array(self._get_screen_size())) self._set_screen_mouse_location(s2d) def _set_cursor_hidden(self, b): """ Toggle cursor.""" props = WindowProperties() props.setCursorHidden(b) self.win.requestProperties(props) def draw_cursor2d(self, task): """ Draw cursor indicator.""" if getattr(self, "cursor", None) and self.mouseWatcherNode.hasMouse(): res = self._get_screen_size() ar = float(res[0]) / res[1] mx = self.mouseWatcherNode.getMouseX() my = self.mouseWatcherNode.getMouseY() self.cursor.setPos(mx * ar, 0, my) return task.cont # def draw_cursor2d(self, task): # """ Draw cursor indicator.""" # if getattr(self, "cursor", None) and self.mouseWatcherNode.hasMouse(): # mx = self.mouseWatcherNode.getMouseX() # my = self.mouseWatcherNode.getMouseY() # p3d = self._convert_coordinate((mx, my)) # p2d = self._convert_coordinate(p3d)[0].squeeze() # res = self._get_screen_size() # ar = float(res[0]) / res[1] # x = p2d[0] * ar # y = p2d[1] # self.cursor.setPos(x, 0., y) # return task.cont def init_physics(self, bbase): """ Initialize the physics resources.""" self.bbase = bbase self.debug_np = self.render.attachNewNode(self.bbase.setup_debug()) def init_ssos(self, ssos): """ Initialize the ssos.""" GSO.loader = Loader # self.graphicsEngine.getDefaultLoader() # Put all the input ssos into one list. self.ssos = [] for sso in ssos: if not isinstance(sso, NodePath): raise TypeError("Must be NodePath: %s (%s)" % (sso, type(sso))) # Set up the node and its descendants. sso.init_tree(tags=("model", )) self.ssos.append(sso) # Number of ssos. self.n_ssos = len(self.ssos) def init_background(self, bg): """ Initialize the background.""" # Put all the input ssos into one list. if not isinstance(bg, NodePath): raise TypeError("Must be NodePath: %s (%s)" % (bg, type(bg))) GSO.loader = Loader # self.graphicsEngine.getDefaultLoader() bg.init_tree(tags=("model", )) self.background = bg self.background.reparentTo(self.scene) def optimize_camera(self): """ Calculate good camera parameters given the current stim.""" top = self.cameras.getTop() p0 = Point3() p1 = Point3() self.sso.calcTightBounds(p0, p1) shape = p1 - p0 extent = (shape[0], shape[2]) extent = [max(extent)] * 2 center = shape / 2. + p0 # Adjust camera's x-position. self.cameras.setX(top, center[0]) self.cameras.setZ(top, p1[2]) # Compute where camera will point. # look_at = Point3(center[0], self.look_at.getY(), self.look_at.getZ()) # look_at = (center[0], center[1], self.look_at.getZ()) look_at = center origin = Point3(center[0], center[1], p1[2]) displacement = self.cameras.getPos(top) - origin distance = displacement.length() fov = self.cam.node().getLens().getFov() target_ratio = 0.65 dx = extent[0] / 2. / target_ratio / tan(radians(fov[0]) / 2.) dz = extent[1] / 2. / target_ratio / tan(radians(fov[1]) / 2.) dr = max(dx, dz) / distance pos = origin + displacement * dr self.cameras.setPos(top, pos) #BP() # Point camera toward stim. self.look_at.setPos(top, look_at) self.cameras.lookAt(self.look_at) def _load(self, model): """ Wrapper for egg/bam loading.""" node = NodePath(GSO.loader.loadSync(model)) return node def toggle_task(self, taskname, sort=0): """ Toggles taskMgr task 'taskname'.""" if not self.taskMgr.hasTaskNamed(taskname): self.taskMgr.add(getattr(self, taskname), taskname, sort=sort) if taskname == "physics": self.reset_physics() else: self.taskMgr.remove(taskname) def reset_physics(self): """ Resets physics.""" self.start_time = self.taskMgr.globalClock.getFrameTime() self.old_elapsed = 0. def physics(self, task): """ Task: simulate physics.""" # Elapsed time. dt = self._get_elapsed() - self.old_elapsed # Update amount of time simulated so far. self.old_elapsed += dt # Step the physics dt time. size_sub = self.bbase.sim_par["size_sub"] n_subs = int(dt / size_sub) self.bbase.step(dt, n_subs, size_sub) return task.cont def repel(self, task): """ Task: perform repel.""" self.bbase.repel() return task.done def bump(self, task): """ Task: perform bump.""" mag0 = Vec3(0, 0, 1. / self.bbase.sim_par["size"]) * 10. pos = Point3(-1, 0, 0) nodes = self.background.descendants() bodies = [n.node() for n in nodes if n.type_ is BulletRigidBodyNode] for body in bodies: mag = mag0 * body.getMass() print mag body.applyForce(mag, pos) #BP() return task.done def physics_once(self, dt): """ Step the physics dt.""" n_subs = 10 size_sub = dt / n_subs self.bbase.step(dt, n_subs, size_sub) # self.bbase.attenuate_velocities(self.bbase.get_bodies()) def bp(self, task): """ Task: break.""" BP() return task.done def toggle_debug(self): """ Shows/hides debug node.""" if self.debug_np.isHidden(): self.debug_np.show() else: self.debug_np.hide() def rotate(self, task): """ Task: rotate camera.""" H = (self.camera_rot.getH() + 1) % 360 self.camera_rot.setH(H) return task.cont def rotate90(self, task): """ Task: rotate in ticks.""" angs = [15, 105, 195, 285] H = int(self.camera_rot.getH()) if H in angs: self.camera_rot.setH(angs[(angs.index(H) + 1) % len(angs)]) else: self.camera_rot.setH(angs[0]) return task.done def ss_task(self, task): """ Task: Take a screenshot.""" self.screenshot() return task.done def ssa_task(self, task): """ Task: Take a screenshot of every sso.""" self.screenshot(namePrefix=self.sso.getName() + ".jpg", defaultFilename=False) if self.n_ssos - 1 == self.ssos.index(self.sso): return task.done self.next() return task.cont def _expunge_events(self): """ Turn OFF any non-permanent key handlers.""" events = self.getAllAccepting() for event in set(events).difference(self.permanent_events): self.ignore(event) def _expunge_tasks(self): """ Turn OFF any non-permanent tasks floating around.""" tasknames = [task.getName() for task in self.taskMgr.getAllTasks()] for taskname in set(tasknames).difference(self.permanent_tasks): self.taskMgr.remove(taskname) def reset_sso(self): """ Reset to initial scene state.""" self.goto_sso(self.ssos.index(self.sso)) def goto_sso(self, i): """ Switches to the i-th SSO.""" print "SSO %d" % i # Remove existing tasks and events. self._expunge_tasks() self._expunge_events() if getattr(self, "sso", False): # Detach from physical world. self.bbase.remove_all() # Reset its state to the initial one. self.cache.restore() # Detach from scene. self.sso.detachNode() # Set the new sso. self.sso = self.ssos[i] self.sso.reparentTo(self.scene) self.cache = self.scene.store_tree() self.attach_physics() self.optimize_camera() def attach_physics(self): # Attach `self.scene` to the physics world. self.scene.init_tree(tags=("shape", )) bnodes = self.scene.descendants(type_=PSO) for bnode in bnodes: bnode.setCollideMask(BitMask32.allOn()) bnode.node().setDeactivationEnabled(False) self.bbase.attach(bnodes) def remove_physics(self): # Remove `self.scene` from the physics world. self.bbase.remove(self.scene.descendants(type_=PSO)) self.scene.destroy_tree(tags=("shape", )) def prev(self, steps=1): """ Task: Go back one SSO.""" i = max(0, self.ssos.index(self.sso) - steps) self.goto_sso(i) def next(self, steps=1): """ Task: Go forward one SSO.""" i = min(self.n_ssos - 1, self.ssos.index(self.sso) + steps) self.goto_sso(i) def _get_elapsed(self): """ Gets the time spent in this phase so far.""" # Current time. current_time = self.taskMgr.globalClock.getFrameTime() # Elapsed time in this phase elapsed = current_time - self.start_time return elapsed def run(self): # Start with first sso. self.goto_sso(0) # Call parent's run(). ShowBase.run(self) def exit(self): """ Stuff to do before exiting.""" sys.exit()
class Viewer(ShowBase, object): """ Viewer for SSOs.""" def __init__(self): ShowBase.__init__(self) resize_window = ConfigVariableBool('viewer-resize-window', '#t') if resize_window.getValue(): self.win_size = (800, 800) # Black background self.win.setClearColor((0.0, 0.0, 0.0, 1.0)) # Set up lights. self.lights = NodePath("lights") # Spotlight. Casts shadows. slight = Spotlight("slight") slight.setScene(self.render) slight.setShadowCaster(True, 2 ** 11, 2 ** 11) # Set shadow mask, so we can exclude objects from casting shadows self.shadow_mask = BitMask32.bit(2) slight.setCameraMask(self.shadow_mask) slight.setColor((1.2, 1.2, 1.2, 1.)) slight.getLens().setFov(45) slight.getLens().setNearFar(1, 100) slnp = self.lights.attachNewNode(slight) slnp.setPos((6, 8, 20)) slnp.lookAt(0, 0, 0) self.render.setLight(slnp) # Ambient light. alight = AmbientLight("alight") a = 0.75 alight.setColor((a, a, a, 1.0)) #alight.setColor((0.8, 0.8, 0.8, 1.0)) alnp = self.lights.attachNewNode(alight) self.render.setLight(alnp) self.lights.reparentTo(self.render) # Set auto shading for shadows use_shaders = ConfigVariableBool('viewer-use-shaders', '#t') if use_shaders.getValue(): self.render.setShaderAuto() # Set antialiasing on self.render.setAntialias(AntialiasAttrib.MAuto) # Camera self.camera_rot = self.render.attachNewNode("camera_rot") self.cameras = self.camera_rot.attachNewNode("cameras") self.cameras.setPos(14, 32, 9.) self.look_at = self.render.attachNewNode("look_at") self.look_at.setPos(Point3(2, 0, 1)) self.cameras.lookAt(self.look_at) self.camera.reparentTo(self.cameras) # Adjust the camera's lens lens = PerspectiveLens() self.camLens = lens self.camLens.setNearFar(0.01, 1000.0) setlens = ConfigVariableBool('viewer-set-cam-lens', '#t') if setlens: self.cam.node().setLens(self.camLens) # # Initialize / set variables self.sso = None self.ssos = [] self.cache = None self.scene = SSO("scene") self.scene.reparentTo(self.render) # Key callbacks. self.accept("shift-control-escape", self.exit) self.accept("escape", self.exit) self.accept("0", self.reset_sso) self.accept("arrow_left", self.prev) self.accept("arrow_right", self.next) self.accept("page_down", self.prev, [100]) self.accept("page_up", self.next, [100]) self.accept("f1", self.toggle_debug) self.accept("o", self.physics_once, extraArgs=[1. / 10]) self.accept("i", self.physics_once, extraArgs=[1. / 10000]) # Remove existing keyboard tasks. self.mandatory_events = ("window-event", "async_loader_0", "render-texture-targets-changed", "shift-control-escape") # Task list: name: (key, args) events = {"physics": ("p",), "repel": ("t",), "bump": ("f",), "rotate": ("r", 20), "rotate90": ("h",), "ss_task": ("s",), "ssa_task": ("w",), "bp": ("b",)} # Add events for key, val in events.iteritems(): call = [key] + list(val[1:]) self.accept(val[0], self.toggle_task, call) # These are the key events that we will never ignore self.permanent_events = self.getAllAccepting() # These are the key events that we will never ignore self.permanent_tasks = [task.getName() for task in self.taskMgr.getAllTasks()] self.start_time = -1 self.old_elapsed = 0 @property def win_size(self): """ Returns window size.""" props = WindowProperties(self.win.getProperties()) return props.getXSize(), props.getYSize() @win_size.setter def win_size(self, wh): """ Sets window size.""" props = WindowProperties(self.win.getProperties()) props.setSize(*wh) self.size = wh self.win.requestProperties(props) def toggle_fullscreen(self): """ Toggles fullscreen mode.""" props = WindowProperties(self.win.getProperties()) if props.getFullscreen(): props.setSize(*self.size) props.setFullscreen(False) else: w = self.pipe.getDisplayWidth() h = self.pipe.getDisplayHeight() props.setSize(w, h) props.setFullscreen(True) self.win.requestProperties(props) def _get_screen_size(self): winx = self.win.getXSize() winy = self.win.getYSize() return winx, winy def _convert_coordinate(self, P0): """ Convert 3 coordinates to 2d projection, and 2d coordinates to 3d extrusion.""" P0 = array(P0) proj_mat = get_projection_mat(self.cam) if P0.size == 2: # 2d to 3d. line = extrude(P0, proj_mat) normal = array((0., 0., 1.)) P = plane_intersection(line, array(self.origin), normal) else: # 3d to 2d. P = project(P0, proj_mat) return P def _get_screen_mouse_location(self): """ Gets mouse location in screen coordinates.""" md = self.win.getPointer(0) s2d = array((md.getX(), md.getY())) return s2d def _set_screen_mouse_location(self, s2d): """ Sets mouse location in screen coordinates.""" self.win.movePointer(0, *s2d.astype("i")) def _get_cursor_location(self): """ Return cursor's 2D or 3D location.""" # Mouse's screen coordinates x = self.mouseWatcherNode.getMouseX() y = self.mouseWatcherNode.getMouseY() return self._convert_coordinate((x, y)) def _set_cursor_location(self, p2d): """ Sets cursor location in window coords [-1, 1].""" s2d = ((p2d * array((1, -1)) + 1.) / 2. * array(self._get_screen_size())) self._set_screen_mouse_location(s2d) def _set_cursor_hidden(self, b): """ Toggle cursor.""" props = WindowProperties() props.setCursorHidden(b) self.win.requestProperties(props) def draw_cursor2d(self, task): """ Draw cursor indicator.""" if getattr(self, "cursor", None) and self.mouseWatcherNode.hasMouse(): res = self._get_screen_size() ar = float(res[0]) / res[1] mx = self.mouseWatcherNode.getMouseX() my = self.mouseWatcherNode.getMouseY() self.cursor.setPos(mx * ar, 0, my) return task.cont # def draw_cursor2d(self, task): # """ Draw cursor indicator.""" # if getattr(self, "cursor", None) and self.mouseWatcherNode.hasMouse(): # mx = self.mouseWatcherNode.getMouseX() # my = self.mouseWatcherNode.getMouseY() # p3d = self._convert_coordinate((mx, my)) # p2d = self._convert_coordinate(p3d)[0].squeeze() # res = self._get_screen_size() # ar = float(res[0]) / res[1] # x = p2d[0] * ar # y = p2d[1] # self.cursor.setPos(x, 0., y) # return task.cont def init_physics(self, bbase): """ Initialize the physics resources.""" self.bbase = bbase self.debug_np = self.render.attachNewNode(self.bbase.setup_debug()) def init_ssos(self, ssos): """ Initialize the ssos.""" GSO.loader = Loader # self.graphicsEngine.getDefaultLoader() # Put all the input ssos into one list. self.ssos = [] for sso in ssos: if not isinstance(sso, NodePath): raise TypeError("Must be NodePath: %s (%s)" % (sso, type(sso))) # Set up the node and its descendants. sso.init_tree(tags=("model",)) self.ssos.append(sso) # Number of ssos. self.n_ssos = len(self.ssos) def init_background(self, bg): """ Initialize the background.""" # Put all the input ssos into one list. if not isinstance(bg, NodePath): raise TypeError("Must be NodePath: %s (%s)" % (bg, type(bg))) GSO.loader = Loader # self.graphicsEngine.getDefaultLoader() bg.init_tree(tags=("model",)) self.background = bg self.background.reparentTo(self.scene) def optimize_camera(self): """ Calculate good camera parameters given the current stim.""" top = self.cameras.getTop() p0 = Point3() p1 = Point3() self.sso.calcTightBounds(p0, p1) shape = p1 - p0 extent = (shape[0], shape[2]) extent = [max(extent)] * 2 center = shape / 2. + p0 # Adjust camera's x-position. self.cameras.setX(top, center[0]) self.cameras.setZ(top, p1[2]) # Compute where camera will point. # look_at = Point3(center[0], self.look_at.getY(), self.look_at.getZ()) # look_at = (center[0], center[1], self.look_at.getZ()) look_at = center origin = Point3(center[0], center[1], p1[2]) displacement = self.cameras.getPos(top) - origin distance = displacement.length() fov = self.cam.node().getLens().getFov() target_ratio = 0.65 dx = extent[0] / 2. / target_ratio / tan(radians(fov[0]) / 2.) dz = extent[1] / 2. / target_ratio / tan(radians(fov[1]) / 2.) dr = max(dx, dz) / distance pos = origin + displacement * dr self.cameras.setPos(top, pos) #BP() # Point camera toward stim. self.look_at.setPos(top, look_at) self.cameras.lookAt(self.look_at) def _load(self, model): """ Wrapper for egg/bam loading.""" node = NodePath(GSO.loader.loadSync(model)) return node def toggle_task(self, taskname, sort=0): """ Toggles taskMgr task 'taskname'.""" if not self.taskMgr.hasTaskNamed(taskname): self.taskMgr.add(getattr(self, taskname), taskname, sort=sort) if taskname == "physics": self.reset_physics() else: self.taskMgr.remove(taskname) def reset_physics(self): """ Resets physics.""" self.start_time = self.taskMgr.globalClock.getFrameTime() self.old_elapsed = 0. def physics(self, task): """ Task: simulate physics.""" # Elapsed time. dt = self._get_elapsed() - self.old_elapsed # Update amount of time simulated so far. self.old_elapsed += dt # Step the physics dt time. size_sub = self.bbase.sim_par["size_sub"] n_subs = int(dt / size_sub) self.bbase.step(dt, n_subs, size_sub) return task.cont def repel(self, task): """ Task: perform repel.""" self.bbase.repel() return task.done def bump(self, task): """ Task: perform bump.""" mag0 = Vec3(0, 0, 1. / self.bbase.sim_par["size"]) * 10. pos = Point3(-1, 0, 0) nodes = self.background.descendants() bodies = [n.node() for n in nodes if n.type_ is BulletRigidBodyNode] for body in bodies: mag = mag0 * body.getMass() print mag body.applyForce(mag, pos) #BP() return task.done def physics_once(self, dt): """ Step the physics dt.""" n_subs = 10 size_sub = dt / n_subs self.bbase.step(dt, n_subs, size_sub) # self.bbase.attenuate_velocities(self.bbase.get_bodies()) def bp(self, task): """ Task: break.""" BP() return task.done def toggle_debug(self): """ Shows/hides debug node.""" if self.debug_np.isHidden(): self.debug_np.show() else: self.debug_np.hide() def rotate(self, task): """ Task: rotate camera.""" H = (self.camera_rot.getH() + 1) % 360 self.camera_rot.setH(H) return task.cont def rotate90(self, task): """ Task: rotate in ticks.""" angs = [15, 105, 195, 285] H = int(self.camera_rot.getH()) if H in angs: self.camera_rot.setH(angs[(angs.index(H) + 1) % len(angs)]) else: self.camera_rot.setH(angs[0]) return task.done def ss_task(self, task): """ Task: Take a screenshot.""" self.screenshot() return task.done def ssa_task(self, task): """ Task: Take a screenshot of every sso.""" self.screenshot(namePrefix=self.sso.getName() + ".jpg", defaultFilename=False) if self.n_ssos - 1 == self.ssos.index(self.sso): return task.done self.next() return task.cont def _expunge_events(self): """ Turn OFF any non-permanent key handlers.""" events = self.getAllAccepting() for event in set(events).difference(self.permanent_events): self.ignore(event) def _expunge_tasks(self): """ Turn OFF any non-permanent tasks floating around.""" tasknames = [task.getName() for task in self.taskMgr.getAllTasks()] for taskname in set(tasknames).difference(self.permanent_tasks): self.taskMgr.remove(taskname) def reset_sso(self): """ Reset to initial scene state.""" self.goto_sso(self.ssos.index(self.sso)) def goto_sso(self, i): """ Switches to the i-th SSO.""" print "SSO %d" % i # Remove existing tasks and events. self._expunge_tasks() self._expunge_events() if getattr(self, "sso", False): # Detach from physical world. self.bbase.remove_all() # Reset its state to the initial one. self.cache.restore() # Detach from scene. self.sso.detachNode() # Set the new sso. self.sso = self.ssos[i] self.sso.reparentTo(self.scene) self.cache = self.scene.store_tree() self.attach_physics() self.optimize_camera() def attach_physics(self): # Attach `self.scene` to the physics world. self.scene.init_tree(tags=("shape",)) bnodes = self.scene.descendants(type_=PSO) for bnode in bnodes: bnode.setCollideMask(BitMask32.allOn()) bnode.node().setDeactivationEnabled(False) self.bbase.attach(bnodes) def remove_physics(self): # Remove `self.scene` from the physics world. self.bbase.remove(self.scene.descendants(type_=PSO)) self.scene.destroy_tree(tags=("shape",)) def prev(self, steps=1): """ Task: Go back one SSO.""" i = max(0, self.ssos.index(self.sso) - steps) self.goto_sso(i) def next(self, steps=1): """ Task: Go forward one SSO.""" i = min(self.n_ssos - 1, self.ssos.index(self.sso) + steps) self.goto_sso(i) def _get_elapsed(self): """ Gets the time spent in this phase so far.""" # Current time. current_time = self.taskMgr.globalClock.getFrameTime() # Elapsed time in this phase elapsed = current_time - self.start_time return elapsed def run(self): # Start with first sso. self.goto_sso(0) # Call parent's run(). ShowBase.run(self) def exit(self): """ Stuff to do before exiting.""" sys.exit()