Added adaptiveEvaluator class inspired by Sergey's work in blender.

Do feature adaptive refinement, then use the cpuEvalLimit API to evaluate
grids of points on faces.

Test harness is tessellateObjFile which has a -blender option to trigger
the gridding tessellation code.
This commit is contained in:
Dirk Van Gelder 2013-12-31 22:35:52 -08:00
parent 8b65c1b2f4
commit 7e0f6955c7
8 changed files with 606 additions and 22 deletions

View File

@ -28,7 +28,7 @@ if( OPENGL_FOUND AND (GLEW_FOUND AND GLFW_FOUND) OR (APPLE AND GLFW_FOUND))
add_subdirectory(glStencilViewer)
add_subdirectory(simpleCpu)
add_subdirectory(limitEval)
add_subdirectory(projectTest)
add_subdirectory(tessellateObjFile)
if (NOT APPLE)
add_subdirectory(uvViewer)
endif()

View File

@ -61,18 +61,17 @@ set(PLATFORM_LIBRARIES
include_directories(
${PROJECT_SOURCE_DIR}/opensubdiv
${PROJECT_SOURCE_DIR}/examples
)
_add_executable(projectTest
_add_executable(tessellateObjFile
main.cpp
)
target_link_libraries(projectTest
target_link_libraries(tessellateObjFile
osdutil
${PLATFORM_LIBRARIES}
)
install(TARGETS projectTest DESTINATION ${CMAKE_BINDIR_BASE})
install(TARGETS tessellateObjFile DESTINATION ${CMAKE_BINDIR_BASE})

View File

@ -59,7 +59,7 @@
#include <windows.h>
#endif
#include <osdutil/adaptiveEvaluator.h>
#include <osdutil/uniformEvaluator.h>
#include <osdutil/topology.h>
@ -78,7 +78,7 @@ using namespace OpenSubdiv;
//------------------------------------------------------------------------------
static bool
createOsdMesh(char *inputFile, char *outputFile, std::string *errorMessage)
uniformTessellate(char *inputFile, char *outputFile, std::string *errorMessage)
{
PxOsdUtilSubdivTopology topology;
@ -90,8 +90,6 @@ createOsdMesh(char *inputFile, char *outputFile, std::string *errorMessage)
topology.refinementLevel = 2;
std::cout << "Did read topology\n";
PxOsdUtilUniformEvaluator uniformEvaluator;
// Create uniformEvaluator
@ -103,13 +101,12 @@ createOsdMesh(char *inputFile, char *outputFile, std::string *errorMessage)
// Push the vertex data
uniformEvaluator.SetCoarsePositions(pointPositions, errorMessage);
// Refine with eight threads
// Refine with one thread
if (not uniformEvaluator.Refine(1, errorMessage)) {
std::cout << "Refine failed with " << *errorMessage << "\n";
return false;
}
// Refine with eight threads
PxOsdUtilSubdivTopology refinedTopology;
const float *positions = NULL;
@ -127,6 +124,55 @@ createOsdMesh(char *inputFile, char *outputFile, std::string *errorMessage)
return true;
}
static bool
blenderStyleTessellate(char *inputFile, char *outputFile, std::string *errorMessage)
{
PxOsdUtilSubdivTopology topology;
std::vector<float> pointPositions;
if (not topology.ReadFromObjFile(inputFile, &pointPositions, errorMessage)) {
return false;
}
topology.refinementLevel = 5;
PxOsdUtilAdaptiveEvaluator adaptiveEvaluator;
// Create adaptiveEvaluator
if (not adaptiveEvaluator.Initialize(topology, errorMessage)) {
std::cout << "Initialize failed with " << *errorMessage << "\n";
return false;
}
// Push the vertex data
adaptiveEvaluator.SetCoarsePositions(
&(pointPositions[0]), pointPositions.size(), errorMessage);
// Refine with one thread
if (not adaptiveEvaluator.Refine(1, errorMessage)) {
std::cout << "Refine failed with " << *errorMessage << "\n";
return false;
}
PxOsdUtilSubdivTopology refinedTopology;
std::vector<float> positions;
if (not adaptiveEvaluator.GetRefinedTopology(
&refinedTopology, &positions, errorMessage)) {
std::cout << "GetRefinedTopology failed with " << *errorMessage <<"\n";
return false;
}
if (not refinedTopology.WriteObjFile(
outputFile, &(positions[0]), errorMessage)) {
std::cout << errorMessage << std::endl;
}
return true;
}
//------------------------------------------------------------------------------
static void
callbackError(OpenSubdiv::OsdErrorType err, const char *message)
@ -140,20 +186,23 @@ callbackError(OpenSubdiv::OsdErrorType err, const char *message)
int main(int argc, char** argv) {
if (argc < 3) {
std::cout << "Usage: projectTest input.obj output\n";
return false;
}
std::cout << "input is " << argv[1] << " and output is " << argv[2] <<std::endl;
OsdSetErrorCallback(callbackError);
std::string errorMessage;
if (not createOsdMesh(argv[1], argv[2], &errorMessage)) {
std::cout << "Failed with error: " << errorMessage << std::endl;
if ((argc == 4) and (std::string(argv[1]) == std::string("-blender"))) {
std::cout << "About to call blender-style tessellate\n";
if (not blenderStyleTessellate(argv[2], argv[3], &errorMessage)) {
std::cout << "Failed with error: " << errorMessage << std::endl;
return -1;
}
} else if (argc != 3) {
std::cout << "Usage: tessellate [-blender] input.obj output.obj\n";
return false;
} else {
if (not uniformTessellate(argv[1], argv[2], &errorMessage)) {
std::cout << "Failed with error: " << errorMessage << std::endl;
}
}
}

View File

@ -31,6 +31,7 @@ include_directories(
# source & headers
set(PUBLIC_HEADER_FILES
adaptiveEvaluator.h
batch.h
drawItem.h
drawController.h
@ -41,6 +42,8 @@ set(PUBLIC_HEADER_FILES
)
add_library(osdutil
adaptiveEvaluator.h
adaptiveEvaluator.cpp
mesh.h
mesh.cpp
refiner.h

View File

@ -0,0 +1,390 @@
//
// Copyright 2013 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "adaptiveEvaluator.h"
#define HBR_ADAPTIVE
#include "../hbr/mesh.h"
#include "../osd/vertex.h"
#ifdef OPENSUBDIV_HAS_OPENMP
#include <omp.h>
#include "../osd/ompComputeController.h"
#endif
#include "../osd/cpuComputeController.h"
#include <fstream>
#include <iostream>
using namespace OpenSubdiv;
using namespace std;
PxOsdUtilAdaptiveEvaluator::PxOsdUtilAdaptiveEvaluator():
_refiner(NULL),
_ownsRefiner(false),
_computeContext(NULL),
_evalLimitContext(NULL),
_vertexBuffer(NULL),
_vvBuffer(NULL),
_vbufP(NULL),
_vbufdPdu(NULL),
_vbufdPdv(NULL),
_pOutput(NULL),
_dPduOutput(NULL),
_dPdvOutput(NULL)
{
}
PxOsdUtilAdaptiveEvaluator::~PxOsdUtilAdaptiveEvaluator()
{
if (_ownsRefiner and _refiner) {
delete _refiner;
}
if (_computeContext)
delete _computeContext;
if (_evalLimitContext)
delete _evalLimitContext;
if (_vertexBuffer)
delete _vertexBuffer;
if (_vvBuffer)
delete _vvBuffer;
if (_vbufP)
delete _vbufP;
if (_vbufdPdu)
delete _vbufdPdu;
if (_vbufdPdv)
delete _vbufdPdv;
}
bool
PxOsdUtilAdaptiveEvaluator::Initialize(
const PxOsdUtilSubdivTopology &t,
string *errorMessage)
{
// create and initialize a refiner, passing "true" for adaptive
// to indicate we wish for adaptive refinement rather than uniform
PxOsdUtilRefiner *refiner = new PxOsdUtilRefiner();
_ownsRefiner = true;
if (not refiner->Initialize(t, true, errorMessage)) {
return false;
}
return Initialize(refiner, errorMessage);
}
bool
PxOsdUtilAdaptiveEvaluator::Initialize(
PxOsdUtilRefiner *refiner,
string *errorMessage)
{
if (refiner->GetAdaptive()) {
if (errorMessage)
*errorMessage = "Adaptive evaluator requires adaptive refiner";
return false;
}
// Note we assume someone else keeps this pointer alive
_refiner = refiner;
_ownsRefiner = false;
const FarMesh<OsdVertex> *fmesh = _refiner->GetFarMesh();
const HbrMesh<OsdVertex> *hmesh = _refiner->GetHbrMesh();
if (not (fmesh and hmesh)) {
if (errorMessage)
*errorMessage = "No valid adaptive far/hbr mesh";
return false;
}
_computeContext = OsdCpuComputeContext::Create(fmesh);
// Three elements (x/y/z) per refined point at every subdivision level
// defined by the farMesh. The coarse vertices seed the beginning of
// this buffer, and Refine populates the rest based on subdivision
_vertexBuffer = OsdCpuVertexBuffer::Create(
3, fmesh->GetNumVertices());
// zeros
memset( _vertexBuffer->BindCpuBuffer(), 0,
3 * fmesh->GetNumVertices() * sizeof(float));
/*
const vector<string> &vvNames = _refiner->GetTopology().vvNames;
// If needed, allocate vertex buffer for other vertex varying
// values, like UVs or gprim data.
if (vvNames.size()) {
// One element in the vertex buffer for each
// named vertex varying attribute in the refined mesh
_vvBuffer = OsdCpuVertexBuffer::Create(
(int)vvNames.size(), fmesh->GetNumVertices());
// zeros
memset( _vvBuffer->BindCpuBuffer(), 0,
vvNames.size() * fmesh->GetNumVertices() * sizeof(float));
}
*/
// A context object used to store data used in refinement
_computeContext = OsdCpuComputeContext::Create(fmesh);
// A context object used to store data used in fast limit surface
// evaluation. This contains vectors of patches and associated
// tables pulled and computed from the adaptive farMesh.
// It also holds onto vertex buffer data through binds
_evalLimitContext = OsdCpuEvalLimitContext::Create(
fmesh, /*requierFVarData*/ false);
// A buffer with one float per target point to use when
// evaluating interpolated weights
OsdCpuVertexBuffer* _vbufP = OsdCpuVertexBuffer::Create(3, 1);
OsdCpuVertexBuffer* _vbufdPdu = OsdCpuVertexBuffer::Create(3, 1);
OsdCpuVertexBuffer* _vbufdPdv = OsdCpuVertexBuffer::Create(3, 1);
_pOutput = _vbufP->BindCpuBuffer();
_dPduOutput = _vbufdPdu->BindCpuBuffer();
_dPdvOutput = _vbufdPdv->BindCpuBuffer();
memset( (void*)_pOutput, 0, 3 * sizeof(float));
memset( (void*)_dPduOutput, 0, 3 * sizeof(float));
memset( (void*)_dPdvOutput, 0, 3 * sizeof(float));
// Setup evaluation context. Values are offset, length, stride */
OsdVertexBufferDescriptor in_desc(0, 3, 3), out_desc(0, 3, 3);
_evalLimitContext->GetVertexData().Bind(in_desc, _vertexBuffer, out_desc,
_vbufP, _vbufdPdu, _vbufdPdv);
std::cout << "Initialized adaptive evaluator\n";
return true;
}
void
PxOsdUtilAdaptiveEvaluator::SetCoarsePositions(
const float *coords, int numFloats, string *errorMessage )
{
//XXX: should be >= num coarse vertices
if (numFloats/3 >= _refiner->GetFarMesh()->GetNumVertices()) {
if (errorMessage)
*errorMessage = "Indexing error in tesselator";
} else {
_vertexBuffer->UpdateData(coords, 0, numFloats / 3);
}
}
bool
PxOsdUtilAdaptiveEvaluator::Refine(
int numThreads, string *errorMessage)
{
const FarMesh<OsdVertex> *fmesh = _refiner->GetFarMesh();
#ifdef OPENSUBDIV_HAS_OPENMP
if (numThreads > 1) {
OsdOmpComputeController ompComputeController(numThreads);
ompComputeController.Refine(_computeContext,
fmesh->GetKernelBatches(),
_vertexBuffer, _vvBuffer);
return true;
}
#endif
OsdCpuComputeController cpuComputeController;
cpuComputeController.Refine(_computeContext,
fmesh->GetKernelBatches(),
_vertexBuffer, _vvBuffer);
return true;
}
void
PxOsdUtilAdaptiveEvaluator::EvaluateLimit(
const OsdEvalCoords &coords, float P[3], float dPdu[3], float dPdv[3])
{
// This controller is an empty object, essentially a namespace.
OsdCpuEvalLimitController cpuEvalLimitController;
cpuEvalLimitController.
EvalLimitSample<OsdCpuVertexBuffer, OsdCpuVertexBuffer>(
coords, _evalLimitContext, 0 /*index*/);
// Copy results from vertex buffers into return parameters
memcpy(P, _pOutput, sizeof(float) * 3);
if (dPdu) {
memcpy(dPdu, _dPduOutput, sizeof(float) * 3);
}
if (dPdv) {
memcpy(dPdv, _dPdvOutput, sizeof(float) * 3);
}
}
void ccgSubSurf__mapGridToFace(int S, float grid_u, float grid_v,
float *face_u, float *face_v)
{
float u, v;
/* - Each grid covers half of the face along the edges.
* - Grid's (0, 0) starts from the middle of the face.
*/
u = 0.5f - 0.5f * grid_u;
v = 0.5f - 0.5f * grid_v;
if (S == 0) {
*face_u = v;
*face_v = u;
}
else if (S == 1) {
*face_u = 1.0f - u;
*face_v = v;
}
else if (S == 2) {
*face_u = 1.0f - v;
*face_v = 1.0f - u;
}
else {
*face_u = v;
*face_v = 1.0f - u;
}
}
bool
PxOsdUtilAdaptiveEvaluator::GetRefinedTopology(
PxOsdUtilSubdivTopology *out,
//positions will have three floats * t->numVertices
std::vector<float> *positions,
std::string *errorMessage)
{
const PxOsdUtilSubdivTopology &t = GetTopology();
positions->clear();
// XXX: dirk
// What are correct values for subfaceGridSize?
// These look good.
// Power of 2 + 1?
int gridSize = 5;
int subfaceGridSize = 3;
int coarseFaceIndex = 0;
int ptexFaceIndex = 0;
int numSubfacesToProcess = 0;
bool done = false;
while (not done ) {
// iterate through faces in coarse topology. Four sided
// faces are traversed in one pass, sub-faces of non quads
// are traversed in order, i.e. three subfaces for each triangle
// These are the indices used by ptex and the eval API
bool subface = false;
if (numSubfacesToProcess > 0) {
// We are iterating over sub-faces of a non-quad
numSubfacesToProcess--;
subface = true;
} else {
int vertsInCoarseFace = t.nverts[coarseFaceIndex++];
if (vertsInCoarseFace != 4) {
// Non quads are subdivided by putting a point
// in the middle of the face and creating
// subfaces.
numSubfacesToProcess = vertsInCoarseFace-1;
subface = true;
}
}
int startingPositionIndex = positions->size();
int currentGridSize = gridSize;
// Subfaces have a smaller gridsize so tessellation lines up.
if (subface)
currentGridSize = subfaceGridSize;
OsdEvalCoords coords;
coords.face = ptexFaceIndex;
for (int x = 0; x < currentGridSize; x++) {
for (int y = 0; y < currentGridSize; y++) {
float grid_u = (float) x / (currentGridSize - 1),
grid_v = (float) y / (currentGridSize - 1);
float P[3];
coords.u = grid_u;
coords.v = grid_v;
EvaluateLimit(coords, P, NULL, NULL);
positions->push_back(P[0]);
positions->push_back(P[1]);
positions->push_back(P[2]);
// If not on edges, add a quad
if ( (x<currentGridSize-1) and (y<currentGridSize-1)) {
int v[4];
int vBase = startingPositionIndex/3;
v[0] = vBase + x*currentGridSize + y;
v[1] = vBase + x*currentGridSize + y+1;
v[2] = vBase + (x+1)*currentGridSize+y+1;
v[3] = vBase + (x+1)*currentGridSize+y;
out->AddFace(4, v);
}
}
}
ptexFaceIndex++;
if ((numSubfacesToProcess == 0) and
(coarseFaceIndex == t.nverts.size())) {
done = true; // last face
}
} // while (not done)
out->name = GetTopology().name + "_refined";
out->numVertices = positions->size()/3;
out->refinementLevel = GetTopology().refinementLevel;
return out->IsValid(errorMessage);
}

View File

@ -0,0 +1,143 @@
//
// Copyright 2013 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#ifndef PXOSDUTIL_ADAPTIVE_EVALUATOR_H
#define PXOSDUTIL_ADAPTIVE_EVALUATOR_H
#include "refiner.h"
#include <string>
#include <vector>
#define HBR_ADAPTIVE
#include "../osd/cpuVertexBuffer.h"
#include "../osd/cpuComputeContext.h"
#include "../osd/cpuEvalLimitController.h"
#include "../osd/cpuEvalLimitContext.h"
#include "../far/mesh.h"
// This class takes a mesh that has undergone adaptive refinement to
// create bspline and gregory patches to a fixed subdivision level,
// and creates required run time OpenSubdiv data structures used to
// call the eval API to sample values on subdivision surfaces on the
// limit surface..
//
class PxOsdUtilAdaptiveEvaluator {
public:
PxOsdUtilAdaptiveEvaluator();
~PxOsdUtilAdaptiveEvaluator();
// Initialize returns false on error. If errorMessage is non-NULL it'll
// be populated upon error.
//
// If successful vertex buffers and compute contexts will have been
// created and are ready to SetCoarse* methods, call Refine, and then
// sample refined values with the Get* methods
//
// Note that calling this method assumes that the evaluator isn't
// responsible for the refiner's lifetime, someone else needs to
// hold onto the refiner pointer. This allows for lightweight sharing
// of refiners among evaluators.
//
bool Initialize(
PxOsdUtilRefiner* refiner,
std::string *errorMessage = NULL);
bool Initialize(
const PxOsdUtilSubdivTopology &topology,
std::string *errorMessage = NULL);
// Set new coarse-mesh CV positions, need to call Refine
// before calling Get* methods. Three floats per
// point packed.
void SetCoarsePositions(
const float *coords, int numFloats,
std::string *errorMessage = NULL
);
// Refine the coarse mesh, needed before calling GetPositions, GetQuads etc
// If numThreads is 1, use single cpu. If numThreads > 1 use Omp and set
// number of omp threads.
//
bool Refine(int numThreads,
std::string *errorMessage = NULL);
void EvaluateLimit(
const OpenSubdiv::OsdEvalCoords &coords,
float P[3], float dPdu[3], float dPdv[3]);
bool GetRefinedTopology(
PxOsdUtilSubdivTopology *t,
//positions will have three floats * t->numVertices
std::vector<float> *positions,
std::string *errorMessage = NULL);
// Forward these calls through to the refiner, which may forward
// to the mesh. Make these top level API calls on the evaluator
// so clients can talk to a single API
//
const std::string &GetName() const { return _refiner->GetName();}
const OpenSubdiv::HbrMesh<OpenSubdiv::OsdVertex> *GetHbrMesh() {
return _refiner->GetHbrMesh();
}
const PxOsdUtilSubdivTopology &GetTopology() const {
return _refiner->GetTopology();
}
const OpenSubdiv::FarMesh<OpenSubdiv::OsdVertex>* GetFarMesh() {
return _refiner->GetFarMesh();
}
private:
// A pointer to the shared refiner used. Note that this class may
// own the refiner pointer (if _ownsRefiner is true), or it may
// assume that someone else is responsible for managing that pointer
// if _ownsRefiner is false.
PxOsdUtilRefiner *_refiner;
bool _ownsRefiner;
OpenSubdiv::OsdCpuComputeContext *_computeContext;
OpenSubdiv::OsdCpuEvalLimitContext *_evalLimitContext;
OpenSubdiv::OsdCpuVertexBuffer *_vertexBuffer;
OpenSubdiv::OsdCpuVertexBuffer *_vvBuffer; // not yet used
OpenSubdiv::OsdCpuVertexBuffer *_vbufP;
OpenSubdiv::OsdCpuVertexBuffer *_vbufdPdu;
OpenSubdiv::OsdCpuVertexBuffer *_vbufdPdv;
const float *_pOutput;
const float *_dPduOutput;
const float *_dPdvOutput;
};
#endif /* PXOSDUTIL_ADAPTIVE_EVALUATOR_H */