Cloning submaterials for material preview [SOLVED]
-
On 22/05/2015 at 04:09, xxxxxxxx wrote:
User Information:
Cinema 4D Version: R16
Platform: Windows ;
Language(s) : C++ ;---------
I have a custom material (created using MaterialData) with submaterials (LINK to Mbase in description). Now, when I implement material preview using MATPREVIEW_GENERATE_IMAGE, it won't render correctly as linked submaterials are not copied into document in MatPreviewGenerateImage::pDoc.
I can copy materials manually just before calling RenderDocument, ie:MatPreviewGenerateImage *image = static_cast<MatPreviewGenerateImage *>(data); RenderData *rdata = image->pDoc->GetActiveRenderData(); BaseContainer bc = rdata->GetData(); ... // GetOriginalMaterial gets material from original document. BaseMaterial *mat = GetOriginalMaterial(); BaseMaterial *cloned_mat = static_cast<BaseMaterial *>(mat->GetClone(COPYFLAGS_NO_MATERIALPREVIEW, NULL)); image->pDoc->InsertMaterial(cloned_mat); node->GetDataInstance()->SetLink(MY_MATERIAL_LINK, cloned_mat); ... image->lResult = RenderDocument(image->pDoc, bc, nullptr, nullptr, image->pDest, RENDERFLAGS_EXTERNAL | RENDERFLAGS_PREVIEWRENDER, image->pThread); ...
This works in few simple situations when submaterial is one of my custom materials and I handle it without using C4D functions, but when it is something more complex (e. g. Banzi) and I try to call CalcSurface on it, it will **crash **(access violation in sla.cdl64).
Note that when I render normally (put my custom material to cube in document and render), I don't need to copy anything and it will render without any problems, the problem is only when rendering material preview.
So my questions are:
1) Is there some standardized way to handle submaterials that will make C4D copy those in MatPreviewGenerateImage::pDoc?
2) If not, how can I copy submaterials into MatPreviewGenerateImage::pDoc so I will be abble to use them during rendering?Sidenote:
I also use MSG_MULTI_MARKMATERIALS to mark submaterials for C4D, but this seems to be used only for things like "Remove unused materials" and does not affect handling of material preview. -
On 22/05/2015 at 07:15, xxxxxxxx wrote:
Hello,
in Cinema 4D there is no such thing as "submaterials" and therefore no standardized way of handling these.
So you would have to setup the preview scene properly with copies of all referenced materials and updated links. In your code is "node" even part of the preview scene?
Alternatively you could try ForceGetLink() when accessing linked entities independent of a document.
Best wishes,
Sebastian -
On 22/05/2015 at 07:32, xxxxxxxx wrote:
Hi,
sorry I forgot to mention "node" - the whole code is handled in Message, ieBool MyMaterial::Message(GeListNode* node, C4DInt32 type, void* data) { switch (type) { ... case MATPREVIEW_GENERATE_IMAGE: { MatPreviewGenerateImage *image = static_cast<MatPreviewGenerateImage *>(data); RenderData *rdata = image->pDoc->GetActiveRenderData(); BaseContainer bc = rdata->GetData(); ... // GetOriginalMaterial gets material from original document. BaseMaterial *mat = GetOriginalMaterial(); BaseMaterial *cloned_mat = static_cast<BaseMaterial *>(mat->GetClone(COPYFLAGS_NO_MATERIALPREVIEW, NULL)); image->pDoc->InsertMaterial(cloned_mat); node->GetDataInstance()->SetLink(MY_MATERIAL_LINK, cloned_mat); ... image->lResult = RenderDocument(image->pDoc, bc, nullptr, nullptr, image->pDest, RENDERFLAGS_EXTERNAL | RENDERFLAGS_PREVIEWRENDER, image->pThread); return TRUE; } return MaterialData::Message(node, type, data); }
so "node" is node of currently rendered material (=my custom material with submaterials) and is the only material that is copied into preview scene by C4D.
I will try the approach with ForceGetLink(), but I am affraid it could be quite dangerous - when rendering, things like InitCalculations and CalcSurface would be called on the same material user might be editing in main thread...
-
On 22/05/2015 at 10:00, xxxxxxxx wrote:
Hello,
the "node" argument of Message() is not the currently rendered material. Please read the documentation for NodeData::Message() to see that this is just a the corresponding GeListNode (BaseMaterial) to this instance of your plugin material. This is not the copy of the material that is used in the preview scene. Also, linking materials in your materials makes the other materials not "child materials" of your material; there are not "submaterials".
If you want to edit the preview scene you can catch the MATPREVIEW_PREPARE_SCENE message. Then you can create copies of your material and the referenced materials and edit the "Object" object.
Best wishes,
Sebastian -
On 22/05/2015 at 11:38, xxxxxxxx wrote:
I believe the message MATPREVIEW_GENERATE_IMAGE is called for copied node, for example this code:
Bool MyMaterial::Message(GeListNode* node, C4DInt32 type, void* data) { switch (type) { ... case MATPREVIEW_GENERATE_IMAGE: { MatPreviewGenerateImage *image = static_cast<MatPreviewGenerateImage *>(data); if (image->pDoc->GetFirstMaterial() == node) GeDebugOut("node in pdoc"); ...
results in "node in pdoc" being printed to debug output...
Anyway, thanks for recommending MATPREVIEW_PREPARE_SCENE. For some reason, I though it is there to create whole scene including objects on my own, but now I see I was wrong in that - I just checked and all objects are already created in this message. So, I will use it as it is probably much better place to copy referenced materials into document than MATPREVIEW_GENERATE_IMAGE.
As for the crashes, I managed to find out it was caused by the fact materials are not initialized if not attached to any object. The reason it worked in normal rendering was the fact that I had the referenced material attached also to another object. Once the material was referenced only by my custom material, normal rendering crashed too...
For now, I solved this by adding hidden object into scene that has attached all referenced materials, so it will force C4D to initialize those materials before rendering.
Obviously, this is not the best solution, so I would like to ask if there is some way to initialize those materials manually? I know MaterialData has InitRender() function (probably) for this purpose, but there is no such function in BaseMaterial. I tried to call it from MaterialData using BaseMaterial::GetNodeData(), but it didn't help. Also, some C4D materials return NULL for GetNodeData...Thanks for any help.
PS: I know I have to use C4DOS.Bl->RetrieveTableX when calling functions from NodeData outside my plugin, so that was not the problem.
-
On 26/05/2015 at 01:09, xxxxxxxx wrote:
Hello,
BaseMaterial has InitTextures() which will call InitRender() of the material. But this is a function that should be called within the rendering pipeline, so I cannot guarantee it will work in your situation. So actually I think adding the materials to an object in the scene is the best solution.
As always, one should never access C4DOS directly.
Best wishes,
Sebastian -
On 26/05/2015 at 11:18, xxxxxxxx wrote:
Hi,
sorry I missed InitTextures, I have had opened R14 documentation at that moment...
I will probably use solution with hidden object as InitTextures seems to be in API since R15 and I would like my plugin to support older versions of C4D...Thanks for all help, consider this solved.