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.
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.
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?
I think I was expecting it to be like make your own “prt volume” object. I wasn’t expecting to make my own modifiers because none of the existing ones would make sense. Also if that were true then the top modifier would have to support that interface or would the iderivedobject have to support it? I’m sure I’ll figure it out once I play with it.
What I don’t like about this design is it adds another transform node and an additional object per object. But I’m used to making meshers of meshers of meshers so it’s not the end of the world.
I have to admit I don’t use nested classes that often and I can’t find any resources that combine nested classes and virtual functions. So I’m getting tripped up by the method definition syntax.
class Bobj : public GeomObject, Public IMaxKrakatoaPRTSource
{
///
//GeomObject methods
//
//
//
public:
/*
do I make a derived nested class here? How else do I override the Stream methods?
eg:
class bStream : public Stream
{
int Read(char* dest, int numbytes);
void DeleteThis();
}
*/
Stream* CreateStream( TimeValue t, Interval& outValidity );
};
Bobj::Stream* Bobj::CreateStream(TimeValue t, Interval& outValidity )
{
//can't new up a Bobj::Stream here
return NULL;
}
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;
}
I can’t get it to call CreateStream(). Do I need to override GetID() or GetInterface(InterfaceID)? Or is it possible that prt sources are filtered out by accident in this build?
When should I see this getting called? Whenever the viewport display or the render updates, right?
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.
Grrr. This is so much harder to do via serialization. Anyways, here is a question. I want to multi-thread my object but I don’t know how many particles it’s going to make until I make them. And I don’t want to make all the particles ahead of time in a disk cache. Does this PRTSource allow for multiple .prt “files” or “streams” ? I’m breaking up the generation task into partitions of attempts. So there is a maximum count per partition, but the actual result might be much smaller. A completed partition will enter a queue that the stream will read into the Source while the remaining ones process.
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?
This is going to be me streaming my Thoughts Ben::Stream::Read( ) {
I’m trying to design a system that doesn’t require additional work to turn my existing methods inside out. I would prefer to describe the input with some interface then push it over since I’m creating the particles procedurally, I think about it like a creation loop. This is more like pulling or importing where the data already exists and you have to parse through it. But I’m excited to try anything.
One way to fix it would be to support array of streams same as you do in the prt loader object. But I guess you would still need to know the number of particles per stream ahead of time. I would want to minimize memory reallocations on my end as well. Even if I have the total count for efficient malloc on your end, I might have been inefficient in generating it.
Do you require a single stream internally?
Do you require completely contiguous memory?
This is how I do point-cloud data for viewport display.
typedef vector<vector> multi_array;
The outer vector is the number of buckets, usually the number of cores. I know the maximum size for each bucket so I can set capacity a fraction of that to minimize the potential reallocs to a known coefficient. I don’t need to combine the buckets because I wrap it in a class that returns the total count and overloads the index access. It’s true that I know the total count here because I don’t stream in the results until I’m finished, and it’s only for the viewport so the memory is small.
So, to sum up…
I’m trying to avoid requiring double the memory by only allocating the size of the buffer numbytes*threadcount.
I want to create efficient loading via multithreading into a queue.
Are you copying the data or just reading it to create a render(or pcache/lcache)? Because all this is moot if I handle all the allocation and deallocations by having my own cache as described above.
I also want to handle viewport/render streams separately. (not that this is currently a problem but I need to incorporate it into the design).
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.