├── .gitignore ├── README.md ├── TextureMapper.cpp ├── TextureMapper.h ├── tinyply.cpp └── tinyply.h /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TextureMapping 2 | Implementation of Patch-Based Optimization for Image-Based Texture Mapping 3 | http://cseweb.ucsd.edu/~viscomp/projects/SIG17TextureMapping/ -------------------------------------------------------------------------------- /TextureMapper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "tinyply.h" 13 | #include "TextureMapper.h" 14 | 15 | using namespace tinyply; 16 | 17 | /** 18 | ** Assuming that source is a vector of cv::Mats 19 | **/ 20 | TextureMapper::TextureMapper(std::string plyFilename, std::vector source, std::vector TcwPoses, int patchSize = 7) : source(source), TcwPoses(TcwPoses), patchSize(patchSize) { 21 | read_ply_file(plyFilename); //gets vertices from the file 22 | /* 23 | init(); //clones source and target 24 | align(source, target); 25 | reconstruct(); */ 26 | projectToSurface(); 27 | } 28 | 29 | void TextureMapper::align(std::vector source, std::vector target) { 30 | int iterations = 1; 31 | cv::Mat completenessPatchMatches = patchSearch(source, target, iterations); 32 | cv::Mat coherencePatchMatches = patchSearch(target, source, iterations); 33 | vote(completenessPatchMatches, coherencePatchMatches); 34 | } 35 | 36 | cv::Mat TextureMapper::patchSearch(std::vector source, std::vector target, int iterations) { 37 | 38 | // convert patch diameter to patch radius 39 | patchSize /= 2; 40 | 41 | // For each source pixel, output a 3-vector to the best match in 42 | // the target, with an error as the last channel. The 3-vector should be the location of the patch center. 43 | int sizes[3] = {source[0].size().width, source[0].size().height, /*number of frames*/ source.size()}; 44 | cv::Mat out(3, sizes, CV_32FC(4)); 45 | 46 | // Iterate over source frames, finding a match in the target where 47 | // the mask is high. 48 | 49 | for (int t = 0; t < source.size(); t++) { 50 | // INITIALIZATION - uniform random assignment of out matrix values. 51 | for (int y = 0; y < source[0].size().height; y++) { 52 | for (int x = 0; x < source[0].size().width; x++) { 53 | int dx = randomInt(patchSize, target[0].size().width-patchSize-1); 54 | int dy = randomInt(patchSize, target[0].size().height-patchSize-1); 55 | int dt = randomInt(0, target.size()-1); 56 | unsigned char* p = out.ptr(x, y, t) + 0; 57 | *p = dx; 58 | p = out.ptr(x, y, t) + 1; 59 | *p = dy; 60 | p = out.ptr(x, y, t) + 2; 61 | *p = dt; 62 | p = out.ptr(x, y, t) + 3; 63 | *p = distance(x, y, t, 64 | dx, dy, dt, 65 | patchSize, HUGE_VAL); 66 | } 67 | } 68 | } 69 | 70 | bool forwardSearch = true; 71 | 72 | //Split the out matrix into 4 channels for dx, dy, dt, and error. 73 | std::vector channels(4); 74 | cv::split(out, channels); 75 | cv::Mat dx = channels[0], dy = channels[1], dt = channels[2], error = channels[3]; 76 | 77 | for (int i = 0; i < iterations; i++) { 78 | //printf("Iteration %d\n", i); 79 | 80 | // PROPAGATION 81 | if (forwardSearch) { 82 | // Forward propagation - compare left, center and up 83 | for (int t = 0; t < source.size(); t++) { 84 | for (int y = 1; y < source[0].size().height; y++) { 85 | for (int x = 1; x < source[0].size().width; x++) { 86 | if (*error.ptr(x, y, t) + 0 > 0) { 87 | float distLeft = distance(x, y, t, 88 | (*dx.ptr(x-1, y, t)) + 1, 89 | (*dy.ptr(x-1, y, t)), 90 | (*dt.ptr(x-1, y, t)), 91 | patchSize, *error.ptr(x, y, t)); 92 | 93 | if (distLeft < *error.ptr(x, y, t)) { 94 | *dx.ptr(x, y, t) = (*dx.ptr(x-1, y, t))+1; 95 | *dy.ptr(x, y, t) = *dy.ptr(x-1, y, t); 96 | *dt.ptr(x, y, t) = *dt.ptr(x-1, y, t); 97 | *error.ptr(x, y, t) = distLeft; 98 | } 99 | 100 | float distUp = distance(x, y, t, 101 | *dx.ptr(x, y-1, t), 102 | (*dy.ptr(x, y-1, t))+1, 103 | *dt.ptr(x, y-1, t), 104 | patchSize, *error.ptr(x, y, t)); 105 | 106 | if (distUp < *error.ptr(x, y, t)) { 107 | *dx.ptr(x, y, t) = *dx.ptr(x, y-1, t); 108 | *dy.ptr(x, y, t) = (*dy.ptr(x, y-1, t))+1; 109 | *dt.ptr(x, y, t) = *dt.ptr(x, y-1, t); 110 | *error.ptr(x, y, t) = distUp; 111 | } 112 | } 113 | 114 | // TODO: Consider searching across time as well 115 | 116 | } 117 | } 118 | } 119 | 120 | } else { 121 | // Backward propagation - compare right, center and down 122 | for (int t = source.size()-1; t >= 0; t--) { 123 | for (int y = source[0].size().height-2; y >= 0; y--) { 124 | for (int x = source[0].size().width-2; x >= 0; x--) { 125 | if (*error.ptr(x, y, t) > 0) { 126 | float distRight = distance(x, y, t, 127 | (*dx.ptr(x+1, y, t))-1, 128 | *dy.ptr(x+1, y, t), 129 | *dt.ptr(x+1, y, t), 130 | patchSize, *error.ptr(x, y, t)); 131 | 132 | if (distRight < *error.ptr(x, y, t)) { 133 | *dx.ptr(x, y, t) = (*dx.ptr(x+1, y, t))-1; 134 | *dy.ptr(x, y, t) = *dy.ptr(x+1, y, t); 135 | *dt.ptr(x, y, t) = *dt.ptr(x+1, y, t); 136 | *error.ptr(x, y, t) = distRight; 137 | } 138 | 139 | float distDown = distance(x, y, t, 140 | *dx.ptr(x, y+1, t), 141 | (*dy.ptr(x, y+1, t))-1, 142 | *dt.ptr(x, y+1, t), 143 | patchSize, *error.ptr(x, y, t)); 144 | 145 | if (distDown < *error.ptr(x, y, t)) { 146 | *dx.ptr(x, y, t) = *dx.ptr(x, y+1, t); 147 | *dy.ptr(x, y, t) = (*dy.ptr(x, y+1, t))-1; 148 | *dt.ptr(x, y, t) = *dt.ptr(x, y+1, t); 149 | *error.ptr(x, y, t) = distDown; 150 | } 151 | } 152 | 153 | // TODO: Consider searching across time as well 154 | 155 | } 156 | } 157 | } 158 | } 159 | 160 | forwardSearch = !forwardSearch; 161 | 162 | // RANDOM SEARCH 163 | for (int t = 0; t < source.size(); t++) { 164 | for (int y = 0; y < source[0].size().height; y++) { 165 | for (int x = 0; x < source[0].size().width; x++) { 166 | if (*error.ptr(x, y, t) > 0) { 167 | 168 | int radius = target[0].size().width > target[0].size().height ? target[0].size().width : target[0].size().height; 169 | 170 | // search an exponentially smaller window each iteration 171 | while (radius > 8) { 172 | // Search around current offset vector (distance-weighted) 173 | 174 | // clamp the search window to the image 175 | int minX = (int)(*dx.ptr(x, y, t)) - radius; 176 | int maxX = (int)(*dx.ptr(x, y, t)) + radius + 1; 177 | int minY = (int)(*dy.ptr(x, y, t)) - radius; 178 | int maxY = (int)(*dy.ptr(x, y, t)) + radius + 1; 179 | if (minX < 0) { minX = 0; } 180 | if (maxX > target[0].size().width) { maxX = target[0].size().width; } 181 | if (minY < 0) { minY = 0; } 182 | if (maxY > target[0].size().height) { maxY = target[0].size().height; } 183 | 184 | int randX = randomInt(minX, maxX-1); 185 | int randY = randomInt(minY, maxY-1); 186 | int randT = randomInt(0, target.size() - 1); 187 | float dist = distance(x, y, t, 188 | randX, randY, randT, 189 | patchSize, *error.ptr(x, y, t)); 190 | if (dist < *error.ptr(x, y, t)) { 191 | *dx.ptr(x, y, t) = randX; 192 | *dy.ptr(x, y, t) = randY; 193 | *dt.ptr(x, y, t) = randT; 194 | *error.ptr(x, y, t) = dist; 195 | } 196 | 197 | radius >>= 1; 198 | 199 | } 200 | } 201 | } 202 | } 203 | } 204 | } 205 | 206 | //Merge output channels back together 207 | std::vector outs = {dx, dy, dt, error}; 208 | cv::merge(outs, out); 209 | 210 | return out; 211 | 212 | } 213 | 214 | float TextureMapper::distance(int sx, int sy, int st, 215 | int tx, int ty, int tt, 216 | int patchSize, float threshold) { 217 | 218 | // Do not use patches on boundaries 219 | if (tx < patchSize || tx >= target[0].size().width-patchSize || 220 | ty < patchSize || ty >= target[0].size().height-patchSize) { 221 | return HUGE_VAL; 222 | } 223 | 224 | // Compute distance between patches 225 | // Average L2 distance in RGB space 226 | float dist = 0; 227 | 228 | int x1 = max(-patchSize, -sx, -tx); 229 | int x2 = min(patchSize, -sx+source[0].size().width-1, -tx+target[0].size().width-1); 230 | int y1 = max(-patchSize, -sy, -ty); 231 | int y2 = min(patchSize, -sy+source[0].size().height-1, -ty+target[0].size().height-1); 232 | 233 | for (int c = 0; c < target[0].channels() /*color channels*/; c++) { 234 | for (int y = y1; y <= y2; y++) { 235 | for (int x = x1; x <= x2; x++) { 236 | 237 | uint8_t const* sourceValue_ptr(source[st].ptr(sx+x, sy+y) + c); 238 | uint8_t const* targetValue_ptr(target[tt].ptr(tx+x, ty+y) + c); 239 | 240 | float delta = *sourceValue_ptr - *targetValue_ptr; 241 | dist += delta * delta; 242 | 243 | // Early termination 244 | if (dist > threshold) {return HUGE_VAL;} 245 | } 246 | } 247 | } 248 | 249 | return dist; 250 | } 251 | 252 | void TextureMapper::vote(cv::Mat completenessPatchMatches, cv::Mat coherencePatchMatches) { 253 | //For each pixel in the target 254 | for (int t = 0; t < target.size(); t++) { 255 | for (int y = 0; y < target[0].size().height; y++) { 256 | for (int x = 0; x < target[0].size().width; x++) { 257 | std::vector>> patches = findSourcePatches(completenessPatchMatches, coherencePatchMatches, x, y, t); 258 | std::vector> completenessPatches = patches[0]; 259 | std::vector> coherencePatches = patches[1]; 260 | 261 | for (int c = 0; c < source[0].channels(); c++) { 262 | Tixi(completenessPatches, coherencePatches, c); 263 | } 264 | 265 | } 266 | } 267 | } 268 | } 269 | 270 | std::vector>> TextureMapper::findSourcePatches(cv::Mat completenessPatchMatches, cv::Mat coherencePatchMatches, int x, int y, int t) { 271 | std::vector>> sourcePatches; 272 | std::vector> completenessPatches; 273 | sourcePatches[0] = completenessPatches; 274 | std::vector> coherencePatches; 275 | sourcePatches[1] = coherencePatches; 276 | //Find patches in target that contain the pixel 277 | int targetx = x; 278 | int targety = y; 279 | int x1 = std::max(-patchSize, -targetx); 280 | int x2 = std::min(patchSize, -targetx+target[0].size().width-1); 281 | int y1 = std::max(-patchSize, -targety); 282 | int y2 = std::min(patchSize, -targety+target[0].size().height-1); 283 | 284 | //Completeness: Find Source patches that have target patches as their most similar patch 285 | //For each pixel in completenessPatchMatches 286 | for (int st = 0; st < source.size(); st++) { 287 | for (int sy = 0; sy < source[0].size().height; sy++) { 288 | for (int sx = 0; sx < source[0].size().width; sx++) { 289 | cv::Vec patchMatch = completenessPatchMatches.at>(st, sy, st); 290 | int stx = patchMatch[0], sty = patchMatch[1], stt = patchMatch[2]; 291 | if ( /* is in x range */(stx >= x1 && stx <= x2) && /** is in y range */ (sty >= y1 && sty <= y2) && stt == t) { 292 | //return value of the target pixel within the source patch 293 | std::vector targetPixel; 294 | //Find target pixel in source patch 295 | int targetPixelX = (x - stx) + sx; 296 | int targetPixelY = (y - sty) + sy; 297 | for (int c = 0; c < source[0].channels(); c++) { 298 | targetPixel.push_back(source[st].at>(targetPixelX, targetPixelY)[c]); 299 | } 300 | sourcePatches[0].push_back(targetPixel); 301 | } 302 | } 303 | } 304 | } 305 | 306 | //Coherence: Find the Source patches most similar to the target patches 307 | for (int patchy = y1; patchy <= y2; patchy++) { 308 | for (int patchx = x1; patchx <= x2; patchx++) { 309 | cv::Vec sourcePatchVec = coherencePatchMatches.at>(patchx, patchy, t); 310 | //return value of the target pixel within the source patch 311 | std::vector targetPixel; 312 | //Find target pixel in source patch 313 | int targetPixelX = (x - patchx) + sourcePatchVec[0]; 314 | int targetPixelY = (y - patchy) + sourcePatchVec[1]; 315 | for (int c = 0; c < source[0].channels(); c++) { 316 | targetPixel.push_back(source[sourcePatchVec[2]].at>(targetPixelX, targetPixelY)[c]); 317 | } 318 | sourcePatches[0].push_back(targetPixel); 319 | } 320 | } 321 | 322 | return sourcePatches; 323 | } 324 | 325 | int TextureMapper::Tixi(std::vector> completenessPatches, std::vector> coherencePatches, int c /*color channel*/) { 326 | //su and sv are the source patches overlapping with pixel xi of the target for the completeness and coherence terms, respectively. 327 | //yu and yv refer to a single pixel in su and sv , respectively, corresponding to the Xith pixel of the target image. 328 | //U and V refer to the number of patches for the completeness and coherence terms, respectively. 329 | //wj = (cos(θ)**2) / (d**2), where θ is the angle between the surface 330 | //normal and the viewing direction at image j and d denotes the distance between the camera and the surface. 331 | int U = completenessPatches.size(); 332 | int V = coherencePatches.size(); 333 | int L = 49; //L is the number of pixels in a patch (7 x 7 = 49) 334 | int alpha = 2; 335 | int lambda = 0.1; 336 | int sum1 = 0; 337 | int N = texture.size(); //N is the number of texture images. 338 | for (int u = 0; u < U; u++) { 339 | int upatch = completenessPatches[u][c]; 340 | sum1 += upatch; 341 | } 342 | int term1 = (1/L)*sum1; 343 | int sum2 = 0; 344 | for (int v; v < V; v++) { 345 | int vpatch = coherencePatches[v][c]; 346 | sum2 += vpatch; 347 | } 348 | int term2 = (alpha / L) * sum2; 349 | int sum3 = 0; 350 | for (int k = 0; k < N; k++) { 351 | //Mk(Xi->k) RGB color of the kth texture at pixel Xi->k, i.e., the result of projecting texture k to camera i 352 | // (Xi->k is pixel position projected from image i to k) 353 | sum3 += Mk(Xi->k); 354 | } 355 | int term3 = (lambda / N) * wi(xi) * sum3; 356 | int denominator = (U / L) + ((alpha * V) / L) + (lambda * wi(xi)); 357 | return ((term1 + term2 + term3) / denominator); 358 | } 359 | 360 | void TextureMapper::reconstruct() { 361 | for (int t = 0; t < texture.size(); t++) { 362 | for (int y = 0; y < texture[0].size().height; y++) { 363 | for (int x = 0; x < texture[0].size().width; x++) { 364 | *texture[t].ptr(x,y) = Mixi(); 365 | } 366 | } 367 | } 368 | } 369 | 370 | int TextureMapper::Mixi() { 371 | int N = texture.size(); 372 | int numerator = 0; 373 | for (int j = 0; j < N; j++) { 374 | //Tj(Xi->j) is the result of projecting target j to camera i 375 | numerator += wj(Xi->j) * Tj(Xi->j); 376 | } 377 | int denominator = 0; 378 | for (int j = 0; j < N; j++) { 379 | denominator += wj(Xi->j); 380 | } 381 | return numerator / denominator; 382 | } 383 | 384 | /** 385 | ** Initialize 386 | ** the targets and textures with their corresponding source images, 387 | ** i.e., Ti = Si and Mi = Si. 388 | **/ 389 | void TextureMapper::init() { 390 | for (int t = 0; t < source.size(); t++) { 391 | target[t] = source[t].clone(); 392 | texture[t] = source[t].clone(); 393 | } 394 | } 395 | 396 | int TextureMapper::randomInt(int min, int max) { 397 | return min + (rand() % static_cast(max - min + 1)); 398 | } 399 | 400 | std::vector TextureMapper::getRGBD(std::vector target, std::vector TcwPoses) { 401 | //Get depth for all of the pixels. This will either require rasterization or ray-tracing (I need to do more research to determine which one). 402 | 403 | } 404 | 405 | bool TextureMapper::projectToSurface() { 406 | // accumulation buffers for colors and weights 407 | int buff_ind; 408 | double *weights; 409 | double *acc_red; 410 | double *acc_grn; 411 | double *acc_blu; 412 | 413 | // init accumulation buffers for colors and weights 414 | acc_red = new double[model->cm.vn]; 415 | acc_grn = new double[model->cm.vn]; 416 | acc_blu = new double[model->cm.vn]; 417 | for(int buff_ind=0; buff_indcm.vn; buff_ind++) 418 | { 419 | acc_red[buff_ind] = 0.0; 420 | acc_grn[buff_ind] = 0.0; 421 | acc_blu[buff_ind] = 0.0; 422 | } 423 | 424 | //for each camera 425 | for (int cam = 0; cam < TcwPoses.size(); cam++) { 426 | //if raster is good 427 | glContext->makeCurrent(); 428 | 429 | // render normal & depth 430 | rendermanager->renderScene(raster->shot, model, RenderHelper::NORMAL, glContext, my_near[cam_ind]*0.5, my_far[cam_ind]*1.25); 431 | 432 | // Unmaking context current 433 | glContext->doneCurrent(); 434 | 435 | 436 | //THIS IS WHERE THE SEARCH FOR VERTICES IS 437 | // For vertex in model 438 | for (int vertex; vertex < this->vertices->count; vertex++) { 439 | //project point to image space 440 | //get vector from the point-to-be-colored to the camera center 441 | //if inside image 442 | // add color buffers 443 | } //end for each vertex 444 | } //end for each camera 445 | // Paint model vertices with colors 446 | } 447 | 448 | int max(int x, int y, int z) { 449 | return std::max(std::max(x, y), z); 450 | } 451 | 452 | int min(int x, int y, int z){ 453 | return std::min(std::min(x, y), z); 454 | } 455 | 456 | class manual_timer 457 | { 458 | std::chrono::high_resolution_clock::time_point t0; 459 | double timestamp{ 0.f }; 460 | public: 461 | void start() { t0 = std::chrono::high_resolution_clock::now(); } 462 | void stop() { timestamp = std::chrono::duration(std::chrono::high_resolution_clock::now() - t0).count() * 1000; } 463 | const double & get() { return timestamp; } 464 | }; 465 | 466 | void TextureMapper::read_ply_file(const std::string & filepath) 467 | { 468 | try 469 | { 470 | std::ifstream ss(filepath, std::ios::binary); 471 | if (ss.fail()) throw std::runtime_error("failed to open " + filepath); 472 | 473 | PlyFile file; 474 | file.parse_header(ss); 475 | 476 | std::cout << "........................................................................\n"; 477 | for (auto c : file.get_comments()) std::cout << "Comment: " << c << std::endl; 478 | for (auto e : file.get_elements()) 479 | { 480 | std::cout << "element - " << e.name << " (" << e.size << ")" << std::endl; 481 | for (auto p : e.properties) std::cout << "\tproperty - " << p.name << " (" << tinyply::PropertyTable[p.propertyType].str << ")" << std::endl; 482 | } 483 | std::cout << "........................................................................\n"; 484 | 485 | // Tinyply treats parsed data as untyped byte buffers. See below for examples. 486 | std::shared_ptr vertices, normals, faces, texcoords; 487 | 488 | // The header information can be used to programmatically extract properties on elements 489 | // known to exist in the header prior to reading the data. For brevity of this sample, properties 490 | // like vertex position are hard-coded: 491 | try { vertices = file.request_properties_from_element("vertex", { "x", "y", "z" }); } 492 | catch (const std::exception & e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } 493 | 494 | try { normals = file.request_properties_from_element("vertex", { "nx", "ny", "nz" }); } 495 | catch (const std::exception & e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } 496 | 497 | try { texcoords = file.request_properties_from_element("vertex", { "u", "v" }); } 498 | catch (const std::exception & e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } 499 | 500 | // Providing a list size hint (the last argument) is a 2x performance improvement. If you have 501 | // arbitrary ply files, it is best to leave this 0. 502 | try { faces = file.request_properties_from_element("face", { "vertex_indices" }, 3); } 503 | catch (const std::exception & e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } 504 | 505 | manual_timer read_timer; 506 | 507 | read_timer.start(); 508 | file.read(ss); 509 | read_timer.stop(); 510 | 511 | std::cout << "Reading took " << read_timer.get() / 1000.f << " seconds." << std::endl; 512 | if (vertices) std::cout << "\tRead " << vertices->count << " total vertices "<< std::endl; 513 | if (normals) std::cout << "\tRead " << normals->count << " total vertex normals " << std::endl; 514 | if (texcoords) std::cout << "\tRead " << texcoords->count << " total vertex texcoords " << std::endl; 515 | if (faces) std::cout << "\tRead " << faces->count << " total faces (triangles) " << std::endl; 516 | 517 | const size_t numVerticesBytes = vertices->buffer.size_bytes(); 518 | std::vector verts(vertices->count); 519 | std::memcpy(verts.data(), vertices->buffer.get(), numVerticesBytes); 520 | 521 | TextureMapper::vertices = verts; 522 | } 523 | catch (const std::exception & e) 524 | { 525 | std::cerr << "Caught tinyply exception: " << e.what() << std::endl; 526 | } 527 | } -------------------------------------------------------------------------------- /TextureMapper.h: -------------------------------------------------------------------------------- 1 | #ifndef TEXTUREMAPPER_H 2 | #define TEXTUREMAPPER_H 3 | 4 | struct float3 {float x, y, z;}; 5 | 6 | class TextureMapper { 7 | 8 | public: 9 | TextureMapper(std::string plyFilename, std::vector source, std::vector TcwPoses, int patchSize = 7); 10 | 11 | private: 12 | int patchSize; 13 | std::vector vertices; 14 | std::vector TcwPoses; 15 | std::vector source; 16 | std::vector target; 17 | std::vector texture; 18 | 19 | void init(); 20 | void read_ply_file(const std::string & filepath); 21 | void align(std::vector source, std::vector target); 22 | void reconstruct(); 23 | int Mixi(); 24 | cv::Mat patchSearch(std::vector source, std::vector target, int iterations); 25 | void vote(cv::Mat completenessPatchMatches, cv::Mat coherencePatchMatches); 26 | std::vector>> findSourcePatches(cv::Mat completenessPatchMatches, cv::Mat coherencePatchMatches, 27 | int x, int y, int t); 28 | bool isInTargetPatch(cv::Vec targetMatch, int x, int y, int t); 29 | int Tixi(std::vector> completenessPatches, std::vector> coherencePatches, int c); 30 | float distance(int sx, int sy, int st, 31 | int tx, int ty, int tt, 32 | int patchSize, float threshold); 33 | int randomInt(int min, int max); 34 | std::vector getRGBD(std::vector target, std::vector TcwPoses, PlyModel model); 35 | bool projectToSurface(); 36 | }; 37 | 38 | #endif -------------------------------------------------------------------------------- /tinyply.cpp: -------------------------------------------------------------------------------- 1 | // This file exists to create a nice static or shared library via cmake 2 | // but can otherwise be omitted if you prefer to compile tinyply 3 | // directly into your own project. 4 | #define TINYPLY_IMPLEMENTATION 5 | #include "tinyply.h" 6 | -------------------------------------------------------------------------------- /tinyply.h: -------------------------------------------------------------------------------- 1 | /* 2 | * tinyply 2.2 (https://github.com/ddiakopoulos/tinyply) 3 | * 4 | * A single-header, zero-dependency (except the C++ STL) public domain implementation 5 | * of the PLY mesh file format. Requires C++11; errors are handled through exceptions. 6 | * 7 | * This software is in the public domain. Where that dedication is not 8 | * recognized, you are granted a perpetual, irrevocable license to copy, 9 | * distribute, and modify this file as you see fit. 10 | * 11 | * Authored by Dimitri Diakopoulos (http://www.dimitridiakopoulos.com) 12 | * 13 | * tinyply.h may be included in many files, however in a single compiled file, 14 | * the implementation must be created with the following defined 15 | * before including the header. 16 | * #define TINYPLY_IMPLEMENTATION 17 | */ 18 | 19 | //////////////////////// 20 | // tinyply header // 21 | //////////////////////// 22 | 23 | #ifndef tinyply_h 24 | #define tinyply_h 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | namespace tinyply 35 | { 36 | 37 | enum class Type : uint8_t 38 | { 39 | INVALID, 40 | INT8, 41 | UINT8, 42 | INT16, 43 | UINT16, 44 | INT32, 45 | UINT32, 46 | FLOAT32, 47 | FLOAT64 48 | }; 49 | 50 | struct PropertyInfo 51 | { 52 | int stride; 53 | std::string str; 54 | }; 55 | 56 | static std::map PropertyTable 57 | { 58 | { Type::INT8, { 1, "char" } }, 59 | { Type::UINT8, { 1, "uchar" } }, 60 | { Type::INT16, { 2, "short" } }, 61 | { Type::UINT16, { 2, "ushort" } }, 62 | { Type::INT32, { 4, "int" } }, 63 | { Type::UINT32, { 4, "uint" } }, 64 | { Type::FLOAT32, { 4, "float" } }, 65 | { Type::FLOAT64, { 8, "double" } }, 66 | { Type::INVALID, { 0, "INVALID" } } 67 | }; 68 | 69 | class Buffer 70 | { 71 | uint8_t * alias{ nullptr }; 72 | struct delete_array { void operator()(uint8_t * p) { delete[] p; } }; 73 | std::unique_ptr data; 74 | size_t size; 75 | public: 76 | Buffer() {}; 77 | Buffer(const size_t size) : data(new uint8_t[size], delete_array()), size(size) { alias = data.get(); } // allocating 78 | Buffer(uint8_t * ptr) { alias = ptr; } // non-allocating, todo: set size? 79 | uint8_t * get() { return alias; } 80 | size_t size_bytes() const { return size; } 81 | }; 82 | 83 | struct PlyData 84 | { 85 | Type t; 86 | size_t count; 87 | Buffer buffer; 88 | bool isList; 89 | }; 90 | 91 | struct PlyProperty 92 | { 93 | PlyProperty(std::istream & is); 94 | PlyProperty(Type type, std::string & _name) : name(_name), propertyType(type) {} 95 | PlyProperty(Type list_type, Type prop_type, std::string & _name, size_t list_count) 96 | : name(_name), propertyType(prop_type), isList(true), listType(list_type), listCount(list_count) {} 97 | std::string name; 98 | Type propertyType; 99 | bool isList{ false }; 100 | Type listType{ Type::INVALID }; 101 | size_t listCount{ 0 }; 102 | }; 103 | 104 | struct PlyElement 105 | { 106 | PlyElement(std::istream & istream); 107 | PlyElement(const std::string & _name, size_t count) : name(_name), size(count) {} 108 | std::string name; 109 | size_t size; 110 | std::vector properties; 111 | }; 112 | 113 | struct PlyFile 114 | { 115 | struct PlyFileImpl; 116 | std::unique_ptr impl; 117 | 118 | PlyFile(); 119 | ~PlyFile(); 120 | 121 | /* 122 | * The ply format requires an ascii header. This can be used to determine at 123 | * runtime which properties or elements exist in the file. Limited validation of the 124 | * header is performed; it is assumed the header correctly reflects the contents of the 125 | * payload. This function may throw. Returns true on success, false on failure. 126 | */ 127 | bool parse_header(std::istream & is); 128 | 129 | /* 130 | * Execute a read operation. Data must be requested via `request_properties_from_element(...)` 131 | * prior to calling this function. 132 | */ 133 | void read(std::istream & is); 134 | 135 | /* 136 | * `write` performs no validation and assumes that the data passed into 137 | * `add_properties_to_element` is well-formed. 138 | */ 139 | void write(std::ostream & os, bool isBinary); 140 | 141 | /* 142 | * These functions are valid after a call to `parse_header(...)`. In the case of 143 | * writing, get_comments() may also be used to add new comments to the ply header. 144 | */ 145 | std::vector get_elements() const; 146 | std::vector get_info() const; 147 | std::vector & get_comments(); 148 | 149 | /* 150 | * In the general case where |list_size_hint| is zero, `read` performs a two-pass 151 | * parse to support variable length lists. The most general use of the 152 | * ply format is storing triangle meshes. When this fact is known a-priori, we can pass 153 | * an expected list length that will apply to this element. Doing so results in an up-front 154 | * memory allocation and a single-pass import, a 2x performance optimization. 155 | */ 156 | std::shared_ptr request_properties_from_element(const std::string & elementKey, 157 | const std::initializer_list propertyKeys, const uint32_t list_size_hint = 0); 158 | 159 | void add_properties_to_element(const std::string & elementKey, 160 | const std::initializer_list propertyKeys, 161 | const Type type, 162 | const size_t count, 163 | uint8_t * data, 164 | const Type listType, 165 | const size_t listCount); 166 | }; 167 | 168 | } // end namespace tinyply 169 | 170 | #endif // end tinyply_h 171 | 172 | //////////////////////////////// 173 | // tinyply implementation // 174 | //////////////////////////////// 175 | 176 | #ifdef TINYPLY_IMPLEMENTATION 177 | 178 | #include 179 | #include 180 | #include 181 | #include 182 | #include 183 | 184 | using namespace tinyply; 185 | using namespace std; 186 | 187 | template inline T2 endian_swap(const T & v) { return v; } 188 | template<> inline uint16_t endian_swap(const uint16_t & v) { return (v << 8) | (v >> 8); } 189 | template<> inline uint32_t endian_swap(const uint32_t & v) { return (v << 24) | ((v << 8) & 0x00ff0000) | ((v >> 8) & 0x0000ff00) | (v >> 24); } 190 | template<> inline uint64_t endian_swap(const uint64_t & v) 191 | { 192 | return (((v & 0x00000000000000ffLL) << 56) | 193 | ((v & 0x000000000000ff00LL) << 40) | 194 | ((v & 0x0000000000ff0000LL) << 24) | 195 | ((v & 0x00000000ff000000LL) << 8) | 196 | ((v & 0x000000ff00000000LL) >> 8) | 197 | ((v & 0x0000ff0000000000LL) >> 24) | 198 | ((v & 0x00ff000000000000LL) >> 40) | 199 | ((v & 0xff00000000000000LL) >> 56)); 200 | } 201 | template<> inline int16_t endian_swap(const int16_t & v) { uint16_t r = endian_swap(*(uint16_t*)&v); return *(int16_t*)&r; } 202 | template<> inline int32_t endian_swap(const int32_t & v) { uint32_t r = endian_swap(*(uint32_t*)&v); return *(int32_t*)&r; } 203 | template<> inline int64_t endian_swap(const int64_t & v) { uint64_t r = endian_swap(*(uint64_t*)&v); return *(int64_t*)&r; } 204 | template<> inline float endian_swap(const uint32_t & v) { union { float f; uint32_t i; }; i = endian_swap(v); return f; } 205 | template<> inline double endian_swap(const uint64_t & v) { union { double d; uint64_t i; }; i = endian_swap(v); return d; } 206 | 207 | inline uint32_t hash_fnv1a(const std::string & str) 208 | { 209 | static const uint32_t fnv1aBase32 = 0x811C9DC5u; 210 | static const uint32_t fnv1aPrime32 = 0x01000193u; 211 | uint32_t result = fnv1aBase32; 212 | for (auto & c : str) { result ^= static_cast(c); result *= fnv1aPrime32; } 213 | return result; 214 | } 215 | 216 | inline Type property_type_from_string(const std::string & t) 217 | { 218 | if (t == "int8" || t == "char") return Type::INT8; 219 | else if (t == "uint8" || t == "uchar") return Type::UINT8; 220 | else if (t == "int16" || t == "short") return Type::INT16; 221 | else if (t == "uint16" || t == "ushort") return Type::UINT16; 222 | else if (t == "int32" || t == "int") return Type::INT32; 223 | else if (t == "uint32" || t == "uint") return Type::UINT32; 224 | else if (t == "float32" || t == "float") return Type::FLOAT32; 225 | else if (t == "float64" || t == "double") return Type::FLOAT64; 226 | return Type::INVALID; 227 | } 228 | 229 | typedef std::function cast_t; 230 | 231 | struct PlyFile::PlyFileImpl 232 | { 233 | struct PlyDataCursor 234 | { 235 | size_t byteOffset{ 0 }; 236 | size_t totalSizeBytes{ 0 }; 237 | }; 238 | 239 | struct ParsingHelper 240 | { 241 | std::shared_ptr data; 242 | std::shared_ptr cursor; 243 | uint32_t list_size_hint; 244 | }; 245 | 246 | struct PropertyLookup 247 | { 248 | ParsingHelper * helper{ nullptr }; 249 | bool skip{ false }; 250 | size_t prop_stride{ 0 }; // precomputed 251 | size_t list_stride{ 0 }; // precomputed 252 | }; 253 | 254 | std::unordered_map userData; 255 | 256 | bool isBinary = false; 257 | bool isBigEndian = false; 258 | std::vector elements; 259 | std::vector comments; 260 | std::vector objInfo; 261 | uint8_t scratch[64]; // large enough for max list size 262 | 263 | void read(std::istream & is); 264 | void write(std::ostream & os, bool isBinary); 265 | 266 | std::shared_ptr request_properties_from_element(const std::string & elementKey, 267 | const std::initializer_list propertyKeys, 268 | const uint32_t list_size_hint); 269 | 270 | void add_properties_to_element(const std::string & elementKey, 271 | const std::initializer_list propertyKeys, 272 | const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount); 273 | 274 | size_t read_property_binary(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is); 275 | size_t read_property_ascii(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is); 276 | 277 | std::vector> make_property_lookup_table() 278 | { 279 | std::vector> element_property_lookup; 280 | 281 | for (auto & element : elements) 282 | { 283 | std::vector lookups; 284 | 285 | for (auto & property : element.properties) 286 | { 287 | PropertyLookup f; 288 | 289 | auto cursorIt = userData.find(hash_fnv1a(element.name + property.name)); 290 | if (cursorIt != userData.end()) f.helper = &cursorIt->second; 291 | else f.skip = true; 292 | 293 | f.prop_stride = PropertyTable[property.propertyType].stride; 294 | if (property.isList) f.list_stride = PropertyTable[property.listType].stride; 295 | 296 | lookups.push_back(f); 297 | } 298 | 299 | element_property_lookup.push_back(lookups); 300 | } 301 | 302 | return element_property_lookup; 303 | } 304 | 305 | bool parse_header(std::istream & is); 306 | void parse_data(std::istream & is, bool firstPass); 307 | void read_header_format(std::istream & is); 308 | void read_header_element(std::istream & is); 309 | void read_header_property(std::istream & is); 310 | void read_header_text(std::string line, std::istream & is, std::vector & place, int erase = 0); 311 | 312 | void write_header(std::ostream & os); 313 | void write_ascii_internal(std::ostream & os); 314 | void write_binary_internal(std::ostream & os); 315 | void write_property_ascii(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset); 316 | void write_property_binary(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset, const size_t & stride); 317 | }; 318 | 319 | PlyProperty::PlyProperty(std::istream & is) : isList(false) 320 | { 321 | std::string type; 322 | is >> type; 323 | if (type == "list") 324 | { 325 | std::string countType; 326 | is >> countType >> type; 327 | listType = property_type_from_string(countType); 328 | isList = true; 329 | } 330 | propertyType = property_type_from_string(type); 331 | is >> name; 332 | } 333 | 334 | PlyElement::PlyElement(std::istream & is) 335 | { 336 | is >> name >> size; 337 | } 338 | 339 | template inline T ply_read_ascii(std::istream & is) 340 | { 341 | T data; 342 | is >> data; 343 | return data; 344 | } 345 | 346 | template 347 | inline void endian_swap_buffer(uint8_t * data_ptr, const size_t num_bytes, const size_t stride) 348 | { 349 | for (size_t count = 0; count < num_bytes; count += stride) 350 | { 351 | *(reinterpret_cast(data_ptr)) = endian_swap(*(reinterpret_cast(data_ptr))); 352 | data_ptr += stride; 353 | } 354 | } 355 | 356 | template void ply_cast_ascii(void * dest, std::istream & is) 357 | { 358 | *(static_cast(dest)) = ply_read_ascii(is); 359 | } 360 | 361 | int64_t find_element(const std::string & key, const std::vector & list) 362 | { 363 | for (size_t i = 0; i < list.size(); i++) if (list[i].name == key) return i; 364 | return -1; 365 | } 366 | 367 | int64_t find_property(const std::string & key, const std::vector & list) 368 | { 369 | for (size_t i = 0; i < list.size(); ++i) if (list[i].name == key) return i; 370 | return -1; 371 | } 372 | 373 | bool PlyFile::PlyFileImpl::parse_header(std::istream & is) 374 | { 375 | std::string line; 376 | while (std::getline(is, line)) 377 | { 378 | std::istringstream ls(line); 379 | std::string token; 380 | ls >> token; 381 | if (token == "ply" || token == "PLY" || token == "") continue; 382 | else if (token == "comment") read_header_text(line, ls, comments, 8); 383 | else if (token == "format") read_header_format(ls); 384 | else if (token == "element") read_header_element(ls); 385 | else if (token == "property") read_header_property(ls); 386 | else if (token == "obj_info") read_header_text(line, ls, objInfo, 9); 387 | else if (token == "end_header") break; 388 | else return false; // unexpected header field 389 | } 390 | return true; 391 | } 392 | 393 | void PlyFile::PlyFileImpl::read_header_text(std::string line, std::istream & is, std::vector& place, int erase) 394 | { 395 | place.push_back((erase > 0) ? line.erase(0, erase) : line); 396 | } 397 | 398 | void PlyFile::PlyFileImpl::read_header_format(std::istream & is) 399 | { 400 | std::string s; 401 | (is >> s); 402 | if (s == "binary_little_endian") isBinary = true; 403 | else if (s == "binary_big_endian") isBinary = isBigEndian = true; 404 | } 405 | 406 | void PlyFile::PlyFileImpl::read_header_element(std::istream & is) 407 | { 408 | elements.emplace_back(is); 409 | } 410 | 411 | void PlyFile::PlyFileImpl::read_header_property(std::istream & is) 412 | { 413 | if (!elements.size()) throw std::runtime_error("no elements defined; file is malformed"); 414 | elements.back().properties.emplace_back(is); 415 | } 416 | 417 | size_t PlyFile::PlyFileImpl::read_property_binary(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is) 418 | { 419 | destOffset += stride; 420 | is.read((char*)dest, stride); 421 | return stride; 422 | } 423 | 424 | size_t PlyFile::PlyFileImpl::read_property_ascii(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is) 425 | { 426 | destOffset += stride; 427 | switch (t) 428 | { 429 | case Type::INT8: *((int8_t *)dest) = ply_read_ascii(is); break; 430 | case Type::UINT8: *((uint8_t *)dest) = ply_read_ascii(is); break; 431 | case Type::INT16: ply_cast_ascii(dest, is); break; 432 | case Type::UINT16: ply_cast_ascii(dest, is); break; 433 | case Type::INT32: ply_cast_ascii(dest, is); break; 434 | case Type::UINT32: ply_cast_ascii(dest, is); break; 435 | case Type::FLOAT32: ply_cast_ascii(dest, is); break; 436 | case Type::FLOAT64: ply_cast_ascii(dest, is); break; 437 | case Type::INVALID: throw std::invalid_argument("invalid ply property"); 438 | } 439 | return stride; 440 | } 441 | 442 | void PlyFile::PlyFileImpl::write_property_ascii(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset) 443 | { 444 | switch (t) 445 | { 446 | case Type::INT8: os << static_cast(*reinterpret_cast(src)); break; 447 | case Type::UINT8: os << static_cast(*reinterpret_cast(src)); break; 448 | case Type::INT16: os << *reinterpret_cast(src); break; 449 | case Type::UINT16: os << *reinterpret_cast(src); break; 450 | case Type::INT32: os << *reinterpret_cast(src); break; 451 | case Type::UINT32: os << *reinterpret_cast(src); break; 452 | case Type::FLOAT32: os << *reinterpret_cast(src); break; 453 | case Type::FLOAT64: os << *reinterpret_cast(src); break; 454 | case Type::INVALID: throw std::invalid_argument("invalid ply property"); 455 | } 456 | os << " "; 457 | srcOffset += PropertyTable[t].stride; 458 | } 459 | 460 | void PlyFile::PlyFileImpl::write_property_binary(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset, const size_t & stride) 461 | { 462 | os.write((char *)src, stride); 463 | srcOffset += stride; 464 | } 465 | 466 | void PlyFile::PlyFileImpl::read(std::istream & is) 467 | { 468 | std::vector> buffers; 469 | for (auto & entry : userData) buffers.push_back(entry.second.data); 470 | 471 | // Discover if we can allocate up front without parsing the file twice 472 | uint32_t list_hints = 0; 473 | for (auto & b : buffers) for (auto & entry : userData) list_hints += entry.second.list_size_hint; 474 | 475 | // No list hints? Then we need to calculate how much memory to allocate 476 | if (list_hints == 0) parse_data(is, true); 477 | 478 | // Count the number of properties (required for allocation) 479 | // e.g. if we have properties x y and z requested, we ensure 480 | // that their buffer points to the same PlyData 481 | std::unordered_map unique_data_count; 482 | for (auto & ptr : buffers) unique_data_count[ptr.get()] += 1; 483 | 484 | // Since group-requested properties share the same cursor, 485 | // we need to find unique cursors so we only allocate once 486 | std::sort(buffers.begin(), buffers.end()); 487 | buffers.erase(std::unique(buffers.begin(), buffers.end()), buffers.end()); 488 | 489 | // We sorted by ptrs on PlyData, need to remap back onto its cursor in the userData table 490 | for (auto & b : buffers) 491 | { 492 | for (auto & entry : userData) 493 | { 494 | if (entry.second.data == b && b->buffer.get() == nullptr) 495 | { 496 | // If we didn't receive any list hints, it means we did two passes over the 497 | // file to compute the total length of all (potentially) variable-length lists 498 | if (list_hints == 0) 499 | { 500 | b->buffer = Buffer(entry.second.cursor->totalSizeBytes); 501 | } 502 | else 503 | { 504 | // otherwise, we can allocate up front, skipping the first pass. 505 | const size_t list_size_multiplier = (entry.second.data->isList ? entry.second.list_size_hint : 1); 506 | auto bytes_per_property = entry.second.data->count * PropertyTable[entry.second.data->t].stride * list_size_multiplier; 507 | bytes_per_property *= unique_data_count[b.get()]; 508 | b->buffer = Buffer(bytes_per_property); 509 | } 510 | 511 | } 512 | } 513 | } 514 | 515 | // Populate the data 516 | parse_data(is, false); 517 | 518 | if (isBigEndian) 519 | { 520 | for (auto & b : buffers) 521 | { 522 | uint8_t * data_ptr = b->buffer.get(); 523 | const size_t stride = PropertyTable[b->t].stride; 524 | const size_t buffer_size_bytes = b->buffer.size_bytes(); 525 | 526 | switch (b->t) 527 | { 528 | case Type::INT16: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; 529 | case Type::UINT16: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; 530 | case Type::INT32: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; 531 | case Type::UINT32: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; 532 | case Type::FLOAT32: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; 533 | case Type::FLOAT64: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; 534 | } 535 | } 536 | } 537 | } 538 | 539 | void PlyFile::PlyFileImpl::write(std::ostream & os, bool _isBinary) 540 | { 541 | // reset cursors 542 | for (auto & d : userData) { d.second.cursor->byteOffset = 0; } 543 | if (_isBinary) write_binary_internal(os); 544 | else write_ascii_internal(os); 545 | } 546 | 547 | void PlyFile::PlyFileImpl::write_binary_internal(std::ostream & os) 548 | { 549 | isBinary = true; 550 | write_header(os); 551 | 552 | uint8_t listSize[4] = { 0, 0, 0, 0 }; 553 | size_t dummyCount = 0; 554 | 555 | auto element_property_lookup = make_property_lookup_table(); 556 | 557 | size_t element_idx = 0; 558 | for (auto & e : elements) 559 | { 560 | for (size_t i = 0; i < e.size; ++i) 561 | { 562 | size_t property_index = 0; 563 | for (auto & p : e.properties) 564 | { 565 | auto & f = element_property_lookup[element_idx][property_index]; 566 | auto * helper = f.helper; 567 | 568 | if (p.isList) 569 | { 570 | std::memcpy(listSize, &p.listCount, sizeof(uint32_t)); 571 | write_property_binary(p.listType, os, listSize, dummyCount, f.list_stride); 572 | write_property_binary(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset, f.prop_stride * p.listCount); 573 | } 574 | else 575 | { 576 | write_property_binary(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset, f.prop_stride); 577 | } 578 | property_index++; 579 | } 580 | } 581 | element_idx++; 582 | } 583 | } 584 | 585 | void PlyFile::PlyFileImpl::write_ascii_internal(std::ostream & os) 586 | { 587 | write_header(os); 588 | 589 | for (auto & e : elements) 590 | { 591 | for (size_t i = 0; i < e.size; ++i) 592 | { 593 | for (auto & p : e.properties) 594 | { 595 | auto & helper = userData[hash_fnv1a(e.name + p.name)]; 596 | if (p.isList) 597 | { 598 | os << p.listCount << " "; 599 | for (int j = 0; j < p.listCount; ++j) 600 | { 601 | write_property_ascii(p.propertyType, os, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset); 602 | } 603 | } 604 | else 605 | { 606 | write_property_ascii(p.propertyType, os, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset); 607 | } 608 | } 609 | os << "\n"; 610 | } 611 | } 612 | } 613 | 614 | void PlyFile::PlyFileImpl::write_header(std::ostream & os) 615 | { 616 | const std::locale & fixLoc = std::locale("C"); 617 | os.imbue(fixLoc); 618 | 619 | os << "ply\n"; 620 | if (isBinary) os << ((isBigEndian) ? "format binary_big_endian 1.0" : "format binary_little_endian 1.0") << "\n"; 621 | else os << "format ascii 1.0\n"; 622 | 623 | for (const auto & comment : comments) os << "comment " << comment << "\n"; 624 | 625 | for (auto & e : elements) 626 | { 627 | os << "element " << e.name << " " << e.size << "\n"; 628 | for (const auto & p : e.properties) 629 | { 630 | if (p.isList) 631 | { 632 | os << "property list " << PropertyTable[p.listType].str << " " 633 | << PropertyTable[p.propertyType].str << " " << p.name << "\n"; 634 | } 635 | else 636 | { 637 | os << "property " << PropertyTable[p.propertyType].str << " " << p.name << "\n"; 638 | } 639 | } 640 | } 641 | os << "end_header\n"; 642 | } 643 | 644 | std::shared_ptr PlyFile::PlyFileImpl::request_properties_from_element(const std::string & elementKey, 645 | const std::initializer_list propertyKeys, 646 | const uint32_t list_size_hint) 647 | { 648 | // Each key in `propertyKey` gets an entry into the userData map (keyed by a hash of 649 | // element name and property name), but groups of properties (requested from the 650 | // public api through this function) all share the same `ParsingHelper`. When it comes 651 | // time to .read(), we check the number of unique PlyData shared pointers 652 | // and allocate a single buffer that will be used by each individual property. 653 | ParsingHelper helper; 654 | helper.data = std::make_shared(); 655 | helper.data->count = 0; 656 | helper.data->isList = false; 657 | helper.data->t = Type::INVALID; 658 | helper.cursor = std::make_shared(); 659 | helper.list_size_hint = list_size_hint; 660 | 661 | if (elements.empty()) throw std::runtime_error("header had no elements defined. malformed file?"); 662 | if (elementKey.empty()) throw std::invalid_argument("`elementKey` argument is empty"); 663 | if (!propertyKeys.size()) throw std::invalid_argument("`propertyKeys` argument is empty"); 664 | 665 | const int64_t elementIndex = find_element(elementKey, elements); 666 | 667 | std::vector keys_not_found; 668 | 669 | // Sanity check if the user requested element is in the pre-parsed header 670 | if (elementIndex >= 0) 671 | { 672 | // We found the element 673 | const PlyElement & element = elements[elementIndex]; 674 | 675 | helper.data->count = element.size; 676 | 677 | // Find each of the keys 678 | for (auto key : propertyKeys) 679 | { 680 | const int64_t propertyIndex = find_property(key, element.properties); 681 | if (propertyIndex >= 0) 682 | { 683 | // We found the property 684 | const PlyProperty & property = element.properties[propertyIndex]; 685 | helper.data->t = property.propertyType; 686 | helper.data->isList = property.isList; 687 | auto result = userData.insert(std::pair(hash_fnv1a(element.name + property.name), helper)); 688 | if (result.second == false) 689 | { 690 | throw std::invalid_argument("element-property key has already been requested: " + hash_fnv1a(element.name + property.name)); 691 | } 692 | } 693 | else keys_not_found.push_back(key); 694 | } 695 | } 696 | else throw std::invalid_argument("the element key was not found in the header: " + elementKey); 697 | 698 | if (keys_not_found.size()) 699 | { 700 | std::stringstream ss; 701 | for (auto & str : keys_not_found) ss << str << ", "; 702 | throw std::invalid_argument("the following property keys were not found in the header: " + ss.str()); 703 | } 704 | return helper.data; 705 | } 706 | 707 | void PlyFile::PlyFileImpl::add_properties_to_element(const std::string & elementKey, 708 | const std::initializer_list propertyKeys, 709 | const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount) 710 | { 711 | ParsingHelper helper; 712 | helper.data = std::make_shared(); 713 | helper.data->count = count; 714 | helper.data->t = type; 715 | helper.data->buffer = Buffer(data); 716 | helper.cursor = std::make_shared(); 717 | 718 | auto create_property_on_element = [&](PlyElement & e) 719 | { 720 | for (auto key : propertyKeys) 721 | { 722 | PlyProperty newProp = (listType == Type::INVALID) ? PlyProperty(type, key) : PlyProperty(listType, type, key, listCount); 723 | userData.insert(std::pair(hash_fnv1a(elementKey + key), helper)); 724 | e.properties.push_back(newProp); 725 | } 726 | }; 727 | 728 | const int64_t idx = find_element(elementKey, elements); 729 | if (idx >= 0) 730 | { 731 | PlyElement & e = elements[idx]; 732 | create_property_on_element(e); 733 | } 734 | else 735 | { 736 | PlyElement newElement = (listType == Type::INVALID) ? PlyElement(elementKey, count) : PlyElement(elementKey, count); 737 | create_property_on_element(newElement); 738 | elements.push_back(newElement); 739 | } 740 | } 741 | 742 | void PlyFile::PlyFileImpl::parse_data(std::istream & is, bool firstPass) 743 | { 744 | std::function read; 745 | std::function skip; 746 | 747 | const auto start = is.tellg(); 748 | 749 | size_t listSize = 0; 750 | size_t dummyCount = 0; 751 | std::string skip_ascii_buffer; 752 | 753 | // Special case mirroring read_property_binary but for list types; this 754 | // has an additional big endian check to flip the data in place immediately 755 | // after reading. We do this as a performance optimization; endian flipping is 756 | // done on regular properties as a post-process after reading (also for optimization) 757 | // but we need the correct little-endian list count as we read the file. 758 | auto read_list_binary = [this](const Type & t, void * dst, size_t & destOffset, std::istream & _is) 759 | { 760 | const size_t stride = PropertyTable[t].stride; // @todo - this is already precomputed 761 | destOffset += stride; 762 | _is.read((char*)dst, stride); 763 | 764 | if (isBigEndian) 765 | { 766 | switch (t) 767 | { 768 | case Type::INT16: endian_swap(*(int16_t*)dst); break; 769 | case Type::UINT16: endian_swap(*(uint16_t*)dst); break; 770 | case Type::INT32: endian_swap(*(int32_t*)dst); break; 771 | case Type::UINT32: endian_swap(*(uint32_t*)dst); break; 772 | } 773 | } 774 | 775 | return stride; 776 | }; 777 | 778 | if (isBinary) 779 | { 780 | read = [this, &listSize, &dummyCount, &read_list_binary](PropertyLookup & f, const PlyProperty & p, uint8_t * dest, size_t & destOffset, std::istream & _is) 781 | { 782 | if (!p.isList) 783 | { 784 | read_property_binary(p.propertyType, f.prop_stride, dest + destOffset, destOffset, _is); 785 | } 786 | else 787 | { 788 | read_list_binary(p.listType, &listSize, dummyCount, _is); // the list size 789 | read_property_binary(p.propertyType, f.prop_stride * listSize, dest + destOffset, destOffset, _is); // properties in list 790 | } 791 | }; 792 | skip = [this, &listSize, &dummyCount, &read_list_binary](PropertyLookup & f, const PlyProperty & p, std::istream & _is) 793 | { 794 | if (!p.isList) 795 | { 796 | _is.read((char*)scratch, f.prop_stride); 797 | return f.prop_stride; 798 | } 799 | read_list_binary(p.listType, &listSize, dummyCount, _is); // the list size (does not count for memory alloc) 800 | return read_property_binary(p.propertyType, f.prop_stride * listSize, scratch, dummyCount, _is); 801 | }; 802 | } 803 | else 804 | { 805 | read = [this, &listSize, &dummyCount](PropertyLookup & f, const PlyProperty & p, uint8_t * dest, size_t & destOffset, std::istream & _is) 806 | { 807 | if (!p.isList) 808 | { 809 | read_property_ascii(p.propertyType, f.prop_stride, dest + destOffset, destOffset, _is); 810 | } 811 | else 812 | { 813 | read_property_ascii(p.listType, f.list_stride, &listSize, dummyCount, _is); // the list size 814 | for (size_t i = 0; i < listSize; ++i) 815 | { 816 | read_property_ascii(p.propertyType, f.prop_stride, dest + destOffset, destOffset, _is); 817 | } 818 | } 819 | }; 820 | skip = [this, &listSize, &dummyCount, &skip_ascii_buffer](PropertyLookup & f, const PlyProperty & p, std::istream & _is) 821 | { 822 | skip_ascii_buffer.clear(); 823 | if (p.isList) 824 | { 825 | read_property_ascii(p.listType, f.list_stride, &listSize, dummyCount, _is); // the list size 826 | for (size_t i = 0; i < listSize; ++i) _is >> skip_ascii_buffer; // properties in list 827 | return listSize * f.prop_stride; 828 | } 829 | _is >> skip_ascii_buffer; 830 | return f.prop_stride; 831 | }; 832 | } 833 | 834 | auto element_property_lookup = make_property_lookup_table(); 835 | 836 | size_t element_idx = 0; 837 | size_t property_index = 0; 838 | for (auto & element : elements) 839 | { 840 | for (size_t count = 0; count < element.size; ++count) 841 | { 842 | property_index = 0; 843 | for (auto & property : element.properties) 844 | { 845 | auto & f = element_property_lookup[element_idx][property_index]; 846 | if (!f.skip) 847 | { 848 | auto * helper = f.helper; 849 | if (firstPass) helper->cursor->totalSizeBytes += skip(f, property, is); 850 | else read(f, property, helper->data->buffer.get(), helper->cursor->byteOffset, is); 851 | } 852 | else skip(f, property, is); 853 | property_index++; 854 | } 855 | } 856 | element_idx++; 857 | } 858 | 859 | // Reset istream reader to the beginning 860 | if (firstPass) is.seekg(start, is.beg); 861 | } 862 | 863 | // Wrap the public interface: 864 | 865 | PlyFile::PlyFile() { impl.reset(new PlyFileImpl()); }; 866 | PlyFile::~PlyFile() { }; 867 | bool PlyFile::parse_header(std::istream & is) { return impl->parse_header(is); } 868 | void PlyFile::read(std::istream & is) { return impl->read(is); } 869 | void PlyFile::write(std::ostream & os, bool isBinary) { return impl->write(os, isBinary); } 870 | std::vector PlyFile::get_elements() const { return impl->elements; } 871 | std::vector & PlyFile::get_comments() { return impl->comments; } 872 | std::vector PlyFile::get_info() const { return impl->objInfo; } 873 | std::shared_ptr PlyFile::request_properties_from_element(const std::string & elementKey, 874 | const std::initializer_list propertyKeys, 875 | const uint32_t list_size_hint) 876 | { 877 | return impl->request_properties_from_element(elementKey, propertyKeys, list_size_hint); 878 | } 879 | void PlyFile::add_properties_to_element(const std::string & elementKey, 880 | const std::initializer_list propertyKeys, 881 | const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount) 882 | { 883 | return impl->add_properties_to_element(elementKey, propertyKeys, type, count, data, listType, listCount); 884 | } 885 | 886 | #endif // end TINYPLY_IMPLEMENTATION --------------------------------------------------------------------------------