panda3d/panda/src/gobj/geomVertexData.cxx
rdb 46fe58a87a Merge remote-tracking branch 'origin/release/1.9.x'
Conflicts:
	panda/src/gobj/geomVertexData.cxx
2015-12-26 15:22:32 +01:00

2886 lines
107 KiB
C++

// Filename: geomVertexData.cxx
// Created by: drose (06Mar05)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
#include "geomVertexData.h"
#include "geom.h"
#include "geomVertexReader.h"
#include "geomVertexWriter.h"
#include "geomVertexRewriter.h"
#include "pStatTimer.h"
#include "bamReader.h"
#include "bamWriter.h"
#include "pset.h"
#include "indent.h"
TypeHandle GeomVertexData::_type_handle;
TypeHandle GeomVertexData::CDataCache::_type_handle;
TypeHandle GeomVertexData::CacheEntry::_type_handle;
TypeHandle GeomVertexData::CData::_type_handle;
TypeHandle GeomVertexDataPipelineReader::_type_handle;
TypeHandle GeomVertexDataPipelineWriter::_type_handle;
PStatCollector GeomVertexData::_convert_pcollector("*:Munge:Convert");
PStatCollector GeomVertexData::_scale_color_pcollector("*:Munge:Scale color");
PStatCollector GeomVertexData::_set_color_pcollector("*:Munge:Set color");
PStatCollector GeomVertexData::_animation_pcollector("*:Animation");
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::Default Constructor
// Access: Private
// Description: Constructs an invalid object. This is only used when
// reading from the bam file.
////////////////////////////////////////////////////////////////////
GeomVertexData::
GeomVertexData() :
_char_pcollector(_animation_pcollector, "unnamed"),
_skinning_pcollector(_char_pcollector, "Skinning"),
_morphs_pcollector(_char_pcollector, "Morphs"),
_blends_pcollector(_char_pcollector, "Calc blends")
{
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::make_cow_copy
// Access: Protected, Virtual
// Description: Required to implement CopyOnWriteObject.
////////////////////////////////////////////////////////////////////
PT(CopyOnWriteObject) GeomVertexData::
make_cow_copy() {
return new GeomVertexData(*this);
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::Constructor
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
GeomVertexData::
GeomVertexData(const string &name,
const GeomVertexFormat *format,
GeomVertexData::UsageHint usage_hint) :
_name(name),
_char_pcollector(PStatCollector(_animation_pcollector, name)),
_skinning_pcollector(_char_pcollector, "Skinning"),
_morphs_pcollector(_char_pcollector, "Morphs"),
_blends_pcollector(_char_pcollector, "Calc blends")
{
nassertv(format->is_registered());
// Create some empty arrays as required by the format.
// Let's ensure the vertex data gets set on all stages at once.
OPEN_ITERATE_ALL_STAGES(_cycler) {
CDStageWriter cdata(_cycler, pipeline_stage);
cdata->_format = format;
cdata->_usage_hint = usage_hint;
int num_arrays = format->get_num_arrays();
for (int i = 0; i < num_arrays; i++) {
PT(GeomVertexArrayData) array = new GeomVertexArrayData
(format->get_array(i), usage_hint);
cdata->_arrays.push_back(array.p());
}
}
CLOSE_ITERATE_ALL_STAGES(_cycler);
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::Copy Constructor
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
GeomVertexData::
GeomVertexData(const GeomVertexData &copy) :
CopyOnWriteObject(copy),
_name(copy._name),
_cycler(copy._cycler),
_char_pcollector(copy._char_pcollector),
_skinning_pcollector(copy._skinning_pcollector),
_morphs_pcollector(copy._morphs_pcollector),
_blends_pcollector(copy._blends_pcollector)
{
OPEN_ITERATE_ALL_STAGES(_cycler) {
CDStageWriter cdata(_cycler, pipeline_stage);
// It's important that we *not* copy the animated_vertices pointer.
cdata->_animated_vertices = NULL;
cdata->_animated_vertices_modified = UpdateSeq();
}
CLOSE_ITERATE_ALL_STAGES(_cycler);
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::Constructor
// Access: Published
// Description: This constructor copies all of the basic properties
// of the source VertexData, like usage_hint and
// animation tables, but does not copy the actual data,
// and it allows you to specify a different format.
////////////////////////////////////////////////////////////////////
GeomVertexData::
GeomVertexData(const GeomVertexData &copy,
const GeomVertexFormat *format) :
CopyOnWriteObject(copy),
_name(copy._name),
_cycler(copy._cycler),
_char_pcollector(copy._char_pcollector),
_skinning_pcollector(copy._skinning_pcollector),
_morphs_pcollector(copy._morphs_pcollector),
_blends_pcollector(copy._blends_pcollector)
{
nassertv(format->is_registered());
// Create some empty arrays as required by the format.
OPEN_ITERATE_ALL_STAGES(_cycler) {
CDStageWriter cdata(_cycler, pipeline_stage);
UsageHint usage_hint = cdata->_usage_hint;
cdata->_arrays.clear();
cdata->_format = format;
int num_arrays = format->get_num_arrays();
for (int i = 0; i < num_arrays; i++) {
PT(GeomVertexArrayData) array = new GeomVertexArrayData
(format->get_array(i), usage_hint);
cdata->_arrays.push_back(array.p());
}
// It's important that we *not* copy the animated_vertices pointer.
cdata->_animated_vertices = NULL;
cdata->_animated_vertices_modified = UpdateSeq();
}
CLOSE_ITERATE_ALL_STAGES(_cycler);
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::Copy Assignment Operator
// Access: Published
// Description: The copy assignment operator is not pipeline-safe.
// This will completely obliterate all stages of the
// pipeline, so don't do it for a GeomVertexData that is
// actively being used for rendering.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
operator = (const GeomVertexData &copy) {
CopyOnWriteObject::operator = (copy);
clear_cache();
_name = copy._name;
_cycler = copy._cycler;
_char_pcollector = copy._char_pcollector;
_skinning_pcollector = copy._skinning_pcollector;
_morphs_pcollector = copy._morphs_pcollector;
_blends_pcollector = copy._blends_pcollector;
OPEN_ITERATE_ALL_STAGES(_cycler) {
CDStageWriter cdata(_cycler, pipeline_stage);
cdata->_modified = Geom::get_next_modified();
cdata->_animated_vertices = NULL;
cdata->_animated_vertices_modified = UpdateSeq();
}
CLOSE_ITERATE_ALL_STAGES(_cycler);
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::Destructor
// Access: Published, Virtual
// Description:
////////////////////////////////////////////////////////////////////
GeomVertexData::
~GeomVertexData() {
clear_cache();
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::compare_to
// Access: Published
// Description: Returns 0 if the two objects are equivalent, even if
// they are not the same pointer.
////////////////////////////////////////////////////////////////////
int GeomVertexData::
compare_to(const GeomVertexData &other) const {
CDReader cdata(_cycler);
CDReader other_cdata(other._cycler);
if (cdata->_usage_hint != other_cdata->_usage_hint) {
return (int)cdata->_usage_hint - (int)other_cdata->_usage_hint;
}
if (cdata->_format != other_cdata->_format) {
return cdata->_format < other_cdata->_format ? -1 : 1;
}
if (cdata->_transform_table != other_cdata->_transform_table) {
return cdata->_transform_table < other_cdata->_transform_table ? -1 : 1;
}
if (cdata->_transform_blend_table != other_cdata->_transform_blend_table) {
return cdata->_transform_blend_table < other_cdata->_transform_blend_table ? -1 : 1;
}
if (cdata->_slider_table != other_cdata->_slider_table) {
return cdata->_slider_table < other_cdata->_slider_table ? -1 : 1;
}
if (cdata->_arrays.size() != other_cdata->_arrays.size()) {
return (int)cdata->_arrays.size() - (int)other_cdata->_arrays.size();
}
for (size_t i = 0; i < cdata->_arrays.size(); ++i) {
if (cdata->_arrays[i] != other_cdata->_arrays[i]) {
return cdata->_arrays[i] < other_cdata->_arrays[i] ? -1 : 1;
}
}
return 0;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::set_name
// Access: Published
// Description: Changes the name of the vertex data. This name is
// reported on the PStats graph for vertex computations.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
set_name(const string &name) {
_name = name;
_char_pcollector = PStatCollector(_animation_pcollector, name);
_skinning_pcollector = PStatCollector(_char_pcollector, "Skinning");
_morphs_pcollector = PStatCollector(_char_pcollector, "Morphs");
_blends_pcollector = PStatCollector(_char_pcollector, "Calc blends");
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::set_usage_hint
// Access: Published
// Description: Changes the UsageHint hint for this vertex data, and
// for all of the arrays that share this data. See
// get_usage_hint().
//
// Don't call this in a downstream thread unless you
// don't mind it blowing away other changes you might
// have recently made in an upstream thread.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
set_usage_hint(GeomVertexData::UsageHint usage_hint) {
CDWriter cdata(_cycler, true);
cdata->_usage_hint = usage_hint;
Arrays::iterator ai;
for (ai = cdata->_arrays.begin();
ai != cdata->_arrays.end();
++ai) {
PT(GeomVertexArrayData) array_obj = (*ai).get_write_pointer();
array_obj->set_usage_hint(usage_hint);
}
clear_cache_stage();
cdata->_modified = Geom::get_next_modified();
cdata->_animated_vertices_modified = UpdateSeq();
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::set_format
// Access: Published
// Description: Changes the format of the vertex data. If the data
// is not empty, this will implicitly change every row
// to match the new format.
//
// Don't call this in a downstream thread unless you
// don't mind it blowing away other changes you might
// have recently made in an upstream thread.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
set_format(const GeomVertexFormat *format) {
Thread *current_thread = Thread::get_current_thread();
nassertv(format->is_registered());
CDLockedReader cdata(_cycler, current_thread);
if (format == cdata->_format) {
// Trivially no-op.
return;
}
CDWriter cdataw(_cycler, cdata, true);
// Put the current data aside, so we can copy it back in below.
CPT(GeomVertexData) orig_data = new GeomVertexData(*this);
// Assign the new format. This means clearing out all of our
// current arrays and replacing them with new, empty arrays.
cdataw->_format = format;
UsageHint usage_hint = cdataw->_usage_hint;
cdataw->_arrays.clear();
int num_arrays = cdataw->_format->get_num_arrays();
for (int i = 0; i < num_arrays; i++) {
PT(GeomVertexArrayData) array = new GeomVertexArrayData
(cdataw->_format->get_array(i), usage_hint);
cdataw->_arrays.push_back(array.p());
}
// Now copy the original data back in. This will automatically
// convert it to the new format.
copy_from(orig_data, false, current_thread);
clear_cache_stage();
cdataw->_modified = Geom::get_next_modified();
cdataw->_animated_vertices.clear();
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::unclean_set_format
// Access: Published
// Description: Changes the format of the vertex data, without
// reformatting the data to match. The data is exactly
// the same after this operation, but will be
// reinterpreted according to the new format. This
// assumes that the new format is fundamentally
// compatible with the old format; in particular, it
// must have the same number of arrays with the same
// stride in each one. No checking is performed that
// the data remains sensible.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
unclean_set_format(const GeomVertexFormat *format) {
Thread *current_thread = Thread::get_current_thread();
nassertv(format->is_registered());
CDLockedReader cdata(_cycler, current_thread);
if (format == cdata->_format) {
// Trivially no-op.
return;
}
#ifndef NDEBUG
nassertv(format->get_num_arrays() == cdata->_format->get_num_arrays());
for (size_t ai = 0; ai < format->get_num_arrays(); ++ai) {
nassertv(format->get_array(ai)->get_stride() == cdata->_format->get_array(ai)->get_stride());
}
nassertv(cdata->_arrays.size() == cdata->_format->get_num_arrays());
#endif // NDEBUG
CDWriter cdataw(_cycler, cdata, true);
// Assign the new format.
cdataw->_format = format;
for (size_t ai = 0; ai < cdataw->_arrays.size(); ++ai) {
PT(GeomVertexArrayData) array_obj = cdataw->_arrays[ai].get_write_pointer();
array_obj->_array_format = format->get_array(ai);
}
clear_cache_stage();
cdataw->_modified = Geom::get_next_modified();
cdataw->_animated_vertices.clear();
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::clear_rows
// Access: Published
// Description: Removes all of the rows from the arrays;
// functionally equivalent to set_num_rows(0) (but
// faster).
//
// Don't call this in a downstream thread unless you
// don't mind it blowing away other changes you might
// have recently made in an upstream thread.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
clear_rows() {
Thread *current_thread = Thread::get_current_thread();
CDWriter cdata(_cycler, true, current_thread);
nassertv(cdata->_format->get_num_arrays() == (int)cdata->_arrays.size());
Arrays::iterator ai;
for (ai = cdata->_arrays.begin();
ai != cdata->_arrays.end();
++ai) {
PT(GeomVertexArrayData) array_obj = (*ai).get_write_pointer();
array_obj->clear_rows();
}
clear_cache_stage();
cdata->_modified = Geom::get_next_modified();
cdata->_animated_vertices.clear();
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::set_transform_table
// Access: Published
// Description: Replaces the TransformTable on this vertex
// data with the indicated table. The length of this
// table should be consistent with the maximum table
// index assigned to the vertices under the
// "transform_index" name.
//
// Don't call this in a downstream thread unless you
// don't mind it blowing away other changes you might
// have recently made in an upstream thread.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
set_transform_table(const TransformTable *table) {
Thread *current_thread = Thread::get_current_thread();
nassertv(table == (TransformTable *)NULL || table->is_registered());
CDWriter cdata(_cycler, true, current_thread);
cdata->_transform_table = (TransformTable *)table;
clear_cache_stage();
cdata->_modified = Geom::get_next_modified();
cdata->_animated_vertices_modified = UpdateSeq();
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::modify_transform_blend_table
// Access: Published
// Description: Returns a modifiable pointer to the current
// TransformBlendTable on this vertex data, if any, or
// NULL if there is not a TransformBlendTable. See
// get_transform_blend_table().
//
// Don't call this in a downstream thread unless you
// don't mind it blowing away other changes you might
// have recently made in an upstream thread.
////////////////////////////////////////////////////////////////////
PT(TransformBlendTable) GeomVertexData::
modify_transform_blend_table() {
CDWriter cdata(_cycler, true);
clear_cache_stage();
cdata->_modified = Geom::get_next_modified();
cdata->_animated_vertices_modified = UpdateSeq();
return cdata->_transform_blend_table.get_write_pointer();
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::set_transform_blend_table
// Access: Published
// Description: Replaces the TransformBlendTable on this vertex
// data with the indicated table. The length of this
// table should be consistent with the maximum table
// index assigned to the vertices under the
// "transform_blend" name.
//
// Don't call this in a downstream thread unless you
// don't mind it blowing away other changes you might
// have recently made in an upstream thread.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
set_transform_blend_table(const TransformBlendTable *table) {
CDWriter cdata(_cycler, true);
cdata->_transform_blend_table = (TransformBlendTable *)table;
clear_cache_stage();
cdata->_modified = Geom::get_next_modified();
cdata->_animated_vertices_modified = UpdateSeq();
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::set_slider_table
// Access: Published
// Description: Replaces the SliderTable on this vertex
// data with the indicated table. There should be an
// entry in this table for each kind of morph offset
// defined in the vertex data.
//
// The SliderTable object must have been registered
// prior to setting it on the GeomVertexData.
//
// Don't call this in a downstream thread unless you
// don't mind it blowing away other changes you might
// have recently made in an upstream thread.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
set_slider_table(const SliderTable *table) {
nassertv(table == (SliderTable *)NULL || table->is_registered());
CDWriter cdata(_cycler, true);
cdata->_slider_table = (SliderTable *)table;
clear_cache_stage();
cdata->_modified = Geom::get_next_modified();
cdata->_animated_vertices_modified = UpdateSeq();
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::request_resident
// Access: Published
// Description: Returns true if the vertex data is currently resident
// in memory. If this returns false, the vertex data will
// be brought back into memory shortly; try again later.
////////////////////////////////////////////////////////////////////
bool GeomVertexData::
request_resident() const {
CDReader cdata(_cycler);
bool resident = true;
Arrays::const_iterator ai;
for (ai = cdata->_arrays.begin();
ai != cdata->_arrays.end();
++ai) {
if (!(*ai).get_read_pointer()->request_resident()) {
resident = false;
}
}
return resident;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::copy_from
// Access: Published
// Description: Copies all the data from the other array into the
// corresponding data types in this array, by matching
// data types name-by-name.
//
// keep_data_objects specifies what to do when one or
// more of the arrays can be copied without the need to
// apply any conversion operation. If it is true, the
// original GeomVertexArrayData objects in this object
// are retained, and their data arrays are copied
// byte-by-byte from the source; if it is false, then the
// GeomVertexArrayData objects are copied pointerwise
// from the source.
//
// Don't call this in a downstream thread unless you
// don't mind it blowing away other changes you might
// have recently made in an upstream thread.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
copy_from(const GeomVertexData *source, bool keep_data_objects,
Thread *current_thread) {
const GeomVertexFormat *source_format = source->get_format();
const GeomVertexFormat *dest_format = get_format();
int num_rows = source->get_num_rows();
int num_arrays = source_format->get_num_arrays();
int source_i;
// First, check to see if any arrays can be simply appropriated for
// the new format, without changing the data.
pset<int> done_arrays;
for (source_i = 0; source_i < num_arrays; ++source_i) {
const GeomVertexArrayFormat *source_array_format =
source_format->get_array(source_i);
bool array_done = false;
int dest_num_arrays = dest_format->get_num_arrays();
for (int dest_i = 0;
dest_i < dest_num_arrays && !array_done;
++dest_i) {
const GeomVertexArrayFormat *dest_array_format =
dest_format->get_array(dest_i);
if (dest_array_format->is_data_subset_of(*source_array_format)) {
// Great! Just use the same data for this one.
if (keep_data_objects) {
// Copy the data, but keep the same GeomVertexArrayData object.
PT(GeomVertexArrayData) dest_data = modify_array(dest_i);
CPT(GeomVertexArrayData) source_data = source->get_array(source_i);
dest_data->modify_handle()->copy_data_from(source_data->get_handle());
} else {
// Copy the GeomVertexArrayData object.
if (get_array(dest_i) != source->get_array(source_i)) {
set_array(dest_i, source->get_array(source_i));
}
}
array_done = true;
done_arrays.insert(dest_i);
}
}
}
// Now make sure the arrays we didn't share are all filled in.
reserve_num_rows(num_rows);
set_num_rows(num_rows);
// Now go back through and copy any data that's left over.
for (source_i = 0; source_i < num_arrays; ++source_i) {
CPT(GeomVertexArrayData) array_obj = source->get_array(source_i);
CPT(GeomVertexArrayDataHandle) array_handle = array_obj->get_handle();
const unsigned char *array_data = array_handle->get_read_pointer(true);
const GeomVertexArrayFormat *source_array_format = source_format->get_array(source_i);
int num_columns = source_array_format->get_num_columns();
for (int di = 0; di < num_columns; ++di) {
const GeomVertexColumn *source_column = source_array_format->get_column(di);
int dest_i = dest_format->get_array_with(source_column->get_name());
if (dest_i >= 0 && done_arrays.count(dest_i) == 0) {
// The data type exists in the new format; we have to copy it.
const GeomVertexArrayFormat *dest_array_format =
dest_format->get_array(dest_i);
const GeomVertexColumn *dest_column =
dest_array_format->get_column(source_column->get_name());
nassertv(dest_column != (const GeomVertexColumn *)NULL);
if (dest_column->is_bytewise_equivalent(*source_column)) {
// We can do a quick bytewise copy.
PT(GeomVertexArrayData) dest_array_obj = modify_array(dest_i);
PT(GeomVertexArrayDataHandle) dest_handle = dest_array_obj->modify_handle();
unsigned char *dest_array_data = dest_handle->get_write_pointer();
bytewise_copy(dest_array_data + dest_column->get_start(),
dest_array_format->get_stride(),
array_data + source_column->get_start(), source_array_format->get_stride(),
source_column, num_rows);
} else if (dest_column->is_packed_argb() &&
source_column->is_uint8_rgba()) {
// A common special case: OpenGL color to DirectX color.
PT(GeomVertexArrayData) dest_array_obj = modify_array(dest_i);
PT(GeomVertexArrayDataHandle) dest_handle = dest_array_obj->modify_handle();
unsigned char *dest_array_data = dest_handle->get_write_pointer();
uint8_rgba_to_packed_argb
(dest_array_data + dest_column->get_start(),
dest_array_format->get_stride(),
array_data + source_column->get_start(), source_array_format->get_stride(),
num_rows);
} else if (dest_column->is_uint8_rgba() &&
source_column->is_packed_argb()) {
// Another common special case: DirectX color to OpenGL
// color.
PT(GeomVertexArrayData) dest_array_obj = modify_array(dest_i);
PT(GeomVertexArrayDataHandle) dest_handle = dest_array_obj->modify_handle();
unsigned char *dest_array_data = dest_handle->get_write_pointer();
packed_argb_to_uint8_rgba
(dest_array_data + dest_column->get_start(),
dest_array_format->get_stride(),
array_data + source_column->get_start(), source_array_format->get_stride(),
num_rows);
} else {
// A generic copy.
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "generic copy " << *dest_column << " from "
<< *source_column << "\n";
}
GeomVertexWriter to(this);
to.set_column(dest_i, dest_column);
GeomVertexReader from(source);
from.set_column(source_i, source_column);
while (!from.is_at_end()) {
to.set_data4(from.get_data4());
}
}
}
}
}
// Also convert the animation tables as necessary.
const GeomVertexAnimationSpec &source_animation = source_format->get_animation();
const GeomVertexAnimationSpec &dest_animation = dest_format->get_animation();
if (source_animation != dest_animation) {
if (dest_animation.get_animation_type() == AT_hardware) {
// Convert Panda-style animation tables to hardware-style
// animation tables.
CPT(TransformBlendTable) blend_table = source->get_transform_blend_table();
if (blend_table != (TransformBlendTable *)NULL) {
PT(TransformTable) transform_table = new TransformTable;
TransformMap already_added;
if (dest_animation.get_indexed_transforms()) {
// Build an indexed transform array. This is easier; this
// means we can put the blends in any order.
GeomVertexWriter weight(this, InternalName::get_transform_weight());
GeomVertexWriter index(this, InternalName::get_transform_index());
GeomVertexReader from(source, InternalName::get_transform_blend());
while (!from.is_at_end()) {
const TransformBlend &blend = blend_table->get_blend(from.get_data1i());
LVecBase4 weights = LVecBase4::zero();
LVecBase4i indices(0, 0, 0, 0);
nassertv(blend.get_num_transforms() <= 4);
for (size_t i = 0; i < blend.get_num_transforms(); i++) {
weights[i] = blend.get_weight(i);
indices[i] = add_transform(transform_table, blend.get_transform(i),
already_added);
}
if (weight.has_column()) {
weight.set_data4(weights);
}
index.set_data4i(indices);
}
} else {
// Build a nonindexed transform array. This means we have to
// use the same n transforms, in the same order, for each vertex.
GeomVertexWriter weight(this, InternalName::get_transform_weight());
GeomVertexReader from(source, InternalName::get_transform_blend());
while (!from.is_at_end()) {
const TransformBlend &blend = blend_table->get_blend(from.get_data1i());
LVecBase4 weights = LVecBase4::zero();
for (size_t i = 0; i < blend.get_num_transforms(); i++) {
int index = add_transform(transform_table, blend.get_transform(i),
already_added);
nassertv(index <= 4);
weights[index] = blend.get_weight(i);
}
if (weight.has_column()) {
weight.set_data4(weights);
}
}
}
clear_transform_blend_table();
set_transform_table(TransformTable::register_table(transform_table));
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::copy_row_from
// Access: Published
// Description: Copies a single row of the data from the other array
// into the indicated row of this array. In this case,
// the source format must exactly match the destination
// format.
//
// Don't call this in a downstream thread unless you
// don't mind it blowing away other changes you might
// have recently made in an upstream thread.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
copy_row_from(int dest_row, const GeomVertexData *source,
int source_row, Thread *current_thread) {
const GeomVertexFormat *source_format = source->get_format();
const GeomVertexFormat *dest_format = get_format();
nassertv(source_format == dest_format);
nassertv(source_row >= 0 && source_row < source->get_num_rows());
if (dest_row >= get_num_rows()) {
// Implicitly add enough rows to get to the indicated row.
set_num_rows(dest_row + 1);
}
int num_arrays = source_format->get_num_arrays();
for (int i = 0; i < num_arrays; ++i) {
PT(GeomVertexArrayData) dest_array_obj = modify_array(i);
PT(GeomVertexArrayDataHandle) dest_handle = dest_array_obj->modify_handle();
unsigned char *dest_array_data = dest_handle->get_write_pointer();
CPT(GeomVertexArrayData) source_array_obj = source->get_array(i);
CPT(GeomVertexArrayDataHandle) source_array_handle = source_array_obj->get_handle();
const unsigned char *source_array_data = source_array_handle->get_read_pointer(true);
const GeomVertexArrayFormat *array_format = source_format->get_array(i);
int stride = array_format->get_stride();
memcpy(dest_array_data + stride * dest_row,
source_array_data + stride * source_row,
stride);
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::convert_to
// Access: Published
// Description: Returns a new GeomVertexData that represents the same
// contents as this one, with all data types matched up
// name-by-name to the indicated new format.
////////////////////////////////////////////////////////////////////
CPT(GeomVertexData) GeomVertexData::
convert_to(const GeomVertexFormat *new_format) const {
Thread *current_thread = Thread::get_current_thread();
if (new_format == get_format()) {
// Trivial case: no change is needed.
return this;
}
// Look up the new format in our cache--maybe we've recently applied
// it.
PT(CacheEntry) entry;
CacheKey key(new_format);
_cache_lock.acquire();
Cache::const_iterator ci = _cache.find(&key);
if (ci == _cache.end()) {
_cache_lock.release();
} else {
entry = (*ci).second;
_cache_lock.release();
nassertr(entry->_source == this, NULL);
// Here's an element in the cache for this computation. Record a
// cache hit, so this element will stay in the cache a while
// longer.
entry->refresh(current_thread);
CDCacheReader cdata(entry->_cycler);
if (cdata->_result != (GeomVertexData *)NULL) {
return cdata->_result;
}
// The cache entry is stale, but we'll recompute it below. Note
// that there's a small race condition here; another thread might
// recompute the cache at the same time. No big deal, since it'll
// compute the same result.
}
// Okay, convert the data to the new format.
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Converting " << get_num_rows() << " rows from " << *get_format()
<< " to " << *new_format << "\n";
}
PStatTimer timer(_convert_pcollector);
PT(GeomVertexData) new_data =
new GeomVertexData(get_name(), new_format, get_usage_hint());
new_data->set_transform_blend_table(get_transform_blend_table());
new_data->set_slider_table(get_slider_table());
new_data->copy_from(this, false);
// Record the new result in the cache.
if (entry == (CacheEntry *)NULL) {
// Create a new entry for the result.
#ifdef USE_MOVE_SEMANTICS
// We don't need the key anymore, move the pointers into the CacheEntry.
entry = new CacheEntry((GeomVertexData *)this, move(key));
#else
entry = new CacheEntry((GeomVertexData *)this, key);
#endif
{
LightMutexHolder holder(_cache_lock);
bool inserted = ((GeomVertexData *)this)->_cache.insert(Cache::value_type(&entry->_key, entry)).second;
if (!inserted) {
// Some other thread must have beat us to the punch. Never
// mind.
return new_data;
}
}
// And tell the cache manager about the new entry. (It might
// immediately request a delete from the cache of the thing we
// just added.)
entry->record(current_thread);
}
// Finally, store the cached result on the entry.
CDCacheWriter cdata(entry->_cycler, true, current_thread);
cdata->_result = new_data;
return new_data;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::scale_color
// Access: Published
// Description: Returns a new GeomVertexData object with the color
// table modified in-place to apply the indicated scale.
//
// If the vertex data does not include a color column, a
// new one will not be added.
////////////////////////////////////////////////////////////////////
CPT(GeomVertexData) GeomVertexData::
scale_color(const LVecBase4 &color_scale) const {
const GeomVertexColumn *old_column =
get_format()->get_column(InternalName::get_color());
if (old_column == (GeomVertexColumn *)NULL) {
return this;
}
PT(GeomVertexData) new_data = new GeomVertexData(*this);
GeomVertexRewriter data(new_data, InternalName::get_color());
while (!data.is_at_end()) {
LColor color = data.get_data4();
data.set_data4(color[0] * color_scale[0],
color[1] * color_scale[1],
color[2] * color_scale[2],
color[3] * color_scale[3]);
}
return new_data;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::scale_color
// Access: Published
// Description: Returns a new GeomVertexData object with the color
// table replaced with a new color table that has been
// scaled by the indicated value. The new color table
// will be added as a new array; if the old color table
// was interleaved with a previous array, the previous
// array will not be repacked.
////////////////////////////////////////////////////////////////////
CPT(GeomVertexData) GeomVertexData::
scale_color(const LVecBase4 &color_scale, int num_components,
GeomVertexData::NumericType numeric_type,
GeomVertexData::Contents contents) const {
int old_color_array = get_format()->get_array_with(InternalName::get_color());
if (old_color_array == -1) {
// Oops, no color anyway.
return set_color(color_scale, num_components, numeric_type, contents);
}
int num_rows = get_num_rows();
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Scaling color for " << num_rows << " vertices by "
<< color_scale << ".\n";
}
PStatTimer timer(_scale_color_pcollector);
PT(GeomVertexData) new_data = replace_column
(InternalName::get_color(), num_components, numeric_type, contents);
// Now go through and apply the scale, copying it to the new data.
GeomVertexWriter to(new_data, InternalName::get_color());
GeomVertexReader from(this, InternalName::get_color());
for (int i = 0; i < num_rows; i++) {
LColor color = from.get_data4();
to.set_data4(color[0] * color_scale[0],
color[1] * color_scale[1],
color[2] * color_scale[2],
color[3] * color_scale[3]);
}
return new_data;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::set_color
// Access: Published
// Description: Returns a new GeomVertexData object with the color
// data modified in-place with the new value.
//
// If the vertex data does not include a color column, a
// new one will not be added.
////////////////////////////////////////////////////////////////////
CPT(GeomVertexData) GeomVertexData::
set_color(const LColor &color) const {
const GeomVertexColumn *old_column =
get_format()->get_column(InternalName::get_color());
if (old_column == (GeomVertexColumn *)NULL) {
return this;
}
PT(GeomVertexData) new_data = new GeomVertexData(*this);
do_set_color(new_data, color);
return new_data;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::set_color
// Access: Published
// Description: Returns a new GeomVertexData object with the color
// table replaced with a new color table for which each
// vertex has the indicated value. The new color table
// will be added as a new array; if the old color table
// was interleaved with a previous array, the previous
// array will not be repacked.
////////////////////////////////////////////////////////////////////
CPT(GeomVertexData) GeomVertexData::
set_color(const LColor &color, int num_components,
GeomVertexData::NumericType numeric_type,
GeomVertexData::Contents contents) const {
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Setting color for " << get_num_rows() << " vertices to "
<< color << ".\n";
}
PStatTimer timer(_set_color_pcollector);
PT(GeomVertexData) new_data = replace_column
(InternalName::get_color(), num_components, numeric_type, contents);
do_set_color(new_data, color);
return new_data;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::reverse_normals
// Access: Published
// Description: Returns a new GeomVertexData object with the normal
// data modified in-place, so that each lighting normal
// is now facing in the opposite direction.
//
// If the vertex data does not include a normal column,
// this returns the original GeomVertexData object,
// unchanged.
////////////////////////////////////////////////////////////////////
CPT(GeomVertexData) GeomVertexData::
reverse_normals() const {
const GeomVertexColumn *old_column =
get_format()->get_column(InternalName::get_normal());
if (old_column == (GeomVertexColumn *)NULL) {
return this;
}
PT(GeomVertexData) new_data = new GeomVertexData(*this);
GeomVertexRewriter to(new_data, InternalName::get_normal());
while (!to.is_at_end()) {
to.set_data3(-to.get_data3());
}
return new_data;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::animate_vertices
// Access: Published
// Description: Returns a GeomVertexData that represents the results
// of computing the vertex animation on the CPU for this
// GeomVertexData.
//
// If there is no CPU-defined vertex animation on this
// object, this just returns the original object.
//
// If there is vertex animation, but the VertexTransform
// values have not changed since last time, this may
// return the same pointer it returned previously. Even
// if the VertexTransform values have changed, it may
// still return the same pointer, but with its contents
// modified (this is preferred, since it allows the
// graphics backend to update vertex buffers optimally).
//
// If force is false, this method may return immediately
// with stale data, if the vertex data is not completely
// resident. If force is true, this method will never
// return stale data, but may block until the data is
// available.
////////////////////////////////////////////////////////////////////
CPT(GeomVertexData) GeomVertexData::
animate_vertices(bool force, Thread *current_thread) const {
#ifdef DO_PIPELINING
{
// In the pipelining case, we take a simple short-route
// optimization: if the vdata isn't animated, we don't need to
// grab any mutex first.
CDReader cdata(_cycler, current_thread);
if (cdata->_format->get_animation().get_animation_type() != AT_panda) {
return this;
}
}
#endif // DO_PIPELINING
PStatTimer timer(((GeomVertexData *)this)->_char_pcollector, current_thread);
// Now that we've short-circuited the short route, we reasonably
// believe the vdata is animated. Grab the mutex and make sure it's
// still animated after we've acquired it.
CDLockedReader cdata(_cycler, current_thread);
if (cdata->_format->get_animation().get_animation_type() != AT_panda) {
return this;
}
UpdateSeq modified;
{
PStatTimer timer2(((GeomVertexData *)this)->_blends_pcollector, current_thread);
if (!cdata->_transform_blend_table.is_null()) {
if (cdata->_slider_table != (SliderTable *)NULL) {
modified =
max(cdata->_transform_blend_table.get_read_pointer()->get_modified(current_thread),
cdata->_slider_table->get_modified(current_thread));
} else {
modified = cdata->_transform_blend_table.get_read_pointer()->get_modified(current_thread);
}
} else if (cdata->_slider_table != (SliderTable *)NULL) {
modified = cdata->_slider_table->get_modified(current_thread);
} else {
// No transform blend table or slider table--ergo, no vertex
// animation.
return this;
}
}
if (cdata->_animated_vertices_modified == modified &&
cdata->_animated_vertices != (GeomVertexData *)NULL) {
// No changes.
return cdata->_animated_vertices;
}
if (!force && !request_resident()) {
// The vertex data isn't resident. Return the best information
// we've got.
if (cdata->_animated_vertices != (GeomVertexData *)NULL) {
return cdata->_animated_vertices;
}
return this;
}
CDWriter cdataw(((GeomVertexData *)this)->_cycler, cdata, false);
cdataw->_animated_vertices_modified = modified;
((GeomVertexData *)this)->update_animated_vertices(cdataw, current_thread);
return cdataw->_animated_vertices;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::clear_animated_vertices
// Access: Published
// Description: Removes the cache of animated vertices computed by a
// previous call to animate_vertices() within the same
// frame. This will force the next call to
// animate_vertices() to recompute these values from
// scratch. Normally it is not necessary to call this.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
clear_animated_vertices() {
CDWriter cdata(_cycler, true);
cdata->_animated_vertices_modified.clear();
cdata->_animated_vertices.clear();
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::transform_vertices
// Access: Published
// Description: Applies the indicated transform matrix to all of the
// vertices in the GeomVertexData. The transform is
// applied to all "point" and "vector" type columns
// described in the format.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
transform_vertices(const LMatrix4 &mat) {
transform_vertices(mat, 0, get_num_rows());
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::transform_vertices
// Access: Published
// Description: Applies the indicated transform matrix to all of the
// vertices from begin_row up to but not including
// end_row. The transform is applied to all "point" and
// "vector" type columns described in the format.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
transform_vertices(const LMatrix4 &mat, int begin_row, int end_row) {
if (end_row <= begin_row) {
// Trivial no-op.
return;
}
const GeomVertexFormat *format = get_format();
size_t ci;
for (ci = 0; ci < format->get_num_points(); ci++) {
GeomVertexRewriter data(this, format->get_point(ci));
do_transform_point_column(format, data, mat, begin_row, end_row);
}
for (ci = 0; ci < format->get_num_vectors(); ci++) {
GeomVertexRewriter data(this, format->get_vector(ci));
do_transform_vector_column(format, data, mat, begin_row, end_row);
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::transform_vertices
// Access: Published
// Description: Applies the indicated transform matrix to all of the
// vertices mentioned in the sparse array. The
// transform is applied to all "point" and "vector"
// type columns described in the format.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
transform_vertices(const LMatrix4 &mat, const SparseArray &rows) {
if (rows.is_zero()) {
// Trivial no-op.
return;
}
const GeomVertexFormat *format = get_format();
size_t ci;
for (ci = 0; ci < format->get_num_points(); ci++) {
GeomVertexRewriter data(this, format->get_point(ci));
for (size_t i = 0; i < rows.get_num_subranges(); ++i) {
int begin_row = rows.get_subrange_begin(i);
int end_row = rows.get_subrange_end(i);
do_transform_point_column(format, data, mat, begin_row, end_row);
}
}
for (ci = 0; ci < format->get_num_vectors(); ci++) {
GeomVertexRewriter data(this, format->get_vector(ci));
for (size_t i = 0; i < rows.get_num_subranges(); ++i) {
int begin_row = rows.get_subrange_begin(i);
int end_row = rows.get_subrange_end(i);
do_transform_vector_column(format, data, mat, begin_row, end_row);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::do_set_color
// Access: Private, Static
// Description: Fills in the color column of the given vertex data
// object with a constant color. Assumes that there
// is already a color column present.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
do_set_color(GeomVertexData *vdata, const LColor &color) {
// This function is used relatively often (by the SceneGraphReducer,
// when flattening colors, and by the munger, when munging colors),
// so I've written out a version that avoids the performance overhead
// of the packer and GeomVertexWriter.
const GeomVertexFormat *format = vdata->get_format();
const GeomVertexColumn *column;
int array_index;
if (!format->get_array_info(InternalName::get_color(), array_index, column)) {
nassertv(false);
}
size_t stride = format->get_array(array_index)->get_stride();
GeomVertexColumn::Packer *packer = column->_packer;
nassertv(packer != NULL);
// Pack into a buffer, which we will then copy.
unsigned char buffer[32];
size_t bufsize = column->get_total_bytes();
#ifdef STDFLOAT_DOUBLE
packer->set_data4d(buffer, color);
#else
packer->set_data4f(buffer, color);
#endif
PT(GeomVertexArrayDataHandle) handle =
vdata->modify_array(array_index)->modify_handle();
unsigned char *write_ptr = handle->get_write_pointer();
unsigned char *end_ptr = write_ptr + handle->get_data_size_bytes();
write_ptr += column->get_start();
if (bufsize == 4) {
// Most common case.
while (write_ptr < end_ptr) {
write_ptr[0] = buffer[0];
write_ptr[1] = buffer[1];
write_ptr[2] = buffer[2];
write_ptr[3] = buffer[3];
write_ptr += stride;
}
} else {
while (write_ptr < end_ptr) {
memcpy(write_ptr, buffer, bufsize);
write_ptr += stride;
}
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::bytewise_copy
// Access: Private, Static
// Description: Quickly copies data without the need to convert it.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
bytewise_copy(unsigned char *to, int to_stride,
const unsigned char *from, int from_stride,
const GeomVertexColumn *from_type,
int num_records) {
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "bytewise_copy(" << (void *)to << ", " << to_stride
<< ", " << (const void *)from << ", " << from_stride
<< ", " << *from_type << ", " << num_records << ")\n";
}
if (to_stride == from_type->get_total_bytes() &&
from_stride == from_type->get_total_bytes()) {
// Fantastic! It's just a linear array of this one data type.
// Copy the whole thing all at once.
memcpy(to, from, num_records * from_type->get_total_bytes());
} else {
// Ok, it's interleaved in with other data. Copy them one record
// at a time.
while (num_records > 0) {
memcpy(to, from, from_type->get_total_bytes());
to += to_stride;
from += from_stride;
num_records--;
}
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::replace_column
// Access: Published
// Description: Returns a new GeomVertexData object, suitable for
// modification, with the indicated data type replaced
// with a new table filled with undefined values. The
// new table will be added as a new array; if the old
// table was interleaved with a previous array, the
// previous array will not be repacked.
//
// If num_components is 0, the indicated name is simply
// removed from the type, without replacing it with
// anything else.
////////////////////////////////////////////////////////////////////
PT(GeomVertexData) GeomVertexData::
replace_column(InternalName *name, int num_components,
GeomVertexData::NumericType numeric_type,
GeomVertexData::Contents contents) const {
CDReader cdata(_cycler);
PT(GeomVertexFormat) new_format = new GeomVertexFormat(*cdata->_format);
// Remove the old description of the type from the format.
bool removed_type_array = false;
int old_type_array = cdata->_format->get_array_with(name);
if (old_type_array != -1) {
GeomVertexArrayFormat *array_format = new_format->modify_array(old_type_array);
if (array_format->get_num_columns() == 1) {
// Actually, this array didn't have any other data types, so
// just drop the whole array.
new_format->remove_array(old_type_array);
removed_type_array = true;
} else {
// Remove the description for the type, but don't bother to
// repack the array.
array_format->remove_column(name);
}
}
// Now define a new array to contain just the type.
int new_type_array = -1;
if (num_components != 0) {
PT(GeomVertexArrayFormat) type_array_format =
new GeomVertexArrayFormat(name, num_components, numeric_type, contents);
new_type_array = new_format->add_array(type_array_format);
}
CPT(GeomVertexFormat) format =
GeomVertexFormat::register_format(new_format);
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Replacing data type " << *name << "; converting "
<< get_num_rows() << " rows from "
<< *cdata->_format << " to " << *format << "\n";
}
PT(GeomVertexData) new_data = new GeomVertexData(*this, format);
int j = 0;
int num_arrays = get_num_arrays();
for (int i = 0; i < num_arrays; ++i) {
if (i == old_type_array) {
if (!removed_type_array) {
// Pointer-copy the original array that includes the type
// (since it also includes other data).
new_data->set_array(j, get_array(i));
++j;
}
} else {
// Just pointer-copy any arrays other than type.
new_data->set_array(j, get_array(i));
++j;
}
}
if (new_type_array != -1) {
nassertr(j == new_type_array, new_data);
// For the new type array, we set up a temporary array that has
// room for the right number of rows.
PT(GeomVertexArrayData) new_array = new GeomVertexArrayData
(format->get_array(j), get_usage_hint());
new_array->set_num_rows(get_num_rows());
new_data->set_array(j, new_array);
}
return new_data;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::output
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
void GeomVertexData::
output(ostream &out) const {
if (!get_name().empty()) {
out << get_name() << " ";
}
out << get_num_rows() << " rows: " << *get_format();
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::write
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
void GeomVertexData::
write(ostream &out, int indent_level) const {
if (!get_name().empty()) {
indent(out, indent_level) << get_name() << "\n";
}
get_format()->write_with_data(out, indent_level + 2, this);
CPT(TransformBlendTable) table = get_transform_blend_table();
if (table != (TransformBlendTable *)NULL) {
indent(out, indent_level)
<< "Transform blend table:\n";
table->write(out, indent_level + 2);
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::describe_vertex
// Access: Published
// Description: Writes a verbose, human-friendly description of the
// indicated vertex number.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
describe_vertex(ostream &out, int row) const {
nassertv_always(row >= 0 && row < get_num_rows());
out << "Vertex " << row << ":\n";
GeomVertexReader reader(this);
reader.set_row_unsafe(row);
const GeomVertexFormat *format = get_format();
const TransformBlendTable *tb_table = NULL;
if (format->get_animation().get_animation_type() == AT_panda) {
tb_table = get_transform_blend_table();
}
int num_columns = format->get_num_columns();
for (int ci = 0; ci < num_columns; ++ci) {
int ai = format->get_array_with(ci);
const GeomVertexColumn *column = format->get_column(ci);
reader.set_column(ai, column);
int num_values = min(column->get_num_values(), 4);
const LVecBase4 &d = reader.get_data4();
out << " " << *column->get_name();
for (int v = 0; v < num_values; v++) {
out << " " << d[v];
}
out << "\n";
if (column->get_name() == InternalName::get_transform_blend() &&
tb_table != NULL) {
// This is an index into the transform blend table. Look up the
// index and report the vertex weighting.
reader.set_column(ai, column);
int bi = reader.get_data1i();
if (bi >= 0 && (size_t)bi < tb_table->get_num_blends()) {
const TransformBlend &blend = tb_table->get_blend(bi);
out << " " << blend << "\n";
}
}
}
// Also show the raw vertex data, why not?
out << "\nraw data:\n";
int num_arrays = format->get_num_arrays();
for (int ai = 0; ai < num_arrays; ++ai) {
const GeomVertexArrayData *array = get_array(ai);
const GeomVertexArrayFormat *aformat = format->get_array(ai);
nassertv(array != NULL && aformat != NULL);
out << " " << *aformat << "\n";
CPT(GeomVertexArrayDataHandle) handle = array->get_handle();
nassertv(handle != (const GeomVertexArrayDataHandle *)NULL);
const unsigned char *data = handle->get_read_pointer(true);
nassertv(data != NULL);
int stride = aformat->get_stride();
int start = stride * row;
if (data != NULL) {
Datagram dg(data + start, stride);
dg.dump_hex(out, 4);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::clear_cache
// Access: Published
// Description: Removes all of the previously-cached results of
// convert_to().
//
// This blows away the entire cache, upstream and
// downstream the pipeline. Use clear_cache_stage()
// instead if you only want to blow away the cache at
// the current stage and upstream.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
clear_cache() {
LightMutexHolder holder(_cache_lock);
for (Cache::iterator ci = _cache.begin();
ci != _cache.end();
++ci) {
CacheEntry *entry = (*ci).second;
entry->erase();
}
_cache.clear();
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::clear_cache_stage
// Access: Published
// Description: Removes all of the previously-cached results of
// convert_to(), at the current pipeline stage and
// upstream. Does not affect the downstream cache.
//
// Don't call this in a downstream thread unless you
// don't mind it blowing away other changes you might
// have recently made in an upstream thread.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
clear_cache_stage() {
LightMutexHolder holder(_cache_lock);
for (Cache::iterator ci = _cache.begin();
ci != _cache.end();
++ci) {
CacheEntry *entry = (*ci).second;
CDCacheWriter cdata(entry->_cycler);
cdata->_result = NULL;
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::packed_argb_to_uint8_rgba
// Access: Private, Static
// Description: Quickly converts DirectX-style color to OpenGL-style
// color.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
packed_argb_to_uint8_rgba(unsigned char *to, int to_stride,
const unsigned char *from, int from_stride,
int num_records) {
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "packed_argb_to_uint8_rgba(" << (void *)to << ", " << to_stride
<< ", " << (const void *)from << ", " << from_stride
<< ", " << num_records << ")\n";
}
while (num_records > 0) {
PN_uint32 dword = *(const PN_uint32 *)from;
to[0] = unpack_abcd_b(dword);
to[1] = unpack_abcd_c(dword);
to[2] = unpack_abcd_d(dword);
to[3] = unpack_abcd_a(dword);
to += to_stride;
from += from_stride;
num_records--;
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::uint8_rgba_to_packed_argb
// Access: Private, Static
// Description: Quickly converts OpenGL-style color to DirectX-style
// color.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
uint8_rgba_to_packed_argb(unsigned char *to, int to_stride,
const unsigned char *from, int from_stride,
int num_records) {
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "uint8_rgba_to_packed_argb(" << (void *)to << ", " << to_stride
<< ", " << (const void *)from << ", " << from_stride
<< ", " << num_records << ")\n";
}
while (num_records > 0) {
*(PN_uint32 *)to = pack_abcd(from[3], from[0], from[1], from[2]);
to += to_stride;
from += from_stride;
num_records--;
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::update_animated_vertices
// Access: Private
// Description: Recomputes the results of computing the vertex
// animation on the CPU, and applies them to the
// existing animated_vertices object.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
update_animated_vertices(GeomVertexData::CData *cdata, Thread *current_thread) {
PStatTimer timer(_char_pcollector, current_thread);
int num_rows = get_num_rows();
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Animating " << num_rows << " vertices for " << get_name()
<< "\n";
}
const GeomVertexFormat *orig_format = cdata->_format;
CPT(GeomVertexFormat) new_format = orig_format;
if (cdata->_animated_vertices == (GeomVertexData *)NULL) {
new_format = orig_format->get_post_animated_format();
cdata->_animated_vertices =
new GeomVertexData(get_name(), new_format,
min(get_usage_hint(), UH_dynamic));
}
PT(GeomVertexData) new_data = cdata->_animated_vertices;
// We have to make a complete copy of the data first so we can
// modify it. If we were clever, we could maybe just figure out the
// subset of the data that might have changed since last frame, but
// that's too much trouble (and isn't obviously faster than just
// copying the whole thing).
new_data->copy_from(this, true);
// First, apply all of the morphs.
CPT(SliderTable) slider_table = cdata->_slider_table;
if (slider_table != (SliderTable *)NULL) {
PStatTimer timer2(_morphs_pcollector);
int num_morphs = orig_format->get_num_morphs();
for (int mi = 0; mi < num_morphs; mi++) {
CPT(InternalName) slider_name = orig_format->get_morph_slider(mi);
const SparseArray &sliders = slider_table->find_sliders(slider_name);
if (!sliders.is_zero()) {
nassertv(!sliders.is_inverse());
int num_slider_subranges = sliders.get_num_subranges();
for (int sni = 0; sni < num_slider_subranges; ++sni) {
int slider_begin = sliders.get_subrange_begin(sni);
int slider_end = sliders.get_subrange_end(sni);
for (int sn = slider_begin; sn < slider_end; ++sn) {
const VertexSlider *slider = slider_table->get_slider(sn);
const SparseArray &rows = slider_table->get_slider_rows(sn);
nassertv(!rows.is_inverse());
PN_stdfloat slider_value = slider->get_slider();
if (slider_value != 0.0f) {
CPT(InternalName) base_name = orig_format->get_morph_base(mi);
CPT(InternalName) delta_name = orig_format->get_morph_delta(mi);
GeomVertexRewriter data(new_data, base_name);
GeomVertexReader delta(this, delta_name);
int num_subranges = rows.get_num_subranges();
if (data.get_column()->get_num_values() == 4) {
if (data.get_column()->has_homogeneous_coord()) {
// Scale the delta by the homogeneous coordinate.
for (int i = 0; i < num_subranges; ++i) {
int begin = rows.get_subrange_begin(i);
int end = rows.get_subrange_end(i);
data.set_row_unsafe(begin);
delta.set_row_unsafe(begin);
for (int j = begin; j < end; ++j) {
LPoint4 vertex = data.get_data4();
LPoint3 d = delta.get_data3();
d *= slider_value * vertex[3];
data.set_data4(vertex[0] + d[0],
vertex[1] + d[1],
vertex[2] + d[2],
vertex[3]);
}
}
} else {
// Just apply the four-component delta.
for (int i = 0; i < num_subranges; ++i) {
int begin = rows.get_subrange_begin(i);
int end = rows.get_subrange_end(i);
data.set_row_unsafe(begin);
delta.set_row_unsafe(begin);
for (int j = begin; j < end; ++j) {
const LPoint4 &vertex = data.get_data4();
LPoint4 d = delta.get_data4();
data.set_data4(vertex + d * slider_value);
}
}
}
} else {
// 3-component or smaller values; don't worry about a
// homogeneous coordinate.
for (int i = 0; i < num_subranges; ++i) {
int begin = rows.get_subrange_begin(i);
int end = rows.get_subrange_end(i);
data.set_row_unsafe(begin);
delta.set_row_unsafe(begin);
for (int j = begin; j < end; ++j) {
const LPoint3 &vertex = data.get_data3();
LPoint3 d = delta.get_data3();
data.set_data3(vertex + d * slider_value);
}
}
}
}
}
}
}
}
}
// Then apply the transforms.
CPT(TransformBlendTable) tb_table = cdata->_transform_blend_table.get_read_pointer();
if (tb_table != (TransformBlendTable *)NULL) {
// Recompute all the blends up front, so we don't have to test
// each one for staleness at each vertex.
{
PStatTimer timer4(_blends_pcollector);
int num_blends = tb_table->get_num_blends();
for (int bi = 0; bi < num_blends; bi++) {
tb_table->get_blend(bi).update_blend(current_thread);
}
}
// Now go through and apply the transforms.
PStatTimer timer3(_skinning_pcollector);
const SparseArray &rows = tb_table->get_rows();
int num_subranges = rows.get_num_subranges();
int blend_array_index = orig_format->get_array_with(InternalName::get_transform_blend());
if (blend_array_index < 0) {
gobj_cat.warning()
<< "Vertex data " << get_name()
<< " has a transform_blend_table, but no transform_blend data.\n";
return;
}
CPT(GeomVertexArrayFormat) blend_array_format = orig_format->get_array(blend_array_index);
if (blend_array_format->get_stride() == 2 &&
blend_array_format->get_column(0)->get_component_bytes() == 2) {
// The blend indices are a table of ushorts. Optimize this
// common case.
CPT(GeomVertexArrayDataHandle) blend_array_handle = cdata->_arrays[blend_array_index].get_read_pointer()->get_handle(current_thread);
const unsigned short *blendt = (const unsigned short *)blend_array_handle->get_read_pointer(true);
size_t ci;
for (ci = 0; ci < new_format->get_num_points(); ci++) {
GeomVertexRewriter data(new_data, new_format->get_point(ci));
for (int i = 0; i < num_subranges; ++i) {
int begin = rows.get_subrange_begin(i);
int end = rows.get_subrange_end(i);
nassertv(begin < end);
int first_vertex = begin;
int first_bi = blendt[first_vertex];
while (first_vertex < end) {
// At this point, first_vertex is the first of a series of
// vertices that shares the blend index first_bi.
// Scan for the end of this series of vertices--we're
// looking for the next vertex with a different blend index.
int next_vertex = first_vertex;
int next_bi = first_bi;
++next_vertex;
while (next_vertex < end) {
next_bi = blendt[next_vertex];
if (next_bi != first_bi) {
break;
}
++next_vertex;
}
// We've just reached the end of the vertices with a matching
// blend index. Transform all those vertices as a block.
LMatrix4 mat;
tb_table->get_blend(first_bi).get_blend(mat, current_thread);
new_data->do_transform_point_column(new_format, data, mat, first_vertex, next_vertex);
first_vertex = next_vertex;
first_bi = next_bi;
}
}
}
for (ci = 0; ci < new_format->get_num_vectors(); ci++) {
GeomVertexRewriter data(new_data, new_format->get_vector(ci));
for (int i = 0; i < num_subranges; ++i) {
int begin = rows.get_subrange_begin(i);
int end = rows.get_subrange_end(i);
nassertv(begin < end);
int first_vertex = begin;
int first_bi = blendt[first_vertex];
while (first_vertex < end) {
// At this point, first_vertex is the first of a series of
// vertices that shares the blend index first_bi.
// Scan for the end of this series of vertices--we're
// looking for the next vertex with a different blend index.
int next_vertex = first_vertex;
int next_bi = first_bi;
++next_vertex;
while (next_vertex < end) {
next_bi = blendt[next_vertex];
if (next_bi != first_bi) {
break;
}
++next_vertex;
}
// We've just reached the end of the vertices with a matching
// blend index. Transform all those vertices as a block.
LMatrix4 mat;
tb_table->get_blend(first_bi).get_blend(mat, current_thread);
new_data->do_transform_vector_column(new_format, data, mat, first_vertex, next_vertex);
first_vertex = next_vertex;
first_bi = next_bi;
}
}
}
} else {
// The blend indices are anything else. Use the
// GeomVertexReader to iterate through them.
GeomVertexReader blendi(this, InternalName::get_transform_blend());
nassertv(blendi.has_column());
size_t ci;
for (ci = 0; ci < new_format->get_num_points(); ci++) {
GeomVertexRewriter data(new_data, new_format->get_point(ci));
for (int i = 0; i < num_subranges; ++i) {
int begin = rows.get_subrange_begin(i);
int end = rows.get_subrange_end(i);
nassertv(begin < end);
blendi.set_row_unsafe(begin);
int first_vertex = begin;
int first_bi = blendi.get_data1i();
while (first_vertex < end) {
// At this point, first_vertex is the first of a series of
// vertices that shares the blend index first_bi.
// Scan for the end of this series of vertices--we're
// looking for the next vertex with a different blend index.
int next_vertex = first_vertex;
int next_bi = first_bi;
++next_vertex;
while (next_vertex < end) {
next_bi = blendi.get_data1i();
if (next_bi != first_bi) {
break;
}
++next_vertex;
}
// We've just reached the end of the vertices with a matching
// blend index. Transform all those vertices as a block.
LMatrix4 mat;
tb_table->get_blend(first_bi).get_blend(mat, current_thread);
new_data->do_transform_point_column(new_format, data, mat, first_vertex, next_vertex);
first_vertex = next_vertex;
first_bi = next_bi;
}
}
}
for (ci = 0; ci < new_format->get_num_vectors(); ci++) {
GeomVertexRewriter data(new_data, new_format->get_vector(ci));
for (int i = 0; i < num_subranges; ++i) {
int begin = rows.get_subrange_begin(i);
int end = rows.get_subrange_end(i);
nassertv(begin != end);
blendi.set_row_unsafe(begin);
int first_vertex = begin;
int first_bi = blendi.get_data1i();
while (first_vertex < end) {
// At this point, first_vertex is the first of a series of
// vertices that shares the blend index first_bi.
// Scan for the end of this series of vertices--we're
// looking for the next vertex with a different blend index.
int next_vertex = first_vertex;
int next_bi = first_bi;
++next_vertex;
while (next_vertex < end) {
next_bi = blendi.get_data1i();
if (next_bi != first_bi) {
break;
}
++next_vertex;
}
// We've just reached the end of the vertices with a matching
// blend index. Transform all those vertices as a block.
LMatrix4 mat;
tb_table->get_blend(first_bi).get_blend(mat, current_thread);
new_data->do_transform_vector_column(new_format, data, mat, first_vertex, next_vertex);
first_vertex = next_vertex;
first_bi = next_bi;
}
}
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::do_transform_point_column
// Access: Private
// Description: Transforms a range of vertices for one particular
// column, as a point.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
do_transform_point_column(const GeomVertexFormat *format, GeomVertexRewriter &data,
const LMatrix4 &mat, int begin_row, int end_row) {
const GeomVertexColumn *data_column = data.get_column();
int num_values = data_column->get_num_values();
if ((num_values == 3 || num_values == 4) &&
data_column->get_numeric_type() == NT_float32) {
// The table of points is a table of LPoint3f's or LPoint4f's.
// Optimize this common case.
GeomVertexArrayDataHandle *data_handle = data.get_array_handle();
size_t stride = data.get_stride();
size_t num_rows = end_row - begin_row;
unsigned char *datat = data_handle->get_write_pointer();
datat += data_column->get_start() + begin_row * stride;
LMatrix4f matf = LCAST(float, mat);
if (num_values == 3) {
table_xform_point3f(datat, num_rows, stride, matf);
} else {
table_xform_vecbase4f(datat, num_rows, stride, matf);
}
} else if (num_values == 4) {
// Use the GeomVertexRewriter to adjust the 4-component
// points.
data.set_row_unsafe(begin_row);
for (int j = begin_row; j < end_row; ++j) {
LPoint4 vertex = data.get_data4();
data.set_data4(vertex * mat);
}
} else {
// Use the GeomVertexRewriter to adjust the 3-component
// points.
data.set_row_unsafe(begin_row);
for (int j = begin_row; j < end_row; ++j) {
LPoint3 vertex = data.get_data3();
data.set_data3(vertex * mat);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::do_transform_vector_column
// Access: Private
// Description: Transforms a range of vertices for one particular
// column, as a vector.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
do_transform_vector_column(const GeomVertexFormat *format, GeomVertexRewriter &data,
const LMatrix4 &mat, int begin_row, int end_row) {
const GeomVertexColumn *data_column = data.get_column();
int num_values = data_column->get_num_values();
LMatrix4 xform;
bool normalize = false;
if (data_column->get_contents() == C_normal) {
// This is to preserve perpendicularity to the surface.
LVecBase3 scale, shear, hpr;
if (decompose_matrix(mat.get_upper_3(), scale, shear, hpr) &&
IS_NEARLY_EQUAL(scale[0], scale[1]) &&
IS_NEARLY_EQUAL(scale[0], scale[2])) {
if (scale[0] == 1) {
// No scale to worry about.
xform = mat;
} else {
// Simply take the uniform scale out of the transformation.
// Not sure if it might be better to just normalize?
compose_matrix(xform, LVecBase3(1, 1, 1), shear, hpr, LVecBase3::zero());
}
} else {
// There is a non-uniform scale, so we need to do all this to
// preserve orthogonality to the surface.
xform.invert_from(mat);
xform.transpose_in_place();
normalize = true;
}
} else {
xform = mat;
}
if ((num_values == 3 || num_values == 4) &&
data_column->get_numeric_type() == NT_float32) {
// The table of vectors is a table of LVector3f's or LVector4f's.
// Optimize this common case.
GeomVertexArrayDataHandle *data_handle = data.get_array_handle();
size_t stride = data.get_stride();
size_t num_rows = end_row - begin_row;
unsigned char *datat = data_handle->get_write_pointer();
datat += data_column->get_start() + begin_row * stride;
LMatrix4f matf = LCAST(float, xform);
if (normalize) {
table_xform_normal3f(datat, num_rows, stride, matf);
} else if (num_values == 3) {
table_xform_vector3f(datat, num_rows, stride, matf);
} else {
table_xform_vecbase4f(datat, num_rows, stride, matf);
}
} else {
// Use the GeomVertexRewriter to transform the vectors.
data.set_row_unsafe(begin_row);
if (normalize) {
for (int j = begin_row; j < end_row; ++j) {
LVector3 vector = data.get_data3();
vector *= xform;
vector.normalize();
data.set_data3(vector);
}
} else {
for (int j = begin_row; j < end_row; ++j) {
LVector3 vector = data.get_data3();
data.set_data3(vector * xform);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::table_xform_point3f
// Access: Private, Static
// Description: Transforms each of the LPoint3f objects in the
// indicated table by the indicated matrix.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
table_xform_point3f(unsigned char *datat, size_t num_rows, size_t stride,
const LMatrix4f &matf) {
// We don't bother checking for the unaligned case here, because in
// practice it doesn't matter with a 3-component point.
for (size_t i = 0; i < num_rows; ++i) {
LPoint3f &vertex = *(LPoint3f *)(&datat[i * stride]);
vertex *= matf;
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::table_xform_normal3f
// Access: Private, Static
// Description: Transforms each of the LVector3f objects in the
// indicated table by the indicated matrix, and also
// normalizes them.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
table_xform_normal3f(unsigned char *datat, size_t num_rows, size_t stride,
const LMatrix4f &matf) {
// We don't bother checking for the unaligned case here, because in
// practice it doesn't matter with a 3-component vector.
for (size_t i = 0; i < num_rows; ++i) {
LNormalf &vertex = *(LNormalf *)(&datat[i * stride]);
vertex *= matf;
vertex.normalize();
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::table_xform_vector3f
// Access: Private, Static
// Description: Transforms each of the LVector3f objects in the
// indicated table by the indicated matrix.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
table_xform_vector3f(unsigned char *datat, size_t num_rows, size_t stride,
const LMatrix4f &matf) {
// We don't bother checking for the unaligned case here, because in
// practice it doesn't matter with a 3-component vector.
for (size_t i = 0; i < num_rows; ++i) {
LVector3f &vertex = *(LVector3f *)(&datat[i * stride]);
vertex *= matf;
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::table_xform_vecbase4f
// Access: Private, Static
// Description: Transforms each of the LVecBase4f objects in the
// indicated table by the indicated matrix.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
table_xform_vecbase4f(unsigned char *datat, size_t num_rows, size_t stride,
const LMatrix4f &matf) {
#if defined(HAVE_EIGEN) && defined(LINMATH_ALIGN)
// Check if the table is unaligned. If it is, we can't use the
// LVecBase4f object directly, which assumes 16-byte alignment.
if (((size_t)datat & 0xf) != 0 || (stride & 0xf) != 0) {
// Instead, we'll use low-level Eigen calls to multiply out the
// unaligned memory.
Eigen::Map<Eigen::Matrix<float, Eigen::Dynamic, 4, Eigen::RowMajor>, Eigen::Unaligned, Eigen::OuterStride<> > table((float *)datat, num_rows, 4, Eigen::OuterStride<>(stride / sizeof(float)));
for (size_t i = 0; i < num_rows; ++i) {
table.row(i) *= matf._m;
}
return;
}
#endif // HAVE_EIGEN
// If the table is properly aligned (or we don't require alignment),
// we can directly use the high-level LVecBase4f object, which will
// do the right thing.
for (size_t i = 0; i < num_rows; ++i) {
LVecBase4f &vertex = *(LVecBase4f *)(&datat[i * stride]);
vertex *= matf;
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::register_with_read_factory
// Access: Public, Static
// Description: Tells the BamReader how to create objects of type
// GeomVertexData.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
register_with_read_factory() {
BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::write_datagram
// Access: Public, Virtual
// Description: Writes the contents of this object to the datagram
// for shipping out to a Bam file.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
write_datagram(BamWriter *manager, Datagram &dg) {
CopyOnWriteObject::write_datagram(manager, dg);
dg.add_string(_name);
manager->write_cdata(dg, _cycler);
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::make_from_bam
// Access: Protected, Static
// Description: This function is called by the BamReader's factory
// when a new object of type GeomVertexData is encountered
// in the Bam file. It should create the GeomVertexData
// and extract its information from the file.
////////////////////////////////////////////////////////////////////
TypedWritable *GeomVertexData::
make_from_bam(const FactoryParams &params) {
GeomVertexData *object = new GeomVertexData;
DatagramIterator scan;
BamReader *manager;
parse_params(params, scan, manager);
object->fillin(scan, manager);
manager->register_finalize(object);
return object;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::complete_pointers
// Access: Public, Virtual
// Description: Receives an array of pointers, one for each time
// manager->read_pointer() was called in fillin().
// Returns the number of pointers processed.
////////////////////////////////////////////////////////////////////
int GeomVertexData::
complete_pointers(TypedWritable **p_list, BamReader *manager) {
int pi = CopyOnWriteObject::complete_pointers(p_list, manager);
return pi;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::require_fully_complete
// Access: Public, Virtual
// Description: Some objects require all of their nested pointers to
// have been completed before the objects themselves can
// be completed. If this is the case, override this
// method to return true, and be careful with circular
// references (which would make the object unreadable
// from a bam file).
////////////////////////////////////////////////////////////////////
bool GeomVertexData::
require_fully_complete() const {
return true;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::finalize
// Access: Public, Virtual
// Description: Called by the BamReader to perform any final actions
// needed for setting up the object after all objects
// have been read and all pointers have been completed.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
finalize(BamReader *manager) {
// NOTE: This method may be called more than once, because the
// Geom::finalize() will call it explicitly. We have to be prepared
// to accept multiple finalize() calls.
// Now we need to register the format that we have read from the bam
// file (since it doesn't come out of the bam file automatically
// registered). This may change the format's pointer, which we
// should then update our own data to reflect. But since this may
// cause the unregistered object to destruct, we have to also tell
// the BamReader to return the new object from now on.
// This extends to the nested array datas, as well as the transform
// table and slider tables, as well.
CDWriter cdata(_cycler, true);
for (size_t i = 0; i < cdata->_arrays.size(); ++i) {
CPT(GeomVertexFormat) new_format =
GeomVertexFormat::register_format(cdata->_format);
manager->change_pointer(cdata->_format, new_format);
cdata->_format = new_format;
CPT(GeomVertexArrayFormat) new_array_format = new_format->get_array(i);
PT(GeomVertexArrayData) array_obj = cdata->_arrays[i].get_unsafe_pointer();
nassertv(new_array_format->is_data_subset_of(*array_obj->_array_format));
manager->change_pointer(array_obj->_array_format, new_array_format);
array_obj->_array_format = new_array_format;
}
if (cdata->_transform_table != (TransformTable *)NULL) {
CPT(TransformTable) new_transform_table =
TransformTable::register_table(cdata->_transform_table);
manager->change_pointer(cdata->_transform_table, new_transform_table);
cdata->_transform_table = new_transform_table;
}
if (cdata->_slider_table != (SliderTable *)NULL) {
CPT(SliderTable) new_slider_table =
SliderTable::register_table(cdata->_slider_table);
manager->change_pointer(cdata->_slider_table, new_slider_table);
cdata->_slider_table = new_slider_table;
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::fillin
// Access: Protected
// Description: This internal function is called by make_from_bam to
// read in all of the relevant data from the BamFile for
// the new GeomVertexData.
////////////////////////////////////////////////////////////////////
void GeomVertexData::
fillin(DatagramIterator &scan, BamReader *manager) {
CopyOnWriteObject::fillin(scan, manager);
set_name(scan.get_string());
manager->read_cdata(scan, _cycler);
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::CDataCache::make_copy
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
CycleData *GeomVertexData::CDataCache::
make_copy() const {
return new CDataCache(*this);
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::CacheEntry::evict_callback
// Access: Public, Virtual
// Description: Called when the entry is evicted from the cache, this
// should clean up the owning object appropriately.
////////////////////////////////////////////////////////////////////
void GeomVertexData::CacheEntry::
evict_callback() {
LightMutexHolder holder(_source->_cache_lock);
Cache::iterator ci = _source->_cache.find(&_key);
nassertv(ci != _source->_cache.end());
nassertv((*ci).second == this);
_source->_cache.erase(ci);
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::CacheEntry::output
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
void GeomVertexData::CacheEntry::
output(ostream &out) const {
out << "vertex data " << (void *)_source << " to "
<< *_key._modifier;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::CData::make_copy
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
CycleData *GeomVertexData::CData::
make_copy() const {
return new CData(*this);
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::CData::write_datagram
// Access: Public, Virtual
// Description: Writes the contents of this object to the datagram
// for shipping out to a Bam file.
////////////////////////////////////////////////////////////////////
void GeomVertexData::CData::
write_datagram(BamWriter *manager, Datagram &dg) const {
manager->write_pointer(dg, _format);
dg.add_uint8(_usage_hint);
dg.add_uint16(_arrays.size());
Arrays::const_iterator ai;
for (ai = _arrays.begin(); ai != _arrays.end(); ++ai) {
manager->write_pointer(dg, (*ai).get_read_pointer());
}
manager->write_pointer(dg, _transform_table);
manager->write_pointer(dg, _transform_blend_table.get_read_pointer());
manager->write_pointer(dg, _slider_table);
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::CData::complete_pointers
// Access: Public, Virtual
// Description: Receives an array of pointers, one for each time
// manager->read_pointer() was called in fillin().
// Returns the number of pointers processed.
////////////////////////////////////////////////////////////////////
int GeomVertexData::CData::
complete_pointers(TypedWritable **p_list, BamReader *manager) {
int pi = CycleData::complete_pointers(p_list, manager);
_format = DCAST(GeomVertexFormat, p_list[pi++]);
Arrays::iterator ai;
for (ai = _arrays.begin(); ai != _arrays.end(); ++ai) {
(*ai) = DCAST(GeomVertexArrayData, p_list[pi++]);
}
_transform_table = DCAST(TransformTable, p_list[pi++]);
_transform_blend_table = DCAST(TransformBlendTable, p_list[pi++]);
_slider_table = DCAST(SliderTable, p_list[pi++]);
_modified = Geom::get_next_modified();
if (!_arrays.empty() && manager->get_file_minor_ver() < 7) {
// Bam files prior to 6.7 did not store a SparseArray in the
// SliderTable or TransformBlendTable entries. We need to make up
// a SparseArray for each of them that reflects the complete
// number of rows in the data.
SparseArray all_rows;
CPT(GeomVertexArrayData) adata = _arrays[0].get_read_pointer();
all_rows.set_range(0, adata->get_num_rows());
if (_slider_table != (SliderTable *)NULL) {
int num_sliders = _slider_table->get_num_sliders();
for (int i = 0; i < num_sliders; ++i) {
((SliderTable *)_slider_table.p())->set_slider_rows(i, all_rows);
}
}
if (!_transform_blend_table.is_null()) {
_transform_blend_table.get_unsafe_pointer()->set_rows(all_rows);
}
}
return pi;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexData::CData::fillin
// Access: Public, Virtual
// Description: This internal function is called by make_from_bam to
// read in all of the relevant data from the BamFile for
// the new GeomVertexData.
////////////////////////////////////////////////////////////////////
void GeomVertexData::CData::
fillin(DatagramIterator &scan, BamReader *manager) {
manager->read_pointer(scan);
_usage_hint = (UsageHint)scan.get_uint8();
size_t num_arrays = scan.get_uint16();
_arrays.reserve(num_arrays);
for (size_t i = 0; i < num_arrays; ++i) {
manager->read_pointer(scan);
_arrays.push_back(NULL);
}
manager->read_pointer(scan);
manager->read_pointer(scan);
manager->read_pointer(scan);
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineBase::get_num_bytes
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
int GeomVertexDataPipelineBase::
get_num_bytes() const {
int num_bytes = sizeof(GeomVertexData);
GeomVertexData::Arrays::const_iterator ai;
for (ai = _cdata->_arrays.begin(); ai != _cdata->_arrays.end(); ++ai) {
num_bytes += (*ai).get_read_pointer()->get_data_size_bytes();
}
return num_bytes;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineReader::get_num_rows
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
int GeomVertexDataPipelineReader::
get_num_rows() const {
nassertr(_cdata->_format->get_num_arrays() == (int)_cdata->_arrays.size(), 0);
nassertr(_got_array_readers, 0);
if (_cdata->_format->get_num_arrays() == 0) {
// No arrays means no rows. Weird but legal.
return 0;
}
// Look up the answer on the first array (since any array will do).
int stride = _cdata->_format->get_array(0)->get_stride();
return _array_readers[0]->get_data_size_bytes() / stride;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineReader::get_array_info
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
bool GeomVertexDataPipelineReader::
get_array_info(const InternalName *name,
const GeomVertexArrayDataHandle *&array_reader,
int &num_values,
GeomVertexDataPipelineReader::NumericType &numeric_type,
int &start, int &stride) const {
nassertr(_got_array_readers, false);
int array_index;
const GeomVertexColumn *column;
if (_cdata->_format->get_array_info(name, array_index, column)) {
array_reader = _array_readers[array_index];
num_values = column->get_num_values();
numeric_type = column->get_numeric_type();
start = column->get_start();
stride = _cdata->_format->get_array(array_index)->get_stride();
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineReader::get_array_info
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
bool GeomVertexDataPipelineReader::
get_array_info(const InternalName *name,
const GeomVertexArrayDataHandle *&array_reader,
int &num_values,
GeomVertexDataPipelineReader::NumericType &numeric_type,
bool &normalized, int &start, int &stride, int &divisor,
int &num_elements, int &element_stride) const {
nassertr(_got_array_readers, false);
int array_index;
const GeomVertexColumn *column;
if (_cdata->_format->get_array_info(name, array_index, column)) {
array_reader = _array_readers[array_index];
num_values = column->get_num_values();
numeric_type = column->get_numeric_type();
normalized = (column->get_contents() == GeomEnums::C_color);
start = column->get_start();
stride = _cdata->_format->get_array(array_index)->get_stride();
divisor = _cdata->_format->get_array(array_index)->get_divisor();
num_elements = column->get_num_elements();
element_stride = column->get_element_stride();
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineReader::get_vertex_info
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
bool GeomVertexDataPipelineReader::
get_vertex_info(const GeomVertexArrayDataHandle *&array_reader,
int &num_values,
GeomVertexDataPipelineReader::NumericType &numeric_type,
int &start, int &stride) const {
nassertr(_got_array_readers, false);
int array_index = _cdata->_format->get_vertex_array_index();
if (array_index >= 0) {
const GeomVertexColumn *column = _cdata->_format->get_vertex_column();
array_reader = _array_readers[array_index];
num_values = column->get_num_values();
numeric_type = column->get_numeric_type();
start = column->get_start();
stride = _cdata->_format->get_array(array_index)->get_stride();
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineReader::get_normal_info
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
bool GeomVertexDataPipelineReader::
get_normal_info(const GeomVertexArrayDataHandle *&array_reader,
GeomVertexDataPipelineReader::NumericType &numeric_type,
int &start, int &stride) const {
nassertr(_got_array_readers, false);
int array_index = _cdata->_format->get_normal_array_index();
if (array_index >= 0) {
const GeomVertexColumn *column = _cdata->_format->get_normal_column();
array_reader = _array_readers[array_index];
numeric_type = column->get_numeric_type();
start = column->get_start();
stride = _cdata->_format->get_array(array_index)->get_stride();
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineReader::get_color_info
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
bool GeomVertexDataPipelineReader::
get_color_info(const GeomVertexArrayDataHandle *&array_reader,
int &num_values,
GeomVertexDataPipelineReader::NumericType &numeric_type,
int &start, int &stride) const {
nassertr(_got_array_readers, false);
int array_index = _cdata->_format->get_color_array_index();
if (array_index >= 0) {
const GeomVertexColumn *column = _cdata->_format->get_color_column();
array_reader = _array_readers[array_index];
num_values = column->get_num_values();
numeric_type = column->get_numeric_type();
start = column->get_start();
stride = _cdata->_format->get_array(array_index)->get_stride();
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineReader::make_array_readers
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
void GeomVertexDataPipelineReader::
make_array_readers() {
nassertv(!_got_array_readers);
_array_readers.reserve(_cdata->_arrays.size());
GeomVertexData::Arrays::const_iterator ai;
for (ai = _cdata->_arrays.begin(); ai != _cdata->_arrays.end(); ++ai) {
CPT(GeomVertexArrayData) array_obj = (*ai).get_read_pointer();
_array_readers.push_back(array_obj->get_handle(_current_thread));
}
_got_array_readers = true;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineReader::delete_array_readers
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
void GeomVertexDataPipelineReader::
delete_array_readers() {
nassertv(_got_array_readers);
_array_readers.clear();
_got_array_readers = false;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineWriter::get_num_rows
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
int GeomVertexDataPipelineWriter::
get_num_rows() const {
nassertr(_cdata->_format->get_num_arrays() == (int)_cdata->_arrays.size(), 0);
nassertr(_got_array_writers, 0);
if (_cdata->_format->get_num_arrays() == 0) {
// No arrays means no rows. Weird but legal.
return 0;
}
// Look up the answer on the first array (since any array will do).
int stride = _cdata->_format->get_array(0)->get_stride();
return _array_writers[0]->get_data_size_bytes() / stride;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineWriter::set_num_rows
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
bool GeomVertexDataPipelineWriter::
set_num_rows(int n) {
nassertr(_got_array_writers, false);
nassertr(_cdata->_format->get_num_arrays() == (int)_cdata->_arrays.size(), false);
bool any_changed = false;
int color_array = -1;
int orig_color_rows = -1;
for (size_t i = 0; i < _cdata->_arrays.size(); i++) {
if (_array_writers[i]->get_num_rows() != n) {
if (_array_writers[i]->get_object()->has_column(InternalName::get_color())) {
color_array = i;
orig_color_rows = _array_writers[i]->get_num_rows();
}
_array_writers[i]->set_num_rows(n);
any_changed = true;
}
}
if (color_array >= 0 && orig_color_rows < n) {
// We have just added some rows; fill the "color" column with
// (1, 1, 1, 1), for the programmer's convenience.
GeomVertexArrayDataHandle *array_writer = _array_writers[color_array];
const GeomVertexArrayFormat *array_format = array_writer->get_array_format();
const GeomVertexColumn *column =
array_format->get_column(InternalName::get_color());
int stride = array_format->get_stride();
unsigned char *start =
array_writer->get_write_pointer() + column->get_start();
unsigned char *stop = start + array_writer->get_data_size_bytes();
unsigned char *pointer = start + stride * orig_color_rows;
int num_values = column->get_num_values();
switch (column->get_numeric_type()) {
case NT_uint8:
case NT_uint16:
case NT_uint32:
case NT_packed_dcba:
case NT_packed_dabc:
while (pointer < stop) {
memset(pointer, 0xff, column->get_total_bytes());
pointer += stride;
}
break;
case NT_float32:
while (pointer < stop) {
PN_float32 *pi = (PN_float32 *)pointer;
for (int i = 0; i < num_values; i++) {
pi[i] = 1.0f;
}
pointer += stride;
}
break;
case NT_float64:
while (pointer < stop) {
PN_float64 *pi = (PN_float64 *)pointer;
for (int i = 0; i < num_values; i++) {
pi[i] = 1.0;
}
pointer += stride;
}
break;
case NT_stdfloat:
case NT_int8:
case NT_int16:
case NT_int32:
// Shouldn't have this type in the format.
nassertr(false, false);
break;
case NT_packed_ufloat:
while (pointer < stop) {
*(PN_int32 *)pointer = 0x781e03c0;
pointer += stride;
}
break;
}
}
if (any_changed) {
_object->clear_cache_stage();
_cdata->_modified = Geom::get_next_modified();
_cdata->_animated_vertices.clear();
}
return any_changed;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineWriter::unclean_set_num_rows
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
bool GeomVertexDataPipelineWriter::
unclean_set_num_rows(int n) {
nassertr(_got_array_writers, false);
nassertr(_cdata->_format->get_num_arrays() == (int)_cdata->_arrays.size(), false);
bool any_changed = false;
for (size_t i = 0; i < _cdata->_arrays.size(); i++) {
if (_array_writers[i]->get_num_rows() != n) {
if (_array_writers[i]->unclean_set_num_rows(n)) {
any_changed = true;
}
}
}
if (any_changed) {
_object->clear_cache_stage();
_cdata->_modified = Geom::get_next_modified();
_cdata->_animated_vertices.clear();
}
return any_changed;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineWriter::reserve_num_rows
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
bool GeomVertexDataPipelineWriter::
reserve_num_rows(int n) {
nassertr(_got_array_writers, false);
nassertr(_cdata->_format->get_num_arrays() == (int)_cdata->_arrays.size(), false);
bool any_changed = false;
for (size_t i = 0; i < _cdata->_arrays.size(); i++) {
if (_array_writers[i]->reserve_num_rows(n)) {
any_changed = true;
}
}
return any_changed;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineWriter::modify_array
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
PT(GeomVertexArrayData) GeomVertexDataPipelineWriter::
modify_array(int i) {
nassertr(i >= 0 && i < (int)_cdata->_arrays.size(), NULL);
PT(GeomVertexArrayData) new_data;
if (_got_array_writers) {
new_data = _array_writers[i]->get_object();
} else {
new_data = _cdata->_arrays[i].get_write_pointer();
}
_object->clear_cache_stage();
_cdata->_modified = Geom::get_next_modified();
_cdata->_animated_vertices_modified = UpdateSeq();
return new_data;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineWriter::set_array
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
void GeomVertexDataPipelineWriter::
set_array(int i, const GeomVertexArrayData *array) {
nassertv(i >= 0 && i < (int)_cdata->_arrays.size());
_cdata->_arrays[i] = (GeomVertexArrayData *)array;
_object->clear_cache_stage();
_cdata->_modified = Geom::get_next_modified();
_cdata->_animated_vertices_modified = UpdateSeq();
if (_got_array_writers) {
_array_writers[i] = _cdata->_arrays[i].get_write_pointer()->modify_handle(_current_thread);
}
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineWriter::make_array_writers
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
void GeomVertexDataPipelineWriter::
make_array_writers() {
nassertv(!_got_array_writers);
_array_writers.reserve(_cdata->_arrays.size());
GeomVertexData::Arrays::iterator ai;
for (ai = _cdata->_arrays.begin(); ai != _cdata->_arrays.end(); ++ai) {
PT(GeomVertexArrayData) array_obj = (*ai).get_write_pointer();
_array_writers.push_back(array_obj->modify_handle(_current_thread));
}
_object->clear_cache_stage();
_cdata->_modified = Geom::get_next_modified();
_cdata->_animated_vertices_modified = UpdateSeq();
_got_array_writers = true;
}
////////////////////////////////////////////////////////////////////
// Function: GeomVertexDataPipelineWriter::delete_array_writers
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
void GeomVertexDataPipelineWriter::
delete_array_writers() {
nassertv(_got_array_writers);
_array_writers.clear();
_got_array_writers = false;
}