Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/pointcache/alembic/abc_mesh.cpp')
-rw-r--r--source/blender/pointcache/alembic/abc_mesh.cpp597
1 files changed, 597 insertions, 0 deletions
diff --git a/source/blender/pointcache/alembic/abc_mesh.cpp b/source/blender/pointcache/alembic/abc_mesh.cpp
new file mode 100644
index 00000000000..8183087bc84
--- /dev/null
+++ b/source/blender/pointcache/alembic/abc_mesh.cpp
@@ -0,0 +1,597 @@
+/*
+ * Copyright 2014, Blender Foundation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "alembic.h"
+
+#include "abc_mesh.h"
+
+extern "C" {
+#include "BLI_math.h"
+
+#include "DNA_object_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_modifier_types.h"
+
+#include "BKE_DerivedMesh.h"
+#include "BKE_cdderivedmesh.h"
+#include "BKE_mesh.h"
+
+#include "PIL_time.h"
+}
+
+#include "PTC_api.h"
+
+//#define USE_TIMING
+
+namespace PTC {
+
+using namespace Abc;
+using namespace AbcGeom;
+
+AbcPointCacheWriter::AbcPointCacheWriter(Scene *scene, Object *ob, PointCacheModifierData *pcmd) :
+ PointCacheWriter(scene, ob, pcmd, &m_archive),
+ m_archive(scene, &ob->id, pcmd->point_cache, m_error_handler)
+{
+ set_error_handler(new ModifierErrorHandler(&pcmd->modifier));
+
+ if (m_archive.archive) {
+ OObject root = m_archive.archive.getTop();
+ m_mesh = OPolyMesh(root, m_pcmd->modifier.name, m_archive.frame_sampling_index());
+
+ OPolyMeshSchema &schema = m_mesh.getSchema();
+ OCompoundProperty geom_props = schema.getArbGeomParams();
+ OCompoundProperty user_props = schema.getUserProperties();
+
+ m_param_smooth = OBoolGeomParam(geom_props, "smooth", false, kUniformScope, 1, 0);
+ m_prop_edges = OInt32ArrayProperty(user_props, "edges", 0);
+ m_prop_edges_index = OInt32ArrayProperty(user_props, "edges_index", 0);
+ m_param_poly_normals = ON3fGeomParam(geom_props, "poly_normals", false, kUniformScope, 1, 0);
+ m_param_vertex_normals = ON3fGeomParam(geom_props, "vertex_normals", false, kVertexScope, 1, 0);
+ }
+}
+
+AbcPointCacheWriter::~AbcPointCacheWriter()
+{
+}
+
+/* XXX modifiers are not allowed to generate poly normals on their own!
+ * see assert in DerivedMesh.c : dm_ensure_display_normals
+ */
+#if 0
+static void ensure_normal_data(DerivedMesh *dm)
+{
+ MVert *mverts = dm->getVertArray(dm);
+ MLoop *mloops = dm->getLoopArray(dm);
+ MPoly *mpolys = dm->getPolyArray(dm);
+ CustomData *cdata = dm->getPolyDataLayout(dm);
+ float (*polynors)[3];
+ int totvert = dm->getNumVerts(dm);
+ int totloop = dm->getNumLoops(dm);
+ int totpoly = dm->getNumPolys(dm);
+
+ if (CustomData_has_layer(cdata, CD_NORMAL))
+ polynors = (float (*)[3])CustomData_get_layer(cdata, CD_NORMAL);
+ else
+ polynors = (float (*)[3])CustomData_add_layer(cdata, CD_NORMAL, CD_CALLOC, NULL, totpoly);
+
+ BKE_mesh_calc_normals_poly(mverts, totvert, mloops, mpolys, totloop, totpoly, polynors, false);
+}
+#endif
+
+static P3fArraySample create_sample_positions(DerivedMesh *dm, std::vector<V3f> &data)
+{
+ MVert *mv, *mverts = dm->getVertArray(dm);
+ int i, totvert = dm->getNumVerts(dm);
+
+ data.reserve(totvert);
+ for (i = 0, mv = mverts; i < totvert; ++i, ++mv) {
+ float *co = mv->co;
+ data.push_back(V3f(co[0], co[1], co[2]));
+ }
+
+ return P3fArraySample(data);
+}
+
+static Int32ArraySample create_sample_vertex_indices(DerivedMesh *dm, std::vector<int> &data)
+{
+ MLoop *ml, *mloops = dm->getLoopArray(dm);
+ int i, totloop = dm->getNumLoops(dm);
+
+ data.reserve(totloop);
+ for (i = 0, ml = mloops; i < totloop; ++i, ++ml) {
+ data.push_back(ml->v);
+ }
+
+ return Int32ArraySample(data);
+}
+
+static Int32ArraySample create_sample_loop_counts(DerivedMesh *dm, std::vector<int> &data)
+{
+ MPoly *mp, *mpolys = dm->getPolyArray(dm);
+ int i, totpoly = dm->getNumPolys(dm);
+
+ data.reserve(totpoly);
+ for (i = 0, mp = mpolys; i < totpoly; ++i, ++mp) {
+ data.push_back(mp->totloop);
+ }
+
+ return Int32ArraySample(data);
+}
+
+static OBoolGeomParam::Sample create_sample_poly_smooth(DerivedMesh *dm, std::vector<bool_t> &data)
+{
+ MPoly *mp, *mpolys = dm->getPolyArray(dm);
+ int i, totpoly = dm->getNumPolys(dm);
+
+ data.reserve(totpoly);
+ for (i = 0, mp = mpolys; i < totpoly; ++i, ++mp) {
+ data.push_back((bool)(mp->flag & ME_SMOOTH));
+ }
+
+ OBoolGeomParam::Sample sample;
+ sample.setVals(BoolArraySample(data));
+ sample.setScope(kUniformScope);
+ return sample;
+}
+
+static OInt32ArrayProperty::sample_type create_sample_edge_vertices(DerivedMesh *dm, std::vector<int> &data)
+{
+ MEdge *me, *medges = dm->getEdgeArray(dm);
+ int i, totedge = dm->getNumEdges(dm);
+
+ data.reserve(totedge * 2);
+ for (i = 0, me = medges; i < totedge; ++i, ++me) {
+ data.push_back(me->v1);
+ data.push_back(me->v2);
+ }
+
+ return OInt32ArrayProperty::sample_type(data);
+}
+
+static OInt32ArrayProperty::sample_type create_sample_edge_indices(DerivedMesh *dm, std::vector<int> &data)
+{
+ MLoop *ml, *mloops = dm->getLoopArray(dm);
+ int i, totloop = dm->getNumLoops(dm);
+
+ data.reserve(totloop);
+ for (i = 0, ml = mloops; i < totloop; ++i, ++ml) {
+ data.push_back(ml->e);
+ }
+
+ return OInt32ArrayProperty::sample_type(data);
+}
+
+static N3fArraySample create_sample_loop_normals(DerivedMesh *dm, std::vector<N3f> &data)
+{
+ CustomData *cdata = dm->getLoopDataLayout(dm);
+ float (*nor)[3], (*loopnors)[3];
+ int i, totloop = dm->getNumLoops(dm);
+
+ if (!CustomData_has_layer(cdata, CD_NORMAL))
+ return N3fArraySample();
+
+ loopnors = (float (*)[3])CustomData_get_layer(cdata, CD_NORMAL);
+
+ data.reserve(totloop);
+ for (i = 0, nor = loopnors; i < totloop; ++i, ++nor) {
+ float *vec = *nor;
+ data.push_back(N3f(vec[0], vec[1], vec[2]));
+ }
+
+ return N3fArraySample(data);
+}
+
+static N3fArraySample create_sample_poly_normals(DerivedMesh *dm, std::vector<N3f> &data)
+{
+ CustomData *cdata = dm->getPolyDataLayout(dm);
+ float (*nor)[3], (*polynors)[3];
+ int i, totpoly = dm->getNumPolys(dm);
+
+ if (!CustomData_has_layer(cdata, CD_NORMAL))
+ return N3fArraySample();
+
+ polynors = (float (*)[3])CustomData_get_layer(cdata, CD_NORMAL);
+
+ data.reserve(totpoly);
+ for (i = 0, nor = polynors; i < totpoly; ++i, ++nor) {
+ float *vec = *nor;
+ data.push_back(N3f(vec[0], vec[1], vec[2]));
+ }
+
+ return N3fArraySample(data);
+}
+
+static N3fArraySample create_sample_vertex_normals(DerivedMesh *dm, std::vector<N3f> &data)
+{
+ MVert *mv, *mverts = dm->getVertArray(dm);
+ int i, totvert = dm->getNumVerts(dm);
+
+ data.reserve(totvert);
+ for (i = 0, mv = mverts; i < totvert; ++i, ++mv) {
+ float nor[3];
+
+ normal_short_to_float_v3(nor, mv->no);
+ data.push_back(N3f(nor[0], nor[1], nor[2]));
+ }
+
+ return N3fArraySample(data);
+}
+
+void AbcPointCacheWriter::write_sample()
+{
+ if (!m_archive.archive)
+ return;
+
+ DerivedMesh *output_dm = m_pcmd->output_dm;
+ if (!output_dm)
+ return;
+
+ /* TODO make this optional by a flag? */
+ /* XXX does not work atm, see comment above */
+ /*ensure_normal_data(output_dm);*/
+
+ OPolyMeshSchema &schema = m_mesh.getSchema();
+
+ std::vector<V3f> positions_buffer;
+ std::vector<int> indices_buffer;
+ std::vector<int> counts_buffer;
+ std::vector<bool_t> smooth_buffer;
+ std::vector<int> edges_buffer;
+ std::vector<int> edges_index_buffer;
+ std::vector<N3f> loop_normals_buffer;
+ std::vector<N3f> poly_normals_buffer;
+ std::vector<N3f> vertex_normals_buffer;
+// std::vector<V2f> uvs;
+// V2fArraySample()
+
+ // TODO decide how to handle vertex/face normals, in caching vs. export ...
+// std::vector<V2f> uvs;
+// OV2fGeomParam::Sample uvs(V2fArraySample(uvs), kFacevaryingScope );
+
+ P3fArraySample positions = create_sample_positions(output_dm, positions_buffer);
+ Int32ArraySample indices = create_sample_vertex_indices(output_dm, indices_buffer);
+ Int32ArraySample counts = create_sample_loop_counts(output_dm, counts_buffer);
+ OBoolGeomParam::Sample smooth = create_sample_poly_smooth(output_dm, smooth_buffer);
+ OInt32ArrayProperty::sample_type edges = create_sample_edge_vertices(output_dm, edges_buffer);
+ OInt32ArrayProperty::sample_type edges_index = create_sample_edge_indices(output_dm, edges_index_buffer);
+ N3fArraySample lnormals = create_sample_loop_normals(output_dm, loop_normals_buffer);
+ N3fArraySample pnormals = create_sample_poly_normals(output_dm, poly_normals_buffer);
+ N3fArraySample vnormals = create_sample_vertex_normals(output_dm, vertex_normals_buffer);
+
+ OPolyMeshSchema::Sample sample = OPolyMeshSchema::Sample(
+ positions,
+ indices,
+ counts,
+ OV2fGeomParam::Sample(), /* XXX define how/which UV map should be considered primary for the alembic schema */
+ ON3fGeomParam::Sample(lnormals, kFacevaryingScope)
+ );
+ schema.set(sample);
+
+ if (pnormals.valid())
+ m_param_poly_normals.set(ON3fGeomParam::Sample(pnormals, kUniformScope));
+ if (vnormals.valid())
+ m_param_vertex_normals.set(ON3fGeomParam::Sample(vnormals, kVertexScope));
+
+ m_param_smooth.set(smooth);
+
+ m_prop_edges.set(edges);
+ m_prop_edges_index.set(edges_index);
+}
+
+
+AbcPointCacheReader::AbcPointCacheReader(Scene *scene, Object *ob, PointCacheModifierData *pcmd) :
+ PointCacheReader(scene, ob, pcmd, &m_archive),
+ m_archive(scene, &ob->id, pcmd->point_cache, m_error_handler)
+{
+ set_error_handler(new ModifierErrorHandler(&pcmd->modifier));
+
+ if (m_archive.archive.valid()) {
+ IObject root = m_archive.archive.getTop();
+ if (root.valid() && root.getChild(m_pcmd->modifier.name)) {
+ m_mesh = IPolyMesh(root, m_pcmd->modifier.name);
+
+ IPolyMeshSchema &schema = m_mesh.getSchema();
+ ICompoundProperty geom_props = schema.getArbGeomParams();
+ ICompoundProperty user_props = schema.getUserProperties();
+
+ m_param_loop_normals = schema.getNormalsParam();
+ m_param_poly_normals = IN3fGeomParam(geom_props, "poly_normals", 0);
+ m_param_vertex_normals = IN3fGeomParam(geom_props, "vertex_normals", 0);
+ m_param_smooth = IBoolGeomParam(geom_props, "smooth", 0);
+ m_prop_edges = IInt32ArrayProperty(user_props, "edges", 0);
+ m_prop_edges_index = IInt32ArrayProperty(user_props, "edges_index", 0);
+ }
+ }
+}
+
+AbcPointCacheReader::~AbcPointCacheReader()
+{
+}
+
+static void apply_sample_positions(DerivedMesh *dm, P3fArraySamplePtr sample)
+{
+ MVert *mv, *mverts = dm->getVertArray(dm);
+ int i, totvert = dm->getNumVerts(dm);
+
+ BLI_assert(sample->size() == totvert);
+
+ const V3f *data = sample->get();
+ for (i = 0, mv = mverts; i < totvert; ++i, ++mv) {
+ const V3f &co = data[i];
+ copy_v3_v3(mv->co, co.getValue());
+ }
+}
+
+static void apply_sample_vertex_indices(DerivedMesh *dm, Int32ArraySamplePtr sample)
+{
+ MLoop *ml, *mloops = dm->getLoopArray(dm);
+ int i, totloop = dm->getNumLoops(dm);
+
+ BLI_assert(sample->size() == totloop);
+
+ const int32_t *data = sample->get();
+ for (i = 0, ml = mloops; i < totloop; ++i, ++ml) {
+ ml->v = data[i];
+ }
+}
+
+static void apply_sample_loop_counts(DerivedMesh *dm, Int32ArraySamplePtr sample)
+{
+ MPoly *mp, *mpolys = dm->getPolyArray(dm);
+ int i, totpoly = dm->getNumPolys(dm);
+
+ BLI_assert(sample->size() == totpoly);
+
+ const int32_t *data = sample->get();
+ int loopstart = 0;
+ for (i = 0, mp = mpolys; i < totpoly; ++i, ++mp) {
+ mp->totloop = data[i];
+ mp->loopstart = loopstart;
+
+ loopstart += mp->totloop;
+ }
+}
+
+static void apply_sample_loop_normals(DerivedMesh *dm, N3fArraySamplePtr sample)
+{
+ CustomData *cdata = dm->getLoopDataLayout(dm);
+ float (*nor)[3], (*loopnors)[3];
+ int i, totloop = dm->getNumLoops(dm);
+
+ BLI_assert(sample->size() == totloop);
+
+ if (CustomData_has_layer(cdata, CD_NORMAL))
+ loopnors = (float (*)[3])CustomData_get_layer(cdata, CD_NORMAL);
+ else
+ loopnors = (float (*)[3])CustomData_add_layer(cdata, CD_NORMAL, CD_CALLOC, NULL, totloop);
+
+ const N3f *data = sample->get();
+ for (i = 0, nor = loopnors; i < totloop; ++i, ++nor) {
+ copy_v3_v3(*nor, data[i].getValue());
+ }
+}
+
+static void apply_sample_poly_normals(DerivedMesh *dm, N3fArraySamplePtr sample)
+{
+ CustomData *cdata = dm->getPolyDataLayout(dm);
+ float (*nor)[3], (*polynors)[3];
+ int i, totpoly = dm->getNumPolys(dm);
+
+ BLI_assert(sample->size() == totpoly);
+
+ if (CustomData_has_layer(cdata, CD_NORMAL))
+ polynors = (float (*)[3])CustomData_get_layer(cdata, CD_NORMAL);
+ else
+ polynors = (float (*)[3])CustomData_add_layer(cdata, CD_NORMAL, CD_CALLOC, NULL, totpoly);
+
+ const N3f *data = sample->get();
+ for (i = 0, nor = polynors; i < totpoly; ++i, ++nor) {
+ copy_v3_v3(*nor, data[i].getValue());
+ }
+}
+
+static void apply_sample_vertex_normals(DerivedMesh *dm, N3fArraySamplePtr sample)
+{
+ MVert *mv, *mverts = dm->getVertArray(dm);
+ int i, totvert = dm->getNumVerts(dm);
+
+ BLI_assert(sample->size() == totvert);
+
+ const N3f *data = sample->get();
+ for (i = 0, mv = mverts; i < totvert; ++i, ++mv) {
+ normal_float_to_short_v3(mv->no, data[i].getValue());
+ }
+}
+
+static void apply_sample_poly_smooth(DerivedMesh *dm, BoolArraySamplePtr sample)
+{
+ MPoly *mp, *mpolys = dm->getPolyArray(dm);
+ int i, totpoly = dm->getNumPolys(dm);
+
+ BLI_assert(sample->size() == totpoly);
+
+ const bool_t *data = sample->get();
+ for (i = 0, mp = mpolys; i < totpoly; ++i, ++mp) {
+ if (data[i]) {
+ mp->flag |= ME_SMOOTH;
+ }
+ }
+}
+
+static void apply_sample_edge_vertices(DerivedMesh *dm, Int32ArraySamplePtr sample)
+{
+ MEdge *me, *medges = dm->getEdgeArray(dm);
+ int i, totedge = dm->getNumEdges(dm);
+
+ BLI_assert(sample->size() == totedge * 2);
+
+ const int32_t *data = sample->get();
+ for (i = 0, me = medges; i < totedge; ++i, ++me) {
+ me->v1 = data[(i << 1)];
+ me->v2 = data[(i << 1) + 1];
+ }
+}
+
+static void apply_sample_edge_indices(DerivedMesh *dm, Int32ArraySamplePtr sample)
+{
+ MLoop *ml, *mloops = dm->getLoopArray(dm);
+ int i, totloop = dm->getNumLoops(dm);
+
+ BLI_assert(sample->size() == totloop);
+
+ const int32_t *data = sample->get();
+ for (i = 0, ml = mloops; i < totloop; ++i, ++ml) {
+ ml->e = data[i];
+ }
+}
+
+PTCReadSampleResult AbcPointCacheReader::read_sample(float frame)
+{
+#ifdef USE_TIMING
+ double start_time;
+ double time_get_sample, time_build_mesh, time_calc_edges, time_calc_normals;
+
+#define PROFILE_START \
+ start_time = PIL_check_seconds_timer();
+#define PROFILE_END(var) \
+ var = PIL_check_seconds_timer() - start_time;
+#else
+#define PROFILE_START ;
+#define PROFILE_END(var) ;
+#endif
+
+ /* discard existing result data */
+ discard_result();
+
+ if (!m_mesh.valid())
+ return PTC_READ_SAMPLE_INVALID;
+
+ IPolyMeshSchema &schema = m_mesh.getSchema();
+ if (!schema.valid() || schema.getPositionsProperty().getNumSamples() == 0)
+ return PTC_READ_SAMPLE_INVALID;
+
+ ISampleSelector ss = m_archive.get_frame_sample_selector(frame);
+
+ PROFILE_START;
+ IPolyMeshSchema::Sample sample;
+ schema.get(sample, ss);
+
+ bool has_normals = false;
+ P3fArraySamplePtr positions = sample.getPositions();
+ Int32ArraySamplePtr indices = sample.getFaceIndices();
+ Int32ArraySamplePtr counts = sample.getFaceCounts();
+ N3fArraySamplePtr lnormals, pnormals, vnormals;
+ if (m_param_poly_normals && m_param_poly_normals.getNumSamples() > 0
+ && m_param_vertex_normals && m_param_vertex_normals.getNumSamples() > 0) {
+ pnormals = m_param_poly_normals.getExpandedValue(ss).getVals();
+ vnormals = m_param_vertex_normals.getExpandedValue(ss).getVals();
+
+ /* we need all normal properties defined, otherwise have to recalculate */
+ has_normals = pnormals->valid() && vnormals->valid();
+ }
+ if (has_normals) {
+ /* note: loop normals are not mandatory, but if poly/vertex normals don't exist they get recalculated anyway */
+ if (m_param_loop_normals && m_param_loop_normals.getNumSamples() > 0)
+ lnormals = m_param_loop_normals.getExpandedValue(ss).getVals();
+ }
+
+ BoolArraySamplePtr smooth;
+ if (m_param_smooth && m_param_smooth.getNumSamples() > 0) {
+ IBoolGeomParam::Sample sample_smooth;
+ m_param_smooth.getExpanded(sample_smooth, ss);
+ smooth = sample_smooth.getVals();
+ }
+
+ bool has_edges = false;
+ Int32ArraySamplePtr edges, edges_index;
+ if (m_prop_edges && m_prop_edges.getNumSamples() > 0
+ && m_prop_edges_index && m_prop_edges_index.getNumSamples() > 0) {
+ m_prop_edges.get(edges, ss);
+ m_prop_edges_index.get(edges_index, ss);
+ BLI_assert(edges->size() % 2 == 0); /* 2 vertex indices per edge */
+
+ has_edges = edges->valid() && edges_index->valid();
+ }
+ PROFILE_END(time_get_sample);
+
+ PROFILE_START;
+ int totverts = positions->size();
+ int totloops = indices->size();
+ int totpolys = counts->size();
+ int totedges = has_edges ? edges->size() >> 1 : 0;
+ m_result = CDDM_new(totverts, totedges, 0, totloops, totpolys);
+
+ apply_sample_positions(m_result, positions);
+ apply_sample_vertex_indices(m_result, indices);
+ apply_sample_loop_counts(m_result, counts);
+ if (has_normals) {
+ apply_sample_poly_normals(m_result, lnormals);
+ apply_sample_vertex_normals(m_result, vnormals);
+
+ if (lnormals->valid())
+ apply_sample_loop_normals(m_result, pnormals);
+ }
+ else {
+ /* make sure normals are recalculated if there is no sample data */
+ m_result->dirty = (DMDirtyFlag)((int)m_result->dirty | DM_DIRTY_NORMALS);
+ }
+ if (has_edges) {
+ apply_sample_edge_vertices(m_result, edges);
+ apply_sample_edge_indices(m_result, edges_index);
+ }
+ if (smooth)
+ apply_sample_poly_smooth(m_result, smooth);
+ PROFILE_END(time_build_mesh);
+
+ PROFILE_START;
+ if (!has_edges)
+ CDDM_calc_edges(m_result);
+ PROFILE_END(time_calc_edges);
+
+ PROFILE_START;
+ DM_ensure_normals(m_result); /* only recalculates normals if no valid samples were found (has_normals == false) */
+ PROFILE_END(time_calc_normals);
+
+// BLI_assert(DM_is_valid(m_result));
+
+#ifdef USE_TIMING
+ printf("-------- Point Cache Timing --------\n");
+ printf("read sample: %f seconds\n", time_get_sample);
+ printf("build mesh: %f seconds\n", time_build_mesh);
+ printf("calculate edges: %f seconds\n", time_calc_edges);
+ printf("calculate normals: %f seconds\n", time_calc_normals);
+ printf("------------------------------------\n");
+#endif
+
+ return PTC_READ_SAMPLE_EXACT;
+}
+
+/* ==== API ==== */
+
+Writer *abc_writer_point_cache(Scene *scene, Object *ob, PointCacheModifierData *pcmd)
+{
+ return new AbcPointCacheWriter(scene, ob, pcmd);
+}
+
+Reader *abc_reader_point_cache(Scene *scene, Object *ob, PointCacheModifierData *pcmd)
+{
+ return new AbcPointCacheReader(scene, ob, pcmd);
+}
+
+} /* namespace PTC */