// Converts block of pointcloud data to long string containing lines of X,Y,Z,R,G,B. DLLEXPORT void read_cloud(int32_t iu, wchar_t *file_name, char sep, double *xyz, uint32_t *colors, bool has_colors, double *normals, bool has_normals, double zlimit, uint32_t &i, char *memblock, uint32_t block_size, uint64_t offset, size_t bufsize, double *sx, double *sy, double *sz, double *bx, double *by, double *bz, int32_t skip) { 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 variable for finding mix/max. double minx = 1e38f, miny = 1e38f, minz = 1e38f, maxx = -1e38f, maxy = -1e38f, maxz = -1e38f; // 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 j, 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; //char *memblock = new char[block_size]; // 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 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()) { // IMPORTANT: 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); // 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 line if Z is above limit. if (r3 < zlimit) { // Store X,Y,Z at 3*i, 3*i+1, 3*i+2. ii = 3 * i; xyz[ii] = r1; xyz[ii + 1] = r2; xyz[ii + 2] = r3; // Capture min/max XYZ. if (r1 < minx) minx = r1; if (r1 > maxx) maxx = r1; if (r2 < miny) miny = r2; if (r2 > maxy) maxy = r2; if (r3 < minz) minz = r3; if (r3 > maxz) maxz = r3; // // Skip white space and convert 3 RGB integers towards end of line. // if (has_colors) { 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 32-bit color in ABGR format. colors[i] = (((B << 8) + G) << 8) + R; } if (has_normals) { // Skip white space and convert first double at start of line. while (*p == ' ' || *p == sep) { ++p; } // Skip intensity for now. while (*p != ' ') { ++p; } // Skip white space. 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; } // Store X,Y,Z at 3*i, 3*i+1, 3*i+2. normals[ii] = r1; normals[ii + 1] = r2; normals[ii + 2] = r3; } // Advance index for storing results. i++; } // Skip lines to downsample cloud. for (j = 0; j < skip; j++) { // Find end of line. while (*p != '\n') { p++; } // Increment past \n at end of line. ++p; // Increment bytes read counter. bytes_read += p - p_start; // Set p_start to start of new line. p_start = p; // If end of block reached, break out of while loop and return. if (!bytes_read || bytes_read >= block_size) { break; } } if (!bytes_read || bytes_read >= block_size) { break; } } } // Save min/max XY for this block. sx[iu] = minx; bx[iu] = maxx; sy[iu] = miny; by[iu] = maxy; sz[iu] = minz; bz[iu] = maxz; //delete[] memblock; } // Combine xyz data in parallel from up to 16 blocks to make pointcloud. DLLEXPORT void add2cloud1(uint32_t doc_serial_number, uint32_t &pc_serial_number, bool has_colors, bool has_normals, int32_t *nums, double *xyz0, double *xyz1, double *xyz2, double *xyz3, double *xyz4, double *xyz5, double *xyz6, double *xyz7, double *xyz8, double *xyz9, double *xyz10, double *xyz11, double *xyz12, double *xyz13, double *xyz14, double *xyz15, uint32_t *colors0, uint32_t *colors1, uint32_t *colors2, uint32_t *colors3, uint32_t *colors4, uint32_t *colors5, uint32_t *colors6, uint32_t *colors7, uint32_t *colors8, uint32_t *colors9, uint32_t *colors10, uint32_t *colors11, uint32_t *colors12, uint32_t *colors13, uint32_t *colors14, uint32_t *colors15, double *normals0, double *normals1, double *normals2, double *normals3, double *normals4, double *normals5, double *normals6, double *normals7, double *normals8, double *normals9, double *normals10, double *normals11, double *normals12, double *normals13, double *normals14, double *normals15, int32_t &duration_d1, int32_t &duration_d2) { chrono::steady_clock::time_point time1 = chrono::steady_clock::now(); int32_t i, count = 0; // Find total number of points in pointcloud. for (i = 0; i < 16; i++) { count += nums[i]; } // Initialize pointcloud with capacity of count points. ON_PointCloud pc = ON_PointCloud(count); // Add points, colors and normals 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. ON_3dPoint *pdest = pc.m_P.Array(); // 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); }); }); pc.m_P.SetCount(count); if (has_colors) { pc.m_C.SetCapacity(count); 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); } if (has_normals) { pc.m_N.SetCapacity(count); ON_3dVector* ndest = pc.m_N.Array(); int32_t m = sizeof(ON_3dVector); ::parallel_invoke( [&] {::parallel_invoke( [&] { ::memcpy(ndest, normals0, nums[0] * m); }, [&] { ::memcpy(ndest + n1, normals1, nums[1] * m); }, [&] { ::memcpy(ndest + n2, normals2, nums[2] * m); }, [&] { ::memcpy(ndest + n3, normals3, nums[3] * m); }, [&] { ::memcpy(ndest + n4, normals4, nums[4] * m); }, [&] { ::memcpy(ndest + n5, normals5, nums[5] * m); }, [&] { ::memcpy(ndest + n6, normals6, nums[6] * m); }, [&] { ::memcpy(ndest + n7, normals7, nums[7] * m); }); }, [&] {::parallel_invoke( [&] { ::memcpy(ndest + n8, normals8, nums[8] * m); }, [&] { ::memcpy(ndest + n9, normals9, nums[9] * m); }, [&] { ::memcpy(ndest + n10, normals10, nums[10] * m); }, [&] { ::memcpy(ndest + n11, normals11, nums[11] * m); }, [&] { ::memcpy(ndest + n12, normals12, nums[12] * m); }, [&] { ::memcpy(ndest + n13, normals13, nums[13] * m); }, [&] { ::memcpy(ndest + n14, normals14, nums[14] * m); }, [&] { ::memcpy(ndest + n15, normals15, nums[15] * m); }); }); pc.m_N.SetCount(count); } chrono::steady_clock::time_point time2 = chrono::steady_clock::now(); duration_d1 = (int32_t)chrono::duration_cast (time2 - time1).count(); // Get doc from RuntimeSerialNumber passed as uint_32_t. CRhinoDoc *pDoc = CRhinoDoc::FromRuntimeSerialNumber(doc_serial_number); // Add pointcloud to document. CRhinoPointCloudObject *pc_object = new CRhinoPointCloudObject(); pc_object->SetPointCloud(pc); if (!pDoc->AddObject(pc_object)) { delete pc_object; pc_object = NULL; } else {// Get RuntimeSerialNumber of pointcloud so it can be found in Python script. pc_serial_number = pc_object ? pc_object->RuntimeSerialNumber() : 0; } chrono::steady_clock::time_point time3 = chrono::steady_clock::now(); duration_d2 = (int32_t)chrono::duration_cast (time3 - time2).count(); } DLLEXPORT void combine_clouds(int32_t num_clouds, uint32_t doc_serial_number, uint32_t *pc_serial_numbers, uint32_t &pc_serial_number, int32_t *counts, int32_t *nexts, double *mm, bool has_colors, int32_t count, bool color_cloud, double zmin, double zrange, int32_t num_hues, int32_t used_hues, int32_t min_hue, uint32_t *hue2color, float more_red, int32_t *td) { chrono::steady_clock::time_point time1, time2, time3, time4, time5; time1 = chrono::steady_clock::now(); // Get doc from RuntimeSerialNumber passed as uint_32_t. CRhinoDoc *pDoc = CRhinoDoc::FromRuntimeSerialNumber(doc_serial_number); int32_t i = 0; // Initialize pointcloud with capacity of count+100 points. ON_PointCloud new_pc = ON_PointCloud(count + 100); // Use memcpy to add points to pointcloud. if (has_colors) { new_pc.m_C.SetCapacity(count + 100); } // // Combine all pointclouds into new cloud. // vector a; for (i = 0; i < num_clouds; i++) a.push_back(i); parallel_for_each(a.begin(), a.end(), [&](int32_t i) { // Get each pointcloud from Rhino Doc using its serial number. const CRhinoObject *obj = pDoc->LookupObjectByRuntimeSerialNumber(pc_serial_numbers[i]); // Cast the object to a constant pointcloud object. const CRhinoPointCloudObject *pc_object = CRhinoPointCloudObject::Cast(obj); // Get pointcloud from pointcloud object. ON_PointCloud pc = pc_object->PointCloud(); // Copy over points from this cloud into new pointcloud with offset = sum of count in preceeding clouds. ::memcpy(new_pc.m_P.Array() + nexts[i], pc.m_P.Array(), counts[i] * sizeof(ON_3dPoint)); // Copy over colors if present. if (has_colors) { ::memcpy(new_pc.m_C.Array() + nexts[i], pc.m_C.Array(), counts[i] * sizeof(uint32_t)); } // Delete point cloud just added to new cloud in order to limit peak memory usage. pDoc->DeleteObject(obj); }); // Set counts for pointcloud and colors. new_pc.m_P.SetCount(count); if (has_colors) { new_pc.m_C.SetCount(count); } time2 = chrono::steady_clock::now(); // // Color mesh according to Z elevation. // if (!has_colors && color_cloud) { double hz; // Calcualte scale factor for z-to-index into hue2color list. hz = used_hues / zrange; new_pc.m_C.SetCapacity(count + 100); // Set BGR color for each point based upon its Z. array a = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 }; int32_t npar = 16, Q1 = count / npar; parallel_for_each(begin(a), end(a), [&](int32_t j) { int32_t i, i1, iu, istart = j * Q1, iend = istart + Q1; if (j == npar - 1) iend = count; double ih; for (i = istart; i < iend; i++) { // Find number of hues corresponding this this z. ih = hz * (new_pc.m_P[i].z - zmin); // Push red/black/white down, eliminate small neg values and convert to integer index. i1 = (int32_t)(more_red * ih + 1e-6); // Add offset to skip red-magenta region at bottom and keep iu from overflowing. iu = min(num_hues - 1, i1 + min_hue); // Add Z-elevation color to list of colors for pointcloud. new_pc.m_C[i] = hue2color[iu]; } }); // Set count of colors. new_pc.m_C.SetCount(count); } time3 = chrono::steady_clock::now(); // // Add new pointcloud to Rhino Doc. // ON_BoundingBox bBox(ON_3dPoint(mm[0], mm[1], mm[2]), ON_3dPoint(mm[3], mm[4], mm[5])); new_pc.m_bbox = bBox; CRhinoPointCloudObject *pc_object = new CRhinoPointCloudObject(); pc_object->SetPointCloud(new_pc); if (!pDoc->AddObject(pc_object)) { delete pc_object; pc_object = NULL; } else {// Get RuntimeSerialNumber of pointcloud so it can be found in Python script. pc_serial_number = pc_object ? pc_object->RuntimeSerialNumber() : 0; } time4 = chrono::steady_clock::now(); // // Adjust views so cloud fills each view and clutter (grid/axes) is removed. Maximize Top view. // // Make list for holding views. ON_SimpleArray view_list; // Get attributes for Shaded view. const CDisplayPipelineAttributes *shaded_attrib = CRhinoDisplayAttrsMgr::StdShadedAttrs(); // Construct bounding box from mm = [xmin, ymin, zmin, xmax, ymax, zmax]. const ON_3dPoint ¢er = ON_3dPoint(0.5*(mm[0] + mm[3]), 0.5*(mm[1] + mm[4]), 0.5*(mm[2] + mm[5])); ON_BoundingBox bBoxp(ON_3dPoint(mm[0], mm[1], mm[2]), ON_3dPoint(mm[3], mm[4], mm[5])); // Make bBox 5 % bigger for most views in order to leave border around cloud. bBox.Transform(ON_Xform::ScaleTransformation(center, 1.05)); // Make perspective view bBox smaller in order to better fill the view. bBoxp.Transform(ON_Xform::ScaleTransformation(center, 0.73)); // Get list of views in Rhino document. int32_t num_views = pDoc->GetViewList(view_list, true, false); // Removed grid/axes, switch to shaded view, zoom extents and maximize Top viewport. for (int i = 0; i < num_views; i++) { CRhinoView *view = view_list[i]; if (view == nullptr) continue; CRhinoViewport &viewport = view->ActiveViewport(); viewport.SetShowConstructionGrid(false); viewport.SetShowConstructionAxes(false); viewport.SetShowWorldAxes(false); viewport.SetDisplayMode(shaded_attrib->Id()); // If min/max not available use slower: RhinoDollyExtents(view) if (view->ActiveViewport().Name() == L"Perspective") { viewport.DollyExtents(bBoxp, ON::world_cs); } //if (view->ActiveViewport().Name() == L"Perspective") { RhinoDollyExtents(view); } else { viewport.DollyExtents(bBox, ON::world_cs); } // If Top view, maximize it. This will also make it the active view. const ON_Viewport vp = viewport.VP(); ON_3dVector dir = vp.CameraDirection(); // Top view has parallel projection and the camera direction is down. if (vp.IsParallelProjection() && dir.x == 0.0 && dir.y == 0.0 && dir.z < 0.0) { if (!view->IsMaximized()) { view->MaximizeRestoreView(); } } } time5 = chrono::steady_clock::now(); td[0] = (int32_t)chrono::duration_cast (time2 - time1).count(); // Combine clouds. td[1] = (int32_t)chrono::duration_cast (time3 - time2).count(); // Add colors. td[2] = (int32_t)chrono::duration_cast (time4 - time3).count(); // Add to Doc. td[3] = (int32_t)chrono::duration_cast (time5 - time4).count(); // Adjust view. }