Merge branch 'release/1.10.x'

This commit is contained in:
rdb 2022-12-19 16:42:29 +01:00
commit 92185bcb28
14 changed files with 279 additions and 61 deletions

View File

@ -49,9 +49,9 @@ class MotionTrailFrame:
class MotionTrail(NodePath, DirectObject): class MotionTrail(NodePath, DirectObject):
"""Generates smooth geometry-based motion trails behind a moving object. """Generates smooth geometry-based motion trails behind a moving object.
To use this class, first define the shape of the cross-section of the trail To use this class, first define the shape of the cross-section polygon that
by repeatedly calling `add_vertex()` and `set_vertex_color()`. is to be extruded along the motion trail by calling `add_vertex()` and
When this is done, `update_vertices()` must be called. `set_vertex_color()`. When this is done, call `update_vertices()`.
To generate the motion trail, either call `register_motion_trail()` To generate the motion trail, either call `register_motion_trail()`
to have Panda update it automatically, or periodically call the method to have Panda update it automatically, or periodically call the method
@ -82,6 +82,10 @@ class MotionTrail(NodePath, DirectObject):
@classmethod @classmethod
def setGlobalEnable(cls, enable): def setGlobalEnable(cls, enable):
"""Set this to False to have the task stop updating all motion trails.
This does not prevent updating them manually using the
`update_motion_trail()` method.
"""
cls.global_enable = enable cls.global_enable = enable
def __init__(self, name, parent_node_path): def __init__(self, name, parent_node_path):
@ -119,14 +123,14 @@ class MotionTrail(NodePath, DirectObject):
self.continuous_motion_trail = True self.continuous_motion_trail = True
self.color_scale = 1.0 self.color_scale = 1.0
## How long the time window is for which the trail is computed. Can be #: How long the time window is for which the trail is computed. Can be
## increased to obtain a longer trail, decreased for a shorter trail. #: increased to obtain a longer trail, decreased for a shorter trail.
self.time_window = 1.0 self.time_window = 1.0
## How often the trail updates, in seconds. The default is 0.0, which #: How often the trail updates, in seconds. The default is 0.0, which
## has the trail updated every frame for the smoothest result. Higher #: has the trail updated every frame for the smoothest result. Higher
## values will generate a choppier trail. The `use_nurbs` option can #: values will generate a choppier trail. The `use_nurbs` option can
## compensate partially for this choppiness, however. #: compensate partially for this choppiness, however.
self.sampling_time = 0.0 self.sampling_time = 0.0
self.square_t = True self.square_t = True
@ -137,9 +141,9 @@ class MotionTrail(NodePath, DirectObject):
# node path states # node path states
self.reparentTo(parent_node_path) self.reparentTo(parent_node_path)
## A `.GeomNode` object containing the generated geometry. By default #: A `.GeomNode` object containing the generated geometry. By default
## parented to the MotionTrail itself, but can be reparented elsewhere #: parented to the MotionTrail itself, but can be reparented elsewhere
## if necessary. #: if necessary.
self.geom_node = GeomNode("motion_trail") self.geom_node = GeomNode("motion_trail")
self.geom_node.setBoundsType(BoundingVolume.BT_box) self.geom_node.setBoundsType(BoundingVolume.BT_box)
self.geom_node_path = self.attachNewNode(self.geom_node) self.geom_node_path = self.attachNewNode(self.geom_node)
@ -170,9 +174,11 @@ class MotionTrail(NodePath, DirectObject):
self.relative_to_render = False self.relative_to_render = False
## Set this to True to use a NURBS curve to generate a smooth trail, #: Set this to True to use a NURBS curve to generate a smooth trail,
## even if the underlying animation or movement is janky. #: even if the underlying animation or movement is janky.
self.use_nurbs = False self.use_nurbs = False
#: This can be changed to fine-tune the resolution of the NURBS curve.
self.resolution_distance = 0.5 self.resolution_distance = 0.5
self.cmotion_trail = CMotionTrail() self.cmotion_trail = CMotionTrail()
@ -249,7 +255,7 @@ class MotionTrail(NodePath, DirectObject):
def add_vertex(self, vertex_id, vertex_function=None, context=None, *, def add_vertex(self, vertex_id, vertex_function=None, context=None, *,
start_color=(1.0, 1.0, 1.0, 1.0), end_color=(0.0, 0.0, 0.0, 1.0)): start_color=(1.0, 1.0, 1.0, 1.0), end_color=(0.0, 0.0, 0.0, 1.0)):
"""This must be called repeatedly to define the polygon that forms the """This must be called initially to define the polygon that forms the
cross-section of the generated motion trail geometry. The first cross-section of the generated motion trail geometry. The first
argument is a user-defined vertex identifier, the second is a function argument is a user-defined vertex identifier, the second is a function
that will be called with three parameters that should return the that will be called with three parameters that should return the
@ -390,21 +396,21 @@ class MotionTrail(NodePath, DirectObject):
def add_geometry_quad(self, v0, v1, v2, v3, c0, c1, c2, c3, t0, t1, t2, t3): def add_geometry_quad(self, v0, v1, v2, v3, c0, c1, c2, c3, t0, t1, t2, t3):
self.vertex_writer.addData3f(v0 [0], v0 [1], v0 [2]) self.vertex_writer.addData3(v0[0], v0[1], v0[2])
self.vertex_writer.addData3f(v1 [0], v1 [1], v1 [2]) self.vertex_writer.addData3(v1[0], v1[1], v1[2])
self.vertex_writer.addData3f(v2 [0], v2 [1], v2 [2]) self.vertex_writer.addData3(v2[0], v2[1], v2[2])
self.vertex_writer.addData3f(v3 [0], v3 [1], v3 [2]) self.vertex_writer.addData3(v3[0], v3[1], v3[2])
self.color_writer.addData4f(c0) self.color_writer.addData4(c0)
self.color_writer.addData4f(c1) self.color_writer.addData4(c1)
self.color_writer.addData4f(c2) self.color_writer.addData4(c2)
self.color_writer.addData4f(c3) self.color_writer.addData4(c3)
if self.texture is not None: if self.texture is not None:
self.texture_writer.addData2f(t0) self.texture_writer.addData2(t0)
self.texture_writer.addData2f(t1) self.texture_writer.addData2(t1)
self.texture_writer.addData2f(t2) self.texture_writer.addData2(t2)
self.texture_writer.addData2f(t3) self.texture_writer.addData2(t3)
vertex_index = self.vertex_index vertex_index = self.vertex_index

