flatten optimization; also SceneGraphReducer::make_compatible_format()
This commit is contained in:
parent
50225ec96e
commit
b9b1e78399
|
|
@ -49,6 +49,7 @@
|
|||
#include "fog.h"
|
||||
#include "fogAttrib.h"
|
||||
#include "geomNode.h"
|
||||
#include "geomTransformer.h"
|
||||
#include "lensNode.h"
|
||||
#include "light.h"
|
||||
#include "lightAttrib.h"
|
||||
|
|
@ -397,6 +398,7 @@ init_libpgraph() {
|
|||
Fog::init_type();
|
||||
FogAttrib::init_type();
|
||||
GeomNode::init_type();
|
||||
GeomTransformer::init_type();
|
||||
LensNode::init_type();
|
||||
Light::init_type();
|
||||
LightAttrib::init_type();
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ PStatCollector GeomTransformer::_apply_set_color_collector("*:Flatten:apply:set
|
|||
PStatCollector GeomTransformer::_apply_scale_color_collector("*:Flatten:apply:scale color");
|
||||
PStatCollector GeomTransformer::_apply_set_format_collector("*:Flatten:apply:set format");
|
||||
|
||||
TypeHandle GeomTransformer::NewCollectedData::_type_handle;
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GeomTransformer::Constructor
|
||||
// Access: Public
|
||||
|
|
@ -68,6 +70,7 @@ GeomTransformer(const GeomTransformer ©) :
|
|||
////////////////////////////////////////////////////////////////////
|
||||
GeomTransformer::
|
||||
~GeomTransformer() {
|
||||
finish_collect(false);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -580,9 +583,18 @@ reverse(GeomNode *node) {
|
|||
// Description: Collects together GeomVertexDatas from different
|
||||
// geoms into one big (or several big) GeomVertexDatas.
|
||||
// Returns the number of unique GeomVertexDatas created.
|
||||
//
|
||||
// If format_only is true, this only makes
|
||||
// GeomVertexFormats compatible; it does not otherwise
|
||||
// combine vertices.
|
||||
//
|
||||
// You should follow this up with a call to
|
||||
// finish_collect(), but you probably don't want to call
|
||||
// this method directly anyway. Call
|
||||
// SceneGraphReducer::collect_vertex_data() instead.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
int GeomTransformer::
|
||||
collect_vertex_data(Geom *geom, int collect_bits) {
|
||||
collect_vertex_data(Geom *geom, int collect_bits, bool format_only) {
|
||||
CPT(GeomVertexData) vdata = geom->get_vertex_data();
|
||||
if (vdata->get_num_rows() > _max_collect_vertices) {
|
||||
// Don't even bother.
|
||||
|
|
@ -609,245 +621,67 @@ collect_vertex_data(Geom *geom, int collect_bits) {
|
|||
key._animation_type = Geom::AT_none;
|
||||
}
|
||||
|
||||
AlreadyCollected::const_iterator ai;
|
||||
ai = _already_collected.find(vdata);
|
||||
if (ai != _already_collected.end()) {
|
||||
AlreadyCollectedMap::const_iterator ai;
|
||||
ai = _already_collected_map.find(vdata);
|
||||
if (ai != _already_collected_map.end()) {
|
||||
// We've previously collected this vertex data; reuse it.
|
||||
const AlreadyCollectedData &acd = (*ai).second;
|
||||
geom->offset_vertices(acd._data, acd._offset);
|
||||
SourceGeom source_geom;
|
||||
source_geom._geom = geom;
|
||||
source_geom._vertex_offset = acd._vertex_offset;
|
||||
acd._ncd->_source_geoms.push_back(source_geom);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We haven't collected this vertex data yet; append the vertices
|
||||
// onto the new data.
|
||||
int num_created = 0;
|
||||
|
||||
NewCollectedData::iterator ni = _new_collected_data.find(key);
|
||||
PT(GeomVertexData) new_data;
|
||||
if (ni != _new_collected_data.end()) {
|
||||
new_data = (*ni).second;
|
||||
// We haven't collected this vertex data yet; associate it with a
|
||||
// new data.
|
||||
NewCollectedMap::iterator ni = _new_collected_map.find(key);
|
||||
NewCollectedData *ncd;
|
||||
if (ni != _new_collected_map.end()) {
|
||||
ncd = (*ni).second;
|
||||
|
||||
} else {
|
||||
// We haven't encountered a compatible GeomVertexData before.
|
||||
// Create a new one.
|
||||
new_data = new GeomVertexData(vdata->get_name(), format,
|
||||
vdata->get_usage_hint());
|
||||
_new_collected_data[key] = new_data;
|
||||
++num_created;
|
||||
// Create a new entry.
|
||||
ncd = new NewCollectedData(vdata);
|
||||
_new_collected_list.push_back(ncd);
|
||||
_new_collected_map[key] = ncd;
|
||||
}
|
||||
|
||||
int offset = new_data->get_num_rows();
|
||||
int new_num_vertices = offset + vdata->get_num_rows();
|
||||
if (new_num_vertices > _max_collect_vertices) {
|
||||
if (ncd->_new_format != format) {
|
||||
ncd->_new_format = format->get_union_format(ncd->_new_format);
|
||||
}
|
||||
|
||||
int this_num_vertices = vdata->get_num_rows();
|
||||
|
||||
if (!format_only &&
|
||||
ncd->_num_vertices + this_num_vertices > _max_collect_vertices) {
|
||||
// Whoa, hold the phone! Too many vertices going into this one
|
||||
// GeomVertexData object; we'd better start over.
|
||||
new_data = new GeomVertexData(vdata->get_name(), format,
|
||||
vdata->get_usage_hint());
|
||||
_new_collected_data[key] = new_data;
|
||||
offset = 0;
|
||||
new_num_vertices = vdata->get_num_rows();
|
||||
++num_created;
|
||||
ncd = new NewCollectedData(vdata);
|
||||
_new_collected_list.push_back(ncd);
|
||||
_new_collected_map[key] = ncd;
|
||||
}
|
||||
|
||||
if (new_data->get_format() != format) {
|
||||
// We're combining two GeomVertexDatas of different formats.
|
||||
// Therefore, we need to expand the format to include the union of
|
||||
// both sets of columns.
|
||||
CPT(GeomVertexFormat) new_format = format->get_union_format(new_data->get_format());
|
||||
new_data->set_format(new_format);
|
||||
nassertr(offset == new_data->get_num_rows(), 0);
|
||||
int vertex_offset = ncd->_num_vertices;
|
||||
|
||||
// Also, convert (non-destructively) the current Geom's vertex
|
||||
// data to the new format, so we can just blindly append the
|
||||
// vertices to new_data, in the lines below.
|
||||
vdata = vdata->convert_to(new_format);
|
||||
format = new_format;
|
||||
}
|
||||
AlreadyCollectedData &acd = _already_collected_map[vdata];
|
||||
acd._ncd = ncd;
|
||||
acd._vertex_offset = vertex_offset;
|
||||
|
||||
new_data->set_num_rows(new_num_vertices);
|
||||
SourceGeom source_geom;
|
||||
source_geom._geom = geom;
|
||||
source_geom._vertex_offset = vertex_offset;
|
||||
ncd->_source_geoms.push_back(source_geom);
|
||||
|
||||
SourceData source_data;
|
||||
source_data._vdata = vdata;
|
||||
source_data._num_vertices = this_num_vertices;
|
||||
|
||||
for (int i = 0; i < vdata->get_num_arrays(); ++i) {
|
||||
PT(GeomVertexArrayData) new_array = new_data->modify_array(i);
|
||||
CPT(GeomVertexArrayData) old_array = vdata->get_array(i);
|
||||
int stride = format->get_array(i)->get_stride();
|
||||
int start_byte = offset * stride;
|
||||
int copy_bytes = old_array->get_data_size_bytes();
|
||||
nassertr(start_byte + copy_bytes == new_array->get_data_size_bytes(), 0);
|
||||
ncd->_source_datas.push_back(source_data);
|
||||
ncd->_num_vertices += this_num_vertices;
|
||||
|
||||
new_array->modify_handle()->copy_subdata_from
|
||||
(start_byte, copy_bytes,
|
||||
old_array->get_handle(), 0, copy_bytes);
|
||||
}
|
||||
|
||||
// Also, copy the animation data (if any). This means combining
|
||||
// transform and/or slider tables, and might therefore mean
|
||||
// remapping transform indices in the vertices. Each of these has a
|
||||
// slightly different way to handle the remapping, because they have
|
||||
// slightly different kinds of data.
|
||||
typedef vector_int IndexMap;
|
||||
|
||||
if (vdata->get_transform_table() != (TransformTable *)NULL ||
|
||||
new_data->get_transform_table() != (TransformTable *)NULL) {
|
||||
// The TransformTable.
|
||||
CPT(TransformTable) old_table;
|
||||
if (vdata->get_transform_table() != (TransformTable *)NULL) {
|
||||
old_table = vdata->get_transform_table();
|
||||
} else {
|
||||
PT(TransformTable) temp_table = new TransformTable;
|
||||
// There's an implicit identity transform for all nodes.
|
||||
PT(VertexTransform) identity_transform = new UserVertexTransform("identity");
|
||||
temp_table->add_transform(identity_transform);
|
||||
old_table = TransformTable::register_table(temp_table);
|
||||
}
|
||||
|
||||
// First, build a mapping of the transforms we already have in the
|
||||
// current table. We must do this because the TransformTable
|
||||
// doesn't automatically unquify index numbers for us (it doesn't
|
||||
// store an index).
|
||||
typedef pmap<const VertexTransform *, int> AddedTransforms;
|
||||
AddedTransforms added_transforms;
|
||||
|
||||
int num_old_transforms = old_table->get_num_transforms();
|
||||
for (int i = 0; i < num_old_transforms; i++) {
|
||||
added_transforms[old_table->get_transform(i)] = i;
|
||||
}
|
||||
|
||||
// Now create a new table. We have to create a new table instead
|
||||
// of modifying the existing one, since a registered
|
||||
// TransformTable cannot be modified.
|
||||
PT(TransformTable) new_table;
|
||||
if (new_data->get_transform_table() != (TransformTable *)NULL) {
|
||||
new_table = new TransformTable(*new_data->get_transform_table());
|
||||
} else {
|
||||
new_table = new TransformTable;
|
||||
}
|
||||
|
||||
// Now walk through the old table and copy over its transforms.
|
||||
// We will build up an IndexMap of old index numbers to new index
|
||||
// numbers while we go, which we can use to modify the vertices.
|
||||
IndexMap transform_map;
|
||||
|
||||
int num_transforms = old_table->get_num_transforms();
|
||||
transform_map.reserve(num_transforms);
|
||||
for (int ti = 0; ti < num_transforms; ++ti) {
|
||||
const VertexTransform *transform = old_table->get_transform(ti);
|
||||
AddedTransforms::iterator ai = added_transforms.find(transform);
|
||||
if (ai != added_transforms.end()) {
|
||||
// Already got this one in the table.
|
||||
transform_map.push_back((*ai).second);
|
||||
} else {
|
||||
// This is a new one.
|
||||
int tj = new_table->add_transform(transform);
|
||||
transform_map.push_back(tj);
|
||||
added_transforms[transform] = tj;
|
||||
}
|
||||
}
|
||||
new_data->set_transform_table(TransformTable::register_table(new_table));
|
||||
|
||||
// And now modify the vertices to update the indices to their new
|
||||
// values in the new table. This requires a nested loop, since
|
||||
// each column of transform_index might define multiple index
|
||||
// values.
|
||||
GeomVertexRewriter index(new_data, InternalName::get_transform_index());
|
||||
if (index.has_column()) {
|
||||
int num_values = index.get_column()->get_num_values();
|
||||
int new_index[4];
|
||||
|
||||
index.set_row(offset);
|
||||
while (!index.is_at_end()) {
|
||||
const int *orig_index = index.get_data4i();
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
nassertr(orig_index[i] >= 0 && orig_index[i] < (int)transform_map.size(), 0);
|
||||
new_index[i] = transform_map[orig_index[i]];
|
||||
}
|
||||
index.set_data4i(new_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vdata->get_transform_blend_table() != (TransformBlendTable *)NULL ||
|
||||
new_data->get_transform_blend_table() != (TransformBlendTable *)NULL) {
|
||||
// The TransformBlendTable. This one is the easiest, because we
|
||||
// can modify it directly, and it will uniquify blend objects for
|
||||
// us.
|
||||
|
||||
CPT(TransformBlendTable) old_btable;
|
||||
if (vdata->get_transform_blend_table() != (TransformBlendTable *)NULL) {
|
||||
old_btable = vdata->get_transform_blend_table();
|
||||
} else {
|
||||
PT(TransformBlendTable) temp_btable;
|
||||
temp_btable = new TransformBlendTable;
|
||||
temp_btable->add_blend(TransformBlend());
|
||||
old_btable = temp_btable;
|
||||
}
|
||||
|
||||
PT(TransformBlendTable) new_btable;
|
||||
if (new_data->get_transform_blend_table() != (TransformBlendTable *)NULL) {
|
||||
new_btable = new_data->modify_transform_blend_table();
|
||||
} else {
|
||||
new_btable = new TransformBlendTable;
|
||||
new_btable->add_blend(TransformBlend());
|
||||
new_data->set_transform_blend_table(new_btable);
|
||||
}
|
||||
|
||||
SparseArray new_rows = old_btable->get_rows();
|
||||
new_rows <<= offset;
|
||||
new_rows |= new_btable->get_rows();
|
||||
new_btable->set_rows(new_rows);
|
||||
|
||||
// We still need to build up the IndexMap.
|
||||
IndexMap blend_map;
|
||||
|
||||
int num_blends = old_btable->get_num_blends();
|
||||
blend_map.reserve(num_blends);
|
||||
for (int bi = 0; bi < num_blends; ++bi) {
|
||||
int bj = new_btable->add_blend(old_btable->get_blend(bi));
|
||||
blend_map.push_back(bj);
|
||||
}
|
||||
|
||||
// Modify the indices. This is simpler than the transform_index,
|
||||
// above, because each column of transform_blend may only define
|
||||
// one index value.
|
||||
GeomVertexRewriter index(new_data, InternalName::get_transform_blend());
|
||||
if (index.has_column()) {
|
||||
index.set_row(offset);
|
||||
while (!index.is_at_end()) {
|
||||
int orig_index = index.get_data1i();
|
||||
nassertr(orig_index >= 0 && orig_index < (int)blend_map.size(), 0);
|
||||
int new_index = blend_map[orig_index];
|
||||
index.set_data1i(new_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vdata->get_slider_table() != (SliderTable *)NULL) {
|
||||
// The SliderTable. This one requires making a copy, like the
|
||||
// TransformTable (since it can't be modified once registered
|
||||
// either), but at least it uniquifies sliders added to it. Also,
|
||||
// it doesn't require indexing into it, so we don't have to build
|
||||
// an IndexMap to modify the vertices with.
|
||||
const SliderTable *old_sliders = vdata->get_slider_table();
|
||||
PT(SliderTable) new_sliders;
|
||||
if (new_data->get_slider_table() != (SliderTable *)NULL) {
|
||||
new_sliders = new SliderTable(*new_data->get_slider_table());
|
||||
} else {
|
||||
new_sliders = new SliderTable;
|
||||
}
|
||||
int num_sliders = old_sliders->get_num_sliders();
|
||||
for (int si = 0; si < num_sliders; ++si) {
|
||||
SparseArray new_rows = old_sliders->get_slider_rows(si);
|
||||
new_rows <<= offset;
|
||||
new_sliders->add_slider(old_sliders->get_slider(si), new_rows);
|
||||
}
|
||||
new_data->set_slider_table(SliderTable::register_table(new_sliders));
|
||||
}
|
||||
|
||||
AlreadyCollectedData &acd = _already_collected[vdata];
|
||||
acd._data = new_data;
|
||||
acd._offset = offset;
|
||||
geom->offset_vertices(new_data, offset);
|
||||
|
||||
return num_created;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -858,11 +692,19 @@ collect_vertex_data(Geom *geom, int collect_bits) {
|
|||
// structures that share the same format into one big
|
||||
// GeomVertexData structure. This is intended to
|
||||
// minimize context switches on the graphics card.
|
||||
//
|
||||
// If format_only is true, this only makes
|
||||
// GeomVertexFormats compatible; it does not otherwise
|
||||
// combine vertices.
|
||||
//
|
||||
// You should follow this up with a call to
|
||||
// finish_collect(), but you probably don't want to call
|
||||
// this method directly anyway. Call
|
||||
// SceneGraphReducer::collect_vertex_data() instead.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
int GeomTransformer::
|
||||
collect_vertex_data(GeomNode *node, int collect_bits) {
|
||||
int num_created = 0;
|
||||
|
||||
collect_vertex_data(GeomNode *node, int collect_bits, bool format_only) {
|
||||
int num_adjusted = 0;
|
||||
GeomTransformer *dynamic = NULL;
|
||||
|
||||
GeomNode::CDWriter cdata(node->_cycler);
|
||||
|
|
@ -880,18 +722,57 @@ collect_vertex_data(GeomNode *node, int collect_bits) {
|
|||
if (dynamic == (GeomTransformer *)NULL) {
|
||||
dynamic = new GeomTransformer(*this);
|
||||
}
|
||||
num_created += dynamic->collect_vertex_data(new_geom, collect_bits);
|
||||
num_adjusted += dynamic->collect_vertex_data(new_geom, collect_bits, format_only);
|
||||
|
||||
} else {
|
||||
num_created += collect_vertex_data(new_geom, collect_bits);
|
||||
num_adjusted += collect_vertex_data(new_geom, collect_bits, format_only);
|
||||
}
|
||||
}
|
||||
|
||||
if (dynamic != (GeomTransformer *)NULL) {
|
||||
num_adjusted += dynamic->finish_collect(format_only);
|
||||
delete dynamic;
|
||||
}
|
||||
|
||||
return num_created;
|
||||
return num_adjusted;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GeomTransformer::finish_collect
|
||||
// Access: Public
|
||||
// Description: This should be called after a call to
|
||||
// collect_vertex_data() to finalize the changes and
|
||||
// apply them to the vertices in the graph. If this is
|
||||
// not called, it will be called automatically by the
|
||||
// GeomTransformer destructor.
|
||||
//
|
||||
// If format_only is true, this returns the number of
|
||||
// GeomVertexDatas modified to use a new format. If
|
||||
// false, it returns the number of GeomVertexDatas
|
||||
// created.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
int GeomTransformer::
|
||||
finish_collect(bool format_only) {
|
||||
int num_adjusted = 0;
|
||||
|
||||
NewCollectedList::iterator nci;
|
||||
for (nci = _new_collected_list.begin();
|
||||
nci != _new_collected_list.end();
|
||||
++nci) {
|
||||
NewCollectedData *ncd = (*nci);
|
||||
if (format_only) {
|
||||
num_adjusted += ncd->apply_format_only_changes();
|
||||
} else {
|
||||
num_adjusted += ncd->apply_collect_changes();
|
||||
}
|
||||
delete ncd;
|
||||
}
|
||||
|
||||
_new_collected_list.clear();
|
||||
_new_collected_map.clear();
|
||||
_already_collected_map.clear();
|
||||
|
||||
return num_adjusted;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -920,3 +801,303 @@ premunge_geom(const Geom *geom, GeomMunger *munger) {
|
|||
|
||||
return geom_copy;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GeomTransformer::NewCollectedData::Constructor
|
||||
// Access: Public
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
GeomTransformer::NewCollectedData::
|
||||
NewCollectedData(const GeomVertexData *source_data) {
|
||||
_new_format = source_data->get_format();
|
||||
_vdata_name = source_data->get_name();
|
||||
_usage_hint = source_data->get_usage_hint();
|
||||
_num_vertices = 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GeomTransformer::NewCollectedData::apply_format_only_changes
|
||||
// Access: Public
|
||||
// Description: Actually adjusts the GeomVertexDatas found in a
|
||||
// collect_vertex_data() format-only call to have the
|
||||
// same vertex format. Returns the number of vdatas
|
||||
// modified.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
int GeomTransformer::NewCollectedData::
|
||||
apply_format_only_changes() {
|
||||
int num_modified = 0;
|
||||
|
||||
// We probably don't need to use a map, since
|
||||
// GeomVertexData::convert_to() already caches its result, but we do
|
||||
// it anyway just in case there's danger of overflowing the cache.
|
||||
// What the heck, it's easy to do.
|
||||
typedef pmap< CPT(GeomVertexData), CPT(GeomVertexData) > VDataMap;
|
||||
VDataMap vdata_map;
|
||||
|
||||
SourceGeoms::iterator sgi;
|
||||
for (sgi = _source_geoms.begin(); sgi != _source_geoms.end(); ++sgi) {
|
||||
SourceGeom &sg = (*sgi);
|
||||
CPT(GeomVertexData) orig_data = sg._geom->get_vertex_data();
|
||||
|
||||
if (orig_data->get_format() != _new_format) {
|
||||
VDataMap::iterator mi = vdata_map.find(orig_data);
|
||||
if (mi != vdata_map.end()) {
|
||||
// Already modified this vdata.
|
||||
sg._geom->set_vertex_data((*mi).second);
|
||||
|
||||
} else {
|
||||
// Modify this vdata to the new format.
|
||||
CPT(GeomVertexData) new_data = orig_data->convert_to(_new_format);
|
||||
vdata_map[orig_data] = new_data;
|
||||
++num_modified;
|
||||
|
||||
sg._geom->set_vertex_data(new_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return num_modified;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GeomTransformer::NewCollectedData::apply_collect_changes
|
||||
// Access: Public
|
||||
// Description: Actually combines all of the vertex datas found in a
|
||||
// previous call to collect_vertex_data().
|
||||
////////////////////////////////////////////////////////////////////
|
||||
int GeomTransformer::NewCollectedData::
|
||||
apply_collect_changes() {
|
||||
if (_num_vertices == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
_new_data =
|
||||
new GeomVertexData(_vdata_name, _new_format, _usage_hint);
|
||||
|
||||
_new_data->unclean_set_num_rows(_num_vertices);
|
||||
|
||||
// Copy each source data into the new GeomVertexData, one at a time.
|
||||
int vertex_offset = 0;
|
||||
SourceDatas::iterator sdi;
|
||||
for (sdi = _source_datas.begin(); sdi != _source_datas.end(); ++sdi) {
|
||||
SourceData &sd = (*sdi);
|
||||
CPT(GeomVertexData) vdata = sd._vdata;
|
||||
|
||||
if (_new_format != vdata->get_format()) {
|
||||
// Convert (non-destructively) the current Geom's vertex
|
||||
// data to the new format, so we can just blindly append the
|
||||
// vertices to _new_data, within append_vdata().
|
||||
vdata = vdata->convert_to(_new_format);
|
||||
}
|
||||
|
||||
append_vdata(vdata, vertex_offset);
|
||||
vertex_offset += sd._num_vertices;
|
||||
}
|
||||
|
||||
nassertr(vertex_offset == _num_vertices, 0);
|
||||
|
||||
if (_new_btable != (TransformBlendTable *)NULL) {
|
||||
_new_btable->set_rows(_new_btable_rows);
|
||||
_new_data->set_transform_blend_table(_new_btable);
|
||||
}
|
||||
|
||||
update_geoms();
|
||||
|
||||
_new_data.clear();
|
||||
_new_btable.clear();
|
||||
_new_btable_rows.clear();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GeomTransformer::NewCollectedData::append_vdata
|
||||
// Access: Public
|
||||
// Description: Appends the vertices from the indicated source
|
||||
// GeomVertexData to the end of the working data.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void GeomTransformer::NewCollectedData::
|
||||
append_vdata(const GeomVertexData *vdata, int vertex_offset) {
|
||||
for (int i = 0; i < vdata->get_num_arrays(); ++i) {
|
||||
PT(GeomVertexArrayData) new_array = _new_data->modify_array(i);
|
||||
CPT(GeomVertexArrayData) old_array = vdata->get_array(i);
|
||||
int stride = _new_format->get_array(i)->get_stride();
|
||||
int start_byte = vertex_offset * stride;
|
||||
int copy_bytes = old_array->get_data_size_bytes();
|
||||
nassertv(start_byte + copy_bytes <= new_array->get_data_size_bytes());
|
||||
|
||||
new_array->modify_handle()->copy_subdata_from
|
||||
(start_byte, copy_bytes,
|
||||
old_array->get_handle(), 0, copy_bytes);
|
||||
}
|
||||
|
||||
// Also, copy the animation data (if any). This means combining
|
||||
// transform and/or slider tables, and might therefore mean
|
||||
// remapping transform indices in the vertices. Each of these has a
|
||||
// slightly different way to handle the remapping, because they have
|
||||
// slightly different kinds of data.
|
||||
|
||||
if (vdata->get_transform_table() != (TransformTable *)NULL ||
|
||||
_new_data->get_transform_table() != (TransformTable *)NULL) {
|
||||
// The TransformTable.
|
||||
CPT(TransformTable) old_table;
|
||||
if (vdata->get_transform_table() != (TransformTable *)NULL) {
|
||||
old_table = vdata->get_transform_table();
|
||||
} else {
|
||||
PT(TransformTable) temp_table = new TransformTable;
|
||||
// There's an implicit identity transform for all nodes.
|
||||
PT(VertexTransform) identity_transform = new UserVertexTransform("identity");
|
||||
temp_table->add_transform(identity_transform);
|
||||
old_table = TransformTable::register_table(temp_table);
|
||||
}
|
||||
|
||||
// First, build a mapping of the transforms we already have in the
|
||||
// current table. We must do this because the TransformTable
|
||||
// doesn't automatically unquify index numbers for us (it doesn't
|
||||
// store an index).
|
||||
typedef pmap<const VertexTransform *, int> AddedTransforms;
|
||||
AddedTransforms added_transforms;
|
||||
|
||||
int num_old_transforms = old_table->get_num_transforms();
|
||||
for (int i = 0; i < num_old_transforms; i++) {
|
||||
added_transforms[old_table->get_transform(i)] = i;
|
||||
}
|
||||
|
||||
// Now create a new table. We have to create a new table instead
|
||||
// of modifying the existing one, since a registered
|
||||
// TransformTable cannot be modified.
|
||||
PT(TransformTable) new_table;
|
||||
if (_new_data->get_transform_table() != (TransformTable *)NULL) {
|
||||
new_table = new TransformTable(*_new_data->get_transform_table());
|
||||
} else {
|
||||
new_table = new TransformTable;
|
||||
}
|
||||
|
||||
// Now walk through the old table and copy over its transforms.
|
||||
// We will build up an IndexMap of old index numbers to new index
|
||||
// numbers while we go, which we can use to modify the vertices.
|
||||
IndexMap transform_map;
|
||||
|
||||
int num_transforms = old_table->get_num_transforms();
|
||||
transform_map.reserve(num_transforms);
|
||||
for (int ti = 0; ti < num_transforms; ++ti) {
|
||||
const VertexTransform *transform = old_table->get_transform(ti);
|
||||
AddedTransforms::iterator ai = added_transforms.find(transform);
|
||||
if (ai != added_transforms.end()) {
|
||||
// Already got this one in the table.
|
||||
transform_map.push_back((*ai).second);
|
||||
} else {
|
||||
// This is a new one.
|
||||
int tj = new_table->add_transform(transform);
|
||||
transform_map.push_back(tj);
|
||||
added_transforms[transform] = tj;
|
||||
}
|
||||
}
|
||||
_new_data->set_transform_table(TransformTable::register_table(new_table));
|
||||
|
||||
// And now modify the vertices to update the indices to their new
|
||||
// values in the new table. This requires a nested loop, since
|
||||
// each column of transform_index might define multiple index
|
||||
// values.
|
||||
GeomVertexRewriter index(_new_data, InternalName::get_transform_index());
|
||||
if (index.has_column()) {
|
||||
int num_values = index.get_column()->get_num_values();
|
||||
int num_rows = vdata->get_num_rows();
|
||||
int new_index[4];
|
||||
|
||||
index.set_row(vertex_offset);
|
||||
for (int ci = 0; ci < num_rows; ++ci) {
|
||||
const int *orig_index = index.get_data4i();
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
nassertv(orig_index[i] >= 0 && orig_index[i] < (int)transform_map.size());
|
||||
new_index[i] = transform_map[orig_index[i]];
|
||||
}
|
||||
index.set_data4i(new_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vdata->get_transform_blend_table() != (TransformBlendTable *)NULL) {
|
||||
// The TransformBlendTable. This one is the easiest, because we
|
||||
// can modify it directly, and it will uniquify blend objects for
|
||||
// us.
|
||||
|
||||
// We have few special optimizations to handle the
|
||||
// TransformBlendTable, since it's a very common case and
|
||||
// therefore worth spending a bit of effort to optimize deeply.
|
||||
|
||||
CPT(TransformBlendTable) old_btable = vdata->get_transform_blend_table();
|
||||
|
||||
if (_new_btable == (TransformBlendTable *)NULL) {
|
||||
_new_btable = new TransformBlendTable;
|
||||
_new_btable->add_blend(TransformBlend());
|
||||
}
|
||||
|
||||
SparseArray new_rows = old_btable->get_rows();
|
||||
new_rows <<= vertex_offset;
|
||||
_new_btable_rows |= new_rows;
|
||||
|
||||
// We still need to build up the IndexMap.
|
||||
IndexMap blend_map;
|
||||
|
||||
int num_blends = old_btable->get_num_blends();
|
||||
blend_map.reserve(num_blends);
|
||||
for (int bi = 0; bi < num_blends; ++bi) {
|
||||
int bj = _new_btable->add_blend(old_btable->get_blend(bi));
|
||||
blend_map.push_back(bj);
|
||||
}
|
||||
|
||||
// Modify the indices. This is simpler than the transform_index,
|
||||
// above, because each column of transform_blend may only define
|
||||
// one index value.
|
||||
GeomVertexRewriter index(_new_data, InternalName::get_transform_blend());
|
||||
if (index.has_column()) {
|
||||
int num_rows = vdata->get_num_rows();
|
||||
index.set_row(vertex_offset);
|
||||
|
||||
for (int ci = 0; ci < num_rows; ++ci) {
|
||||
int orig_index = index.get_data1i();
|
||||
nassertv(orig_index >= 0 && orig_index < (int)blend_map.size());
|
||||
int new_index = blend_map[orig_index];
|
||||
index.set_data1i(new_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vdata->get_slider_table() != (SliderTable *)NULL) {
|
||||
// The SliderTable. This one requires making a copy, like the
|
||||
// TransformTable (since it can't be modified once registered
|
||||
// either), but at least it uniquifies sliders added to it. Also,
|
||||
// it doesn't require indexing into it, so we don't have to build
|
||||
// an IndexMap to modify the vertices with.
|
||||
const SliderTable *old_sliders = vdata->get_slider_table();
|
||||
PT(SliderTable) new_sliders;
|
||||
if (_new_data->get_slider_table() != (SliderTable *)NULL) {
|
||||
new_sliders = new SliderTable(*_new_data->get_slider_table());
|
||||
} else {
|
||||
new_sliders = new SliderTable;
|
||||
}
|
||||
int num_sliders = old_sliders->get_num_sliders();
|
||||
for (int si = 0; si < num_sliders; ++si) {
|
||||
SparseArray new_rows = old_sliders->get_slider_rows(si);
|
||||
new_rows <<= vertex_offset;
|
||||
new_sliders->add_slider(old_sliders->get_slider(si), new_rows);
|
||||
}
|
||||
_new_data->set_slider_table(SliderTable::register_table(new_sliders));
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GeomTransformer::NewCollectedData::update_geoms
|
||||
// Access: Public
|
||||
// Description: Updates all of the source Geoms to reference the new
|
||||
// vertex data.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void GeomTransformer::NewCollectedData::
|
||||
update_geoms() {
|
||||
SourceGeoms::iterator sgi;
|
||||
for (sgi = _source_geoms.begin(); sgi != _source_geoms.end(); ++sgi) {
|
||||
SourceGeom &sg = (*sgi);
|
||||
sg._geom->offset_vertices(_new_data, sg._vertex_offset);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,8 +79,9 @@ public:
|
|||
bool doubleside(GeomNode *node);
|
||||
bool reverse(GeomNode *node);
|
||||
|
||||
int collect_vertex_data(Geom *geom, int collect_bits);
|
||||
int collect_vertex_data(GeomNode *node, int collect_bits);
|
||||
int collect_vertex_data(Geom *geom, int collect_bits, bool format_only);
|
||||
int collect_vertex_data(GeomNode *node, int collect_bits, bool format_only);
|
||||
int finish_collect(bool format_only);
|
||||
|
||||
PT(Geom) premunge_geom(const Geom *geom, GeomMunger *munger);
|
||||
|
||||
|
|
@ -138,14 +139,6 @@ private:
|
|||
typedef pmap<CPT(GeomVertexData), CPT(GeomVertexData) > ReversedNormals;
|
||||
ReversedNormals _reversed_normals;
|
||||
|
||||
class AlreadyCollectedData {
|
||||
public:
|
||||
CPT(GeomVertexData) _data;
|
||||
int _offset;
|
||||
};
|
||||
typedef pmap< CPT(GeomVertexData), AlreadyCollectedData> AlreadyCollected;
|
||||
AlreadyCollected _already_collected;
|
||||
|
||||
class NewCollectedKey {
|
||||
public:
|
||||
INLINE bool operator < (const NewCollectedKey &other) const;
|
||||
|
|
@ -155,14 +148,81 @@ private:
|
|||
Geom::UsageHint _usage_hint;
|
||||
Geom::AnimationType _animation_type;
|
||||
};
|
||||
typedef pmap< NewCollectedKey, PT(GeomVertexData) > NewCollectedData;
|
||||
NewCollectedData _new_collected_data;
|
||||
|
||||
class SourceData {
|
||||
public:
|
||||
const GeomVertexData *_vdata;
|
||||
int _num_vertices;
|
||||
};
|
||||
typedef pvector<SourceData> SourceDatas;
|
||||
class SourceGeom {
|
||||
public:
|
||||
Geom *_geom;
|
||||
int _vertex_offset;
|
||||
};
|
||||
typedef pvector<SourceGeom> SourceGeoms;
|
||||
class NewCollectedData {
|
||||
public:
|
||||
ALLOC_DELETED_CHAIN(NewCollectedData);
|
||||
|
||||
NewCollectedData(const GeomVertexData *source_data);
|
||||
void add_source_data(const GeomVertexData *source_data);
|
||||
int apply_format_only_changes();
|
||||
int apply_collect_changes();
|
||||
|
||||
CPT(GeomVertexFormat) _new_format;
|
||||
string _vdata_name;
|
||||
GeomEnums::UsageHint _usage_hint;
|
||||
SourceDatas _source_datas;
|
||||
SourceGeoms _source_geoms;
|
||||
int _num_vertices;
|
||||
|
||||
private:
|
||||
// These are used just during apply_changes().
|
||||
void append_vdata(const GeomVertexData *vdata, int vertex_offset);
|
||||
void update_geoms();
|
||||
|
||||
typedef vector_int IndexMap;
|
||||
|
||||
PT(GeomVertexData) _new_data;
|
||||
PT(TransformBlendTable) _new_btable;
|
||||
SparseArray _new_btable_rows;
|
||||
|
||||
// We need a TypeHandle just for ALLOC_DELETED_CHAIN.
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
register_type(_type_handle, "GeomTransformer::NewCollectedData");
|
||||
}
|
||||
|
||||
private:
|
||||
static TypeHandle _type_handle;
|
||||
};
|
||||
typedef pvector<NewCollectedData *> NewCollectedList;
|
||||
typedef pmap<NewCollectedKey, NewCollectedData *> NewCollectedMap;
|
||||
NewCollectedList _new_collected_list;
|
||||
NewCollectedMap _new_collected_map;
|
||||
|
||||
class AlreadyCollectedData {
|
||||
public:
|
||||
NewCollectedData *_ncd;
|
||||
int _vertex_offset;
|
||||
};
|
||||
typedef pmap<CPT(GeomVertexData), AlreadyCollectedData> AlreadyCollectedMap;
|
||||
AlreadyCollectedMap _already_collected_map;
|
||||
|
||||
static PStatCollector _apply_vertex_collector;
|
||||
static PStatCollector _apply_texcoord_collector;
|
||||
static PStatCollector _apply_set_color_collector;
|
||||
static PStatCollector _apply_scale_color_collector;
|
||||
static PStatCollector _apply_set_format_collector;
|
||||
|
||||
public:
|
||||
static void init_type() {
|
||||
NewCollectedData::init_type();
|
||||
}
|
||||
};
|
||||
|
||||
#include "geomTransformer.I"
|
||||
|
|
|
|||
|
|
@ -117,6 +117,16 @@ get_num_geom_vertex_datas() const {
|
|||
return _num_geom_vertex_datas;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: SceneGraphAnalyzer::get_num_geom_vertex_formats
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
int SceneGraphAnalyzer::
|
||||
get_num_geom_vertex_formats() const {
|
||||
return _num_geom_vertex_formats;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: SceneGraphAnalyzer::get_vertex_data_size
|
||||
// Access: Published
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ void SceneGraphAnalyzer::
|
|||
clear() {
|
||||
_nodes.clear();
|
||||
_vdatas.clear();
|
||||
_vformats.clear();
|
||||
_vadatas.clear();
|
||||
_unique_vdatas.clear();
|
||||
_unique_vadatas.clear();
|
||||
|
|
@ -78,6 +79,7 @@ clear() {
|
|||
_num_geom_nodes = 0;
|
||||
_num_geoms = 0;
|
||||
_num_geom_vertex_datas = 0;
|
||||
_num_geom_vertex_formats = 0;
|
||||
_vertex_data_size = 0;
|
||||
_prim_data_size = 0;
|
||||
|
||||
|
|
@ -138,7 +140,9 @@ write(ostream &out, int indent_level) const {
|
|||
|
||||
indent(out, indent_level)
|
||||
<< _num_geoms << " Geoms, with " << _num_geom_vertex_datas
|
||||
<< " GeomVertexDatas, appear on " << _num_geom_nodes << " GeomNodes.\n";
|
||||
<< " GeomVertexDatas and " << _num_geom_vertex_formats
|
||||
<< " GeomVertexFormats, appear on " << _num_geom_nodes
|
||||
<< " GeomNodes.\n";
|
||||
|
||||
indent(out, indent_level)
|
||||
<< _num_vertices << " vertices, " << _num_normals << " normals, "
|
||||
|
|
@ -373,6 +377,13 @@ collect_statistics(const Geom *geom) {
|
|||
if (result.second) {
|
||||
// This is the first time we've encountered this vertex data.
|
||||
++_num_geom_vertex_datas;
|
||||
|
||||
CPT(GeomVertexFormat) vformat = vdata->get_format();
|
||||
bool format_inserted = _vformats.insert(vformat).second;
|
||||
if (format_inserted) {
|
||||
// This is the first time we've encountered this vertex format.
|
||||
++_num_geom_vertex_formats;
|
||||
}
|
||||
|
||||
int &dup_count = (*(_unique_vdatas.insert(UniqueVDatas::value_type(vdata, 0)).first)).second;
|
||||
++dup_count;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ PUBLISHED:
|
|||
INLINE int get_num_geom_nodes() const;
|
||||
INLINE int get_num_geoms() const;
|
||||
INLINE int get_num_geom_vertex_datas() const;
|
||||
INLINE int get_num_geom_vertex_formats() const;
|
||||
INLINE int get_vertex_data_size() const;
|
||||
|
||||
INLINE int get_num_vertices() const;
|
||||
|
|
@ -104,6 +105,7 @@ private:
|
|||
|
||||
typedef pmap<PandaNode *, int> Nodes;
|
||||
typedef pmap<CPT(GeomVertexData), VDataTracker> VDatas;
|
||||
typedef pset<CPT(GeomVertexFormat) > VFormats;
|
||||
typedef pset<CPT(GeomVertexArrayData) > VADatas;
|
||||
typedef pmap<const GeomVertexData *, int, IndirectCompareTo<GeomVertexData> > UniqueVDatas;
|
||||
typedef pmap<const GeomVertexArrayData *, int, IndirectCompareTo<GeomVertexArrayData> > UniqueVADatas;
|
||||
|
|
@ -113,6 +115,7 @@ private:
|
|||
|
||||
Nodes _nodes;
|
||||
VDatas _vdatas;
|
||||
VFormats _vformats;
|
||||
VADatas _vadatas;
|
||||
VADatas _prim_vadatas;
|
||||
UniqueVDatas _unique_vdatas;
|
||||
|
|
@ -129,6 +132,7 @@ private:
|
|||
int _num_geom_nodes;
|
||||
int _num_geoms;
|
||||
int _num_geom_vertex_datas;
|
||||
int _num_geom_vertex_formats;
|
||||
size_t _vertex_data_size;
|
||||
size_t _prim_data_size;
|
||||
|
||||
|
|
|
|||
|
|
@ -121,6 +121,37 @@ apply_attribs(PandaNode *node, const AccumulatedAttribs &attribs,
|
|||
r_apply_attribs(node, attribs, attrib_types, transformer);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: SceneGraphReducer::make_compatible_format
|
||||
// Access: Published
|
||||
// Description: Walks through the tree at this node and below and
|
||||
// unifies the GeomVertexFormat for any GeomVertexData
|
||||
// objects that are found, so that all eligible vdatas
|
||||
// (according to collect_bits; see collect_vertex_data)
|
||||
// will share the same vertex format.
|
||||
//
|
||||
// This will add unused columns where necessary to match
|
||||
// formats. It can result in suboptimal performance if
|
||||
// used needlessly.
|
||||
//
|
||||
// There is usually no reason to call this explicitly,
|
||||
// since collect_vertex_data() will do this anyway if it
|
||||
// has not been done already. However, calling it ahead
|
||||
// of time can make that future call to
|
||||
// collect_vertex_data() run a little bit faster.
|
||||
//
|
||||
// The return value is the number of vertex datas
|
||||
// modified.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE int SceneGraphReducer::
|
||||
make_compatible_format(PandaNode *root, int collect_bits) {
|
||||
PStatTimer timer(_collect_collector);
|
||||
int count = 0;
|
||||
count += r_collect_vertex_data(root, collect_bits, _transformer, true);
|
||||
count += _transformer.finish_collect(true);
|
||||
return count;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: SceneGraphReducer::collect_vertex_data
|
||||
// Access: Published
|
||||
|
|
@ -140,7 +171,10 @@ apply_attribs(PandaNode *node, const AccumulatedAttribs &attribs,
|
|||
INLINE int SceneGraphReducer::
|
||||
collect_vertex_data(PandaNode *root, int collect_bits) {
|
||||
PStatTimer timer(_collect_collector);
|
||||
return r_collect_vertex_data(root, collect_bits, _transformer);
|
||||
int count = 0;
|
||||
count += r_collect_vertex_data(root, collect_bits, _transformer, false);
|
||||
count += _transformer.finish_collect(false);
|
||||
return count;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -726,8 +726,8 @@ r_remove_column(PandaNode *node, const InternalName *column,
|
|||
////////////////////////////////////////////////////////////////////
|
||||
int SceneGraphReducer::
|
||||
r_collect_vertex_data(PandaNode *node, int collect_bits,
|
||||
GeomTransformer &transformer) {
|
||||
int num_created = 0;
|
||||
GeomTransformer &transformer, bool format_only) {
|
||||
int num_adjusted = 0;
|
||||
|
||||
int this_node_bits = 0;
|
||||
if (node->is_of_type(ModelNode::get_class_type())) {
|
||||
|
|
@ -746,37 +746,35 @@ r_collect_vertex_data(PandaNode *node, int collect_bits,
|
|||
|
||||
if (node->is_geom_node()) {
|
||||
// When we come to a geom node, collect.
|
||||
if (new_transformer.collect_vertex_data(DCAST(GeomNode, node), collect_bits)) {
|
||||
++num_created;
|
||||
}
|
||||
num_adjusted += new_transformer.collect_vertex_data(DCAST(GeomNode, node), collect_bits, format_only);
|
||||
}
|
||||
|
||||
PandaNode::Children children = node->get_children();
|
||||
int num_children = children.get_num_children();
|
||||
for (int i = 0; i < num_children; ++i) {
|
||||
num_created +=
|
||||
r_collect_vertex_data(children.get_child(i), collect_bits, new_transformer);
|
||||
num_adjusted +=
|
||||
r_collect_vertex_data(children.get_child(i), collect_bits, new_transformer, format_only);
|
||||
}
|
||||
|
||||
num_adjusted += new_transformer.finish_collect(format_only);
|
||||
|
||||
} else {
|
||||
// Keep the same collection.
|
||||
|
||||
if (node->is_geom_node()) {
|
||||
if (transformer.collect_vertex_data(DCAST(GeomNode, node), collect_bits)) {
|
||||
++num_created;
|
||||
}
|
||||
num_adjusted += transformer.collect_vertex_data(DCAST(GeomNode, node), collect_bits, format_only);
|
||||
}
|
||||
|
||||
PandaNode::Children children = node->get_children();
|
||||
int num_children = children.get_num_children();
|
||||
for (int i = 0; i < num_children; ++i) {
|
||||
num_created +=
|
||||
r_collect_vertex_data(children.get_child(i), collect_bits, transformer);
|
||||
num_adjusted +=
|
||||
r_collect_vertex_data(children.get_child(i), collect_bits, transformer, format_only);
|
||||
}
|
||||
}
|
||||
|
||||
Thread::consider_yield();
|
||||
return num_created;
|
||||
return num_adjusted;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ PUBLISHED:
|
|||
|
||||
int remove_column(PandaNode *root, const InternalName *column);
|
||||
|
||||
INLINE int make_compatible_format(PandaNode *root, int collect_bits = ~0);
|
||||
INLINE int collect_vertex_data(PandaNode *root, int collect_bits = ~0);
|
||||
INLINE int make_nonindexed(PandaNode *root, int nonindexed_bits = ~0);
|
||||
void unify(PandaNode *root, bool preserve_order);
|
||||
|
|
@ -173,7 +174,7 @@ protected:
|
|||
GeomTransformer &transformer);
|
||||
|
||||
int r_collect_vertex_data(PandaNode *node, int collect_bits,
|
||||
GeomTransformer &transformer);
|
||||
GeomTransformer &transformer, bool format_only);
|
||||
int r_make_nonindexed(PandaNode *node, int collect_bits);
|
||||
void r_unify(PandaNode *node, int max_indices, bool preserve_order);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue