//-***************************************************************************** // // 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 "WriteGeo.h" #include "ArbGeomParams.h" #include #include //-***************************************************************************** #if AI_VERSION_ARCH_NUM == 3 #if AI_VERSION_MAJOR_NUM < 4 #define AiNodeGetNodeEntry(node) ((node)->base_node) #endif #endif bool nodeHasParameter( struct AtNode * node, const std::string & paramName) { return AiNodeEntryLookUpParameter( AiNodeGetNodeEntry( node ), paramName.c_str() ) != NULL; } //-***************************************************************************** void ApplyTransformation( struct AtNode * node, MatrixSampleMap * xformSamples, ProcArgs &args ) { if ( !node || !xformSamples || xformSamples->empty() ) { return; } // confirm that this node has a parameter if ( !nodeHasParameter( node, "matrix" ) ) { return; } // check to see that we're not a single identity matrix if (xformSamples->size() == 1 && xformSamples->begin()->second == Imath::M44d()) { return; } std::vector sampleTimes; sampleTimes.reserve(xformSamples->size()); std::vector mlist; mlist.reserve( 16* xformSamples->size() ); for ( MatrixSampleMap::iterator I = xformSamples->begin(); I != xformSamples->end(); ++I ) { // build up a vector of relative sample times to feed to // "transform_time_samples" or "time_samples" sampleTimes.push_back( GetRelativeSampleTime(args, (*I).first) ); for (int i = 0; i < 16; i++) { mlist.push_back( (*I).second.getValue()[i] ); } } AiNodeSetArray(node, "matrix", ArrayConvert(1, xformSamples->size(), AI_TYPE_MATRIX, &mlist[0])); if ( sampleTimes.size() > 1 ) { // persp_camera calls it time_samples while the primitives call it // transform_time_samples if ( nodeHasParameter( node, "transform_time_samples" ) ) { AiNodeSetArray(node, "transform_time_samples", ArrayConvert(sampleTimes.size(), 1, AI_TYPE_FLOAT, &sampleTimes[0])); } else if ( nodeHasParameter( node, "time_samples" ) ) { AiNodeSetArray(node, "time_samples", ArrayConvert(sampleTimes.size(), 1, AI_TYPE_FLOAT, &sampleTimes[0])); } else { //TODO, warn if neither is present? Should be there in all //commercial versions of arnold by now. } } } //-***************************************************************************** template void ProcessIndexedBuiltinParam( geomParamT param, const SampleTimeSet & sampleTimes, std::vector & values, std::vector & idxs, size_t elementSize) { if ( !param.valid() ) { return; } bool isFirstSample = true; for ( SampleTimeSet::iterator I = sampleTimes.begin(); I != sampleTimes.end(); ++I, isFirstSample = false) { ISampleSelector sampleSelector( *I ); switch ( param.getScope() ) { case kVaryingScope: case kVertexScope: { // a value per-point, idxs should be the same as vidxs // so we'll leave it empty // we'll get the expanded form here typename geomParamT::Sample sample = param.getExpandedValue( sampleSelector); size_t footprint = sample.getVals()->size() * elementSize; values.reserve( values.size() + footprint ); values.insert( values.end(), (float32_t*) sample.getVals()->get(), ((float32_t*) sample.getVals()->get()) + footprint ); break; } case kFacevaryingScope: { // get the indexed form and feed to nidxs typename geomParamT::Sample sample = param.getIndexedValue( sampleSelector); if ( isFirstSample ) { idxs.reserve( sample.getIndices()->size() ); idxs.insert( idxs.end(), sample.getIndices()->get(), sample.getIndices()->get() + sample.getIndices()->size() ); } size_t footprint = sample.getVals()->size() * elementSize; values.reserve( values.size() + footprint ); values.insert( values.end(), (const float32_t*) sample.getVals()->get(), ((const float32_t*) sample.getVals()->get()) + footprint ); break; } default: break; } } } //-***************************************************************************** namespace { // Arnold scene build is single-threaded so we don't have to lock around // access to this for now. typedef std::map NodeCache; NodeCache g_meshCache; } //-************************************************************************* // This is templated to handle shared behavior of IPolyMesh and ISubD // We send in our empty sampleTimes and vidxs because polymesh needs those // for processing animated normal. // The return value is the polymesh node. If instanced, it will be returned // for the first created instance only. template AtNode * ProcessPolyMeshBase( primT & prim, ProcArgs & args, SampleTimeSet & sampleTimes, std::vector & vidxs, int subdiv_iterations, MatrixSampleMap * xformSamples, const std::string & facesetName = "" ) { if ( !prim.valid() ) { return NULL; } typename primT::schema_type &ps = prim.getSchema(); TimeSamplingPtr ts = ps.getTimeSampling(); if ( ps.getTopologyVariance() != kHeterogenousTopology ) { GetRelevantSampleTimes( args, ts, ps.getNumSamples(), sampleTimes ); } else { sampleTimes.insert( args.frame / args.fps ); } std::string name = args.nameprefix + prim.getFullName(); AtNode * instanceNode = NULL; std::string cacheId; if ( args.makeInstance ) { std::ostringstream buffer; AbcA::ArraySampleKey sampleKey; for ( SampleTimeSet::iterator I = sampleTimes.begin(); I != sampleTimes.end(); ++I ) { ISampleSelector sampleSelector( *I ); ps.getPositionsProperty().getKey(sampleKey, sampleSelector); buffer << GetRelativeSampleTime( args, (*I) ) << ":"; sampleKey.digest.print(buffer); buffer << ":"; } buffer << "@" << subdiv_iterations; buffer << "@" << facesetName; cacheId = buffer.str(); instanceNode = AiNode( "ginstance" ); AiNodeSetStr( instanceNode, "name", name.c_str() ); args.createdNodes.push_back(instanceNode); if ( args.proceduralNode ) { AiNodeSetInt( instanceNode, "visibility", AiNodeGetInt( args.proceduralNode, "visibility" ) ); } else { AiNodeSetInt( instanceNode, "visibility", AI_RAY_ALL ); } ApplyTransformation( instanceNode, xformSamples, args ); NodeCache::iterator I = g_meshCache.find(cacheId); if ( I != g_meshCache.end() ) { AiNodeSetPtr(instanceNode, "node", (*I).second ); return NULL; } } SampleTimeSet singleSampleTimes; singleSampleTimes.insert( args.frame / args.fps ); std::vector nsides; std::vector vlist; std::vector uvlist; std::vector uvidxs; // POTENTIAL OPTIMIZATIONS LEFT TO THE READER // 1) vlist needn't be copied if it's a single sample bool isFirstSample = true; for ( SampleTimeSet::iterator I = sampleTimes.begin(); I != sampleTimes.end(); ++I, isFirstSample = false) { ISampleSelector sampleSelector( *I ); typename primT::schema_type::Sample sample = ps.getValue( sampleSelector ); if ( isFirstSample ) { size_t numPolys = sample.getFaceCounts()->size(); nsides.reserve( sample.getFaceCounts()->size() ); for ( size_t i = 0; i < numPolys; ++i ) { int32_t n = sample.getFaceCounts()->get()[i]; if ( n > 255 ) { // TODO, warning about unsupported face return NULL; } nsides.push_back( (AtByte) n ); } size_t vidxSize = sample.getFaceIndices()->size(); vidxs.reserve( vidxSize ); vidxs.insert( vidxs.end(), sample.getFaceIndices()->get(), sample.getFaceIndices()->get() + vidxSize ); } vlist.reserve( vlist.size() + sample.getPositions()->size() * 3); vlist.insert( vlist.end(), (const float32_t*) sample.getPositions()->get(), ((const float32_t*) sample.getPositions()->get()) + sample.getPositions()->size() * 3 ); } ProcessIndexedBuiltinParam( ps.getUVsParam(), singleSampleTimes, uvlist, uvidxs, 2); AtNode* meshNode = AiNode( "polymesh" ); if (!meshNode) { AiMsgError("Failed to make polymesh node for %s", prim.getFullName().c_str()); return NULL; } args.createdNodes.push_back(meshNode); if ( instanceNode != NULL) { AiNodeSetStr( meshNode, "name", (name + ":src").c_str() ); } else { AiNodeSetStr( meshNode, "name", name.c_str() ); } AiNodeSetArray(meshNode, "vidxs", ArrayConvert(vidxs.size(), 1, AI_TYPE_UINT, (void*)&vidxs[0])); AiNodeSetArray(meshNode, "nsides", ArrayConvert(nsides.size(), 1, AI_TYPE_BYTE, &(nsides[0]))); AiNodeSetArray(meshNode, "vlist", ArrayConvert( vlist.size() / sampleTimes.size(), sampleTimes.size(), AI_TYPE_FLOAT, (void*)(&(vlist[0])))); if ( !uvlist.empty() ) { //TODO, option to disable v flipping for (size_t i = 1, e = uvlist.size(); i < e; i += 2) { uvlist[i] = 1.0 - uvlist[i]; } AiNodeSetArray(meshNode, "uvlist", ArrayConvert( uvlist.size(), 1, AI_TYPE_FLOAT, (void*)(&(uvlist[0])))); if ( !uvidxs.empty() ) { AiNodeSetArray(meshNode, "uvidxs", ArrayConvert(uvidxs.size(), 1, AI_TYPE_UINT, &(uvidxs[0]))); } else { AiNodeSetArray(meshNode, "uvidxs", ArrayConvert(vidxs.size(), 1, AI_TYPE_UINT, &(vidxs[0]))); } } if ( sampleTimes.size() > 1 ) { std::vector relativeSampleTimes; relativeSampleTimes.reserve( sampleTimes.size() ); for (SampleTimeSet::const_iterator I = sampleTimes.begin(); I != sampleTimes.end(); ++I ) { relativeSampleTimes.push_back( GetRelativeSampleTime( args, (*I) ) ); } AiNodeSetArray( meshNode, "deform_time_samples", ArrayConvert(relativeSampleTimes.size(), 1, AI_TYPE_FLOAT, &relativeSampleTimes[0])); } // faceset visibility array if ( !facesetName.empty() ) { if ( ps.hasFaceSet( facesetName ) ) { ISampleSelector frameSelector( *singleSampleTimes.begin() ); IFaceSet faceSet = ps.getFaceSet( facesetName ); IFaceSetSchema::Sample faceSetSample = faceSet.getSchema().getValue( frameSelector ); std::set facesToKeep; facesToKeep.insert( faceSetSample.getFaces()->get(), faceSetSample.getFaces()->get() + faceSetSample.getFaces()->size() ); bool *faceVisArray = new bool(nsides.size()); for ( int i = 0; i < (int) nsides.size(); ++i ) { faceVisArray[i] = facesToKeep.find( i ) != facesToKeep.end(); } if ( AiNodeDeclare( meshNode, "face_visibility", "uniform BOOL" ) ) { AiNodeSetArray( meshNode, "face_visibility", ArrayConvert( nsides.size(), 1, AI_TYPE_BOOLEAN, faceVisArray ) ); } delete[] faceVisArray; } } { ICompoundProperty arbGeomParams = ps.getArbGeomParams(); ISampleSelector frameSelector( *singleSampleTimes.begin() ); AddArbitraryGeomParams( arbGeomParams, frameSelector, meshNode ); } if ( instanceNode == NULL ) { if ( xformSamples ) { ApplyTransformation( meshNode, xformSamples, args ); } return meshNode; } else { AiNodeSetInt( meshNode, "visibility", 0 ); AiNodeSetPtr(instanceNode, "node", meshNode ); g_meshCache[cacheId] = meshNode; return meshNode; } } //-************************************************************************* void ProcessPolyMesh( IPolyMesh &polymesh, ProcArgs &args, MatrixSampleMap * xformSamples, const std::string & facesetName ) { SampleTimeSet sampleTimes; std::vector vidxs; AtNode * meshNode = ProcessPolyMeshBase( polymesh, args, sampleTimes, vidxs, 0, xformSamples, facesetName ); // This is a valid condition for the second instance onward and just // means that we don't need to do anything further. if ( !meshNode ) { return; } IPolyMeshSchema &ps = polymesh.getSchema(); std::vector nlist; std::vector nidxs; ProcessIndexedBuiltinParam( ps.getNormalsParam(), sampleTimes, nlist, nidxs, 3); if ( !nlist.empty() ) { AiNodeSetArray(meshNode, "nlist", ArrayConvert( nlist.size() / sampleTimes.size(), sampleTimes.size(), AI_TYPE_FLOAT, (void*)(&(nlist[0])))); if ( !nidxs.empty() ) { AiNodeSetArray(meshNode, "nidxs", ArrayConvert(nidxs.size(), 1, AI_TYPE_UINT, &(nidxs[0]))); } else { AiNodeSetArray(meshNode, "nidxs", ArrayConvert(vidxs.size(), 1, AI_TYPE_UINT, &(vidxs[0]))); } } } //-************************************************************************* void ProcessSubD( ISubD &subd, ProcArgs &args, MatrixSampleMap * xformSamples, const std::string & facesetName ) { SampleTimeSet sampleTimes; std::vector vidxs; AtNode * meshNode = ProcessPolyMeshBase( subd, args, sampleTimes, vidxs, args.subdIterations, xformSamples, facesetName ); // This is a valid condition for the second instance onward and just // means that we don't need to do anything further. if ( !meshNode ) { return; } AiNodeSetStr( meshNode, "subdiv_type", "catclark" ); }