View File

@ -1151,7 +1151,7 @@ class ShowBase(DirectObject.DirectObject):
Creates the render scene graph, the primary scene graph for Creates the render scene graph, the primary scene graph for
rendering 3-d geometry. rendering 3-d geometry.
""" """
## This is the root of the 3-D scene graph. #: This is the root of the 3-D scene graph.
self.render = NodePath('render') self.render = NodePath('render')
self.render.setAttrib(RescaleNormalAttrib.makeDefault()) self.render.setAttrib(RescaleNormalAttrib.makeDefault())
@ -1170,7 +1170,7 @@ class ShowBase(DirectObject.DirectObject):
# for the benefit of creating DirectGui elements before ShowBase. # for the benefit of creating DirectGui elements before ShowBase.
from . import ShowBaseGlobal from . import ShowBaseGlobal
## This is the root of the 2-D scene graph. #: This is the root of the 2-D scene graph.
self.render2d = ShowBaseGlobal.render2d self.render2d = ShowBaseGlobal.render2d
# Set up some overrides to turn off certain properties which # Set up some overrides to turn off certain properties which
@ -1191,12 +1191,12 @@ class ShowBase(DirectObject.DirectObject):
self.render2d.setMaterialOff(1) self.render2d.setMaterialOff(1)
self.render2d.setTwoSided(1) self.render2d.setTwoSided(1)
## The normal 2-d DisplayRegion has an aspect ratio that #: The normal 2-d DisplayRegion has an aspect ratio that
## matches the window, but its coordinate system is square. #: matches the window, but its coordinate system is square.
## This means anything we parent to render2d gets stretched. #: This means anything we parent to render2d gets stretched.
## For things where that makes a difference, we set up #: For things where that makes a difference, we set up
## aspect2d, which scales things back to the right aspect #: aspect2d, which scales things back to the right aspect
## ratio along the X axis (Z is still from -1 to 1) #: ratio along the X axis (Z is still from -1 to 1)
self.aspect2d = ShowBaseGlobal.aspect2d self.aspect2d = ShowBaseGlobal.aspect2d
aspectRatio = self.getAspectRatio() aspectRatio = self.getAspectRatio()
@ -1204,13 +1204,13 @@ class ShowBase(DirectObject.DirectObject):
self.a2dBackground = self.aspect2d.attachNewNode("a2dBackground") self.a2dBackground = self.aspect2d.attachNewNode("a2dBackground")
## The Z position of the top border of the aspect2d screen. #: The Z position of the top border of the aspect2d screen.
self.a2dTop = 1.0 self.a2dTop = 1.0
## The Z position of the bottom border of the aspect2d screen. #: The Z position of the bottom border of the aspect2d screen.
self.a2dBottom = -1.0 self.a2dBottom = -1.0
## The X position of the left border of the aspect2d screen. #: The X position of the left border of the aspect2d screen.
self.a2dLeft = -aspectRatio self.a2dLeft = -aspectRatio
## The X position of the right border of the aspect2d screen. #: The X position of the right border of the aspect2d screen.
self.a2dRight = aspectRatio self.a2dRight = aspectRatio
self.a2dTopCenter = self.aspect2d.attachNewNode("a2dTopCenter") self.a2dTopCenter = self.aspect2d.attachNewNode("a2dTopCenter")
@ -1250,9 +1250,9 @@ class ShowBase(DirectObject.DirectObject):
self.a2dBottomRight.setPos(self.a2dRight, 0, self.a2dBottom) self.a2dBottomRight.setPos(self.a2dRight, 0, self.a2dBottom)
self.a2dBottomRightNs.setPos(self.a2dRight, 0, self.a2dBottom) self.a2dBottomRightNs.setPos(self.a2dRight, 0, self.a2dBottom)
## This special root, pixel2d, uses units in pixels that are relative #: This special root, pixel2d, uses units in pixels that are relative
## to the window. The upperleft corner of the window is (0, 0), #: to the window. The upperleft corner of the window is (0, 0),
## the lowerleft corner is (xsize, -ysize), in this coordinate system. #: the lowerleft corner is (xsize, -ysize), in this coordinate system.
self.pixel2d = self.render2d.attachNewNode(PGTop("pixel2d")) self.pixel2d = self.render2d.attachNewNode(PGTop("pixel2d"))
self.pixel2d.setPos(-1, 0, 1) self.pixel2d.setPos(-1, 0, 1)
xsize, ysize = self.getSize() xsize, ysize = self.getSize()
@ -1282,25 +1282,25 @@ class ShowBase(DirectObject.DirectObject):
self.render2dp.setMaterialOff(1) self.render2dp.setMaterialOff(1)
self.render2dp.setTwoSided(1) self.render2dp.setTwoSided(1)
## The normal 2-d DisplayRegion has an aspect ratio that #: The normal 2-d DisplayRegion has an aspect ratio that
## matches the window, but its coordinate system is square. #: matches the window, but its coordinate system is square.
## This means anything we parent to render2dp gets stretched. #: This means anything we parent to render2dp gets stretched.
## For things where that makes a difference, we set up #: For things where that makes a difference, we set up
## aspect2dp, which scales things back to the right aspect #: aspect2dp, which scales things back to the right aspect
## ratio along the X axis (Z is still from -1 to 1) #: ratio along the X axis (Z is still from -1 to 1)
self.aspect2dp = self.render2dp.attachNewNode(PGTop("aspect2dp")) self.aspect2dp = self.render2dp.attachNewNode(PGTop("aspect2dp"))
self.aspect2dp.node().setStartSort(16384) self.aspect2dp.node().setStartSort(16384)
aspectRatio = self.getAspectRatio() aspectRatio = self.getAspectRatio()
self.aspect2dp.setScale(1.0 / aspectRatio, 1.0, 1.0) self.aspect2dp.setScale(1.0 / aspectRatio, 1.0, 1.0)
## The Z position of the top border of the aspect2dp screen. #: The Z position of the top border of the aspect2dp screen.
self.a2dpTop = 1.0 self.a2dpTop = 1.0
## The Z position of the bottom border of the aspect2dp screen. #: The Z position of the bottom border of the aspect2dp screen.
self.a2dpBottom = -1.0 self.a2dpBottom = -1.0
## The X position of the left border of the aspect2dp screen. #: The X position of the left border of the aspect2dp screen.
self.a2dpLeft = -aspectRatio self.a2dpLeft = -aspectRatio
## The X position of the right border of the aspect2dp screen. #: The X position of the right border of the aspect2dp screen.
self.a2dpRight = aspectRatio self.a2dpRight = aspectRatio
self.a2dpTopCenter = self.aspect2dp.attachNewNode("a2dpTopCenter") self.a2dpTopCenter = self.aspect2dp.attachNewNode("a2dpTopCenter")
@ -1324,9 +1324,9 @@ class ShowBase(DirectObject.DirectObject):
self.a2dpBottomLeft.setPos(self.a2dpLeft, 0, self.a2dpBottom) self.a2dpBottomLeft.setPos(self.a2dpLeft, 0, self.a2dpBottom)
self.a2dpBottomRight.setPos(self.a2dpRight, 0, self.a2dpBottom) self.a2dpBottomRight.setPos(self.a2dpRight, 0, self.a2dpBottom)
## This special root, pixel2d, uses units in pixels that are relative #: This special root, pixel2dp, uses units in pixels that are relative
## to the window. The upperleft corner of the window is (0, 0), #: to the window. The upperleft corner of the window is (0, 0),
## the lowerleft corner is (xsize, -ysize), in this coordinate system. #: the lowerleft corner is (xsize, -ysize), in this coordinate system.
self.pixel2dp = self.render2dp.attachNewNode(PGTop("pixel2dp")) self.pixel2dp = self.render2dp.attachNewNode(PGTop("pixel2dp"))
self.pixel2dp.node().setStartSort(16384) self.pixel2dp.node().setStartSort(16384)
self.pixel2dp.setPos(-1, 0, 1) self.pixel2dp.setPos(-1, 0, 1)
@ -1647,11 +1647,11 @@ class ShowBase(DirectObject.DirectObject):
mw = self.buttonThrowers[0].getParent() mw = self.buttonThrowers[0].getParent()
## A special ButtonThrower to generate keyboard events and #: A special ButtonThrower to generate keyboard events and
## include the time from the OS. This is separate only to #: include the time from the OS. This is separate only to
## support legacy code that did not expect a time parameter; it #: support legacy code that did not expect a time parameter; it
## will eventually be folded into the normal ButtonThrower, #: will eventually be folded into the normal ButtonThrower,
## above. #: above.
self.timeButtonThrower = mw.attachNewNode(ButtonThrower('timeButtons')) self.timeButtonThrower = mw.attachNewNode(ButtonThrower('timeButtons'))
self.timeButtonThrower.node().setPrefix('time-') self.timeButtonThrower.node().setPrefix('time-')
self.timeButtonThrower.node().setTimeFlag(1) self.timeButtonThrower.node().setTimeFlag(1)

