source: other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/mesh.h@ 28897

Last change on this file since 28897 was 28897, checked in by davidb, 10 years ago

GUI front-end to server base plus web page content

File size: 21.2 KB
Line 
1// Copyright 2011 Google Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you
4// may not use this file except in compliance with the License. You
5// may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12// implied. See the License for the specific language governing
13// permissions and limitations under the License.
14
15#ifndef WEBGL_LOADER_MESH_H_
16#define WEBGL_LOADER_MESH_H_
17
18#include <float.h>
19#include <limits.h>
20#include <math.h>
21#include <stdio.h>
22#include <stdlib.h>
23
24#include <map>
25#include <string>
26#include <utility>
27#include <vector>
28
29#include "base.h"
30#include "bounds.h"
31#include "stream.h"
32#include "utf8.h"
33
34// A short list of floats, useful for parsing a single vector
35// attribute.
36class ShortFloatList {
37 public:
38 // MeshLab can create position attributes with
39 // color coordinates like: v x y z r g b
40 static const size_t kMaxNumFloats = 6;
41 ShortFloatList()
42 : size_(0)
43 {
44 clear();
45 }
46
47 void clear() {
48 for (size_t i = 0; i < kMaxNumFloats; ++i) {
49 a_[i] = 0.f;
50 }
51 }
52
53 // Parse up to kMaxNumFloats from C string.
54 // TODO: this should instead return endptr, since size
55 // is recoverable.
56 size_t ParseLine(const char* line) {
57 for (size_ = 0; size_ != kMaxNumFloats; ++size_) {
58 char* endptr = NULL;
59 a_[size_] = strtof(line, &endptr);
60 if (endptr == NULL || line == endptr) break;
61 line = endptr;
62 }
63 return size_;
64 }
65
66 float operator[](size_t idx) const {
67 return a_[idx];
68 }
69
70 void AppendTo(AttribList* attribs) const {
71 AppendNTo(attribs, size_);
72 }
73
74 void AppendNTo(AttribList* attribs, const size_t sz) const {
75 attribs->insert(attribs->end(), a_, a_ + sz);
76 }
77
78 bool empty() const { return size_ == 0; }
79
80 size_t size() const { return size_; }
81 private:
82 float a_[kMaxNumFloats];
83 size_t size_;
84};
85
86class IndexFlattener {
87 public:
88 explicit IndexFlattener(size_t num_positions)
89 : count_(0),
90 table_(num_positions) {
91 }
92
93 int count() const { return count_; }
94
95 void reserve(size_t size) {
96 table_.reserve(size);
97 }
98
99 // Returns a pair of: < flattened index, newly inserted >.
100 std::pair<int, bool> GetFlattenedIndex(int position_index,
101 int texcoord_index,
102 int normal_index) {
103 if (position_index >= static_cast<int>(table_.size())) {
104 table_.resize(position_index + 1);
105 }
106 // First, optimistically look up position_index in the table.
107 IndexType& index = table_[position_index];
108 if (index.position_or_flat == kIndexUnknown) {
109 // This is the first time we've seen this position in the table,
110 // so fill it. Since the table is indexed by position, we can
111 // use the position_or_flat_index field to store the flat index.
112 const int flat_index = count_++;
113 index.position_or_flat = flat_index;
114 index.texcoord = texcoord_index;
115 index.normal = normal_index;
116 return std::make_pair(flat_index, true);
117 } else if (index.position_or_flat == kIndexNotInTable) {
118 // There are multiple flattened indices at this position index,
119 // so resort to the map.
120 return GetFlattenedIndexFromMap(position_index,
121 texcoord_index,
122 normal_index);
123 } else if (index.texcoord == texcoord_index &&
124 index.normal == normal_index) {
125 // The other indices match, so we can use the value cached in
126 // the table.
127 return std::make_pair(index.position_or_flat, false);
128 }
129 // The other indices don't match, so we mark this table entry,
130 // and insert both the old and new indices into the map.
131 const IndexType old_index(position_index, index.texcoord, index.normal);
132 map_.insert(std::make_pair(old_index, index.position_or_flat));
133 index.position_or_flat = kIndexNotInTable;
134 const IndexType new_index(position_index, texcoord_index, normal_index);
135 const int flat_index = count_++;
136 map_.insert(std::make_pair(new_index, flat_index));
137 return std::make_pair(flat_index, true);
138 }
139 private:
140 std::pair<int, bool> GetFlattenedIndexFromMap(int position_index,
141 int texcoord_index,
142 int normal_index) {
143 IndexType index(position_index, texcoord_index, normal_index);
144 MapType::iterator iter = map_.lower_bound(index);
145 if (iter == map_.end() || iter->first != index) {
146 const int flat_index = count_++;
147 map_.insert(iter, std::make_pair(index, flat_index));
148 return std::make_pair(flat_index, true);
149 } else {
150 return std::make_pair(iter->second, false);
151 }
152 }
153
154 static const int kIndexUnknown = -1;
155 static const int kIndexNotInTable = -2;
156
157 struct IndexType {
158 IndexType()
159 : position_or_flat(kIndexUnknown),
160 texcoord(kIndexUnknown),
161 normal(kIndexUnknown)
162 { }
163
164 IndexType(int position_index, int texcoord_index, int normal_index)
165 : position_or_flat(position_index),
166 texcoord(texcoord_index),
167 normal(normal_index)
168 { }
169
170 // I'm being tricky/lazy here. The table_ stores the flattened
171 // index in the first field, since it is indexed by position. The
172 // map_ stores position and uses this struct as a key to lookup the
173 // flattened index.
174 int position_or_flat;
175 int texcoord;
176 int normal;
177
178 // An ordering for std::map.
179 bool operator<(const IndexType& that) const {
180 if (position_or_flat == that.position_or_flat) {
181 if (texcoord == that.texcoord) {
182 return normal < that.normal;
183 } else {
184 return texcoord < that.texcoord;
185 }
186 } else {
187 return position_or_flat < that.position_or_flat;
188 }
189 }
190
191 bool operator==(const IndexType& that) const {
192 return position_or_flat == that.position_or_flat &&
193 texcoord == that.texcoord && normal == that.normal;
194 }
195
196 bool operator!=(const IndexType& that) const {
197 return !operator==(that);
198 }
199 };
200 typedef std::map<IndexType, int> MapType;
201
202 int count_;
203 std::vector<IndexType> table_;
204 MapType map_;
205};
206
207static inline size_t positionDim() { return 3; }
208static inline size_t texcoordDim() { return 2; }
209static inline size_t normalDim() { return 3; }
210
211// TODO(wonchun): Make a c'tor to properly initialize.
212struct GroupStart {
213 size_t offset; // offset into draw_mesh_.indices.
214 unsigned int group_line;
215 int min_index, max_index; // range into attribs.
216 webgl_loader::Bounds bounds;
217};
218
219class DrawBatch {
220 public:
221 DrawBatch()
222 : flattener_(0),
223 current_group_line_(0xFFFFFFFF) {
224 }
225
226 const std::vector<GroupStart>& group_starts() const {
227 return group_starts_;
228 }
229
230 void Init(AttribList* positions, AttribList* texcoords, AttribList* normals) {
231 positions_ = positions;
232 texcoords_ = texcoords;
233 normals_ = normals;
234 flattener_.reserve(1024);
235 }
236
237 void AddTriangle(unsigned int group_line, int* indices) {
238 if (group_line != current_group_line_) {
239 current_group_line_ = group_line;
240 GroupStart group_start;
241 group_start.offset = draw_mesh_.indices.size();
242 group_start.group_line = group_line;
243 group_start.min_index = INT_MAX;
244 group_start.max_index = INT_MIN;
245 group_start.bounds.Clear();
246 group_starts_.push_back(group_start);
247 }
248 GroupStart& group = group_starts_.back();
249 for (size_t i = 0; i < 9; i += 3) {
250 // .OBJ files use 1-based indexing.
251 const int position_index = indices[i + 0] - 1;
252 const int texcoord_index = indices[i + 1] - 1;
253 const int normal_index = indices[i + 2] - 1;
254 const std::pair<int, bool> flattened = flattener_.GetFlattenedIndex(
255 position_index, texcoord_index, normal_index);
256 const int flat_index = flattened.first;
257 CHECK(flat_index >= 0);
258 draw_mesh_.indices.push_back(flat_index);
259 if (flattened.second) {
260 // This is a new index. Keep track of index ranges and vertex
261 // bounds.
262 if (flat_index > group.max_index) {
263 group.max_index = flat_index;
264 }
265 if (flat_index < group.min_index) {
266 group.min_index = flat_index;
267 }
268 const size_t new_loc = draw_mesh_.attribs.size();
269 CHECK(8*size_t(flat_index) == new_loc);
270 for (size_t i = 0; i < positionDim(); ++i) {
271 draw_mesh_.attribs.push_back(
272 positions_->at(positionDim() * position_index + i));
273 }
274 if (texcoord_index == -1) {
275 for (size_t i = 0; i < texcoordDim(); ++i) {
276 draw_mesh_.attribs.push_back(0);
277 }
278 } else {
279 for (size_t i = 0; i < texcoordDim(); ++i) {
280 draw_mesh_.attribs.push_back(
281 texcoords_->at(texcoordDim() * texcoord_index + i));
282 }
283 }
284 if (normal_index == -1) {
285 for (size_t i = 0; i < normalDim(); ++i) {
286 draw_mesh_.attribs.push_back(0);
287 }
288 } else {
289 for (size_t i = 0; i < normalDim(); ++i) {
290 draw_mesh_.attribs.push_back(
291 normals_->at(normalDim() * normal_index + i));
292 }
293 }
294 // TODO: is the covariance body useful for anything?
295 group.bounds.EncloseAttrib(&draw_mesh_.attribs[new_loc]);
296 }
297 }
298 }
299
300 const DrawMesh& draw_mesh() const {
301 return draw_mesh_;
302 }
303 private:
304 AttribList* positions_, *texcoords_, *normals_;
305 DrawMesh draw_mesh_;
306 IndexFlattener flattener_;
307 unsigned int current_group_line_;
308 std::vector<GroupStart> group_starts_;
309};
310
311struct Material {
312 std::string name;
313 float Kd[3];
314 std::string map_Kd;
315
316 void DumpJson(FILE* out = stdout) const {
317 fprintf(out, " \"%s\": { ", name.c_str());
318 if (map_Kd.empty()) {
319 fprintf(out, "\"Kd\": [%hu, %hu, %hu] }",
320 Quantize(Kd[0], 0, 1, 255),
321 Quantize(Kd[1], 0, 1, 255),
322 Quantize(Kd[2], 0, 1, 255));
323 } else {
324 fprintf(out, "\"map_Kd\": \"%s\" }", map_Kd.c_str());
325 }
326 }
327};
328
329typedef std::vector<Material> MaterialList;
330
331class WavefrontMtlFile {
332 public:
333 explicit WavefrontMtlFile(FILE* fp) {
334 ParseFile(fp);
335 }
336
337 const MaterialList& materials() const {
338 return materials_;
339 }
340
341 private:
342 // TODO: factor this parsing stuff out.
343 void ParseFile(FILE* fp) {
344 // TODO: don't use a fixed-size buffer.
345 const size_t kLineBufferSize = 256;
346 char buffer[kLineBufferSize];
347 unsigned int line_num = 1;
348 while (fgets(buffer, kLineBufferSize, fp) != NULL) {
349 char* stripped = StripLeadingWhitespace(buffer);
350 TerminateAtNewlineOrComment(stripped);
351 ParseLine(stripped, line_num++);
352 }
353 }
354
355 void ParseLine(const char* line, unsigned int line_num) {
356 switch (*line) {
357 case 'K':
358 ParseColor(line + 1, line_num);
359 break;
360 case 'm':
361 if (0 == strncmp(line + 1, "ap_Kd", 5)) {
362 ParseMapKd(line + 6, line_num);
363 }
364 break;
365 case 'n':
366 if (0 == strncmp(line + 1, "ewmtl", 5)) {
367 ParseNewmtl(line + 6, line_num);
368 }
369 default:
370 break;
371 }
372 }
373
374 void ParseColor(const char* line, unsigned int line_num) {
375 switch (*line) {
376 case 'd': {
377 ShortFloatList floats;
378 floats.ParseLine(line + 1);
379 float* Kd = current_->Kd;
380 Kd[0] = floats[0];
381 Kd[1] = floats[1];
382 Kd[2] = floats[2];
383 break;
384 }
385 default:
386 break;
387 }
388 }
389
390 void ParseMapKd(const char* line, unsigned int line_num) {
391 current_->map_Kd = StripLeadingWhitespace(line);
392 }
393
394 void ParseNewmtl(const char* line, unsigned int line_num) {
395 materials_.push_back(Material());
396 current_ = &materials_.back();
397 ToLower(StripLeadingWhitespace(line), &current_->name);
398 }
399
400 Material* current_;
401 MaterialList materials_;
402};
403
404typedef std::map<std::string, DrawBatch> MaterialBatches;
405
406// TODO: consider splitting this into a low-level parser and a high-level
407// object.
408class WavefrontObjFile {
409 public:
410 explicit WavefrontObjFile(FILE* fp) {
411 current_batch_ = &material_batches_[""];
412 current_batch_->Init(&positions_, &texcoords_, &normals_);
413 current_group_line_ = 0;
414 line_to_groups_.insert(std::make_pair(0, "default"));
415 ParseFile(fp);
416 }
417
418 const MaterialList& materials() const {
419 return materials_;
420 }
421
422 const MaterialBatches& material_batches() const {
423 return material_batches_;
424 }
425
426 const std::string& LineToGroup(unsigned int line) const {
427 typedef LineToGroups::const_iterator Iterator;
428 typedef std::pair<Iterator, Iterator> EqualRange;
429 EqualRange equal_range = line_to_groups_.equal_range(line);
430 const std::string* best_group = NULL;
431 int best_count = 0;
432 for (Iterator iter = equal_range.first; iter != equal_range.second;
433 ++iter) {
434 const std::string& group = iter->second;
435 const int count = group_counts_.find(group)->second;
436 if (!best_group || (count < best_count)) {
437 best_group = &group;
438 best_count = count;
439 }
440 }
441 if (!best_group) {
442 ErrorLine("no suitable group found", line);
443 }
444 return *best_group;
445 }
446
447 void DumpDebug() const {
448 printf("positions size: " PRIuS "\n"
449 "texcoords size: " PRIuS "\n"
450 "normals size: " PRIuS "\n",
451 positions_.size(), texcoords_.size(), normals_.size());
452 }
453 private:
454 WavefrontObjFile() { } // For testing.
455
456 void ParseFile(FILE* fp) {
457 // TODO: don't use a fixed-size buffer.
458 const size_t kLineBufferSize = 256;
459 char buffer[kLineBufferSize] = { 0 };
460 unsigned int line_num = 1;
461 while (fgets(buffer, kLineBufferSize, fp) != NULL) {
462 char* stripped = StripLeadingWhitespace(buffer);
463 TerminateAtNewlineOrComment(stripped);
464 ParseLine(stripped, line_num++);
465 }
466 }
467
468 void ParseLine(const char* line, unsigned int line_num) {
469 switch (*line) {
470 case 'v':
471 ParseAttrib(line + 1, line_num);
472 break;
473 case 'f':
474 ParseFace(line + 1, line_num);
475 break;
476 case 'g':
477 if (isspace(line[1])) {
478 ParseGroup(line + 2, line_num);
479 } else {
480 goto unknown;
481 }
482 break;
483 case '\0':
484 case '#':
485 break; // Do nothing for comments or blank lines.
486 case 'p':
487 WarnLine("point unsupported", line_num);
488 break;
489 case 'l':
490 WarnLine("line unsupported", line_num);
491 break;
492 case 'u':
493 if (0 == strncmp(line + 1, "semtl", 5)) {
494 ParseUsemtl(line + 6, line_num);
495 } else {
496 goto unknown;
497 }
498 break;
499 case 'm':
500 if (0 == strncmp(line + 1, "tllib", 5)) {
501 ParseMtllib(line + 6, line_num);
502 } else {
503 goto unknown;
504 }
505 break;
506 case 's':
507 ParseSmoothingGroup(line + 1, line_num);
508 break;
509 unknown:
510 default:
511 WarnLine("unknown keyword", line_num);
512 break;
513 }
514 }
515
516 void ParseAttrib(const char* line, unsigned int line_num) {
517 ShortFloatList floats;
518 floats.ParseLine(line + 1);
519 if (isspace(*line)) {
520 ParsePosition(floats, line_num);
521 } else if (*line == 't') {
522 ParseTexCoord(floats, line_num);
523 } else if (*line == 'n') {
524 ParseNormal(floats, line_num);
525 } else {
526 WarnLine("unknown attribute format", line_num);
527 }
528 }
529
530 void ParsePosition(const ShortFloatList& floats, unsigned int line_num) {
531 if (floats.size() != positionDim() &&
532 floats.size() != 6) { // ignore r g b for now.
533 ErrorLine("bad position", line_num);
534 }
535 floats.AppendNTo(&positions_, positionDim());
536 }
537
538 void ParseTexCoord(const ShortFloatList& floats, unsigned int line_num) {
539 if ((floats.size() < 1) || (floats.size() > 3)) {
540 // TODO: correctly handle 3-D texcoords intead of just
541 // truncating.
542 ErrorLine("bad texcoord", line_num);
543 }
544 floats.AppendNTo(&texcoords_, texcoordDim());
545 }
546
547 void ParseNormal(const ShortFloatList& floats, unsigned int line_num) {
548 if (floats.size() != normalDim()) {
549 ErrorLine("bad normal", line_num);
550 }
551 // Normalize to avoid out-of-bounds quantization. This should be
552 // optional, in case someone wants to be using the normal magnitude as
553 // something meaningful.
554 const float x = floats[0];
555 const float y = floats[1];
556 const float z = floats[2];
557 const float scale = 1.0/sqrt(x*x + y*y + z*z);
558 if (isfinite(scale)) {
559 normals_.push_back(scale * x);
560 normals_.push_back(scale * y);
561 normals_.push_back(scale * z);
562 } else {
563 normals_.push_back(0);
564 normals_.push_back(0);
565 normals_.push_back(0);
566 }
567 }
568
569 // Parses faces and converts to triangle fans. This is not a
570 // particularly good tesselation in general case, but it is really
571 // simple, and is perfectly fine for triangles and quads.
572 void ParseFace(const char* line, unsigned int line_num) {
573 // Also handle face outlines as faces.
574 if (*line == 'o') ++line;
575
576 // TODO: instead of storing these indices as-is, it might make
577 // sense to flatten them right away. This can reduce memory
578 // consumption and improve access locality, especially since .OBJ
579 // face indices are so needlessly large.
580 int indices[9] = { 0 };
581 // The first index acts as the pivot for the triangle fan.
582 line = ParseIndices(line, line_num, indices + 0, indices + 1, indices + 2);
583 if (line == NULL) {
584 ErrorLine("bad first index", line_num);
585 }
586 line = ParseIndices(line, line_num, indices + 3, indices + 4, indices + 5);
587 if (line == NULL) {
588 ErrorLine("bad second index", line_num);
589 }
590 // After the first two indices, each index introduces a new
591 // triangle to the fan.
592 while ((line = ParseIndices(line, line_num,
593 indices + 6, indices + 7, indices + 8))) {
594 current_batch_->AddTriangle(current_group_line_, indices);
595 // The most recent vertex is reused for the next triangle.
596 indices[3] = indices[6];
597 indices[4] = indices[7];
598 indices[5] = indices[8];
599 indices[6] = indices[7] = indices[8] = 0;
600 }
601 }
602
603 // Parse a single group of indices, separated by slashes ('/').
604 // TODO: convert negative indices (that is, relative to the end of
605 // the current vertex positions) to more conventional positive
606 // indices.
607 const char* ParseIndices(const char* line, unsigned int line_num,
608 int* position_index, int* texcoord_index,
609 int* normal_index) {
610 const char* endptr = NULL;
611 *position_index = strtoint(line, &endptr);
612 if (*position_index == 0) {
613 return NULL;
614 }
615 if (endptr != NULL && *endptr == '/') {
616 *texcoord_index = strtoint(endptr + 1, &endptr);
617 } else {
618 *texcoord_index = *normal_index = 0;
619 }
620 if (endptr != NULL && *endptr == '/') {
621 *normal_index = strtoint(endptr + 1, &endptr);
622 } else {
623 *normal_index = 0;
624 }
625 return endptr;
626 }
627
628 // .OBJ files can specify multiple groups for a set of faces. This
629 // implementation finds the "most unique" group for a set of faces
630 // and uses that for the batch. In the first pass, we use the line
631 // number of the "g" command to tag the faces. Afterwards, after we
632 // collect group populations, we can go back and give them real
633 // names.
634 void ParseGroup(const char* line, unsigned int line_num) {
635 std::string token;
636 while ((line = ConsumeFirstToken(line, &token))) {
637 ToLowerInplace(&token);
638 group_counts_[token]++;
639 line_to_groups_.insert(std::make_pair(line_num, token));
640 }
641 current_group_line_ = line_num;
642 }
643
644 void ParseSmoothingGroup(const char* line, unsigned int line_num) {
645 static bool once = true;
646 if (once) {
647 WarnLine("s ignored", line_num);
648 once = false;
649 }
650 }
651
652 void ParseMtllib(const char* line, unsigned int line_num) {
653 FILE* fp = fopen(StripLeadingWhitespace(line), "r");
654 if (!fp) {
655 WarnLine("mtllib not found", line_num);
656 return;
657 }
658 WavefrontMtlFile mtlfile(fp);
659 fclose(fp);
660 materials_ = mtlfile.materials();
661 for (size_t i = 0; i < materials_.size(); ++i) {
662 DrawBatch& draw_batch = material_batches_[materials_[i].name];
663 draw_batch.Init(&positions_, &texcoords_, &normals_);
664 }
665 }
666
667 void ParseUsemtl(const char* line, unsigned int line_num) {
668 std::string usemtl;
669 ToLower(StripLeadingWhitespace(line), &usemtl);
670 MaterialBatches::iterator iter = material_batches_.find(usemtl);
671 if (iter == material_batches_.end()) {
672 ErrorLine("material not found", line_num);
673 }
674 current_batch_ = &iter->second;
675 }
676
677 void WarnLine(const char* why, unsigned int line_num) const {
678 fprintf(stderr, "WARNING: %s at line %u\n", why, line_num);
679 }
680
681 void ErrorLine(const char* why, unsigned int line_num) const {
682 fprintf(stderr, "ERROR: %s at line %u\n", why, line_num);
683 exit(-1);
684 }
685
686 AttribList positions_;
687 AttribList texcoords_;
688 AttribList normals_;
689 MaterialList materials_;
690
691 // Currently, batch by texture (i.e. map_Kd).
692 MaterialBatches material_batches_;
693 DrawBatch* current_batch_;
694
695 typedef std::multimap<unsigned int, std::string> LineToGroups;
696 LineToGroups line_to_groups_;
697 std::map<std::string, int> group_counts_;
698 unsigned int current_group_line_;
699};
700
701#endif // WEBGL_LOADER_MESH_H_
Note: See TracBrowser for help on using the repository browser.