Drawing large amounts of billboarded triangles in the viewport

  • Hi @mastergog, thanks for reaching out to us.

    With regard to your request there are a few considerations:

    • the public API currently shipped with Cinema is not providing you with the best options to effectively tackle this problem. Since R22, with the new Viewport system being included in Cinema 4D, there's a not-yet public API that could be used to get this sort of things being natively executed on GPU. Considering the fluid state of such API, please write us directly via email to further specify your final intent and we'll be eager to evaluate the request to get you accessing it.
    • using the BaseDraw class which is the sole option, if accessing the new API is not possible, I have a few considerations:
      • adopt caching mechanism using two checks: one for checking that the point cloud doesn't changes and the second to check that the camera has rotated. The first check will ensure that the volume you're representing has not changed, the second will ensure that you'll redraw your triangles only if the camera has rotated.
      • separate the voxel data acquisition which should happen likely in the ObjectData::GetVirtualObjects(), from the triangles representation which should take place in the ObjectData::Draw(): this will let you use Cinema deformers with your ObjectData and at the same time be sure that triangles will be correctly facing if a volume is deformed.
      • use the BaseDraw::DrawPolygonObject()which should perform better than drawing single triangles one after the other.

    The basic idea is this:

    1. in ObjectData::GetVirtualObjects() you will process your data and return an zero-polygon PolygonObject which contains all the points representing the centroids of your voxels. Only when your data changes the PolygonObject cache will be updated
    2. in ObjectData::Draw() you will take the data generated in the previous step and use it to generate a PolygonObject with camera-pointing-normal triangles. This shall be stored somewhere to be used as a cached geometry, that will be updated only if the camera rotation changes. The in this overridden method you will call the DrawPolygonObject passing to it the just-created PolygonObject.
      Finally, about back-face culling, back-faces are automatically culled unless you specify DRAWOBJECT::NOBACKCULL.

    Cheers, R

  • @r_gigante Hey thanks for the great suggestions.
    Not sure why I didn't think of adding a camera check... Now in combination with the BaseDraw::DrawPolygonObject() the performance is quite ok.
    If I could bother you guys for 1 more question : ), would really appreciate it. It is regarding per vertex colors. I followed the guide here - https://developers.maxon.net/docs/Cinema4DCPPSDK/html/page_manual_vertexcolortag.html
    Created the VertexColorTag , inserted in the PolygonObject, got the write address and filled it up, but i'm crashing at VertexColorTag::Get() during the write process.
    Is there anything specific when using the per vertex color tag in a PolygonObject that is drawn with BaseDraw::DrawPolygonObject(). Basically should that guide work out of the box?


  • Hi @mastergog the manual just illustrates the standard case and, as much as for any other snippet, it might be adapted to your scope to deliver the desired function.

    Give the limited information, I can only assume that the VertexColorHandle passed as first parameter to VertexColorTag::Get() might be invalid. Have you checked if vcTag->GetDataAddressW(); returns a valid pointer? Can you share a code to reproduce the issue? What's the instance scope in the code of the PolygonObject you've inserted to Tag into?

    Cheers, r

  • @maxon Hey sorry for the weird phrasing, had some other tasks and I was just wondering if there is some known issue with this functionality before i got back to it.

    Anyway about the code. As suggested, created a zero polygon PolygonObject* with my base vertices in GetVirtualObjects() and inserted it under the cache root.
    In Draw() I find that PolygonObject and use its point data to allocate a PolygonObject with actual polygons that is drawn with DrawPolygonObject(). This just created PolygonObject pointer is kept as a member in my class.

    So the vertex color tag code is the following. And the VertexColorHandle seems to be valid. Tried this code in both my Draw() and GetVirtualObjects() implementation to the same effect.

                    BaseTag* tag = polygonObjectToDraw->GetTag(Tvertexcolor);
    		if (tag == nullptr) {
    			tag = VertexColorTag::Alloc(polygonObjectToDraw->GetPointCount());
    			VertexColorTag* vcTag = static_cast<VertexColorTag*>(tag);
    		VertexColorTag* vcTag = static_cast<VertexColorTag*>(tag);
    		VertexColorHandle handle = vcTag->GetDataAddressW();
    		for (Int32 i = 0; i < Int32(polygonObjectToDraw->GetPointCount()); i++)
    			VertexColorStruct vcs;
    			VertexColorTag::Get(handle, i, vcs);
    			for (int p = 0; p < 4; p
    				vcs[p] = maxon::ColorA32(1.0f, 1.0f, 0.0f, 1.0f);
    			VertexColorTag::Set(handle, i, vcs);
    		baseDraw->DrawPolygonObject(baseDrawHelp, polygonObjectToDraw, DRAWOBJECT::NONE);

  • @maxon Hey so the crashing was because I was looping out of bounds it seems. Corrected my code like so:

                   BaseTag* tag = polygonObjectToDraw->GetTag(Tvertexcolor);
    		if (tag == nullptr) {
    			tag = VertexColorTag::Alloc(polygonObjectToDraw->GetPolygonCount() * 4);
    			VertexColorTag* vcTag = static_cast<VertexColorTag*>(tag);
    		VertexColorTag* vcTag = static_cast<VertexColorTag*>(tag);
    		VertexColorHandle handle = vcTag->GetDataAddressW();
    		for (Int32 i = 0; i < Int32(polygonObjectToDraw->GetPolygonCount()); i++)
    			VertexColorStruct vcs;
    			VertexColorTag::Get(handle, i, vcs);
    			for (int p = 0; p < 4; p++)
    				vcs[p] = maxon::ColorA32(1.0f, 1.0f, 0.0f, 1.0f);
    			VertexColorTag::Set(handle, i, vcs);
    		baseDraw->DrawPolygonObject(baseDrawHelp, polygonObjectToDraw, DRAWOBJECT::NONE);

    Still this doesn't use the color i've specified, but rather the default grey.

  • @maxon Hey guys, one more update.
    Edited my code so that I allocate the full PolygonObject in GetVirtualObjects(), place it under the cache, and just modify it in Draw(), without using DrawPolygonObject()
    Managed to get the per vertex colors to show when I select my object, click "c" so i can get the edit mode and see the cache. Then I select the PolygonObject in the cache with the per vertex color tag and double click the tag as if I'm editing the colors. That's when my colors show up.

    Any ideas how to get them without going into edit mode and double clicking the tag?


  • @maxon @r_gigante

    Hey guys, it was suggested to me that the vertex color tag is designed to pass data to the renderer not the viewport. And the reason my colors were showing up using the paint tool was because it overrides the viewport. Is that correct?

    Also one of your tutorials showed how to create a Material with a vertex map using a vertex color tag, so I tried that. And applied the Material to my object and the colors showed up. So perhaps this is the "correct" way to use the vertex color tag for representation in the viewport? Achieved this through the UI but I assume there's a way to do it programmatically. correct?

    Thanks for taking the time!


  • Ok resolved this by making the following connection TextureTag->Material->Shader->VertexColorTag and inserting the TextureTag in my object.

    Going to close this. Thanks for the support.

  • Hi @mastergog,

    we are sorry that it took us so long. We actually were aware of your problem, and had it in the workings, but were a bit busy with all the new Apple stuff and Cinema supporting it from the get go. Thank your for sharing your solution and thank you for your understanding.


  • @zipit Oh no worries you provided great support to get me started. As someone had suggested will also write you guys a more detailed email about the next representations I plan on implementing.


  • As already told by @zipit, I'm terribly sorry for coming so late here.
    I've prepared a full example showing how to fill a VertexColorTag in the ObjectData::GetVirtualObjects() and use this values to color the triangles shown up in the ObjectData::Draw() method. My code actually uses the VertexColorTag::SetColor()/GetColor().

    The VertexColorTag in this case is not specify to each vertex of the triangle but specifies the color for the whole triangle: this is because the tag is created in the ObjectData::GetVirtualObjects() and store color for each point of the cloud, but then it's used in the ObjectData::Draw() method to specify the ObjectColorProperty used to color the drawn PolygonObject. As far as I know the BaseDraw::DrawPolygonObject() ignores any VertexColorTag assigned to the PolygonObject to be drawn.

    BaseObject* PC_12974::GetVirtualObjects(BaseObject* op, HierarchyHelp* hh)
    	// check cache before continue
    	BaseObject* cache = op->GetCache();
    	if (cache)
    		return cache;
    	// init the random gen and get a arbitrary number of tris
    	_randomTrisCnt = (SAFEINT32(_rndGen.Get01() * (1 + _max - _min)) + _min);
    	// allocate a PolygonObject and fill it with points randomly
    	PolygonObject* po = PolygonObject::Alloc(_randomTrisCnt, 0);
    	if (!po)
    		return BaseObject::Alloc(Onull);
    	// fill the points array with random positions
    	Vector* points = po->GetPointW();
    	for (Int32 i = 0; i < _randomTrisCnt; i++)
    		points[i] = Vector (100.0 * _rndGen.Get11(), 100.0 * _rndGen.Get11(), 100.0 * _rndGen.Get11());
    	// look for VertexColor tag
    	BaseTag* tag = po->GetTag(Tvertexcolor);
    	// if not existing just create a new one
    	VertexColorTag* vcTag = nullptr;
    	if (!tag)
    		tag = VertexColorTag::Alloc(_randomTrisCnt);
    	vcTag = static_cast<VertexColorTag*>(tag);
    	VertexColorHandle handle = vcTag->GetDataAddressW();
    	// fill the vertexcolor tag with random values
    	for (Int32 i = 0; handle && i < _randomTrisCnt; i++)
    		VertexColorTag::SetColor(handle, nullptr, nullptr, i, maxon::Color32(_rndGen.Get01(), _rndGen.Get01(), _rndGen.Get01()));
    	// update host object
    	return po;
    Bool PC_12974::TriangleOnPoint(const Vector &pos, PolygonObject& po)
    	// given a point create a triangle centered over there.
    	Vector* points = po.GetPointW();
    	const Matrix poMG = po.GetMg();
    	const Vector posMG = poMG * pos;
    	if (!points)
    		return false;
    	// set vertexes positions for the triangle.
    	points[0] = Vector(0,  0, _triangleRad) + posMG;
    	points[1] = Vector(_triangleRad * Cos(-PI / 6), 0, _triangleRad * Sin(-PI / 6)) + posMG;
    	points[2] = Vector(_triangleRad * Cos(-PI * 5 / 6), 0, _triangleRad * Sin(-PI * 5 / 6)) + posMG;
    	// create the polygon
    	CPolygon* polys = po.GetPolygonW();
    	if (!polys)
    		return false;
    	polys[0] = CPolygon(0, 1, 2);
    	return true;
    DRAWRESULT 	PC_12974::Draw (BaseObject *op, DRAWPASS drawpass, BaseDraw *bd, BaseDrawHelp *bh)
    	// skip anything but OBJECT drawpass
    	if (drawpass != DRAWPASS::OBJECT)
    		return DRAWRESULT::SKIP;
    	// check for the object and its cache
    	if (op && op->GetCache())
    		// get the PolygonObject from the cache
    		PolygonObject* opPO = static_cast<PolygonObject*>(op->GetCache());
    		// look for the attached VertexColor Tag
    		BaseTag* const tag = opPO->GetTag(Tvertexcolor);
    		if (!tag)
    			return DRAWRESULT::SKIP;
    		VertexColorTag* const vcTag = static_cast<VertexColorTag*>(tag);
    		// retrieve the read-only handle
    		ConstVertexColorHandle vcTagHandleR = vcTag->GetDataAddressR();
    		// get the points and iterate over the VertexColor to set the color
    		// of each triangle drawn
    		maxon::Int pointCnt = opPO->GetPointCount();
    		Vector const *points = opPO->GetPointR();
    		for (maxon::Int i = 0; i < pointCnt; i++)
    			maxon::Color32 vtxCol(0.0);
    			// get the color
    			if (vcTagHandleR && vcTag->IsPerPointColor())
    			  vtxCol = VertexColorTag::GetColor(vcTagHandleR, nullptr, nullptr, (Int32)i);
    			// set the color in the ObjectColorProperties
    			ObjectColorProperties colProp;
    			colProp.usecolor = ID_BASEOBJECT_USECOLOR_ALWAYS;
    			colProp.color.x = vtxCol.r;
    			colProp.color.y = vtxCol.g;
    			colProp.color.z = vtxCol.b;
    			colProp.xray = false;
    			// allocate the temp triangle
    			PolygonObject* tri = PolygonObject::Alloc(3, 1);
    			// set the color prop, create the tri and draw!
    			if (TriangleOnPoint(op->GetMg() * points[i], *tri))
    				bd->DrawPolygonObject(bh, tri, DRAWOBJECT::NONE, nullptr);
    			// free the tri
    	return DRAWRESULT::OK;

    Feel free to comment and again sorry for the silence.