View File

@ -71,6 +71,8 @@ Miscellaneous
* Fix texture transforms sometimes not being flattened (#1392) * Fix texture transforms sometimes not being flattened (#1392)
* Fix support for `#pragma include <file.glsl>` in GLSL shaders * Fix support for `#pragma include <file.glsl>` in GLSL shaders
* Fix `ShaderBuffer.prepare()` not doing anything * Fix `ShaderBuffer.prepare()` not doing anything
* Implement deepcopy for PointerToArray
* Fix Texture deepcopy keeping a reference to the original RAM image
* Fix bf-cbc encryption no longer working when building with OpenSSL 3.0 * Fix bf-cbc encryption no longer working when building with OpenSSL 3.0
* PandaNode bounds_type property was erroneously marked read-only * PandaNode bounds_type property was erroneously marked read-only
* Fix warnings when copying OdeTriMeshGeom objects * Fix warnings when copying OdeTriMeshGeom objects
@ -81,8 +83,10 @@ Miscellaneous
* Add various useful functions to interrogatedb module * Add various useful functions to interrogatedb module
* Fix Python 3 issues unpacking uint types in Python 3 (#1380) * Fix Python 3 issues unpacking uint types in Python 3 (#1380)
* Fix interrogate syntax error with C++11-style attributes in declarators * Fix interrogate syntax error with C++11-style attributes in declarators
* Fix double-precision color values not being clamped by GeomVertexWriter
* Fix regression with BufferViewer in double-precision build (#1365) * Fix regression with BufferViewer in double-precision build (#1365)
* Fix `PandaNode.nested_vertices` not updating properly * Fix `PandaNode.nested_vertices` not updating properly
* Prevent Panda calculating bounding volume of Geom with custom bounding volume
* Add `do_events()` and `process_event()` snake_case aliases in eventMgr * Add `do_events()` and `process_event()` snake_case aliases in eventMgr
* Support second arg of None in `replace_texture()` / `replace_material()` * Support second arg of None in `replace_texture()` / `replace_material()`
* Support `os.fspath()` for ConfigVariableFilename objects (#1406) * Support `os.fspath()` for ConfigVariableFilename objects (#1406)

View File

@ -39,7 +39,8 @@
// Ask all the windows whether they are OK to be closed. // Ask all the windows whether they are OK to be closed.
bool should_close = true; bool should_close = true;
for (NSWindow *window in [app windows]) { for (NSWindow *window in [app windows]) {
if (![[window delegate] windowShouldClose:window]) { id<NSWindowDelegate> delegate = [window delegate];
if (delegate != nil && ![delegate windowShouldClose:window]) {
should_close = false; should_close = false;
} }
} }

View File

@ -121,6 +121,8 @@ PUBLISHED:
EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags)); EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags));
EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const); EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const);
EXTENSION(PointerToArray<Element> __deepcopy__(PyObject *memo) const);
#endif #endif
#else // CPPPARSER #else // CPPPARSER
@ -279,6 +281,8 @@ PUBLISHED:
EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const); EXTENSION(int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const);
EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const); EXTENSION(void __releasebuffer__(PyObject *self, Py_buffer *view) const);
EXTENSION(ConstPointerToArray<Element> __deepcopy__(PyObject *memo) const);
#endif #endif
#else // CPPPARSER #else // CPPPARSER

