//-***************************************************************************** // // Copyright (c) 2009-2011, // 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 "util.h" #include "PointHelper.h" #include "NodeIteratorVisitorHelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include MStatus getPointArbGeomParamsInfos( const Alembic::AbcGeom::IPoints & iNode, MObject & iObject, PointSampleDataList & iData ) { MStatus status( MS::kSuccess); Alembic::AbcGeom::IPointsSchema schema = iNode.getSchema(); Alembic::AbcGeom::IPointsSchema::Sample samp; Alembic::Abc::ICompoundProperty props = schema.getArbGeomParams(); if ( !props.valid() ) { return status; } size_t numProps = props.getNumProperties(); MFnTypedAttribute tAttr; MObject attrObj; for (unsigned int i = 0; i < numProps; ++ i) { const Alembic::Abc::PropertyHeader & propHeader = props.getPropertyHeader(i); const std::string propName = propHeader.getName(); int propExtent = propHeader.getDataType().getExtent(); Alembic::Abc::IArrayProperty arrayProp( props, propName ); // Get geometry scope Alembic::AbcGeom::GeometryScope propScope = Alembic::AbcGeom::GetGeometryScope(arrayProp.getMetaData()); Alembic::AbcCoreAbstract::DataType dtype = arrayProp.getDataType(); Alembic::Util::PlainOldDataType pod = dtype.getPod(); // Ignore all but numerical switch (pod) { case Alembic::Util::kBooleanPOD: case Alembic::Util::kUint8POD: case Alembic::Util::kInt8POD: case Alembic::Util::kInt16POD: case Alembic::Util::kInt32POD: case Alembic::Util::kInt64POD: case Alembic::Util::kFloat16POD: case Alembic::Util::kFloat32POD: case Alembic::Util::kFloat64POD: { break; } default: { continue; } } Alembic::AbcCoreAbstract::ArraySamplePtr samp; // Storing property information for fast lookup when reading each frame PointsSampleData sampleInfo; sampleInfo.origName = propName; sampleInfo.scope = propScope; sampleInfo.name = propName; sampleInfo.extent = propExtent; sampleInfo.arrayProp = arrayProp; MFnDependencyNode fnparticle(iObject); if ( fnparticle.hasAttribute( sampleInfo.name.c_str() ) ) { MPlug attrPlug = fnparticle.findPlug(sampleInfo.name.c_str(), true); MFnAttribute attr( attrPlug.attribute(&status)); if ( sampleInfo.extent == 1 && attr.accepts(MFnData::kDoubleArray, &status)) { // We can skip attribute creation iData.push_back( sampleInfo ); continue; } else if (attr.accepts(MFnData::kVectorArray, &status)) { // We can skip attribute creation iData.push_back( sampleInfo ); continue; } else { // Attribute exists but is of the wrong type, we need to rename it std::string abcPrefix("abc_"); sampleInfo.name = abcPrefix + sampleInfo.name; } } iData.push_back( sampleInfo ); } return status; } MStatus readArbGeomParams(size_t pSize, Alembic::AbcCoreAbstract::index_t index, MFnArrayAttrsData & dynDataFn, PointSampleDataList & iData) { MStatus status(MS::kSuccess); size_t numProps = iData.size(); if ( iData.empty() || pSize == 0 ) { // iData is empty, feeding empty array"); return status; } for (unsigned int i = 0; i < numProps; ++ i) { PointsSampleData sampleInfo = iData[ i ]; Alembic::Util::Dimensions dim; sampleInfo.arrayProp.getDimensions(dim, Alembic::Abc::ISampleSelector(index)); unsigned int sampSize( dim.numPoints() ); if ( sampleInfo.scope == Alembic::AbcGeom::kVaryingScope && sampSize != pSize) { MString warn = "Ignoring malformed kVarying attribute: "; warn += sampleInfo.name.c_str(); warn += ", the number of point in the sample doesn't match the number of particle"; MGlobal::displayWarning(warn); continue; } // We don't forget the extent when alocating the array std::vector< double > samp(pSize * sampleInfo.extent); // Read everything as double sampleInfo.arrayProp.getAs( &samp.front(), Alembic::Util::kFloat64POD, Alembic::Abc::ISampleSelector(index)); // 1d array if ( sampleInfo.extent == 1 ) { MDoubleArray doubleArray = dynDataFn.doubleArray( sampleInfo.name.c_str(), &status); doubleArray.setLength( pSize ); if ( sampleInfo.scope <= Alembic::AbcGeom::kUniformScope) { for (unsigned int i = 0; i < pSize; ++i ) { doubleArray[i] = samp[0]; } } else { for (unsigned int i = 0; i < pSize; ++i ) { doubleArray[i] = samp[i]; } } continue; } else // 2d or 3d array { MVectorArray arr = dynDataFn.vectorArray( sampleInfo.name.c_str(), &status); arr.setLength(pSize); MVector vec; if ( sampleInfo.scope <= Alembic::AbcGeom::kUniformScope) { for (unsigned int i = 0; i < pSize; ++i) { vec.x = samp[0]; vec.y = samp[1]; if (sampleInfo.extent == 3) { vec.z = samp[2]; } arr[i] = vec; } } else { for (unsigned int i = 0; i < pSize; ++i) { vec.x = samp[sampleInfo.extent*i]; vec.y = samp[sampleInfo.extent*i+1]; if (sampleInfo.extent == 3) { vec.z = samp[sampleInfo.extent*i+2]; } arr[i] = vec; } } continue; } } return status; } MStatus createPerParticleAttributes( const Alembic::AbcGeom::IPoints & iNode, MObject & iObject ) { // We need to create a custom attribute for each abcGeomParam found under the PointSchema // nParticle object can only deal with doubleArray or vectorArray, so the only thing we need to know is the extent // But because sometimes the writer does not specify it explicitly, we need to investigate the header // we compare the sample position size to any geomParam sample size, if it is equal to twice or three time the position size, // we assume having an extent of 2 or 3 // Initialise maya object MStatus status; MFnTypedAttribute tAttr; MDagModifier modifier; MObject attrObj; // Handle radius // IF we have information for the radius, set the shading to blobby surface Alembic::AbcGeom::IFloatGeomParam widthProp = iNode.getSchema().getWidthsParam(); if ( widthProp.valid() ) { attrObj = tAttr.create("radiusPP", "radiusPP", MFnData::kDoubleArray, MObject::kNullObj, &status); MCHECKERROR(status); status = modifier.addAttribute(iObject, attrObj); status = modifier.doIt(); MCHECKERROR(status); // particleRenderType is an enum, 7 is for blobby surface MPlug renderTypePlug = MFnDependencyNode(iObject).findPlug("particleRenderType", true); status = modifier.newPlugValueInt(renderTypePlug, 7); status = modifier.doIt(); MCHECKERROR(status); } PointSampleDataList iData; status = getPointArbGeomParamsInfos(iNode, iObject, iData); size_t numValidProps = iData.size(); if ( numValidProps == 0 ) { // No param found, skipping return status; } for (unsigned int i = 0; i < numValidProps; ++ i) { PointsSampleData & sampleInfo = iData[ i ]; if ( MFnDependencyNode(iObject).hasAttribute(sampleInfo.name.c_str() )) { continue; } if (sampleInfo.extent == 1) { attrObj = tAttr.create(sampleInfo.name.c_str(), sampleInfo.name.c_str(), MFnData::kDoubleArray, MObject::kNullObj, &status); MCHECKERROR(status); status = modifier.addAttribute(iObject, attrObj); MCHECKERROR(status); } else { attrObj = tAttr.create(sampleInfo.name.c_str(), sampleInfo.name.c_str(), MFnData::kVectorArray, MObject::kNullObj, &status); MCHECKERROR(status); status = modifier.addAttribute(iObject, attrObj); MCHECKERROR(status); } } return modifier.doIt(); } MStatus read(double iFrame, const Alembic::AbcGeom::IPoints & iNode, int & isInitializedConstant, MFnArrayAttrsData & dynDataFn, PointSampleDataList & iData) { // We feed the MFnArrayAttrsData with all the nescessary doubleArray and vectorArray // it is then used to feed the outDataPlug of the alembic node MStatus status = MS::kSuccess; Alembic::AbcGeom::IPointsSchema schema = iNode.getSchema(); // Get IPoints Samples Alembic::AbcGeom::IPointsSchema::Sample samp, ceilsSamp; Alembic::AbcCoreAbstract::index_t index, ceilIndex; // TODO, might want to interpolate the points when we can getWeightAndIndex(iFrame, schema.getTimeSampling(), schema.getNumSamples(), index, ceilIndex); schema.get(samp, index); size_t pSize = samp.getPositions()->size(); //size_t idSize = samp.getIds()->size(); MDoubleArray countArray = dynDataFn.doubleArray("count", &status); countArray.append( (double)pSize ); // ID Alembic::Abc::UInt64ArraySamplePtr idPtr = samp.getIds(); MDoubleArray idArray = dynDataFn.doubleArray("id", &status); idArray.setLength( pSize ); // POSITION Alembic::Abc::P3fArraySamplePtr p3ptr = samp.getPositions(); MVectorArray positionArray = dynDataFn.vectorArray("position", &status); positionArray.setLength( pSize ); // VELOCITY Alembic::Abc::V3fArraySamplePtr v3fptr= samp.getVelocities(); MVectorArray velocityArray = dynDataFn.vectorArray("velocity", &status); velocityArray.setLength( pSize ); // RADIUS Alembic::AbcGeom::IFloatGeomParam widthProp = schema.getWidthsParam(); Alembic::Abc::FloatArraySamplePtr fptr; MDoubleArray radiusArray; Alembic::AbcGeom::GeometryScope widthScope( Alembic::AbcGeom::kUnknownScope ); if ( widthProp.valid() ) { // TODO, shouldn't width be 2*radius ? Alembic::AbcGeom::IFloatGeomParam::Sample widthSamp; widthProp.getExpanded(widthSamp, index); radiusArray = dynDataFn.doubleArray("radiusPP", &status); radiusArray.setLength(pSize); fptr = widthSamp.getVals(); widthScope = widthSamp.getScope(); } MVector vec; for (unsigned int i = 0; i < pSize; ++i ) { idArray[i] = (*idPtr)[i]; vec.x = (*p3ptr)[i].x; vec.y = (*p3ptr)[i].y; vec.z = (*p3ptr)[i].z; positionArray[i] = vec; if ( v3fptr ) { vec.x = (*v3fptr)[i].x; vec.y = (*v3fptr)[i].y; vec.z = (*v3fptr)[i].z; velocityArray[i] = vec; } if ( widthProp.valid() ) { if ( widthScope < Alembic::AbcGeom::kVaryingScope ) { // Special Case, we got only one value, populate all particles with this value radiusArray[i] = (*fptr)[0]; } else { radiusArray[i] = (*fptr)[i]; } } } if ( schema.isConstant() ) { isInitializedConstant = true; } Alembic::Abc::ICompoundProperty props = schema.getArbGeomParams(); if ( !props.valid() ) { return status; } status = readArbGeomParams( pSize, index, dynDataFn, iData ); MCHECKERROR(status); return status; } MStatus create(double iFrame, const Alembic::AbcGeom::IPoints & iNode, MObject & iParent, MObject & iObject) { MFnTypedAttribute tAttr; MObject attrObj; MStatus status = MS::kSuccess; Alembic::AbcGeom::IPointsSchema schema = iNode.getSchema(); // object has no samples, bail early if (schema.getNumSamples() == 0) { return status; } MDagModifier modifier; iObject = modifier.createNode("nParticle", iParent, &status); MCHECKERROR(status); status = modifier.renameNode(iObject, iNode.getName().c_str()); status = modifier.doIt(); // To call the DG evaluation, the nParticle needs to be attached to a nucleus node MItDependencyNodes dgIt( MFn::kNucleus ); MObject obj; MFnDagNode nParticleFn( iObject ); // Look for a nucleus node while ( !dgIt.isDone() ) { obj = dgIt.thisNode(); break; } MString nSolver("\"\""); if (!obj.isNull() && obj.hasFn( MFn::kNucleus)) { MFnDependencyNode nucleusFn(obj); nSolver = nucleusFn.name(); } // I found no way to do this from the API, so we call a dirty simple mel command that need to have an active selection MString cmd; cmd += "select "; cmd += nParticleFn.fullPathName(); cmd += ";\n"; cmd += "assignNSolver "; cmd += nSolver; cmd += ";\n"; cmd += "select -clear;"; // If there is no nSolver in the scene, the nSolver string will be empty and it will trigger the creation // of a new one MGlobal::executeCommand( cmd, true, false ); MCHECKERROR(status); // Set attribute "isDynamic" to off, it could crash if maya tries to compute collision // against another nParticleShape // It is not related to alembic. It crashes also with nCached particle collision // In maya Attribute Editor, the attribute is called "enable" MPlug enablePlug = nParticleFn.findPlug("isDynamic", true); status = modifier.newPlugValueBool(enablePlug, false); // Assign default particle shader initialParticleSE to correctly display them in the viewport MSelectionList sel; sel.add( "initialParticleSE" ); MObject particleSG; sel.getDependNode(0, particleSG); MFnSet fnSG( particleSG ); fnSG.addMember(iObject); status = createPerParticleAttributes(iNode, iObject); return status; }