diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index 25e22e52fb..6a2eafb0c9 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -365,7 +365,7 @@ class build_apps(setuptools.Command): tmp.update(self.file_handlers) self.file_handlers = tmp - tmp = self.package_data_dirs.copy() + tmp = PACKAGE_DATA_DIRS.copy() tmp.update(self.package_data_dirs) self.package_data_dirs = tmp diff --git a/direct/src/particles/ForceGroup.py b/direct/src/particles/ForceGroup.py index 9cd451f5ec..b1991ad8ae 100644 --- a/direct/src/particles/ForceGroup.py +++ b/direct/src/particles/ForceGroup.py @@ -6,13 +6,14 @@ from direct.showbase.PhysicsManagerGlobal import * from direct.directnotify import DirectNotifyGlobal import sys + class ForceGroup(DirectObject): notify = DirectNotifyGlobal.directNotify.newCategory('ForceGroup') id = 1 def __init__(self, name=None): - if (name == None): + if name is None: self.name = 'ForceGroup-%d' % ForceGroup.id ForceGroup.id += 1 else: @@ -60,9 +61,12 @@ class ForceGroup(DirectObject): # Get/set def getName(self): + """Deprecated: access .name directly instead.""" return self.name + def getNode(self): return self.node + def getNodePath(self): return self.nodePath @@ -124,3 +128,9 @@ class ForceGroup(DirectObject): file.write(fname + ' = AngularVectorForce(Quat(%.4f, %.4f, %.4f))\n' % (vec[0], vec[1], vec[2], vec[3])) file.write(fname + '.setActive(%d)\n' % f.getActive()) file.write(targ + '.addForce(%s)\n' % fname) + + is_enabled = isEnabled + get_node = getNode + get_node_path = getNodePath + as_list = asList + print_params = printParams diff --git a/direct/src/particles/ParticleEffect.py b/direct/src/particles/ParticleEffect.py index 06b701b388..41930dd46b 100644 --- a/direct/src/particles/ParticleEffect.py +++ b/direct/src/particles/ParticleEffect.py @@ -14,12 +14,13 @@ if sys.version_info < (3, 0): FileNotFoundError = IOError + class ParticleEffect(NodePath): notify = DirectNotifyGlobal.directNotify.newCategory('ParticleEffect') pid = 1 def __init__(self, name=None, particles=None): - if name == None: + if name is None: name = 'particle-effect-%d' % ParticleEffect.pid ParticleEffect.pid += 1 NodePath.__init__(self, name) @@ -31,7 +32,7 @@ class ParticleEffect(NodePath): self.particlesDict = {} self.forceGroupDict = {} # The effect's particle system - if particles != None: + if particles is not None: self.addParticles(particles) self.renderParent = None @@ -61,7 +62,7 @@ class ParticleEffect(NodePath): assert self.notify.debug('start() - name: %s' % self.name) self.renderParent = renderParent self.enable() - if parent != None: + if parent is not None: self.reparentTo(parent) def enable(self): @@ -134,7 +135,7 @@ class ParticleEffect(NodePath): particles.addForce(fg[i]) def removeParticles(self, particles): - if particles == None: + if particles is None: self.notify.warning('removeParticles() - particles == None!') return particles.nodePath.detachNode() @@ -231,10 +232,13 @@ class ParticleEffect(NodePath): for particles in self.getParticlesList(): particles.softStop() - def softStart(self): + def softStart(self, firstBirthDelay=None): if self.__isValid(): for particles in self.getParticlesList(): - particles.softStart() + if firstBirthDelay is not None: + particles.softStart(br=-1, first_birth_delay=firstBirthDelay) + else: + particles.softStart() else: # Not asserting here since we want to crash live clients for more expedient bugfix # (Sorry, live clients) @@ -243,3 +247,25 @@ class ParticleEffect(NodePath): def __isValid(self): return hasattr(self, 'forceGroupDict') and \ hasattr(self, 'particlesDict') + + # Snake-case aliases. + is_enabled = isEnabled + add_force_group = addForceGroup + add_force = addForce + remove_force_group = removeForceGroup + remove_force = removeForce + remove_all_forces = removeAllForces + add_particles = addParticles + remove_particles = removeParticles + remove_all_particles = removeAllParticles + get_particles_list = getParticlesList + get_particles_named = getParticlesNamed + get_particles_dict = getParticlesDict + get_force_group_list = getForceGroupList + get_force_group_named = getForceGroupNamed + get_force_group_dict = getForceGroupDict + save_config = saveConfig + load_config = loadConfig + clear_to_initial = clearToInitial + soft_stop = softStop + soft_start = softStart diff --git a/direct/src/particles/Particles.py b/direct/src/particles/Particles.py index adbfc8bf3f..3dfa5493dd 100644 --- a/direct/src/particles/Particles.py +++ b/direct/src/particles/Particles.py @@ -600,3 +600,17 @@ class Particles(ParticleSystem): base.physicsMgr.doPhysics(remainder,self) self.render() + + # Snake-case aliases. + is_enabled = isEnabled + set_factory = setFactory + set_renderer = setRenderer + set_emitter = setEmitter + add_force = addForce + remove_force = removeForce + set_render_node_path = setRenderNodePath + get_factory = getFactory + get_emitter = getEmitter + get_renderer = getRenderer + print_params = printParams + get_pool_size_ranges = getPoolSizeRanges diff --git a/panda/src/express/virtualFileSystem.cxx b/panda/src/express/virtualFileSystem.cxx index 84b6f67224..eda84c4075 100644 --- a/panda/src/express/virtualFileSystem.cxx +++ b/panda/src/express/virtualFileSystem.cxx @@ -187,7 +187,7 @@ mount_loop(const Filename &virtual_filename, const Filename &mount_point, /** * Adds the given VirtualFileMount object to the mount list. This is a lower- - * level function that the other flavors of mount(); it requires you to create + * level function than the other flavors of mount(); it requires you to create * a VirtualFileMount object specifically. */ bool VirtualFileSystem:: diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index 66663cc278..4f58e1af6e 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx @@ -7378,15 +7378,21 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z, z_size = 1; } + int num_views = tex->get_num_views(); if (tex->get_x_size() != w || tex->get_y_size() != h || tex->get_z_size() != z_size || tex->get_component_type() != component_type || tex->get_format() != format || - tex->get_texture_type() != texture_type) { + tex->get_texture_type() != texture_type || + view >= num_views) { - // Re-setup the texture; its properties have changed. - tex->setup_texture(texture_type, w, h, z_size, - component_type, format); + tex->setup_texture(texture_type, w, h, z_size, component_type, format); + + // The above resets the number of views to 1, so set this back. + num_views = std::max(view + 1, num_views); + if (num_views > 1) { + tex->set_num_views(num_views); + } } nassertr(z < tex->get_z_size(), false); @@ -7458,6 +7464,7 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z, } if (view > 0) { image_ptr += (view * tex->get_z_size()) * image_size; + nassertr(view < tex->get_num_views(), false); } } @@ -14445,7 +14452,17 @@ do_extract_texture_data(CLP(TextureContext) *gtc) { return false; } - tex->set_ram_image(image, compression, page_size); + int num_views = tex->get_num_views(); + if (num_views == 1) { + // Replace the entire image, since we are modifying the only view. + tex->set_ram_image(image, compression, page_size); + } else { + // We're only modifying a single view, so we can't stomp all over the + // existing content. + PTA_uchar ram_image = tex->modify_ram_image(); + nassertr(ram_image.size() == image.size() * num_views, false); + memcpy(ram_image.p() + image.size() * gtc->get_view(), image.p(), image.size()); + } if (gtc->_uses_mipmaps) { // Also get the mipmap levels. @@ -14461,7 +14478,12 @@ do_extract_texture_data(CLP(TextureContext) *gtc) { type, compression, n)) { return false; } - tex->set_ram_mipmap_image(n, image, page_size); + if (num_views == 1) { + tex->set_ram_mipmap_image(n, image, page_size); + } else { + PTA_uchar ram_mipmap_image = tex->modify_ram_mipmap_image(n); + memcpy(ram_mipmap_image.p() + image.size() * gtc->get_view(), image.p(), image.size()); + } } } @@ -14525,13 +14547,13 @@ extract_texture_image(PTA_uchar &image, size_t &page_size, #ifndef OPENGLES } else if (target == GL_TEXTURE_BUFFER) { // In the case of a buffer texture, we need to get it from the buffer. - image = PTA_uchar::empty_array(tex->get_expected_ram_mipmap_image_size(n)); + image = PTA_uchar::empty_array(tex->get_expected_ram_mipmap_view_size(n)); _glGetBufferSubData(target, 0, image.size(), image.p()); #endif } else if (compression == Texture::CM_off) { // An uncompressed 1-d, 2-d, or 3-d texture. - image = PTA_uchar::empty_array(tex->get_expected_ram_mipmap_image_size(n)); + image = PTA_uchar::empty_array(tex->get_expected_ram_mipmap_view_size(n)); GLenum external_format = get_external_image_format(tex); GLenum pixel_type = get_component_type(type); glGetTexImage(target, n, external_format, pixel_type, image.p()); diff --git a/panda/src/gobj/indexBufferContext.cxx b/panda/src/gobj/indexBufferContext.cxx index 2c3e68ba0b..202eeb0193 100644 --- a/panda/src/gobj/indexBufferContext.cxx +++ b/panda/src/gobj/indexBufferContext.cxx @@ -20,7 +20,13 @@ TypeHandle IndexBufferContext::_type_handle; */ void IndexBufferContext:: output(std::ostream &out) const { - out << *get_data() << ", " << get_data_size_bytes(); + GeomPrimitive *prim = get_data(); + if (prim != nullptr) { + out << *prim; + } else { + out << "NULL"; + } + out << ", " << get_data_size_bytes(); } /** diff --git a/panda/src/gobj/vertexBufferContext.cxx b/panda/src/gobj/vertexBufferContext.cxx index b0eab66a74..8e18dab109 100644 --- a/panda/src/gobj/vertexBufferContext.cxx +++ b/panda/src/gobj/vertexBufferContext.cxx @@ -21,7 +21,13 @@ TypeHandle VertexBufferContext::_type_handle; */ void VertexBufferContext:: output(std::ostream &out) const { - out << *get_data() << ", " << get_data_size_bytes(); + GeomVertexArrayData *data = get_data(); + if (data != nullptr) { + out << *data; + } else { + out << "NULL"; + } + out << ", " << get_data_size_bytes(); } /** diff --git a/panda/src/particlesystem/particleSystem.I b/panda/src/particlesystem/particleSystem.I index a96083f7e2..731ae9d7dc 100644 --- a/panda/src/particlesystem/particleSystem.I +++ b/panda/src/particlesystem/particleSystem.I @@ -58,6 +58,18 @@ soft_start(PN_stdfloat br) { _tics_since_birth = 0.0f; } +/** + * Causes system to use birth rate set by set_birth_rate(), with the system's + * first birth being delayed by the value of first_birth_delay. Note that a + * negative delay is perfectly valid, causing the first birth to happen + * sooner rather than later. + */ +INLINE void ParticleSystem:: +soft_start(PN_stdfloat br, PN_stdfloat first_birth_delay) { + soft_start(br); + _tics_since_birth = -first_birth_delay; +} + /** * Causes system to use birth rate set by set_soft_birth_rate() */ @@ -342,6 +354,14 @@ get_floor_z() const { return _floor_z; } +/** + +*/ +INLINE PN_stdfloat ParticleSystem:: +get_tics_since_birth() const { + return _tics_since_birth; +} + /** */ diff --git a/panda/src/particlesystem/particleSystem.h b/panda/src/particlesystem/particleSystem.h index 1e5bfdaac8..23e6f5ce82 100644 --- a/panda/src/particlesystem/particleSystem.h +++ b/panda/src/particlesystem/particleSystem.h @@ -83,6 +83,7 @@ PUBLISHED: INLINE BaseParticleEmitter *get_emitter() const; INLINE BaseParticleFactory *get_factory() const; INLINE PN_stdfloat get_floor_z() const; + INLINE PN_stdfloat get_tics_since_birth() const; // particle template vector @@ -96,6 +97,7 @@ PUBLISHED: INLINE void clear_to_initial(); INLINE void soft_stop(PN_stdfloat br = 0.0); INLINE void soft_start(PN_stdfloat br = 0.0); + INLINE void soft_start(PN_stdfloat br, PN_stdfloat first_birth_delay); void update(PN_stdfloat dt); virtual void output(std::ostream &out) const; diff --git a/tests/particles/test_particlesystem.py b/tests/particles/test_particlesystem.py new file mode 100644 index 0000000000..9e2ff85c20 --- /dev/null +++ b/tests/particles/test_particlesystem.py @@ -0,0 +1,103 @@ +from panda3d.core import NodePath, PandaNode +from direct.particles.ParticleEffect import ParticleEffect +from direct.particles.Particles import Particles + + +def test_particle_birth_rate(): + # Tests a system with a standard birth rate of 0.5, that it is + # indeed birthing at that rate. It serves as a control for the + # next test as well. + system = Particles("testSystem", 2) + + system.set_render_parent(NodePath(PandaNode("test"))) + system.set_spawn_render_node_path(NodePath(PandaNode("test"))) + + assert system.get_birth_rate() == 0.5 + assert system.get_tics_since_birth() == 0 + assert system.get_living_particles() == 0 + + system.update(0.6) + assert system.get_living_particles() == 1 + + system.update(0.5) + assert system.get_living_particles() == 2 + + # Should still be 2, since the pool size was 2. + system.update(0.5) + assert system.get_living_particles() == 2 + + +def test_particle_soft_start(): + # Create a particle effect and a particle system. + # The effect serves to test the Python-level "soft_start" method, + # while the system serves to test the C++-level "soft_start" method + # (via the associated Python "soft_start" method) + effect = ParticleEffect() + system = Particles("testSystem", 10) + + # Setup some dummy nodes, since it seems to want them + system.set_render_parent(NodePath(PandaNode("test"))) + system.set_spawn_render_node_path(NodePath(PandaNode("test"))) + + # Add the system to the effect + effect.add_particles(system) + + # Re-assign the system, just to make sure that we have the + # right object. + system = effect.get_particles_list()[0] + + # First, standard "soft_start"--i.e. without either changing + # the birth-rate or applying a delay. This should work as it + # used to. + effect.soft_start() + + assert system.get_birth_rate() == 0.5 + + # Now, check that the pre-existing single-parameter soft-start, + # which alters the birth-rate, still does so. + system.soft_start(1) + + assert system.get_birth_rate() == 1 + + # Next, birth-delaying. + + # Run a standard soft-start, then check that the birth-timer + # is zero, as used to be the case on running this command. + effect.soft_start() + + assert system.get_tics_since_birth() == 0 + + # Run an delayed soft-start via the system, then check that the + # birth-timer has the assigned value, and that the birth-rate is + # unchanged. + + # (We pass in a birth-rate ("br") of -1 because the related code + # checks for a birth-rate greater than 0, I believe. This allows + # us to change the delay without affecting the birth-rate.) + system.soft_start(br=-1, first_birth_delay=-2) + + assert system.get_birth_rate() == 1 + assert system.get_tics_since_birth() == 2 + + # Now, run a delayed soft-start via the effect, and + # again check that the birth-timer has changed as intended, + # and the birth-rate hasn't changed at all. + effect.soft_start(firstBirthDelay=0.25) + + assert system.get_birth_rate() == 1 + assert system.get_tics_since_birth() == -0.25 + + # Update the system, advancing it far enough that it should + # have birthed a particle if not for the delay, but not + # so far that it should have birthed a particle >with< + # the delay. Check thus that no particles have been birthed. + system.update(1) + + assert system.get_living_particles() == 0 + + # Update the system again, this time far enough that with the + # delay it should have birthed just one particle, and + # then check that this is the case. + system.update(1) + + assert system.get_living_particles() == 1