View File

@ -440,6 +440,20 @@ __releasebuffer__(PyObject *self, Py_buffer *view) const {
} }
} }
/**
* A special Python method that is invoked by copy.deepcopy(pta). This makes
* sure that there is truly a unique copy of the array.
*/
template<class Element>
INLINE PointerToArray<Element> Extension<PointerToArray<Element> >::
__deepcopy__(PyObject *memo) const {
PointerToArray<Element> copy;
if (!this->_this->is_null()) {
copy.v() = this->_this->v();
}
return copy;
}
/** /**
* This is used to implement the buffer protocol, in order to allow efficient * This is used to implement the buffer protocol, in order to allow efficient
* access to the array data through a Python multiview object. * access to the array data through a Python multiview object.
@ -610,3 +624,17 @@ __releasebuffer__(PyObject *self, Py_buffer *view) const {
view->internal = nullptr; view->internal = nullptr;
} }
} }
/**
* A special Python method that is invoked by copy.deepcopy(pta). This makes
* sure that there is truly a unique copy of the array.
*/
template<class Element>
INLINE ConstPointerToArray<Element> Extension<ConstPointerToArray<Element> >::
__deepcopy__(PyObject *memo) const {
PointerToArray<Element> copy;
if (!this->_this->is_null()) {
copy.v() = this->_this->v();
}
return copy;
}

