How to color point in C++ SDK

I am working on importing meshes. Sometimes they have bad faces which I highlight in my C++ DLL with a point using:

// Place black point in center of each bad face to show location.|
CRhinoDoc *pDoc = CRhinoDoc::FromRuntimeSerialNumber(doc_serial_number);
ON_3dmObjectAttributes attribs;
CRhinoPointObject *ptObject = new CRhinoPointObject(attribs);|
ON_3dPoint pt = ON_3dPoint(fcxyz[3 * i], fcxyz[3 * i + 1], fcxyz[3 * i + 2]);
ptObject->SetPoint(pt);
pDoc->AddObject(ptObject); // FUTURE: Add color to dot.

How do I color this point? I have not yet found this detail in the documentation. I can do it in Python using Rhinocommon but have not found the trick for doing this in the C++ code.

Regards,
Terry.

You would change the color on the ON_3dmObjectAttributes class and set its color source to be ByObject.

attribs.m_color.SetRGB(255,0,0);
attribs.SetColorSource(ON:: color_from_object);
CRhinoPointObject* ptObject = ...

Hi @stevebaer ,
Can you show also example how to set attributes to the list of points/pointcloud ?

Steve,

Thanks for the quick response. Good to see the details on using the attribs to control the color.

Keep up the good work (in spite of the too strongly worded complaints about Rhino I have recently read on the Forum).

With a million lines of code under my belt, I know zero-bug perfection will never arrive. But we can asymptotically approach this goal. With a wind behind our backs, perhaps we will even approach it with a critically damped response; not so fast that more bugs are created and not so slow that Forum complaints climb.

Regards,
Terry.

1 Like

For a single colored point cloud you would do exactly the same thing as I showed for set a point object’s color. If the point cloud should have individual colors for every point in the cloud, you should set up the m_C color array on ON_PointCloud to have a list of colors the same length as your list of points in the cloud.

I was wondering how to add attributes in such case:

CRhinoPointCloudObject* cloudNew_obj = new CRhinoPointCloudObject();
        cloudNew_obj->SetPointCloud(cloudNew);
        context.m_doc.AddPointCloudObject(cloudNew.m_P.Count(), cloudNew.m_P );
        context.m_doc.Redraw();

Since add pointcloud object takes only array of points and I do not know how to set attributes from already assigned m_C array.

Dale showed me how to do similar thing by adding object:

CRhinoPointCloudObject* cloud_obj = new CRhinoPointCloudObject();
cloud_obj->SetPointCloud(cloud);
context.m_doc.AddObject(cloud_obj);
context.m_doc.Redraw();

But I would also like to learn about attributes for AddPointCloudObject.

Would be it be possible to show example for the first case?

There is a version of the CRhinoPointCloudObject constructor that takes an attributes parameter so you would do what Terry did.

I realize that I’m giving you a poor excuse for a sample, but I’m not at my computer right now.

1 Like

Petras,

Below is my C++ code for reading a point cloud with colors and adding it to a Rhino Doc.

Code to read a point cloud from file_name and make a colored point cloud:

// Converts block of pointcloud data to long string containing lines of X,Y,Z,R,G,B.
DLLEXPORT void read_cloud(wchar_t *file_name, char sep, double *xyz, uint32_t *colors, uint32_t &i,
	char *memblock, int32_t block_size, uint64_t offset, size_t bufsize) {
	static double pow10[17] = { 1., 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8, 1e-9, 1e-10, 1e-11, 1e-12, 1e-13, 1e-14, 1e-15, 1e-16 };
	// Define variables for 3 doubles and fraction.
	double r1 = 0, r2 = 0, r3 = 0, f;
	// Define variables for 3 integers, number of bytes read from block and power of ten for fraction.
	int32_t R = 0, G = 0, B = 0,  n;
	uint64_t ii, bytes_read = 0;
	// Define variables for pointing to memblock and saving pointer location at start of parsing block.
	const char *p, *p_start;
	// Initialize flags for negating value and controlling continuing parsing.
	bool neg = false;
	// Set buffer size for 64 transfers.
	//const size_t bufsize = max(4096, 2 << (int32_t)log2(block_size >> 6));
	unique_ptr<char[]> buf(new char[bufsize]);
	// Set file_size as streampos type.
	streampos size_of_block = block_size;
	// Set file offset to start of next block.
	streampos file_offset = offset;
	//
	// Open file for binary read.
	//
	ifstream in_file(file_name, ios::binary | ios::in);
	// Import block of point cloud.
	if (in_file.is_open()) {
		// Increase default buffer size.  Improves write speed 4.5X, from 0.66 GB/s to 3.1 GB/s
		in_file.rdbuf()->pubsetbuf(buf.get(), bufsize); // IMPORTANT
		// Move file pointer to start of block.
		in_file.seekg(file_offset);
		// Read memblock of data into memory.
		in_file.read(memblock, size_of_block);
		// Parse each line of block.
		p = memblock;
		while (true) {
			// Get pointer location at start.
			p_start = p;
			// Skip white space and convert first double at start of line.
			while (*p == ' ' || *p == sep) { ++p; }
			// Get possible minius sign at start.
			if (*p == '-') { neg = true; ++p; }
			// Convert digits before decimal points.
			if (*p >= '0' && *p <= '9') {
				r1 = (*p - '0'); ++p;
				while (*p >= '0' && *p <= '9') { r1 = (r1*10.0) + (*p - '0'); ++p; }
			}
			// Get digits after decimal point.
			if (*p == '.') {
				// Advance pointer to first digit.
				++p;
				// If char is a number, add to f, advance pointer and power of ten.
				if (*p >= '0' && *p <= '9') {
					f = (*p - '0'); ++p; n = 1;
					// Convert additional chars, advancing pointer and power of ten.
					while (*p >= '0' && *p <= '9') { f = (f*10.0) + (*p - '0');	++p; ++n; }
					// Scale digits by power of 10 to make fraction.
					r1 += f * pow10[n];
				}
			}
			// Create negative result if minus sign was present and reset minus flag.
			if (neg) { r1 = -r1; neg = false; }
			//
			// Skip white space and convert second double.
			//
			while (*p == ' ' || *p == sep) { ++p; }
			if (*p == '-') { neg = true; ++p; }
			if (*p >= '0' && *p <= '9') {
				r2 = (*p - '0'); ++p;
				while (*p >= '0' && *p <= '9') { r2 = (r2*10.0) + (*p - '0'); ++p; }
			}
			if (*p == '.') {
				++p;
				if (*p >= '0' && *p <= '9') {
					f = (*p - '0'); ++p; n = 1;
					while (*p >= '0' && *p <= '9') { f = (f*10.0) + (*p - '0');	++p; ++n; }
					r2 += f * pow10[n];
				}
			}
			if (neg) { r2 = -r2; neg = false; }
			//
			// Skip white space and convert third double.
			//
			while (*p == ' ' || *p == sep) { ++p; }
			if (*p == '-') { neg = true; ++p; }
			if (*p >= '0' && *p <= '9') {
				r3 = (*p - '0'); ++p;
				while (*p >= '0' && *p <= '9') { r3 = (r3*10.0) + (*p - '0'); ++p; }
			}
			if (*p == '.') {
				++p;
				if (*p >= '0' && *p <= '9') {
					f = (*p - '0'); ++p; n = 1;
					while (*p >= '0' && *p <= '9') { f = (f*10.0) + (*p - '0');	++p; ++n; }
					r3 += f * pow10[n];
				}
			}
			if (neg) { r3 = -r3; neg = false; }
			//
			// Skip white space and convert 3 RGB integers towards end of line.
			//
			while (*p == ' ' || *p == sep) { ++p; }
			if (*p >= '0' && *p <= '9') {
				R = (*p - '0'); ++p;
				while (*p >= '0' && *p <= '9') { R = (R * 10) + (*p - '0'); ++p; }
			}
			while (*p == ' ' || *p == sep) { ++p; }
			if (*p >= '0' && *p <= '9') {
				G = (*p - '0'); ++p;
				while (*p >= '0' && *p <= '9') { G = (G * 10) + (*p - '0'); ++p; }
			}
			while (*p == ' ' || *p == sep) { ++p; }
			if (*p >= '0' && *p <= '9') {
				B = (*p - '0'); ++p;
				while (*p >= '0' && *p <= '9') { B = (B * 10) + (*p - '0'); ++p; }
			}
			// Store X,Y,Z at 3*i, 3*i+1, 3*i+2.
			ii = 3 * i;
			xyz[ii] = r1; xyz[++ii] = r2; xyz[++ii] = r3;
			// Store 32-bit color in ABGR format.
			colors[i++] = (((B << 8) + G) << 8) + R;
			// Increment past \r\n at end of line.
			while (*p == '\r' || *p == '\n') { ++p; }
			// Increment bytes read counter.
			bytes_read += p - p_start;
			// If end of block reached, break out of while loop and return.
			if (!bytes_read || bytes_read >= block_size) { break; }
		}
	}
}

