1 //-*****************************************************************************
2 //
3 // Copyright (c) 2009-2011,
4 // Sony Pictures Imageworks Inc. and
5 // Industrial Light & Magic, a division of Lucasfilm Entertainment Company Ltd.
6 //
7 // All rights reserved.
8 //
9 // Redistribution and use in source and binary forms, with or without
10 // modification, are permitted provided that the following conditions are
11 // met:
12 // * Redistributions of source code must retain the above copyright
13 // notice, this list of conditions and the following disclaimer.
14 // * Redistributions in binary form must reproduce the above
15 // copyright notice, this list of conditions and the following disclaimer
16 // in the documentation and/or other materials provided with the
17 // distribution.
18 // * Neither the name of Sony Pictures Imageworks, nor
19 // Industrial Light & Magic, nor the names of their contributors may be used
20 // to endorse or promote products derived from this software without specific
21 // prior written permission.
22 //
23 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 //
35 //-*****************************************************************************
36
37 #include "WriteGeo.h"
38 #include "ArbGeomParams.h"
39
40 #include <ai.h>
41 #include <sstream>
42
43 //-*****************************************************************************
44
45 #if AI_VERSION_ARCH_NUM == 3
46 #if AI_VERSION_MAJOR_NUM < 4
47 #define AiNodeGetNodeEntry(node) ((node)->base_node)
48 #endif
49 #endif
50
nodeHasParameter(struct AtNode * node,const std::string & paramName)51 bool nodeHasParameter( struct AtNode * node, const std::string & paramName)
52 {
53 return AiNodeEntryLookUpParameter( AiNodeGetNodeEntry( node ),
54 paramName.c_str() ) != NULL;
55 }
56
57
58 //-*****************************************************************************
59
ApplyTransformation(struct AtNode * node,MatrixSampleMap * xformSamples,ProcArgs & args)60 void ApplyTransformation( struct AtNode * node,
61 MatrixSampleMap * xformSamples, ProcArgs &args )
62 {
63 if ( !node || !xformSamples || xformSamples->empty() )
64 {
65 return;
66 }
67
68 // confirm that this node has a parameter
69 if ( !nodeHasParameter( node, "matrix" ) )
70 {
71 return;
72 }
73
74 // check to see that we're not a single identity matrix
75 if (xformSamples->size() == 1 &&
76 xformSamples->begin()->second == Imath::M44d())
77 {
78 return;
79 }
80
81
82 std::vector<float> sampleTimes;
83 sampleTimes.reserve(xformSamples->size());
84
85 std::vector<float> mlist;
86 mlist.reserve( 16* xformSamples->size() );
87
88 for ( MatrixSampleMap::iterator I = xformSamples->begin();
89 I != xformSamples->end(); ++I )
90 {
91 // build up a vector of relative sample times to feed to
92 // "transform_time_samples" or "time_samples"
93 sampleTimes.push_back( GetRelativeSampleTime(args, (*I).first) );
94
95
96 for (int i = 0; i < 16; i++)
97 {
98 mlist.push_back( (*I).second.getValue()[i] );
99 }
100 }
101
102 AiNodeSetArray(node, "matrix",
103 ArrayConvert(1, xformSamples->size(),
104 AI_TYPE_MATRIX, &mlist[0]));
105
106
107 if ( sampleTimes.size() > 1 )
108 {
109 // persp_camera calls it time_samples while the primitives call it
110 // transform_time_samples
111 if ( nodeHasParameter( node, "transform_time_samples" ) )
112 {
113 AiNodeSetArray(node, "transform_time_samples",
114 ArrayConvert(sampleTimes.size(), 1,
115 AI_TYPE_FLOAT, &sampleTimes[0]));
116 }
117 else if ( nodeHasParameter( node, "time_samples" ) )
118 {
119 AiNodeSetArray(node, "time_samples",
120 ArrayConvert(sampleTimes.size(), 1,
121 AI_TYPE_FLOAT, &sampleTimes[0]));
122 }
123 else
124 {
125 //TODO, warn if neither is present? Should be there in all
126 //commercial versions of arnold by now.
127 }
128 }
129 }
130
131 //-*****************************************************************************
132
133
134 template <typename geomParamT>
ProcessIndexedBuiltinParam(geomParamT param,const SampleTimeSet & sampleTimes,std::vector<float> & values,std::vector<AtUInt32> & idxs,size_t elementSize)135 void ProcessIndexedBuiltinParam(
136 geomParamT param,
137 const SampleTimeSet & sampleTimes,
138 std::vector<float> & values,
139 std::vector<AtUInt32> & idxs,
140 size_t elementSize)
141 {
142 if ( !param.valid() ) { return; }
143
144 bool isFirstSample = true;
145 for ( SampleTimeSet::iterator I = sampleTimes.begin();
146 I != sampleTimes.end(); ++I, isFirstSample = false)
147 {
148 ISampleSelector sampleSelector( *I );
149
150
151 switch ( param.getScope() )
152 {
153 case kVaryingScope:
154 case kVertexScope:
155 {
156 // a value per-point, idxs should be the same as vidxs
157 // so we'll leave it empty
158
159 // we'll get the expanded form here
160 typename geomParamT::Sample sample = param.getExpandedValue(
161 sampleSelector);
162
163 size_t footprint = sample.getVals()->size() * elementSize;
164
165 values.reserve( values.size() + footprint );
166 values.insert( values.end(),
167 (float32_t*) sample.getVals()->get(),
168 ((float32_t*) sample.getVals()->get()) + footprint );
169
170 break;
171 }
172 case kFacevaryingScope:
173 {
174 // get the indexed form and feed to nidxs
175
176 typename geomParamT::Sample sample = param.getIndexedValue(
177 sampleSelector);
178
179 if ( isFirstSample )
180 {
181 idxs.reserve( sample.getIndices()->size() );
182 idxs.insert( idxs.end(),
183 sample.getIndices()->get(),
184 sample.getIndices()->get() +
185 sample.getIndices()->size() );
186 }
187
188 size_t footprint = sample.getVals()->size() * elementSize;
189 values.reserve( values.size() + footprint );
190 values.insert( values.end(),
191 (const float32_t*) sample.getVals()->get(),
192 ((const float32_t*) sample.getVals()->get()) + footprint );
193
194 break;
195 }
196 default:
197 break;
198 }
199
200
201 }
202
203
204 }
205
206 //-*****************************************************************************
207
208 namespace
209 {
210 // Arnold scene build is single-threaded so we don't have to lock around
211 // access to this for now.
212 typedef std::map<std::string, AtNode *> NodeCache;
213 NodeCache g_meshCache;
214 }
215
216
217 //-*************************************************************************
218 // This is templated to handle shared behavior of IPolyMesh and ISubD
219
220 // We send in our empty sampleTimes and vidxs because polymesh needs those
221 // for processing animated normal.
222
223
224 // The return value is the polymesh node. If instanced, it will be returned
225 // for the first created instance only.
226 template <typename primT>
ProcessPolyMeshBase(primT & prim,ProcArgs & args,SampleTimeSet & sampleTimes,std::vector<AtUInt32> & vidxs,int subdiv_iterations,MatrixSampleMap * xformSamples,const std::string & facesetName="")227 AtNode * ProcessPolyMeshBase(
228 primT & prim, ProcArgs & args,
229 SampleTimeSet & sampleTimes,
230 std::vector<AtUInt32> & vidxs,
231 int subdiv_iterations,
232 MatrixSampleMap * xformSamples,
233 const std::string & facesetName = "" )
234 {
235 if ( !prim.valid() )
236 {
237 return NULL;
238 }
239
240 typename primT::schema_type &ps = prim.getSchema();
241 TimeSamplingPtr ts = ps.getTimeSampling();
242
243 if ( ps.getTopologyVariance() != kHeterogenousTopology )
244 {
245 GetRelevantSampleTimes( args, ts, ps.getNumSamples(), sampleTimes );
246 }
247 else
248 {
249 sampleTimes.insert( args.frame / args.fps );
250 }
251
252 std::string name = args.nameprefix + prim.getFullName();
253
254 AtNode * instanceNode = NULL;
255
256 std::string cacheId;
257
258 if ( args.makeInstance )
259 {
260 std::ostringstream buffer;
261 AbcA::ArraySampleKey sampleKey;
262
263
264 for ( SampleTimeSet::iterator I = sampleTimes.begin();
265 I != sampleTimes.end(); ++I )
266 {
267 ISampleSelector sampleSelector( *I );
268 ps.getPositionsProperty().getKey(sampleKey, sampleSelector);
269
270 buffer << GetRelativeSampleTime( args, (*I) ) << ":";
271 sampleKey.digest.print(buffer);
272 buffer << ":";
273 }
274
275 buffer << "@" << subdiv_iterations;
276 buffer << "@" << facesetName;
277
278 cacheId = buffer.str();
279
280 instanceNode = AiNode( "ginstance" );
281 AiNodeSetStr( instanceNode, "name", name.c_str() );
282 args.createdNodes.push_back(instanceNode);
283
284 if ( args.proceduralNode )
285 {
286 AiNodeSetInt( instanceNode, "visibility",
287 AiNodeGetInt( args.proceduralNode, "visibility" ) );
288
289 }
290 else
291 {
292 AiNodeSetInt( instanceNode, "visibility", AI_RAY_ALL );
293 }
294
295 ApplyTransformation( instanceNode, xformSamples, args );
296
297
298 NodeCache::iterator I = g_meshCache.find(cacheId);
299 if ( I != g_meshCache.end() )
300 {
301 AiNodeSetPtr(instanceNode, "node", (*I).second );
302 return NULL;
303 }
304
305 }
306
307
308
309 SampleTimeSet singleSampleTimes;
310 singleSampleTimes.insert( args.frame / args.fps );
311
312
313 std::vector<AtByte> nsides;
314 std::vector<float> vlist;
315
316 std::vector<float> uvlist;
317 std::vector<AtUInt32> uvidxs;
318
319
320 // POTENTIAL OPTIMIZATIONS LEFT TO THE READER
321 // 1) vlist needn't be copied if it's a single sample
322
323 bool isFirstSample = true;
324 for ( SampleTimeSet::iterator I = sampleTimes.begin();
325 I != sampleTimes.end(); ++I, isFirstSample = false)
326 {
327 ISampleSelector sampleSelector( *I );
328 typename primT::schema_type::Sample sample = ps.getValue( sampleSelector );
329
330 if ( isFirstSample )
331 {
332 size_t numPolys = sample.getFaceCounts()->size();
333 nsides.reserve( sample.getFaceCounts()->size() );
334 for ( size_t i = 0; i < numPolys; ++i )
335 {
336 int32_t n = sample.getFaceCounts()->get()[i];
337
338 if ( n > 255 )
339 {
340 // TODO, warning about unsupported face
341 return NULL;
342 }
343
344 nsides.push_back( (AtByte) n );
345 }
346
347 size_t vidxSize = sample.getFaceIndices()->size();
348 vidxs.reserve( vidxSize );
349 vidxs.insert( vidxs.end(), sample.getFaceIndices()->get(),
350 sample.getFaceIndices()->get() + vidxSize );
351 }
352
353
354 vlist.reserve( vlist.size() + sample.getPositions()->size() * 3);
355 vlist.insert( vlist.end(),
356 (const float32_t*) sample.getPositions()->get(),
357 ((const float32_t*) sample.getPositions()->get()) +
358 sample.getPositions()->size() * 3 );
359 }
360
361 ProcessIndexedBuiltinParam(
362 ps.getUVsParam(),
363 singleSampleTimes,
364 uvlist,
365 uvidxs,
366 2);
367
368
369 AtNode* meshNode = AiNode( "polymesh" );
370
371 if (!meshNode)
372 {
373 AiMsgError("Failed to make polymesh node for %s",
374 prim.getFullName().c_str());
375 return NULL;
376 }
377
378 args.createdNodes.push_back(meshNode);
379
380 if ( instanceNode != NULL)
381 {
382 AiNodeSetStr( meshNode, "name", (name + ":src").c_str() );
383 }
384 else
385 {
386 AiNodeSetStr( meshNode, "name", name.c_str() );
387 }
388
389
390
391
392 AiNodeSetArray(meshNode, "vidxs",
393 ArrayConvert(vidxs.size(), 1, AI_TYPE_UINT,
394 (void*)&vidxs[0]));
395
396 AiNodeSetArray(meshNode, "nsides",
397 ArrayConvert(nsides.size(), 1, AI_TYPE_BYTE,
398 &(nsides[0])));
399
400 AiNodeSetArray(meshNode, "vlist",
401 ArrayConvert( vlist.size() / sampleTimes.size(),
402 sampleTimes.size(), AI_TYPE_FLOAT, (void*)(&(vlist[0]))));
403
404 if ( !uvlist.empty() )
405 {
406 //TODO, option to disable v flipping
407 for (size_t i = 1, e = uvlist.size(); i < e; i += 2)
408 {
409 uvlist[i] = 1.0 - uvlist[i];
410 }
411
412 AiNodeSetArray(meshNode, "uvlist",
413 ArrayConvert( uvlist.size(), 1, AI_TYPE_FLOAT,
414 (void*)(&(uvlist[0]))));
415
416 if ( !uvidxs.empty() )
417 {
418 AiNodeSetArray(meshNode, "uvidxs",
419 ArrayConvert(uvidxs.size(), 1, AI_TYPE_UINT,
420 &(uvidxs[0])));
421 }
422 else
423 {
424 AiNodeSetArray(meshNode, "uvidxs",
425 ArrayConvert(vidxs.size(), 1, AI_TYPE_UINT,
426 &(vidxs[0])));
427 }
428 }
429
430 if ( sampleTimes.size() > 1 )
431 {
432 std::vector<float> relativeSampleTimes;
433 relativeSampleTimes.reserve( sampleTimes.size() );
434
435 for (SampleTimeSet::const_iterator I = sampleTimes.begin();
436 I != sampleTimes.end(); ++I )
437 {
438 relativeSampleTimes.push_back(
439 GetRelativeSampleTime( args, (*I) ) );
440
441 }
442
443 AiNodeSetArray( meshNode, "deform_time_samples",
444 ArrayConvert(relativeSampleTimes.size(), 1,
445 AI_TYPE_FLOAT, &relativeSampleTimes[0]));
446 }
447
448 // faceset visibility array
449 if ( !facesetName.empty() )
450 {
451 if ( ps.hasFaceSet( facesetName ) )
452 {
453 ISampleSelector frameSelector( *singleSampleTimes.begin() );
454
455
456 IFaceSet faceSet = ps.getFaceSet( facesetName );
457 IFaceSetSchema::Sample faceSetSample =
458 faceSet.getSchema().getValue( frameSelector );
459
460 std::set<int> facesToKeep;
461
462
463 facesToKeep.insert( faceSetSample.getFaces()->get(),
464 faceSetSample.getFaces()->get() +
465 faceSetSample.getFaces()->size() );
466
467 bool *faceVisArray = new bool(nsides.size());
468
469 for ( int i = 0; i < (int) nsides.size(); ++i )
470 {
471 faceVisArray[i] = facesToKeep.find( i ) != facesToKeep.end();
472 }
473
474 if ( AiNodeDeclare( meshNode, "face_visibility", "uniform BOOL" ) )
475 {
476 AiNodeSetArray( meshNode, "face_visibility",
477 ArrayConvert( nsides.size(), 1, AI_TYPE_BOOLEAN,
478 faceVisArray ) );
479 }
480
481 delete[] faceVisArray;
482 }
483 }
484
485 {
486 ICompoundProperty arbGeomParams = ps.getArbGeomParams();
487 ISampleSelector frameSelector( *singleSampleTimes.begin() );
488
489 AddArbitraryGeomParams( arbGeomParams, frameSelector, meshNode );
490 }
491
492
493 if ( instanceNode == NULL )
494 {
495 if ( xformSamples )
496 {
497 ApplyTransformation( meshNode, xformSamples, args );
498 }
499
500 return meshNode;
501 }
502 else
503 {
504 AiNodeSetInt( meshNode, "visibility", 0 );
505
506 AiNodeSetPtr(instanceNode, "node", meshNode );
507 g_meshCache[cacheId] = meshNode;
508 return meshNode;
509
510 }
511
512 }
513
514 //-*************************************************************************
515
ProcessPolyMesh(IPolyMesh & polymesh,ProcArgs & args,MatrixSampleMap * xformSamples,const std::string & facesetName)516 void ProcessPolyMesh( IPolyMesh &polymesh, ProcArgs &args,
517 MatrixSampleMap * xformSamples, const std::string & facesetName )
518 {
519 SampleTimeSet sampleTimes;
520 std::vector<AtUInt32> vidxs;
521
522 AtNode * meshNode = ProcessPolyMeshBase(
523 polymesh, args, sampleTimes, vidxs, 0, xformSamples,
524 facesetName );
525
526 // This is a valid condition for the second instance onward and just
527 // means that we don't need to do anything further.
528 if ( !meshNode )
529 {
530 return;
531 }
532
533 IPolyMeshSchema &ps = polymesh.getSchema();
534
535 std::vector<float> nlist;
536 std::vector<AtUInt32> nidxs;
537
538 ProcessIndexedBuiltinParam(
539 ps.getNormalsParam(),
540 sampleTimes,
541 nlist,
542 nidxs,
543 3);
544
545 if ( !nlist.empty() )
546 {
547 AiNodeSetArray(meshNode, "nlist",
548 ArrayConvert( nlist.size() / sampleTimes.size(),
549 sampleTimes.size(), AI_TYPE_FLOAT, (void*)(&(nlist[0]))));
550
551 if ( !nidxs.empty() )
552 {
553 AiNodeSetArray(meshNode, "nidxs",
554 ArrayConvert(nidxs.size(), 1, AI_TYPE_UINT,
555 &(nidxs[0])));
556 }
557 else
558 {
559 AiNodeSetArray(meshNode, "nidxs",
560 ArrayConvert(vidxs.size(), 1, AI_TYPE_UINT,
561 &(vidxs[0])));
562 }
563 }
564
565 }
566
567 //-*************************************************************************
568
ProcessSubD(ISubD & subd,ProcArgs & args,MatrixSampleMap * xformSamples,const std::string & facesetName)569 void ProcessSubD( ISubD &subd, ProcArgs &args,
570 MatrixSampleMap * xformSamples, const std::string & facesetName )
571 {
572 SampleTimeSet sampleTimes;
573 std::vector<AtUInt32> vidxs;
574
575 AtNode * meshNode = ProcessPolyMeshBase(
576 subd, args, sampleTimes, vidxs, args.subdIterations,
577 xformSamples, facesetName );
578
579 // This is a valid condition for the second instance onward and just
580 // means that we don't need to do anything further.
581 if ( !meshNode )
582 {
583 return;
584 }
585
586
587 AiNodeSetStr( meshNode, "subdiv_type", "catclark" );
588 }
589
590