View File

@ -44,6 +44,8 @@ public:
INLINE int __getbuffer__(PyObject *self, Py_buffer *view, int flags); INLINE int __getbuffer__(PyObject *self, Py_buffer *view, int flags);
INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const; INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const;
INLINE PointerToArray<Element> __deepcopy__(PyObject *memo) const;
}; };
template<> template<>
@ -81,6 +83,8 @@ public:
INLINE int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const; INLINE int __getbuffer__(PyObject *self, Py_buffer *view, int flags) const;
INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const; INLINE void __releasebuffer__(PyObject *self, Py_buffer *view) const;
INLINE ConstPointerToArray<Element> __deepcopy__(PyObject *memo) const;
}; };
template<> template<>

View File

@ -4472,6 +4472,20 @@ get_data4f(const unsigned char *pointer) {
return _v4; return _v4;
} }
/**
*
*/
const LVecBase4d &GeomVertexColumn::Packer_argb_packed::
get_data4d(const unsigned char *pointer) {
uint32_t dword = *(const uint32_t *)pointer;
_v4d.set(GeomVertexData::unpack_abcd_b(dword),
GeomVertexData::unpack_abcd_c(dword),
GeomVertexData::unpack_abcd_d(dword),
GeomVertexData::unpack_abcd_a(dword));
_v4d /= 255.0;
return _v4d;
}
/** /**
* *
*/ */
@ -4486,6 +4500,20 @@ set_data4f(unsigned char *pointer, const LVecBase4f &data) {
(unsigned int)(min(max(data[2], 0.0f), 1.0f) * 255.0f)); (unsigned int)(min(max(data[2], 0.0f), 1.0f) * 255.0f));
} }
/**
*
*/
void GeomVertexColumn::Packer_argb_packed::
set_data4d(unsigned char *pointer, const LVecBase4d &data) {
// when packing an argb, we want to make sure we cap the input values at 1
// since going above one will cause the value to be truncated.
*(uint32_t *)pointer = GeomVertexData::pack_abcd
((unsigned int)(min(max(data[3], 0.0), 1.0) * 255.0),
(unsigned int)(min(max(data[0], 0.0), 1.0) * 255.0),
(unsigned int)(min(max(data[1], 0.0), 1.0) * 255.0),
(unsigned int)(min(max(data[2], 0.0), 1.0) * 255.0));
}
/** /**
* *
*/ */
@ -4497,6 +4525,17 @@ get_data4f(const unsigned char *pointer) {
return _v4; return _v4;
} }
/**
*
*/
const LVecBase4d &GeomVertexColumn::Packer_rgba_uint8_4::
get_data4d(const unsigned char *pointer) {
_v4d.set((double)pointer[0], (double)pointer[1],
(double)pointer[2], (double)pointer[3]);
_v4d /= 255.0;
return _v4d;
}
/** /**
* *
*/ */
@ -4508,6 +4547,17 @@ set_data4f(unsigned char *pointer, const LVecBase4f &data) {
pointer[3] = (unsigned int)(min(max(data[3], 0.0f), 1.0f) * 255.0f); pointer[3] = (unsigned int)(min(max(data[3], 0.0f), 1.0f) * 255.0f);
} }
/**
*
*/
void GeomVertexColumn::Packer_rgba_uint8_4::
set_data4d(unsigned char *pointer, const LVecBase4d &data) {
pointer[0] = (unsigned int)(min(max(data[0], 0.0), 1.0) * 255.0);
pointer[1] = (unsigned int)(min(max(data[1], 0.0), 1.0) * 255.0);
pointer[2] = (unsigned int)(min(max(data[2], 0.0), 1.0) * 255.0);
pointer[3] = (unsigned int)(min(max(data[3], 0.0), 1.0) * 255.0);
}
/** /**
* *
*/ */