DLLEXPORT void read_make_cloud(wchar_t *file_name, char sep, uint64_t bufsize, uint32_t doc_serial_number, uint32_t &pc_serial_number,
	bool has_colors, int32_t nxyz, int32_t ncolors, uint32_t *block_sizes, uint64_t *offsets, int32_t &count, int32_t &duration_d1, int32_t &duration_d2, int32_t &duration_d3) {
	chrono::steady_clock::time_point time1 = chrono::steady_clock::now();
	int32_t i = 0;
	uint32_t num0 = 0; uint32_t num1 = 0; uint32_t num2 = 0; uint32_t num3 = 0; uint32_t num4 = 0; uint32_t num5 = 0; uint32_t num6 = 0; uint32_t num7 = 0;
	uint32_t num8 = 0; uint32_t num9 = 0; uint32_t num10 = 0; uint32_t num11 = 0; uint32_t num12 = 0; uint32_t num13 = 0; uint32_t num14 = 0; uint32_t num15 = 0;
	double *xyz0 = new double[nxyz]; double *xyz1 = new double[nxyz]; double *xyz2 = new double[nxyz]; double *xyz3 = new double[nxyz];
	double *xyz4 = new double[nxyz]; double *xyz5 = new double[nxyz]; double *xyz6 = new double[nxyz]; double *xyz7 = new double[nxyz];
	double *xyz8 = new double[nxyz]; double *xyz9 = new double[nxyz]; double *xyz10 = new double[nxyz]; double *xyz11 = new double[nxyz];
	double *xyz12 = new double[nxyz]; double *xyz13 = new double[nxyz]; double *xyz14 = new double[nxyz]; double *xyz15 = new double[nxyz];
	uint32_t *colors0 = new uint32_t[ncolors]; uint32_t *colors1 = new uint32_t[ncolors]; uint32_t *colors2 = new uint32_t[ncolors]; uint32_t *colors3 = new uint32_t[ncolors];
	uint32_t *colors4 = new uint32_t[ncolors]; uint32_t *colors5 = new uint32_t[ncolors]; uint32_t *colors6 = new uint32_t[ncolors]; uint32_t *colors7 = new uint32_t[ncolors];
	uint32_t *colors8 = new uint32_t[ncolors]; uint32_t *colors9 = new uint32_t[ncolors]; uint32_t *colors10 = new uint32_t[ncolors]; uint32_t *colors11 = new uint32_t[ncolors];
	uint32_t *colors12 = new uint32_t[ncolors]; uint32_t *colors13 = new uint32_t[ncolors]; uint32_t *colors14 = new uint32_t[ncolors]; uint32_t *colors15 = new uint32_t[ncolors];
	char *memblock0 = new char[block_sizes[0]]; char *memblock1 = new char[block_sizes[1]]; char *memblock2 = new char[block_sizes[2]]; char *memblock3 = new char[block_sizes[3]];
	char *memblock4 = new char[block_sizes[4]]; char *memblock5 = new char[block_sizes[5]]; char *memblock6 = new char[block_sizes[6]]; char *memblock7 = new char[block_sizes[7]];
	char *memblock8 = new char[block_sizes[8]]; char *memblock9 = new char[block_sizes[9]]; char *memblock10 = new char[block_sizes[10]]; char *memblock11 = new char[block_sizes[11]];
	char *memblock12 = new char[block_sizes[12]]; char *memblock13 = new char[block_sizes[13]]; char *memblock14 = new char[block_sizes[14]]; char *memblock15 = new char[block_sizes[15]];
	// Read pointcloud data.
	parallel_invoke(
		[&] {parallel_invoke(
			[&] { read_cloud(file_name, sep, xyz0, colors0, num0, memblock0, block_sizes[0], offsets[0], bufsize); },
			[&] { read_cloud(file_name, sep, xyz1, colors1, num1, memblock1, block_sizes[1], offsets[1], bufsize); },
			[&] { read_cloud(file_name, sep, xyz2, colors2, num2, memblock2, block_sizes[2], offsets[2], bufsize); },
			[&] { read_cloud(file_name, sep, xyz3, colors3, num3, memblock3, block_sizes[3], offsets[3], bufsize); },
			[&] { read_cloud(file_name, sep, xyz4, colors4, num4, memblock4, block_sizes[4], offsets[4], bufsize); },
			[&] { read_cloud(file_name, sep, xyz5, colors5, num5, memblock5, block_sizes[5], offsets[5], bufsize); },
			[&] { read_cloud(file_name, sep, xyz6, colors6, num6, memblock6, block_sizes[6], offsets[6], bufsize); },
			[&] { read_cloud(file_name, sep, xyz7, colors7, num7, memblock7, block_sizes[7], offsets[7], bufsize); });		
	},
		[&] {parallel_invoke(
			[&] { read_cloud(file_name, sep, xyz8, colors8, num8, memblock8, block_sizes[8], offsets[8], bufsize); },
			[&] { read_cloud(file_name, sep, xyz9, colors9, num9, memblock9, block_sizes[9], offsets[9], bufsize); },
			[&] { read_cloud(file_name, sep, xyz10, colors10, num10, memblock10, block_sizes[10], offsets[10], bufsize); },
			[&] { read_cloud(file_name, sep, xyz11, colors11, num11, memblock11, block_sizes[11], offsets[11], bufsize); },
			[&] { read_cloud(file_name, sep, xyz12, colors12, num12, memblock12, block_sizes[12], offsets[12], bufsize); },
			[&] { read_cloud(file_name, sep, xyz13, colors13, num13, memblock13, block_sizes[13], offsets[13], bufsize); },
			[&] { read_cloud(file_name, sep, xyz14, colors14, num14, memblock14, block_sizes[14], offsets[14], bufsize); },
			[&] { read_cloud(file_name, sep, xyz15, colors15, num15, memblock15, block_sizes[15], offsets[15], bufsize); });
		}
	);
	chrono::steady_clock::time_point time2 = chrono::steady_clock::now();
	duration_d1 = (int32_t)chrono::duration_cast<chrono::microseconds> (time2 - time1).count();
	uint32_t nums[16] = { num0, num1, num2, num3, num4, num5, num6, num7, num8, num9, num10, num11, num12, num13, num14, num15 };
	// Find total number of points in pointcloud.
	for (i = 0; i < 16; i++) { count += nums[i]; }
	// Add points and colors to pointcloud.
	// Runs 40% slower if points & colors are added group by group because of increased cache misses.
	// So all points and then all colors are added which is in the same order as their c-types declaration order.
	// For points, set destination for memcpy to be Point Array in pointcloud.
	// Initialize pointcloud with capacity of count+100 points.
	ON_PointCloud pc = ON_PointCloud(count + 100);
	ON_3dPoint *pdest = pc.m_P.Array();
	pc.m_C.SetCapacity(count + 100);
	// Use memcpy to add points to pointcloud.
	int32_t m = sizeof(ON_3dPoint);
	int32_t n1 = nums[0], n2 = n1 + nums[1], n3 = n2 + nums[2], n4 = n3 + nums[3], n5 = n4 + nums[4], n6 = n5 + nums[5], n7 = n6 + nums[6], n8 = n7 + nums[7],
		n9 = n8 + nums[8], n10 = n9 + nums[9], n11 = n10 + nums[10], n12 = n11 + nums[11], n13 = n12 + nums[12], n14 = n13 + nums[13], n15 = n14 + nums[14];
	parallel_invoke(
		[&] {parallel_invoke(
			[&] { ::memcpy(pdest, xyz0, nums[0] * m); },
			[&] { ::memcpy(pdest + n1, xyz1, nums[1] * m); },
			[&] { ::memcpy(pdest + n2, xyz2, nums[2] * m); },
			[&] { ::memcpy(pdest + n3, xyz3, nums[3] * m); },
			[&] { ::memcpy(pdest + n4, xyz4, nums[4] * m); },
			[&] { ::memcpy(pdest + n5, xyz5, nums[5] * m); },
			[&] { ::memcpy(pdest + n6, xyz6, nums[6] * m); },
			[&] { ::memcpy(pdest + n7, xyz7, nums[7] * m); });
	},
		[&] {parallel_invoke(
			[&] { ::memcpy(pdest + n8, xyz8, nums[8] * m); },
			[&] { ::memcpy(pdest + n9, xyz9, nums[9] * m); },
			[&] { ::memcpy(pdest + n10, xyz10, nums[10] * m); },
			[&] { ::memcpy(pdest + n11, xyz11, nums[11] * m); },
			[&] { ::memcpy(pdest + n12, xyz12, nums[12] * m); },
			[&] { ::memcpy(pdest + n13, xyz13, nums[13] * m); },
			[&] { ::memcpy(pdest + n14, xyz14, nums[14] * m); },
			[&] { ::memcpy(pdest + n15, xyz15, nums[15] * m); });
	},
		[&] { if (has_colors) {
		pc.m_C.SetCapacity(count + 100);
		ON_Color *cdest = pc.m_C.Array();
		int32_t m = sizeof(uint32_t);
		parallel_invoke(
			[&] {parallel_invoke(
				[&] { ::memcpy(cdest, colors0, nums[0] * m); },
				[&] { ::memcpy(cdest + n1, colors1, nums[1] * m); },
				[&] { ::memcpy(cdest + n2, colors2, nums[2] * m); },
				[&] { ::memcpy(cdest + n3, colors3, nums[3] * m); },
				[&] { ::memcpy(cdest + n4, colors4, nums[4] * m); },
				[&] { ::memcpy(cdest + n5, colors5, nums[5] * m); },
				[&] { ::memcpy(cdest + n6, colors6, nums[6] * m); },
				[&] { ::memcpy(cdest + n7, colors7, nums[7] * m); });
		},
			[&] {parallel_invoke(
				[&] { ::memcpy(cdest + n8, colors8, nums[8] * m); },
				[&] { ::memcpy(cdest + n9, colors9, nums[9] * m); },
				[&] { ::memcpy(cdest + n10, colors10, nums[10] * m); },
				[&] { ::memcpy(cdest + n11, colors11, nums[11] * m); },
				[&] { ::memcpy(cdest + n12, colors12, nums[12] * m); },
				[&] { ::memcpy(cdest + n13, colors13, nums[13] * m); },
				[&] { ::memcpy(cdest + n14, colors14, nums[14] * m); },
				[&] { ::memcpy(cdest + n15, colors15, nums[15] * m); });
		});
		pc.m_C.SetCount(count);
	}
	});
	pc.m_P.SetCount(count);
	chrono::steady_clock::time_point time3 = chrono::steady_clock::now();
	// Get doc from RuntimeSerialNumber passed as uint_32_t.
	CRhinoDoc *pDoc = CRhinoDoc::FromRuntimeSerialNumber(doc_serial_number);
	// Add pointcloud to document.
	// Use next line in order to take advantage of Rhino checks.
	*const ON_PointCloud &cpc = *pc;
    CRhinoPointCloudObject *pcObject = pDoc->AddPointCloudObject(cpc);
	// Get RuntimeSerialNumber of pointcloud so it can be found in Python script.
	pc_serial_number = pcObject ? pcObject->RuntimeSerialNumber() : 0;
	chrono::steady_clock::time_point time4 = chrono::steady_clock::now();
	// Remove memory created on heap to support operations.
	delete[] xyz0, xyz1, xyz2, xyz3, xyz4, xyz5, xyz6, xyz7, xyz8, xyz9, xyz10, xyz11, xyz12, xyz13, xyz14, xyz14;
	delete[] colors0, colors1, colors2, colors3, colors4, colors5, colors6, colors7, colors8, colors9, colors10, colors11, colors12, colors13, colors14, colors15;
	delete[] memblock0, memblock1, memblock2, memblock3, memblock4, memblock5, memblock6, memblock7, memblock8, memblock9, memblock10, memblock11, memblock12, memblock13, memblock14, memblock15;
	duration_d2 = (int32_t)chrono::duration_cast<chrono::microseconds> (time3 - time2).count();
	duration_d3 = (int32_t)chrono::duration_cast<chrono::microseconds> (time4 - time3).count();
}

Hope this helps you. This code reads a colored point cloud and adds it to the Rhino Doc over 10X faster than Rhino Import does.

Regards,
Terry.

Thanks Terry it seems that you are also using AddObject instead of AddPointCloudObject.

AddPointCloudObject is a function that internally creates a CRhinoPointCloudObject and then calls AddObject. These wrapper functions perform some level of geometry checking to make sure you aren’t sending bogus data to Rhino.

Thanks it helps to resolve my issue;)

I updated the code to use AddPointCloudObject as you desired.
I also restored some commented out lines that should not have been.

Nice to see this example. Thanks.

Can you also explain why you are using for rhinodoc serial number?

I call the C++ DLL from a Python script and want to add the pointcloud to the Rhino Doc I have open when running the script. So I get the Doc serial number while in the Python script and then pass it to the C++ code. Likewise I pass back the serial number of the pointcloud so I can access it in the Python script. Serial numbers are the only way I know of to pass back and forth the Doc and pointcloud information.

1 Like