330 lines
12 KiB
C++
330 lines
12 KiB
C++
//-*****************************************************************************
|
|
//
|
|
// Copyright (c) 2009-2012,
|
|
// Sony Pictures Imageworks Inc. and
|
|
// Industrial Light & Magic, a division of Lucasfilm Entertainment Company Ltd.
|
|
//
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following disclaimer
|
|
// in the documentation and/or other materials provided with the
|
|
// distribution.
|
|
// * Neither the name of Sony Pictures Imageworks, nor
|
|
// Industrial Light & Magic, nor the names of their contributors may be used
|
|
// to endorse or promote products derived from this software without specific
|
|
// prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
//-*****************************************************************************
|
|
|
|
#include "MayaNurbsSurfaceWriter.h"
|
|
#include "MayaUtility.h"
|
|
|
|
MayaNurbsSurfaceWriter::MayaNurbsSurfaceWriter(MDagPath & iDag,
|
|
Alembic::Abc::OObject & iParent, Alembic::Util::uint32_t iTimeIndex,
|
|
const JobArgs & iArgs) :
|
|
mIsSurfaceAnimated(false), mDagPath(iDag)
|
|
{
|
|
MStatus stat;
|
|
MObject surface = iDag.node();
|
|
|
|
MFnNurbsSurface nurbs(mDagPath, &stat);
|
|
if (!stat)
|
|
{
|
|
MGlobal::displayError(
|
|
"MFnNurbsSurface() failed for MayaNurbsSurfaceWriter" );
|
|
}
|
|
|
|
MString name = nurbs.name();
|
|
name = util::stripNamespaces(name, iArgs.stripNamespace);
|
|
|
|
|
|
Alembic::AbcGeom::ONuPatch obj(iParent, name.asChar(), iTimeIndex);
|
|
mSchema = obj.getSchema();
|
|
|
|
Alembic::Abc::OCompoundProperty cp;
|
|
Alembic::Abc::OCompoundProperty up;
|
|
if (AttributesWriter::hasAnyAttr(nurbs, iArgs))
|
|
{
|
|
cp = mSchema.getArbGeomParams();
|
|
up = mSchema.getUserProperties();
|
|
}
|
|
|
|
mAttrs = AttributesWriterPtr(new AttributesWriter(cp, up, obj, nurbs,
|
|
iTimeIndex, iArgs, true));
|
|
|
|
// for now if it a trim surface, treat it like it's animated
|
|
if ( iTimeIndex != 0 && (nurbs.isTrimmedSurface() ||
|
|
util::isAnimated(surface)) )
|
|
{
|
|
mIsSurfaceAnimated = true;
|
|
}
|
|
|
|
if (!mIsSurfaceAnimated || iArgs.setFirstAnimShape)
|
|
{
|
|
write();
|
|
}
|
|
}
|
|
|
|
bool MayaNurbsSurfaceWriter::isAnimated() const
|
|
{
|
|
return mIsSurfaceAnimated ||
|
|
(mAttrs != NULL && mAttrs->isAnimated());
|
|
}
|
|
|
|
|
|
unsigned int MayaNurbsSurfaceWriter::getNumCVs()
|
|
{
|
|
MFnNurbsSurface nurbs(mDagPath);
|
|
return nurbs.numCVsInU() * nurbs.numCVsInV();
|
|
}
|
|
|
|
|
|
// the arrays being passed in are assumed to be empty
|
|
void MayaNurbsSurfaceWriter::write()
|
|
{
|
|
MFnNurbsSurface nurbs(mDagPath);
|
|
|
|
double startU, endU, startV, endV;
|
|
nurbs.getKnotDomain(startU, endU, startV, endV);
|
|
|
|
Alembic::AbcGeom::ONuPatchSchema::Sample samp;
|
|
samp.setUOrder(nurbs.degreeU() + 1);
|
|
samp.setVOrder(nurbs.degreeV() + 1);
|
|
|
|
unsigned int numKnotsInU = nurbs.numKnotsInU();
|
|
std::vector<float> sampKnotsInU;
|
|
|
|
// guard against a degenerative case
|
|
if (numKnotsInU > 1)
|
|
{
|
|
MDoubleArray knotsInU;
|
|
nurbs.getKnotsInU(knotsInU);
|
|
|
|
// pad the start and end with a knot on each side, since thats what
|
|
// most apps, like Houdini and Renderman want these two extra knots
|
|
sampKnotsInU.reserve(numKnotsInU+2);
|
|
|
|
// push_back a dummy value, we will set it below
|
|
sampKnotsInU.push_back(0.0);
|
|
for (unsigned int i = 0; i < numKnotsInU; i++)
|
|
sampKnotsInU.push_back( static_cast<float>(knotsInU[i]));
|
|
|
|
double k1 = sampKnotsInU[1];
|
|
double k2 = sampKnotsInU[2];
|
|
double klast_1 = sampKnotsInU[numKnotsInU];
|
|
double klast_2 = sampKnotsInU[numKnotsInU-1];
|
|
|
|
sampKnotsInU[0] = static_cast<float>(2.0 * k1 - k2);
|
|
sampKnotsInU.push_back(static_cast<float>(2.0 * klast_1 - klast_2));
|
|
samp.setUKnot(Alembic::Abc::FloatArraySample(sampKnotsInU));
|
|
}
|
|
|
|
|
|
unsigned int numKnotsInV = nurbs.numKnotsInV();
|
|
std::vector<float> sampKnotsInV;
|
|
|
|
// do it for V
|
|
if (numKnotsInV > 1)
|
|
{
|
|
MDoubleArray knotsInV;
|
|
nurbs.getKnotsInV(knotsInV);
|
|
|
|
// pad the start and end with a knot on each side, since thats what
|
|
// most apps, like Houdini and Renderman want these two extra knots
|
|
sampKnotsInV.reserve(numKnotsInV+2);
|
|
|
|
// push_back a dummy value, we will set it below
|
|
sampKnotsInV.push_back(0.0);
|
|
for (unsigned int i = 0; i < numKnotsInV; i++)
|
|
sampKnotsInV.push_back(static_cast<float>(knotsInV[i]));
|
|
|
|
double k1 = sampKnotsInV[1];
|
|
double k2 = sampKnotsInV[2];
|
|
double klast_1 = sampKnotsInV[numKnotsInV];
|
|
double klast_2 = sampKnotsInV[numKnotsInV-1];
|
|
|
|
sampKnotsInV[0] = static_cast<float>(2.0 * k1 - k2);
|
|
sampKnotsInV.push_back(static_cast<float>(2.0 * klast_1 - klast_2));
|
|
samp.setVKnot(Alembic::Abc::FloatArraySample(sampKnotsInV));
|
|
}
|
|
|
|
// for closed and periodic we are saving duplicate information
|
|
MPointArray cvArray;
|
|
nurbs.getCVs(cvArray);
|
|
unsigned int numCVs = cvArray.length();
|
|
int numCVsInU = nurbs.numCVsInU();
|
|
int numCVsInV = nurbs.numCVsInV();
|
|
|
|
samp.setNu(numCVsInU);
|
|
samp.setNv(numCVsInV);
|
|
|
|
std::vector<Alembic::Abc::V3f> sampPos;
|
|
sampPos.reserve(numCVs);
|
|
|
|
std::vector<float> sampPosWeights;
|
|
sampPosWeights.reserve(numCVs);
|
|
bool weightsOne = true;
|
|
|
|
// Maya stores the data where v varies the fastest (v,u order)
|
|
// so we need to pack the data differently u,v order
|
|
// (v reversed to make clockwise???)
|
|
for (int v = numCVsInV - 1; v > -1; v--)
|
|
{
|
|
for (int u = 0; u < numCVsInU; u++)
|
|
{
|
|
int index = u * numCVsInV + v;
|
|
sampPos.push_back(Alembic::Abc::V3f(
|
|
static_cast<float>(cvArray[index].x),
|
|
static_cast<float>(cvArray[index].y),
|
|
static_cast<float>(cvArray[index].z) ));
|
|
|
|
if (fabs(cvArray[index].w - 1.0) > 1e-12)
|
|
{
|
|
weightsOne = false;
|
|
}
|
|
sampPosWeights.push_back(static_cast<float>(cvArray[index].w));
|
|
}
|
|
}
|
|
samp.setPositions(Alembic::Abc::V3fArraySample(sampPos));
|
|
if (!weightsOne)
|
|
{
|
|
samp.setPositionWeights(Alembic::Abc::FloatArraySample(sampPosWeights));
|
|
}
|
|
|
|
if (!nurbs.isTrimmedSurface())
|
|
{
|
|
mSchema.set(samp);
|
|
return;
|
|
}
|
|
|
|
unsigned int numRegions = nurbs.numRegions();
|
|
|
|
// each boundary is a curvegroup, it can have multiple trim curve segments
|
|
|
|
// A Maya's trimmed NURBS surface has multiple regions.
|
|
// Inside a region, there are multiple boundaries.
|
|
// There are one CCW outer boundary and optional CW inner boundaries.
|
|
// Each boundary is a closed boundary and consists of multiple curves.
|
|
|
|
// Alembic has the same semantic as RenderMan.
|
|
// RenderMan's doc says: "The curves of a loop connect
|
|
// in head-to-tail fashion and must be explicitly closed. "
|
|
|
|
// A Maya boundary is equivalent to an Alembic/RenderMan loop
|
|
std::vector<Alembic::Util::int32_t> trimNumCurves;
|
|
std::vector<Alembic::Util::int32_t> trimNumPos;
|
|
std::vector<Alembic::Util::int32_t> trimOrder;
|
|
std::vector<float> trimKnot;
|
|
std::vector<float> trimMin;
|
|
std::vector<float> trimMax;
|
|
std::vector<float> trimU;
|
|
std::vector<float> trimV;
|
|
std::vector<float> trimW;
|
|
|
|
Alembic::Util::int32_t numLoops = 0;
|
|
for (unsigned int i = 0; i < numRegions; i++)
|
|
{
|
|
MTrimBoundaryArray result;
|
|
|
|
// if the 3rd argument is set to be true, return the 2D curve
|
|
nurbs.getTrimBoundaries(result, i, true);
|
|
unsigned int numBoundaries = result.length();
|
|
for (unsigned int j = 0; j < numBoundaries; j++)
|
|
{
|
|
MObjectArray boundary = result[j];
|
|
unsigned int numTrimCurve = boundary.length();
|
|
trimNumCurves.push_back(static_cast<Alembic::Util::int32_t>(numTrimCurve));
|
|
numLoops++;
|
|
for (unsigned int k = 0; k < numTrimCurve; k++)
|
|
{
|
|
MObject curveObj = boundary[k];
|
|
if (curveObj.hasFn(MFn::kNurbsCurve))
|
|
{
|
|
MFnNurbsCurve mFnCurve(curveObj);
|
|
|
|
Alembic::Util::int32_t numCVs = mFnCurve.numCVs();
|
|
trimNumPos.push_back(numCVs);
|
|
trimOrder.push_back(mFnCurve.degree()+1);
|
|
|
|
double start, end;
|
|
mFnCurve.getKnotDomain(start, end);
|
|
trimMin.push_back(static_cast<float>(start));
|
|
trimMax.push_back(static_cast<float>(end));
|
|
|
|
MPointArray cvArray;
|
|
mFnCurve.getCVs(cvArray);
|
|
//append to curveGrp.cv vector
|
|
double offsetV = startV+endV;
|
|
for (Alembic::Util::int32_t l = 0; l < numCVs; l++)
|
|
{
|
|
trimU.push_back(static_cast<float>(cvArray[l].x));
|
|
|
|
// v' = maxV + minV - v
|
|
// this is because we flipped v on the surface
|
|
trimV.push_back(
|
|
static_cast<float>(offsetV-cvArray[l].y));
|
|
|
|
trimW.push_back(static_cast<float>(cvArray[l].w));
|
|
}
|
|
|
|
MDoubleArray knot;
|
|
mFnCurve.getKnots(knot);
|
|
unsigned int numKnots = knot.length();
|
|
|
|
// push_back a dummy value, we will set it below
|
|
std::size_t totalNumKnots = trimKnot.size();
|
|
trimKnot.push_back(0.0);
|
|
for (unsigned int l = 0; l < numKnots; l++)
|
|
{
|
|
trimKnot.push_back(static_cast<float>(knot[l]));
|
|
}
|
|
|
|
// for a knot sequence with multiple end knots, duplicate
|
|
// the existing first and last knots once more.
|
|
// for a knot sequence with uniform end knots, create their
|
|
// the new knots offset at an interval equal to the existing
|
|
// first and last knot intervals
|
|
double k1 = trimKnot[totalNumKnots+1];
|
|
double k2 = trimKnot[totalNumKnots+2];
|
|
double klast_1 = trimKnot[trimKnot.size()-1];
|
|
double klast_2 = trimKnot[trimKnot.size()-2];
|
|
trimKnot[totalNumKnots] = static_cast<float>(2.0 * k1 - k2);
|
|
trimKnot.push_back(
|
|
static_cast<float>(2.0 * klast_1 - klast_2));
|
|
}
|
|
} // for k
|
|
} // for j
|
|
} // for i
|
|
|
|
samp.setTrimCurve(numLoops,
|
|
Alembic::Abc::Int32ArraySample(trimNumCurves),
|
|
Alembic::Abc::Int32ArraySample(trimNumPos),
|
|
Alembic::Abc::Int32ArraySample(trimOrder),
|
|
Alembic::Abc::FloatArraySample(trimKnot),
|
|
Alembic::Abc::FloatArraySample(trimMin),
|
|
Alembic::Abc::FloatArraySample(trimMax),
|
|
Alembic::Abc::FloatArraySample(trimU),
|
|
Alembic::Abc::FloatArraySample(trimV),
|
|
Alembic::Abc::FloatArraySample(trimW));
|
|
|
|
mSchema.set(samp);
|
|
}
|