View File

@ -381,7 +381,9 @@ private:
class Packer_argb_packed final : public Packer_color { class Packer_argb_packed final : public Packer_color {
public: public:
virtual const LVecBase4f &get_data4f(const unsigned char *pointer); virtual const LVecBase4f &get_data4f(const unsigned char *pointer);
virtual const LVecBase4d &get_data4d(const unsigned char *pointer);
virtual void set_data4f(unsigned char *pointer, const LVecBase4f &value); virtual void set_data4f(unsigned char *pointer, const LVecBase4f &value);
virtual void set_data4d(unsigned char *pointer, const LVecBase4d &value);
virtual const char *get_name() const { virtual const char *get_name() const {
return "Packer_argb_packed"; return "Packer_argb_packed";
@ -391,7 +393,9 @@ private:
class Packer_rgba_uint8_4 final : public Packer_color { class Packer_rgba_uint8_4 final : public Packer_color {
public: public:
virtual const LVecBase4f &get_data4f(const unsigned char *pointer); virtual const LVecBase4f &get_data4f(const unsigned char *pointer);
virtual const LVecBase4d &get_data4d(const unsigned char *pointer);
virtual void set_data4f(unsigned char *pointer, const LVecBase4f &value); virtual void set_data4f(unsigned char *pointer, const LVecBase4f &value);
virtual void set_data4d(unsigned char *pointer, const LVecBase4d &value);
virtual const char *get_name() const { virtual const char *get_name() const {
return "Packer_rgba_uint8_4"; return "Packer_rgba_uint8_4";

View File

@ -46,6 +46,7 @@
#include "pnmImage.h" #include "pnmImage.h"
#include "pfmFile.h" #include "pfmFile.h"
#include "asyncTask.h" #include "asyncTask.h"
#include "extension.h"
class TextureContext; class TextureContext;
class FactoryParams; class FactoryParams;
@ -473,6 +474,8 @@ PUBLISHED:
MAKE_PROPERTY(keep_ram_image, get_keep_ram_image, set_keep_ram_image); MAKE_PROPERTY(keep_ram_image, get_keep_ram_image, set_keep_ram_image);
MAKE_PROPERTY(cacheable, is_cacheable); MAKE_PROPERTY(cacheable, is_cacheable);
EXTENSION(PT(Texture) __deepcopy__(PyObject *memo) const);
BLOCKING INLINE bool compress_ram_image(CompressionMode compression = CM_on, BLOCKING INLINE bool compress_ram_image(CompressionMode compression = CM_on,
QualityLevel quality_level = QL_default, QualityLevel quality_level = QL_default,
GraphicsStateGuardianBase *gsg = nullptr); GraphicsStateGuardianBase *gsg = nullptr);
@ -1122,6 +1125,7 @@ private:
static TypeHandle _type_handle; static TypeHandle _type_handle;
friend class Extension<Texture>;
friend class TextureContext; friend class TextureContext;
friend class PreparedGraphicsObjects; friend class PreparedGraphicsObjects;
friend class TexturePool; friend class TexturePool;

View File

@ -138,4 +138,32 @@ set_ram_image_as(PyObject *image, const std::string &provided_format) {
Dtool_Raise_ArgTypeError(image, 0, "Texture.set_ram_image_as", "CPTA_uchar or buffer"); Dtool_Raise_ArgTypeError(image, 0, "Texture.set_ram_image_as", "CPTA_uchar or buffer");
} }
/**
* A special Python method that is invoked by copy.deepcopy(tex). This makes
* sure that the copy has a unique copy of the RAM image.
*/
PT(Texture) Extension<Texture>::
__deepcopy__(PyObject *memo) const {
PT(Texture) copy = _this->make_copy();
{
Texture::CDWriter cdata(copy->_cycler, true);
for (Texture::RamImage &image : cdata->_ram_images) {
if (image._image.get_ref_count() > 1) {
PTA_uchar new_image;
new_image.v() = image._image.v();
image._image = std::move(new_image);
}
}
{
Texture::RamImage &image = cdata->_simple_ram_image;
if (image._image.get_ref_count() > 1) {
PTA_uchar new_image;
new_image.v() = image._image.v();
image._image = std::move(new_image);
}
}
}
return copy;
}
#endif // HAVE_PYTHON #endif // HAVE_PYTHON

View File

@ -32,6 +32,8 @@ public:
void set_ram_image(PyObject *image, Texture::CompressionMode compression = Texture::CM_off, void set_ram_image(PyObject *image, Texture::CompressionMode compression = Texture::CM_off,
size_t page_size = 0); size_t page_size = 0);
void set_ram_image_as(PyObject *image, const std::string &provided_format); void set_ram_image_as(PyObject *image, const std::string &provided_format);
PT(Texture) __deepcopy__(PyObject *memo) const;
}; };
#endif // HAVE_PYTHON #endif // HAVE_PYTHON

View File

@ -69,3 +69,63 @@ def test_cpta_float_pickle():
data_pta2 = loads(dumps(data_pta, proto)) data_pta2 = loads(dumps(data_pta, proto))
assert tuple(data_pta2) == (1.0, 2.0, 3.0) assert tuple(data_pta2) == (1.0, 2.0, 3.0)
assert data_pta2.get_data() == data_pta.get_data() assert data_pta2.get_data() == data_pta.get_data()
def test_pta_float_copy():
from panda3d.core import PTA_float
from copy import copy
null_pta = PTA_float()
assert copy(null_pta).is_null()
empty_pta = PTA_float([])
empty_pta_copy = copy(empty_pta)
assert not empty_pta_copy.is_null()
assert len(empty_pta_copy) == 0
assert empty_pta_copy.get_ref_count() == 2
data_pta = PTA_float([1.0, 2.0, 3.0])
data_pta_copy = copy(data_pta)
assert not data_pta_copy.is_null()
assert data_pta_copy.get_ref_count() == 2
assert tuple(data_pta_copy) == (1.0, 2.0, 3.0)
def test_pta_float_deepcopy():
from panda3d.core import PTA_float
from copy import deepcopy
null_pta = PTA_float()
assert deepcopy(null_pta).is_null()
empty_pta = PTA_float([])
empty_pta_copy = deepcopy(empty_pta)
assert not empty_pta_copy.is_null()
assert len(empty_pta_copy) == 0
assert empty_pta_copy.get_ref_count() == 1
data_pta = PTA_float([1.0, 2.0, 3.0])
data_pta_copy = deepcopy(data_pta)
assert not data_pta_copy.is_null()
assert data_pta_copy.get_ref_count() == 1
assert tuple(data_pta_copy) == (1.0, 2.0, 3.0)
def test_cpta_float_deepcopy():
from panda3d.core import PTA_float, CPTA_float
from copy import deepcopy
null_pta = CPTA_float(PTA_float())
assert deepcopy(null_pta).is_null()
empty_pta = CPTA_float([])
empty_pta_copy = deepcopy(empty_pta)
assert not empty_pta_copy.is_null()
assert len(empty_pta_copy) == 0
assert empty_pta_copy.get_ref_count() == 1
data_pta = CPTA_float([1.0, 2.0, 3.0])
data_pta_copy = deepcopy(data_pta)
assert not data_pta_copy.is_null()
assert data_pta_copy.get_ref_count() == 1
assert tuple(data_pta_copy) == (1.0, 2.0, 3.0)

View File

@ -134,3 +134,26 @@ def test_texture_clear_half():
assert col.y == -inf assert col.y == -inf
assert col.z == -inf assert col.z == -inf
assert math.isnan(col.w) assert math.isnan(col.w)
def test_texture_deepcopy():
from copy import deepcopy
empty_tex = Texture("empty-texture")
empty_tex.setup_2d_texture(16, 16, Texture.T_unsigned_byte, Texture.F_rgba)
assert not empty_tex.has_ram_image()
empty_tex2 = deepcopy(empty_tex)
assert empty_tex2.name == empty_tex.name
assert not empty_tex2.has_ram_image()
tex = Texture("texture")
tex.setup_2d_texture(16, 16, Texture.T_unsigned_byte, Texture.F_rgba)
img = tex.make_ram_image()
assert tex.has_ram_image()
assert img.get_ref_count() == 2
tex2 = deepcopy(tex)
assert tex2.name == tex.name
assert tex2.has_ram_image()
img2 = tex2.get_ram_image()
assert img2.get_ref_count() == 2