Extensible PRT object

Here is a treat for the developer types. The next beta of Krakatoa will contain a new “PRT Source” object that will stream PRT data from arbitrary 3rd party 3ds Max objects. The “PRT Source” support KCMs and whatnot just like a PRT Loader and PRT Volume, so you can now make custom particle objects that Krakatoa will recognize.

Try it out!

IMaxKrakatoaPRTSource.h (2.25 KB)

You’ll need a new build. I’ll make one now.

The idea I was running with was that there will be a PRT Source object to create (part of Krakatoa) that will have an INode reference to some other object that I will try to get the IMaxKrakatoaPRTSource off of. I considered just looking for IMaxKrakatoaPRTSource via a node enumeration, but then you’ll be responsible for viewport display, etc. The downside of this approach is that you’ll end up with 2 different objects per particle source.

The Krakatoa PRTSource object (which is in the next build) gets KCMs. The INode it references will be evaluated (via EvalWorldState) and the result of that operation will be queried for the IMaxKrakatoaPRTSource interface, and the PRT data will be extracted. Here is the gist of it.

INode* pTargetNode = m_pblock->GetINode( kTargetNode );
if( pTargetNode ){
  ObjectState os = pTargetNode->EvalWorldState( t );
  if( os.obj ){
    IMaxKrakatoaPRTSource* pSrc = static_cast<IMaxKrakatoaPRTSource*>( os.obj->GetInterface(IMaxKrakatoaPRTSource_ID) );
    if( pSrc ){
      Interval valid = FOREVER;
      IMaxKrakatoaPRTSource::Stream* pStream = pSrc->CreateStream( t, valid );
      
     //Make the magic happen here
     read_prt_data_from_stream( pStream );
     
     pStream->DeleteThis();
    }
  }
}

The main benefit of the two node approach is that the 3rd party node (ie. yours) has its own stack and modifiers (WSM and OSM), and the Krakatoa PRTSource object will have its own stack of particle modifiers.

I had initially designed it so that the Krakatoa PRTSource exposes a ReferenceTarget parameter that it would query the interface off of. This was nice since it didn’t make any extra scene objects, and I could forward the UI to appear when the Krakatoa PRTSource was selected. The downside to this is that your particle sourcing object had to implement everything itself (ie. no modifiers allowed).

Does that design sound more like what you were expecting?

Sorry about that. I’m a weird C++ nerd sometimes and I enjoy using obscure constructs. The gist is that you need to fully qualify IMaxKrakatoaPRTSource::Stream when inheriting it and using it in CreateStream’s definition. You don’t need to make your bStream a nested class if you don’t want to.

class Bobj : public GeomObject, public IMaxKrakatoaPRTSource
{
///
//GeomObject methods
//
//
//
    public:
    class bStream : public IMaxKrakatoaPRTSource::Stream
    {
       virtual int Read(char* dest, int numbytes);
       virtual void DeleteThis();
    }

    IMaxKrakatoaPRTSource::Stream* CreateStream( TimeValue t, Interval& outValidity );
};

IMaxKrakatoaPRTSource::Stream* Bobj::CreateStream(TimeValue t, Interval& outValidity )
{
   return new bStream;
}

int Bobj::bStream::Read(char* dest, int numbytes){
   return eof;
}

void Bobj::bStream::DeleteThis(){
   delete this;
}

Your object will need to override GetInterface like so:

BaseInterface* Bobj::GetInterface( Interface_ID id ){
   if( id == IMaxKrakatoaPRTSource_ID )
      return static_cast<IMaxKrakatoaPRTSource*>( this );
   return GeomObject::GetInterface( id );
}

You should be able to see your particles in the viewport in addition to at render time.

Well, to deal with this we have a couple of options, the easiest of which is to allow the encoded PRT stream to set the particle count to -1 which would mean “an unknown amount of particles”. It would be somewhat less efficient since I can’t allocate the memory all in one go, but it wouldn’t be too bad.

If I understand your suggestion, it would be that the Stream object has multiple PRT Streams available in it. ie. Read the first PRT Header with 1000 particles, then after reading 1000 particle try to read another PRT Header, etc. until eof is reached. That would work too I suppose so its really your call.

In general though, do you think this process is too difficult? Is there another design that you would prefer?

Currently I am making a copy of the data in the object then streaming it to the renderer when requested. This isn’t a necessary step, so I plan to either remove the caching, or at least make it optional.

I think that allowing the stream to specify an unknown number of particles will allow you the freedom you want. I’ll add that to the next build. What I am thinking we should do is put a negative number in the PRT header where the particle count is specified. I will then try to read particles from the stream in batches the size of the absolute value of the specified negative number.

Ex.
PRT Header has particle count -450
Krakatoa will call IMaxKrakatoaPRTSource::Stream::Read( &buffer, abs(-450) ) until IMaxKrakatoaPRTSource::Stream::eof is returned.

P.S.
In general you are allowed to return fewer particles than the requested amount, since the stream is not exhausted until IMaxKrakatoaPRTSource::Stream::eof is returned. Returning 0 particles will likely cause the machine to lock up though.