| 1 | import datetime |
| 2 | from logging import debug, info |
| 3 | from os import getcwd |
| 4 | from os.path import exists, dirname |
| 5 | from panda3d.core import get_model_path, AntialiasAttrib, PandaNode, \ |
| 6 | LightRampAttrib, Camera, OrthographicLens, NodePath, OmniBoundingVolume, \ |
| 7 | AmbientLight as P3DAmbientLight, Spotlight as P3DSpotlight, Point2, \ |
| 8 | Point3, Texture |
| 9 | from direct.filter.CommonFilters import CommonFilters |
| 10 | from direct.actor.Actor import Actor |
| 11 | from lib.lib.p3d.p3d import LibP3d |
| 12 | |
| 13 | |
| 14 | class RenderToTexture: |
| 15 | |
| 16 | def __init__(self, size=(256, 256)): |
| 17 | self.__set_buffer(size) |
| 18 | self.__set_display_region() |
| 19 | self.__set_camera() |
| 20 | self.__set_root() |
| 21 | self.display_region.set_camera(self.camera) |
| 22 | |
| 23 | def __set_buffer(self, size): |
| 24 | self.buffer = base.win.make_texture_buffer('result buffer', size[0], |
| 25 | size[1]) |
| 26 | self.buffer.set_sort(-100) |
| 27 | |
| 28 | def __set_display_region(self): |
| 29 | self.display_region = self.buffer.make_display_region() |
| 30 | self.display_region.set_sort(20) |
| 31 | |
| 32 | def __set_camera(self): |
| 33 | self.camera = NodePath(Camera('camera 2d')) |
| 34 | lens = OrthographicLens() |
| 35 | lens.set_film_size(1, 1) |
| 36 | lens.set_near_far(-1000, 1000) |
| 37 | self.camera.node().set_lens(lens) |
| 38 | |
| 39 | def __set_root(self): |
| 40 | self.root = NodePath('root') |
| 41 | self.root.set_depth_test(False) |
| 42 | self.root.set_depth_write(False) |
| 43 | self.camera.reparent_to(self.root) |
| 44 | |
| 45 | @property |
| 46 | def texture(self): return self.buffer.get_texture() |
| 47 | |
| 48 | def destroy(self): |
| 49 | base.graphicsEngine.remove_window(self.buffer) |
| 50 | if base.win: # if you close the window during a race |
| 51 | base.win.remove_display_region(self.display_region) |
| 52 | list(map(lambda node: node.remove_node(), [self.camera, self.root])) |
| 53 | |
| 54 | |
| 55 | class P3dGfxMgr: |
| 56 | |
| 57 | def __init__(self, model_path, antialiasing, shaders, srgb): |
| 58 | self.root = P3dNode(render) |
| 59 | self.__srgb = srgb |
| 60 | self.callbacks = {} |
| 61 | self.filters = None |
| 62 | get_model_path().append_directory(model_path) |
| 63 | if LibP3d.runtime(): |
| 64 | root_dir = LibP3d.p3dpath(dirname(__file__)) |
| 65 | paths = [root_dir + '/' + model_path, root_dir] |
| 66 | list(map(get_model_path().append_directory, paths)) |
| 67 | render.set_shader_auto() |
| 68 | # render.set_two_sided(True) # it breaks shadows |
| 69 | if antialiasing: render.set_antialias(AntialiasAttrib.MAuto) |
| 70 | if shaders and base.win: |
| 71 | self.filters = CommonFilters(base.win, base.cam) |
| 72 | |
| 73 | def load_model(self, filename, callback=None, anim=None): |
| 74 | ext = '.bam' if exists(filename + '.bam') else '' |
| 75 | if anim: |
| 76 | anim_dct = {'anim': filename + '-Anim' + ext} |
| 77 | node = P3dNode(self.set_srgb(Actor(filename + ext, anim_dct))) |
| 78 | elif callback: |
| 79 | callb = lambda model: callback(P3dNode(self.set_srgb(model))) |
| 80 | node = loader.loadModel(filename + ext, callback=callb) |
| 81 | else: |
| 82 | node = P3dNode(self.set_srgb( |
| 83 | loader.loadModel(LibP3d.p3dpath(filename + ext)))) |
| 84 | return node |
| 85 | |
| 86 | def set_srgb(self, model): |
| 87 | if self.__srgb: |
| 88 | for texture in model.find_all_textures(): |
| 89 | if texture.get_format() in [Texture.F_rgba, Texture.F_rgbm]: |
| 90 | texture.set_format(Texture.F_srgb_alpha) |
| 91 | elif texture.get_format() in [Texture.F_rgb]: |
| 92 | texture.set_format(Texture.F_srgb) |
| 93 | return model |
| 94 | |
| 95 | @staticmethod |
| 96 | def toggle_aa(): |
| 97 | aa_not_none = render.get_antialias() != AntialiasAttrib.MNone |
| 98 | if render.has_antialias() and aa_not_none: |
| 99 | render.clear_antialias() |
| 100 | else: render.set_antialias(AntialiasAttrib.MAuto, 1) |
| 101 | |
| 102 | def set_toon(self): |
| 103 | tmp_node = NodePath(PandaNode('temp node')) |
| 104 | tmp_node.set_attrib(LightRampAttrib.make_single_threshold(.5, .4)) |
| 105 | tmp_node.set_shader_auto() |
| 106 | base.cam.node().set_initial_state(tmp_node.get_state()) |
| 107 | self.filters.set_cartoon_ink(separation=1) |
| 108 | |
| 109 | def set_bloom(self): |
| 110 | if not base.win: return |
| 111 | self.filters.setBloom( |
| 112 | blend=(.3, .4, .3, 0), mintrigger=.6, maxtrigger=1.0, desat=.6, |
| 113 | intensity=1.0, size='medium') |
| 114 | # default: (.3, .4, .3, 0), .6, 1, .6, 1, 'medium' |
| 115 | |
| 116 | @staticmethod |
| 117 | def pos2d(node): |
| 118 | p3d = base.cam.get_relative_point(node.node, Point3(0, 0, 0)) |
| 119 | p2d = Point2() |
| 120 | return p2d if base.camLens.project(p3d, p2d) else None |
| 121 | |
| 122 | @staticmethod |
| 123 | def screen_coord(pos): |
| 124 | new_node = NodePath('temp') |
| 125 | new_node.set_pos(pos) |
| 126 | coord3d = new_node.get_pos(base.cam) |
| 127 | new_node.remove_node() |
| 128 | coord2d = Point2() |
| 129 | base.camLens.project(coord3d, coord2d) |
| 130 | coord_r2d = Point3(coord2d[0], 0, coord2d[1]) |
| 131 | coord_a2d = base.aspect2d.get_relative_point(render2d, coord_r2d) |
| 132 | return coord_a2d[0], coord_a2d[2] |
| 133 | |
| 134 | @staticmethod |
| 135 | def world_from_to(pos): |
| 136 | p_from, p_to = Point3(), Point3() # in camera coordinates |
| 137 | base.camLens.extrude(pos, p_from, p_to) |
| 138 | p_from = render.get_relative_point(base.cam, p_from) # global coords |
| 139 | p_to = render.get_relative_point(base.cam, p_to) # global coords |
| 140 | return p_from, p_to |
| 141 | |
| 142 | @property |
| 143 | def shader_support(self): |
| 144 | return base.win.get_gsg().get_supports_basic_shaders() |
| 145 | |
| 146 | def screenshot(self, path=None): |
| 147 | time = datetime.datetime.now().strftime('%y%m%d%H%M%S') |
| 148 | #res = base.win.save_screenshot(Filename(path or ("yocto%s.png" % time))) |
| 149 | #debug('screenshot %s (%s)' % (path or ("yocto%s.png" % time), res)) |
| 150 | res = base.screenshot(path or ("yocto%s.png" % time), False) |
| 151 | info('screenshot %s (%s; %s)' % (path or ("yocto%s.png" % time), res, getcwd())) |
| 152 | |
| 153 | @staticmethod |
| 154 | def enable_shader(): render.set_shader_auto() |
| 155 | |
| 156 | @staticmethod |
| 157 | def disable_shader(): render.set_shader_off() |
| 158 | |
| 159 | @staticmethod |
| 160 | def print_stats(two_d=True, three_d=True, analyze=True, ls=True): |
| 161 | '''Print graphics stats. They use standard output (from p3d).''' |
| 162 | info = [] |
| 163 | if two_d and analyze: |
| 164 | info +=[('render2d.analyze', base.render2d.analyze)] |
| 165 | if three_d and analyze: |
| 166 | info +=[('render.analyze', base.render.analyze)] |
| 167 | if two_d and ls: |
| 168 | info +=[('render2d.ls', base.render2d.ls)] |
| 169 | if three_d and ls: |
| 170 | info +=[('render.ls', base.render.ls)] |
| 171 | for elm in info: |
| 172 | print('\n\n#####\n%s()' % elm[0]) |
| 173 | elm[1]() |
| 174 | |
| 175 | |
| 176 | class P3dNode: |
| 177 | |
| 178 | def __init__(self, nodepath): |
| 179 | self.nodepath = nodepath |
| 180 | self.node.set_python_tag('libnode', self) |
| 181 | |
| 182 | def set_collide_mask(self, mask): return self.node.set_collide_mask(mask) |
| 183 | def set_x(self, val): return self.node.set_x(val) |
| 184 | def set_y(self, val): return self.node.set_y(val) |
| 185 | def set_z(self, val): return self.node.set_z(val) |
| 186 | def set_hpr(self, val): return self.node.set_hpr(val) |
| 187 | def set_h(self, val): return self.node.set_h(val) |
| 188 | def set_p(self, val): return self.node.set_p(val) |
| 189 | def set_r(self, val): return self.node.set_r(val) |
| 190 | def set_scale(self, val): return self.node.set_scale(val) |
| 191 | def set_transparency(self, val): return self.node.set_transparency(val) |
| 192 | def set_alpha_scale(self, val): return self.node.set_alpha_scale(val) |
| 193 | def set_texture(self, texturestage, texture): |
| 194 | return self.node.set_texture(texturestage, texture) |
| 195 | def has_tag(self, name): return self.node.has_tag(name) |
| 196 | def get_tag(self, name): return self.node.get_tag(name) |
| 197 | def get_python_tag(self, name): return self.node.get_python_tag(name) |
| 198 | def remove_node(self): return self.node.remove_node() |
| 199 | def flatten_strong(self): return self.node.flatten_strong() |
| 200 | def clear_model_nodes(self): return self.node.clear_model_nodes() |
| 201 | def show(self): return self.node.show() |
| 202 | def set_depth_offset(self, val): return self.node.set_depth_offset(val) |
| 203 | def loop(self, val): return self.node.loop(val) |
| 204 | def cleanup(self): return self.node.cleanup() |
| 205 | def write_bam_file(self, fname): return self.node.write_bam_file(fname) |
| 206 | |
| 207 | def attach_node(self, name): |
| 208 | return P3dNode(self.node.attach_new_node(name)) |
| 209 | |
| 210 | def add_shape(self, shape): |
| 211 | return self.node.node().add_shape(shape._mesh_shape) |
| 212 | #TODO: don't access a protected member |
| 213 | |
| 214 | @property |
| 215 | def name(self): return self.node.get_name() |
| 216 | |
| 217 | @property |
| 218 | def node(self): return self.nodepath |
| 219 | |
| 220 | @property |
| 221 | def p3dnode(self): return self.node.node() |
| 222 | |
| 223 | def set_pos(self, pos): return self.node.set_pos(pos._vec) |
| 224 | #TODO: don't access a protected member |
| 225 | |
| 226 | def get_pos(self, other=None): |
| 227 | return self.node.get_pos(* [] if other is None else [other.node]) |
| 228 | |
| 229 | @property |
| 230 | def x(self): return self.node.get_x() |
| 231 | |
| 232 | @property |
| 233 | def y(self): return self.node.get_y() |
| 234 | |
| 235 | @property |
| 236 | def z(self): return self.node.get_z() |
| 237 | |
| 238 | @property |
| 239 | def hpr(self): return self.node.get_hpr() |
| 240 | |
| 241 | @property |
| 242 | def h(self): return self.node.get_h() |
| 243 | |
| 244 | @property |
| 245 | def p(self): return self.node.get_p() |
| 246 | |
| 247 | @property |
| 248 | def r(self): return self.node.get_r() |
| 249 | |
| 250 | @property |
| 251 | def scale(self): return self.node.get_scale() |
| 252 | |
| 253 | @property |
| 254 | def is_empty(self): return self.node.is_empty() |
| 255 | |
| 256 | def get_relative_vector(self, node, vec): |
| 257 | return self.node.get_relative_vector(node.node, vec) |
| 258 | |
| 259 | def set_material(self, mat): return self.node.set_material(mat, 1) |
| 260 | |
| 261 | def set_python_tag(self, name, val): |
| 262 | return self.node.set_python_tag(name, val) |
| 263 | |
| 264 | def get_distance(self, other_node): |
| 265 | return self.node.get_distance(other_node.node) |
| 266 | |
| 267 | def reparent_to(self, parent): return self.node.reparent_to(parent.node) |
| 268 | |
| 269 | def wrt_reparent_to(self, parent): |
| 270 | return self.node.wrt_reparent_to(parent.node) |
| 271 | |
| 272 | @staticmethod |
| 273 | def __get_pandanode(nodepath): |
| 274 | if nodepath.has_python_tag('libnode'): |
| 275 | return nodepath.get_python_tag('libnode') |
| 276 | return P3dNode(nodepath) |
| 277 | |
| 278 | def find_all_matches(self, name): |
| 279 | nodes = self.node.find_all_matches(name) |
| 280 | return [self.__get_pandanode(node) for node in nodes] |
| 281 | |
| 282 | def find(self, name): |
| 283 | model = self.node.find(name) |
| 284 | if model: return self.__get_pandanode(model) |
| 285 | |
| 286 | def optimize(self): |
| 287 | self.node.prepare_scene(base.win.get_gsg()) # crash with texture.set_format |
| 288 | self.node.premunge_scene(base.win.get_gsg()) |
| 289 | |
| 290 | def hide(self, mask=None): |
| 291 | return self.node.hide(*[] if mask is None else [mask]) |
| 292 | |
| 293 | @property |
| 294 | def tight_bounds(self): return self.node.get_tight_bounds() |
| 295 | |
| 296 | @property |
| 297 | def parent(self): return self.node.get_parent() |
| 298 | |
| 299 | @property |
| 300 | def children(self): return self.node.get_children() |
| 301 | |
| 302 | def destroy(self): return self.node.remove_node() |
| 303 | |
| 304 | |
| 305 | class P3dAnimNode: |
| 306 | |
| 307 | def __init__(self, filepath, anim_dct): |
| 308 | self.node = Actor(filepath, anim_dct) |
| 309 | |
| 310 | def loop(self, val): return self.node.loop(val) |
| 311 | |
| 312 | def reparent_to(self, node): self.node.reparent_to(node) |
| 313 | |
| 314 | @property |
| 315 | def name(self): return self.node.get_name() |
| 316 | |
| 317 | def optimize(self): |
| 318 | self.node.prepare_scene(base.win.get_gsg()) |
| 319 | self.node.premunge_scene(base.win.get_gsg()) |
| 320 | |
| 321 | def set_omni(self): |
| 322 | self.node.node().set_bounds(OmniBoundingVolume()) |
| 323 | self.node.node().set_final(True) |
| 324 | |
| 325 | def destroy(self): self.node.cleanup() |
| 326 | |
| 327 | |
| 328 | class P3dAmbientLight: |
| 329 | |
| 330 | def __init__(self, color): |
| 331 | ambient_lgt = P3DAmbientLight('ambient light') |
| 332 | ambient_lgt.set_color(color) |
| 333 | self.ambient_np = render.attach_new_node(ambient_lgt) |
| 334 | render.set_light(self.ambient_np) |
| 335 | |
| 336 | def destroy(self): |
| 337 | render.clear_light(self.ambient_np) |
| 338 | self.ambient_np.remove_node() |
| 339 | |
| 340 | |
| 341 | class P3dSpotlight: |
| 342 | |
| 343 | def __init__(self, mask=None): |
| 344 | self.spot_lgt = render.attach_new_node(P3DSpotlight('spot')) |
| 345 | snode = self.spot_lgt.node() |
| 346 | snode.set_scene(render) |
| 347 | snode.set_shadow_caster(True, 1024, 1024) |
| 348 | snode.get_lens().set_fov(40) |
| 349 | snode.get_lens().set_near_far(20, 200) |
| 350 | if mask: snode.set_camera_mask(mask) |
| 351 | render.set_light(self.spot_lgt) |
| 352 | |
| 353 | def set_pos(self, pos): return self.spot_lgt.set_pos(*pos) |
| 354 | |
| 355 | def look_at(self, pos): return self.spot_lgt.look_at(*pos) |
| 356 | |
| 357 | def set_color(self, color): return self.spot_lgt.set_color(*color) |
| 358 | |
| 359 | def destroy(self): |
| 360 | render.clear_light(self.spot_lgt) |
| 361 | self.spot_lgt.remove_node() |