Bläddra i källkod

Added latest UTF8 compressors (r104).

alteredq 13 år sedan
förälder
incheckning
995d3de2ab

+ 3 - 0
utils/exporters/utf8-r104/build.bat

@@ -0,0 +1,3 @@
+g++ ./src/objcompress.cc -O2 -Wall -o objcompress
+g++ ./src/obj2utf8.cc -O2 -Wall -o obj2utf8
+g++ ./src/obj2utf8x.cc -O2 -Wall -o obj2utf8x

BIN
utils/exporters/utf8-r104/obj2utf8.exe


BIN
utils/exporters/utf8-r104/obj2utf8x.exe


BIN
utils/exporters/utf8-r104/objcompress.exe


+ 20 - 0
utils/exporters/utf8-r104/src/README

@@ -0,0 +1,20 @@
+Usage: ./objcompress in.obj [out.utf8]
+
+        If 'out' is specified, then attempt to write out a compressed,
+        UTF-8 version to 'out.'
+
+        If not, write a JSON version to STDOUT.
+
+Usage: ./objanalyze in.obj [list of cache sizes]
+
+        Perform vertex cache analysis on in.obj using specified sizes.
+        For example: ./objanalyze in.obj 6 16 24 32
+        Maximum cache size is 32.
+
+Building:
+
+Since there are no external dependences outside of the C/C++ standard
+libraries, you can pretty much build this however you please. I've
+included a cheeky way to do this on POSIX-like systems by including a
+build shell script at the top of the file itself. You can build by
+making the .cc file executable, and running it on the command line.

+ 188 - 0
utils/exporters/utf8-r104/src/base.h

@@ -0,0 +1,188 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+#ifndef WEBGL_LOADER_BASE_H_
+#define WEBGL_LOADER_BASE_H_
+
+#include <ctype.h>
+#include <float.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+// TODO: consider using C99 spellings.
+typedef unsigned char uint8;
+typedef unsigned short uint16;
+typedef short int16;
+typedef unsigned int uint32;
+
+// printf format strings for size_t.
+#ifdef _WIN32
+# define PRIuS "%Iu"
+#else  // Most compilers use the C99 format string.
+# define PRIuS "%zu"
+#endif
+
+#ifndef isfinite
+# define isfinite _finite
+#endif
+
+typedef std::vector<float> AttribList;
+typedef std::vector<int> IndexList;
+typedef std::vector<uint16> QuantizedAttribList;
+typedef std::vector<uint16> OptimizedIndexList;
+
+// TODO: these data structures ought to go elsewhere.
+struct DrawMesh {
+  // Interleaved vertex format:
+  //  3-D Position
+  //  3-D Normal
+  //  2-D TexCoord
+  // Note that these
+  AttribList attribs;
+  // Indices are 0-indexed.
+  IndexList indices;
+};
+
+struct WebGLMesh {
+  QuantizedAttribList attribs;
+  OptimizedIndexList indices;
+};
+
+typedef std::vector<WebGLMesh> WebGLMeshList;
+
+static inline int strtoint(const char* str, const char** endptr) {
+  return static_cast<int>(strtol(str, const_cast<char**>(endptr), 10));
+}
+
+static inline const char* StripLeadingWhitespace(const char* str) {
+  while (isspace(*str)) {
+    ++str;
+  }
+  return str;
+}
+
+static inline char* StripLeadingWhitespace(char* str) {
+  while (isspace(*str)) {
+    ++str;
+  }
+  return str;
+}
+
+// Like basename.
+static inline const char* StripLeadingDir(const char* const str) {
+  const char* last_slash = NULL;
+  const char* pos = str;
+  while (const char ch = *pos) {
+    if (ch == '/' || ch == '\\') {
+      last_slash = pos;
+    }
+    ++pos;
+  }
+  return last_slash ? (last_slash + 1) : str;
+}
+
+static inline void TerminateAtNewlineOrComment(char* str) {
+  char* newline = strpbrk(str, "#\r\n");
+  if (newline) {
+    *newline = '\0';
+  }
+}
+
+static inline const char* ConsumeFirstToken(const char* const line,
+                                            std::string* token) {
+  const char* curr = line;
+  while (char ch = *curr) {
+    if (isspace(ch)) {
+      token->assign(line, curr);
+      return curr + 1;
+    }
+    ++curr;
+  }
+  if (curr == line) {
+    return NULL;
+  }
+  token->assign(line, curr);
+  return curr;
+}
+
+static inline void ToLower(const char* in, std::string* out) {
+  while (char ch = *in) {
+    out->push_back(tolower(ch));
+    ++in;
+  }
+}
+
+static inline void ToLowerInplace(std::string* in) {
+  std::string& s = *in;
+  for (size_t i = 0; i < s.size(); ++i) {
+    s[i] = tolower(s[i]);
+  }
+}
+
+// Jenkin's One-at-a-time Hash. Not the best, but simple and
+// portable.
+uint32 SimpleHash(char *key, size_t len, uint32 seed = 0) {
+  uint32 hash = seed;
+  for(size_t i = 0; i < len; ++i) {
+    hash += static_cast<unsigned char>(key[i]);
+    hash += (hash << 10);
+    hash ^= (hash >> 6);
+  }
+  hash += (hash << 3);
+  hash ^= (hash >> 11);
+  hash += (hash << 15);
+  return hash;
+}
+
+void ToHex(uint32 w, char out[9]) {
+  const char kOffset0 = '0';
+  const char kOffset10 = 'a' - 10;
+  out[8] = '\0';
+  for (size_t i = 8; i > 0;) {
+    uint32 bits = w & 0xF;
+    out[--i] = bits + ((bits < 10) ? kOffset0 : kOffset10);
+    w >>= 4;
+  }
+}
+
+uint16 Quantize(float f, float in_min, float in_scale, uint16 out_max) {
+  return static_cast<uint16>(out_max * ((f-in_min) / in_scale));
+}
+
+// TODO: Visual Studio calls this someting different.
+#ifdef putc_unlocked
+# define PutChar putc_unlocked
+#else
+# define PutChar putc
+#endif  // putc_unlocked
+
+#ifndef CHECK
+# define CHECK(PRED) if (!(PRED)) {                                     \
+    fprintf(stderr, "%s:%d CHECK failed: ", __FILE__, __LINE__);        \
+    fputs(#PRED "\n", stderr);                                          \
+    exit(-1); } else
+#endif  // CHECK
+
+#ifndef DCHECK
+# ifdef DEBUG
+#  define DCHECK(PRED) CHECK(PRED)
+# else
+#  define DCHECK(PRED)
+# endif  // DEBUG
+#endif  // DCHECK
+
+#endif  // WEBGL_LOADER_BASE_H_

+ 123 - 0
utils/exporters/utf8-r104/src/bounds.h

@@ -0,0 +1,123 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#ifndef WEBGL_LOADER_BOUNDS_H_
+#define WEBGL_LOADER_BOUNDS_H_
+
+#include <stdio.h>
+
+#include "base.h"
+
+namespace webgl_loader {
+
+// TODO: arbitrary vertex formats.
+
+struct Bounds {
+  float mins[8];
+  float maxes[8];
+
+  void Clear() {
+    for (size_t i = 0; i < 8; ++i) {
+      mins[i] = FLT_MAX;
+      maxes[i] = -FLT_MAX;
+    }
+  }
+
+  void EncloseAttrib(const float* attribs) {
+    for (size_t i = 0; i < 8; ++i) {
+      const float attrib = attribs[i];
+      if (mins[i] > attrib) {
+        mins[i] = attrib;
+      }
+      if (maxes[i] < attrib) {
+        maxes[i] = attrib;
+      }
+    }
+  }
+
+  void Enclose(const AttribList& attribs) {
+    for (size_t i = 0; i < attribs.size(); i += 8) {
+      EncloseAttrib(&attribs[i]);
+    }
+  }
+
+  float UniformScale() const {
+    const float x = maxes[0] - mins[0];
+    const float y = maxes[1] - mins[1];
+    const float z = maxes[2] - mins[2];
+    return (x > y)  // TODO: max3
+        ? ((x > z) ? x : z)
+        : ((y > z) ? y : z);
+  }
+};
+
+// TODO: make maxPosition et. al. configurable.
+struct BoundsParams {
+  static BoundsParams FromBounds(const Bounds& bounds) {
+    BoundsParams ret;
+    const float scale = bounds.UniformScale();
+    // Position. Use a uniform scale.
+    for (size_t i = 0; i < 3; ++i) {
+      const int maxPosition = (1 << 14) - 1;  // 16383;
+      ret.mins[i] = bounds.mins[i];
+      ret.scales[i] = scale;
+      ret.outputMaxes[i] = maxPosition;
+      ret.decodeOffsets[i] = maxPosition * bounds.mins[i] / scale;
+      ret.decodeScales[i] = scale / maxPosition;
+    }
+    // TexCoord.
+    // TODO: get bounds-dependent texcoords working!
+    for (size_t i = 3; i < 5; ++i) {
+      // const float texScale = bounds.maxes[i] - bounds.mins[i];
+      const int maxTexcoord = (1 << 10) - 1;  // 1023
+      ret.mins[i] = 0;  //bounds.mins[i];
+      ret.scales[i] = 1;  //texScale;
+      ret.outputMaxes[i] = maxTexcoord;
+      ret.decodeOffsets[i] = 0;  //maxTexcoord * bounds.mins[i] / texScale;
+      ret.decodeScales[i] = 1.0f / maxTexcoord;  // texScale / maxTexcoord;
+    }
+    // Normal. Always uniform range.
+    for (size_t i = 5; i < 8; ++i) {
+      ret.mins[i] = -1;
+      ret.scales[i] = 2.f;
+      ret.outputMaxes[i] = (1 << 10) - 1;  // 1023
+      ret.decodeOffsets[i] = 1 - (1 << 9);  // -511
+      ret.decodeScales[i] = 1.0 / 511;
+    }
+    return ret;
+  }
+
+  void DumpJson(FILE* out = stdout) {
+    // TODO: use JsonSink.
+    fputs("{\n", out);
+    fprintf(out, "    \"decodeOffsets\": [%d,%d,%d,%d,%d,%d,%d,%d],\n",
+            decodeOffsets[0], decodeOffsets[1], decodeOffsets[2],
+            decodeOffsets[3], decodeOffsets[4], decodeOffsets[5],
+            decodeOffsets[6], decodeOffsets[7]);
+    fprintf(out, "    \"decodeScales\": [%f,%f,%f,%f,%f,%f,%f,%f]\n",
+            decodeScales[0], decodeScales[1], decodeScales[2], decodeScales[3],
+            decodeScales[4], decodeScales[5], decodeScales[6], decodeScales[7]);
+    fputs("  }", out);
+  }
+
+  float mins[8];
+  float scales[8];
+  int outputMaxes[8];
+  int decodeOffsets[8];
+  float decodeScales[8];
+};
+
+}  // namespace webgl_loader
+
+#endif  // WEBGL_LOADER_BOUNDS_H_

+ 539 - 0
utils/exporters/utf8-r104/src/compress.h

@@ -0,0 +1,539 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#ifndef WEBGL_LOADER_COMPRESS_H_
+#define WEBGL_LOADER_COMPRESS_H_
+
+#include <math.h>
+
+#include "base.h"
+#include "bounds.h"
+#include "stream.h"
+#include "utf8.h"
+
+namespace webgl_loader {
+
+void AttribsToQuantizedAttribs(const AttribList& interleaved_attribs,
+                               const BoundsParams& bounds_params,
+                               QuantizedAttribList* quantized_attribs) {
+  quantized_attribs->resize(interleaved_attribs.size());
+  for (size_t i = 0; i < interleaved_attribs.size(); i += 8) {
+    for (size_t j = 0; j < 8; ++j) {
+      quantized_attribs->at(i + j) = Quantize(interleaved_attribs[i + j],
+                                              bounds_params.mins[j],
+                                              bounds_params.scales[j],
+                                              bounds_params.outputMaxes[j]);
+    }
+  }
+}
+
+uint16 ZigZag(int16 word) {
+  return (word >> 15) ^ (word << 1);
+}
+
+void CompressAABBToUtf8(const Bounds& bounds,
+                        const BoundsParams& total_bounds,
+                        ByteSinkInterface* utf8) {
+  const int maxPosition = (1 << 14) - 1;  // 16383;
+  uint16 mins[3] = { 0 };
+  uint16 maxes[3] = { 0 };
+  for (int i = 0; i < 3; ++i) {
+    float total_min = total_bounds.mins[i];
+    float total_scale = total_bounds.scales[i];
+    mins[i] = Quantize(bounds.mins[i], total_min, total_scale, maxPosition);
+    maxes[i] = Quantize(bounds.maxes[i], total_min, total_scale, maxPosition);
+  }
+  for (int i = 0; i < 3; ++i) {
+    Uint16ToUtf8(mins[i], utf8);
+  }
+  for (int i = 0; i < 3; ++i) {
+    Uint16ToUtf8(maxes[i] - mins[i], utf8);
+  }
+}
+
+void CompressIndicesToUtf8(const OptimizedIndexList& list,
+                           ByteSinkInterface* utf8) {
+  // For indices, we don't do delta from the most recent index, but
+  // from the high water mark. The assumption is that the high water
+  // mark only ever moves by one at a time. Foruntately, the vertex
+  // optimizer does that for us, to optimize for per-transform vertex
+  // fetch order.
+  uint16 index_high_water_mark = 0;
+  for (size_t i = 0; i < list.size(); ++i) {
+    const int index = list[i];
+    CHECK(index >= 0);
+    CHECK(index <= index_high_water_mark);
+    CHECK(Uint16ToUtf8(index_high_water_mark - index, utf8));
+    if (index == index_high_water_mark) {
+      ++index_high_water_mark;
+    }
+  }
+}
+
+void CompressQuantizedAttribsToUtf8(const QuantizedAttribList& attribs,
+                                    ByteSinkInterface* utf8) {
+  for (size_t i = 0; i < 8; ++i) {
+    // Use a transposed representation, and delta compression.
+    uint16 prev = 0;
+    for (size_t j = i; j < attribs.size(); j += 8) {
+      const uint16 word = attribs[j];
+      const uint16 za = ZigZag(static_cast<int16>(word - prev));
+      prev = word;
+      CHECK(Uint16ToUtf8(za, utf8));
+    }
+  }
+}
+
+class EdgeCachingCompressor {
+ public:
+  // Assuming that the vertex cache optimizer LRU is 32 vertices, we
+  // expect ~64 triangles, and ~96 edges.
+  static const size_t kMaxLruSize = 96;
+  static const int kLruSentinel = -1;
+
+  EdgeCachingCompressor(const QuantizedAttribList& attribs,
+                        OptimizedIndexList& indices)
+      : attribs_(attribs),
+        indices_(indices),
+        deltas_(attribs.size()),
+        index_high_water_mark_(0),
+        lru_size_(0) {
+    memset(last_attrib_, 0, sizeof(last_attrib_));
+  }
+
+  // Work in progress. Does not remotely work.
+  void CompressWithLRU(ByteSinkInterface* utf8) {
+    size_t match_indices[3];
+    size_t match_winding[3];
+    for (size_t i = 0; i < indices_.size(); i += 3) {
+      const uint16* triangle = &indices_[i];
+      // Try to find edge matches to cheaply encode indices and employ
+      // parallelogram prediction.
+      const size_t num_matches = LruEdge(triangle,
+                                         match_indices, match_winding);
+      switch (num_matches) {
+        case 0: 
+          LruEdgeZero(triangle);
+          // No edges match, so use simple predictor.
+          continue;
+        case 1: 
+          LruEdgeOne(triangle[match_winding[1]], 
+                     triangle[match_winding[2]], match_indices[0]);
+          break;
+        case 2: 
+          LruEdgeTwo(triangle[match_winding[2]], 
+                     match_indices[0], match_indices[1]); 
+          break;
+        case 3: 
+          LruEdgeThree(match_indices[0], match_indices[1], match_indices[2]);
+          break;
+        default:
+          DumpDebug();
+          CHECK(false);
+      }
+    }
+  }
+
+  // Instead of using an LRU cache of edges, simply scan the history
+  // for matching edges.
+  void Compress(ByteSinkInterface* utf8) {
+    // TODO: do this pre-quantization.
+    // Normal prediction.
+    const size_t num_attribs = attribs_.size() / 8;
+    std::vector<int> crosses(3 * num_attribs);
+    for (size_t i = 0; i < indices_.size(); i += 3) {
+      // Compute face cross products.
+      const uint16 i0 = indices_[i + 0];
+      const uint16 i1 = indices_[i + 1];
+      const uint16 i2 = indices_[i + 2];
+      int e1[3], e2[3], cross[3];
+      e1[0] = attribs_[8*i1 + 0] - attribs_[8*i0 + 0];
+      e1[1] = attribs_[8*i1 + 1] - attribs_[8*i0 + 1];
+      e1[2] = attribs_[8*i1 + 2] - attribs_[8*i0 + 2];
+      e2[0] = attribs_[8*i2 + 0] - attribs_[8*i0 + 0];
+      e2[1] = attribs_[8*i2 + 1] - attribs_[8*i0 + 1];
+      e2[2] = attribs_[8*i2 + 2] - attribs_[8*i0 + 2];
+      cross[0] = e1[1] * e2[2] - e1[2] * e2[1];
+      cross[1] = e1[2] * e2[0] - e1[0] * e2[2];
+      cross[2] = e1[0] * e2[1] - e1[1] * e2[0];
+      // Accumulate face cross product into each vertex.
+      for (size_t j = 0; j < 3; ++j) {
+        crosses[3*i0 + j] += cross[j];
+        crosses[3*i1 + j] += cross[j];
+        crosses[3*i2 + j] += cross[j];
+      }
+    }
+    // Compute normal residues.
+    for (size_t idx = 0; idx < num_attribs; ++idx) {
+      float pnx = crosses[3*idx + 0];
+      float pny = crosses[3*idx + 1];
+      float pnz = crosses[3*idx + 2];
+      const float pnorm = 511.0 / sqrt(pnx*pnx + pny*pny + pnz*pnz);
+      pnx *= pnorm;
+      pny *= pnorm;
+      pnz *= pnorm;
+
+      float nx = attribs_[8*idx + 5] - 511;
+      float ny = attribs_[8*idx + 6] - 511;
+      float nz = attribs_[8*idx + 7] - 511;
+      const float norm = 511.0 / sqrt(nx*nx + ny*ny + nz*nz);
+      nx *= norm;
+      ny *= norm;
+      nz *= norm;
+
+      const uint16 dx = ZigZag(nx - pnx);
+      const uint16 dy = ZigZag(ny - pny);
+      const uint16 dz = ZigZag(nz - pnz);
+
+      deltas_[5*num_attribs + idx] = dx;
+      deltas_[6*num_attribs + idx] = dy;
+      deltas_[7*num_attribs + idx] = dz;
+    }
+    for (size_t triangle_start_index = 0; 
+         triangle_start_index < indices_.size(); triangle_start_index += 3) {
+      const uint16 i0 = indices_[triangle_start_index + 0];
+      const uint16 i1 = indices_[triangle_start_index + 1];
+      const uint16 i2 = indices_[triangle_start_index + 2];
+      // To force simple compression, set |max_backref| to 0 here
+      // and in loader.js.
+      // |max_backref| should be configurable and communicated.
+      const uint16 max_backref = triangle_start_index < kMaxLruSize ?
+          triangle_start_index : kMaxLruSize;
+      // Scan the index list for matching edges.
+      uint16 backref = 0;
+      for (; backref < max_backref; backref += 3) {
+        const size_t candidate_start_index = triangle_start_index - backref;
+        const uint16 j0 = indices_[candidate_start_index + 0];
+        const uint16 j1 = indices_[candidate_start_index + 1];
+        const uint16 j2 = indices_[candidate_start_index + 2];
+        // Compare input and candidate triangle edges in a
+        // winding-sensitive order. Matching edges must reference
+        // vertices in opposite order, and the first check sees if the
+        // triangles are in strip order. If necessary, re-order the
+        // triangle in |indices_| so that the matching edge appears
+        // first.
+        if (j1 == i1 && j2 == i0) {
+          ParallelogramPredictor(backref, j0, triangle_start_index);
+          break;
+        } else if (j1 == i0 && j2 == i2) {
+          indices_[triangle_start_index + 0] = i2;
+          indices_[triangle_start_index + 1] = i0;
+          indices_[triangle_start_index + 2] = i1;
+          ParallelogramPredictor(backref, j0, triangle_start_index);
+          break;
+        } else if (j1 == i2 && j2 == i1) {
+          indices_[triangle_start_index + 0] = i1;
+          indices_[triangle_start_index + 1] = i2;
+          indices_[triangle_start_index + 2] = i0;
+          ParallelogramPredictor(backref, j0, triangle_start_index);
+          break;
+        } else if (j2 == i1 && j0 == i0) {
+          ParallelogramPredictor(backref + 1, j1, triangle_start_index);
+          break;
+        } else if (j2 == i0 && j0 == i2) {
+          indices_[triangle_start_index + 0] = i2;
+          indices_[triangle_start_index + 1] = i0;
+          indices_[triangle_start_index + 2] = i1;
+          ParallelogramPredictor(backref + 1, j1, triangle_start_index);
+          break;
+        } else if (j2 == i2 && j0 == i1) {
+          indices_[triangle_start_index + 0] = i1;
+          indices_[triangle_start_index + 1] = i2;
+          indices_[triangle_start_index + 2] = i0;
+          ParallelogramPredictor(backref + 1, j1, triangle_start_index);
+          break;
+        } else if (j0 == i1 && j1 == i0) {
+          ParallelogramPredictor(backref + 2, j2, triangle_start_index);
+          break;
+        } else if (j0 == i0 && j1 == i2) {
+          indices_[triangle_start_index + 0] = i2;
+          indices_[triangle_start_index + 1] = i0;
+          indices_[triangle_start_index + 2] = i1;
+          ParallelogramPredictor(backref + 2, j2, triangle_start_index);
+          break;
+        } else if (j0 == i2 && j1 == i1) {
+          indices_[triangle_start_index + 0] = i1;
+          indices_[triangle_start_index + 1] = i2;
+          indices_[triangle_start_index + 2] = i0;
+          ParallelogramPredictor(backref + 2, j2, triangle_start_index);
+          break;
+        }
+      }
+      if (backref == max_backref) {
+        SimplePredictor(max_backref, triangle_start_index);
+      }
+    }
+    // Emit as UTF-8.
+    for (size_t i = 0; i < deltas_.size(); ++i) {
+      if (!Uint16ToUtf8(deltas_[i], utf8)) {
+        // TODO: bounds-dependent texcoords are still busted :(
+        Uint16ToUtf8(0, utf8);
+      }
+    }
+    for (size_t i = 0; i < codes_.size(); ++i) {
+      CHECK(Uint16ToUtf8(codes_[i], utf8));
+    }
+  }
+
+  const QuantizedAttribList& deltas() const { return deltas_; }
+
+  const OptimizedIndexList& codes() const { return codes_; }
+
+  void DumpDebug(FILE* fp = stdout) {
+    for (size_t i = 0; i < lru_size_; ++i) {
+      fprintf(fp, PRIuS ": %d\n", i, edge_lru_[i]);
+    }
+  }
+
+ private:
+  // The simple predictor is slightly (maybe 5%) more effective than
+  // |CompressQuantizedAttribsToUtf8|. Instead of delta encoding in
+  // attribute order, we use the last referenced attribute as the
+  // predictor.
+  void SimplePredictor(size_t max_backref, size_t triangle_start_index) {
+    const uint16 i0 = indices_[triangle_start_index + 0];
+    const uint16 i1 = indices_[triangle_start_index + 1];
+    const uint16 i2 = indices_[triangle_start_index + 2];
+    if (HighWaterMark(i0, max_backref)) {
+      // Would it be faster to do the dumb delta, in this case?
+      EncodeDeltaAttrib(i0, last_attrib_);
+    }
+    if (HighWaterMark(i1)) {
+      EncodeDeltaAttrib(i1, &attribs_[8*i0]);
+    }
+    if (HighWaterMark(i2)) {
+      // We get a little frisky with the third vertex in the triangle.
+      // Instead of simply using the previous vertex, use the average
+      // of the first two.
+      for (size_t j = 0; j < 8; ++j) {
+        int average = attribs_[8*i0 + j];
+        average += attribs_[8*i1 + j];
+        average /= 2;
+        last_attrib_[j] = average;
+      }
+      EncodeDeltaAttrib(i2, last_attrib_);
+      // The above doesn't add much. Consider the simpler:
+      // EncodeDeltaAttrib(i2, &attribs_[8*i1]);
+    }
+  }
+
+  void EncodeDeltaAttrib(size_t index, const uint16* predicted) {
+    const size_t num_attribs = attribs_.size() / 8;
+    for (size_t i = 0; i < 5; ++i) {
+      const int delta = attribs_[8*index + i] - predicted[i];
+      const uint16 code = ZigZag(delta);
+      deltas_[num_attribs*i + index] = code;
+    }
+    UpdateLastAttrib(index);
+  }
+
+  void ParallelogramPredictor(uint16 backref_edge,
+                              size_t backref_vert,
+                              size_t triangle_start_index) {
+    codes_.push_back(backref_edge);  // Encoding matching edge.
+    const uint16 i2 = indices_[triangle_start_index + 2];
+    if (HighWaterMark(i2)) {  // Encode third vertex.
+      // Parallelogram prediction for the new vertex.
+      const uint16 i0 = indices_[triangle_start_index + 0];
+      const uint16 i1 = indices_[triangle_start_index + 1];
+      const size_t num_attribs = attribs_.size() / 8;
+      for (size_t j = 0; j < 5; ++j) {
+        const uint16 orig = attribs_[8*i2 + j]; 
+        int delta = attribs_[8*i0 + j];
+        delta += attribs_[8*i1 + j];
+        delta -= attribs_[8*backref_vert + j];
+        last_attrib_[j] = orig;
+        const uint16 code = ZigZag(orig - delta);
+        deltas_[num_attribs*j + i2] = code;
+      }
+    }
+  }
+
+  // Returns |true| if |index_high_water_mark_| is incremented, otherwise
+  // returns |false| and automatically updates |last_attrib_|. 
+  bool HighWaterMark(uint16 index, uint16 start_code = 0) {
+    codes_.push_back(index_high_water_mark_ - index + start_code);
+    if (index == index_high_water_mark_) {
+      ++index_high_water_mark_;
+      return true;
+    } else {
+      UpdateLastAttrib(index);
+    }
+    return false;
+  }
+
+  void UpdateLastAttrib(uint16 index) {
+    for (size_t i = 0; i < 8; ++i) {
+      last_attrib_[i] = attribs_[8*index + i];
+    }
+  }
+
+  // Find edge matches of |triangle| referenced in |edge_lru_|
+  // |match_indices| stores where the matches occur in |edge_lru_|
+  // |match_winding| stores where the matches occur in |triangle|
+  size_t LruEdge(const uint16* triangle,
+                 size_t* match_indices,
+                 size_t* match_winding) {
+    const uint16 i0 = triangle[0];
+    const uint16 i1 = triangle[1];
+    const uint16 i2 = triangle[2];
+    // The primary thing is to find the first matching edge, if
+    // any. If we assume that our mesh is mostly manifold, then each
+    // edge is shared by at most two triangles (with the indices in
+    // opposite order), so we actually want to eventually remove all
+    // matching edges. However, this means we have to continue
+    // scanning the array to find all matching edges, not just the
+    // first. The edges that don't match will then pushed to the
+    // front.
+    size_t num_matches = 0;
+    for (size_t i = 0; i < lru_size_ && num_matches < 3; ++i) {
+      const int edge_index = edge_lru_[i];
+      // |winding| is a tricky detail used to dereference the edge to
+      // yield |e0| and |e1|, since we must handle the edge that wraps
+      // the last and first vertex. For now, just implement this in a
+      // straightforward way using a switch, but since this code would
+      // actually also need to run in the decompressor, we must
+      // optimize it.
+      const int winding = edge_index % 3;
+      uint16 e0, e1;
+      switch (winding) {
+        case 0:
+          e0 = indices_[edge_index + 1];
+          e1 = indices_[edge_index + 2];
+          break;
+        case 1:
+          e0 = indices_[edge_index + 2];
+          e1 = indices_[edge_index];
+          break;
+        case 2:
+          e0 = indices_[edge_index];
+          e1 = indices_[edge_index + 1];
+          break;
+        default:
+          DumpDebug();
+          CHECK(false);
+      }
+
+      // Check each edge of the input triangle against |e0| and
+      // |e1|. Note that we reverse the winding of the input triangle.
+      // TODO: does this properly handle degenerate input?
+      if (e0 == i1 && e1 == i0) {
+        match_winding[num_matches] = 2;
+        match_indices[++num_matches] = i;
+      } else if (e0 == i2 && e1 == i1) {
+        match_winding[num_matches] = 0;
+        match_indices[++num_matches] = i;
+      } else if (e0 == i0 && e1 == i2) {
+        match_winding[num_matches] = 1;
+        match_indices[++num_matches] = i;
+      }
+    }
+    switch (num_matches) {
+      case 1:
+        match_winding[1] = (match_winding[0] + 1) % 3;  // Fall through.
+      case 2:
+        match_winding[2] = 3 - match_winding[1] - match_winding[0];
+      default: ;  // Do nothing.
+    }
+    return num_matches;
+  }
+
+  // If no edges were found in |triangle|, then simply push the edges
+  // onto |edge_lru_|.
+  void LruEdgeZero(const uint16* triangle) {
+    // Shift |edge_lru_| by three elements. Note that the |edge_lru_|
+    // array has at least three extra elements to make this simple.
+    lru_size_ += 3;
+    if (lru_size_ > kMaxLruSize) lru_size_ = kMaxLruSize;
+    memmove(edge_lru_ + 3, edge_lru_, lru_size_ * sizeof(int));
+    // Push |triangle| to front of |edge_lru_|
+    edge_lru_[0] = triangle[0];
+    edge_lru_[1] = triangle[1];
+    edge_lru_[2] = triangle[2];
+  }
+
+  // Remove one edge and add two.
+  void LruEdgeOne(size_t i0, size_t i1, size_t match_index) {
+    CHECK(match_index < lru_size_);
+    // Shift |edge_lru_| by one element, starting with |match_index| + 1.
+    memmove(edge_lru_ + match_index + 2, edge_lru_ + match_index + 1,
+            (lru_size_ - match_index) * sizeof(int));
+    // Shift |edge_lru_| by two elements until reaching |match_index|.
+    memmove(edge_lru_ + 2, edge_lru_, match_index * sizeof(int));
+    edge_lru_[0] = i0;
+    edge_lru_[1] = i1;
+  }
+
+  // Remove two edges and add one.
+  void LruEdgeTwo(int i0, size_t match_index0, size_t match_index1) {
+    CHECK(match_index0 < lru_size_);
+    CHECK(match_index1 < lru_size_);
+
+    // memmove 1
+    // memmove 2
+    edge_lru_[0] = i0;
+  }
+
+  // All edges were found, so just remove them from |edge_lru_|.
+  void LruEdgeThree(size_t match_index0, 
+                    size_t match_index1, 
+                    size_t match_index2) {
+    const size_t shift_two = match_index1 - 1;
+    for (size_t i = match_index0; i < shift_two; ++i) {
+      edge_lru_[i] = edge_lru_[i + 1];
+    }
+    const size_t shift_three = match_index2 - 2;
+    for (size_t i = shift_two; i < shift_three; ++i) {
+      edge_lru_[i] = edge_lru_[i + 2];
+    }
+    lru_size_ -= 3;
+    for (size_t i = shift_three; i < lru_size_; ++i) {
+      edge_lru_[i] = edge_lru_[i + 3];
+    }
+  }
+
+  // |attribs_| and |indices_| is the input mesh.
+  const QuantizedAttribList& attribs_;
+  // |indices_| are non-const because |Compress| may update triangle
+  // winding order.
+  OptimizedIndexList& indices_;
+  // |deltas_| contains the compressed attributes. They can be
+  // compressed in one of two ways:
+  // (1) with parallelogram prediction, compared with the predicted vertex,
+  // (2) otherwise, compared with the last referenced vertex.
+  // Note that even (2) is different and probably better than what
+  // |CompressQuantizedAttribsToutf8| does, which is comparing with
+  // the last encoded vertex.
+  QuantizedAttribList deltas_;
+  // |codes_| contains the compressed indices. Small values encode an
+  // edge match; that is, the first edge of the next triangle matches
+  // a recently-seen edge.
+  OptimizedIndexList codes_; 
+  // |index_high_water_mark_| is used as it is in |CompressIndicesToUtf8|.
+  uint16 index_high_water_mark_;
+  // |last_attrib_referenced_| is the index of the last referenced
+  // attribute. This is used to delta encode attributes when no edge match
+  // is found.
+  uint16 last_attrib_[8];
+  size_t lru_size_;
+  // |edge_lru_| contains the LRU lits of edge references. It stores
+  // indices to the input |indices_|. By convention, an edge points to
+  // the vertex opposite the edge in question. We pad the array by a
+  // triangle to simplify edge cases.
+  int edge_lru_[kMaxLruSize + 3];
+};
+
+}  // namespace webgl_loader
+
+#endif  // WEBGL_LOADER_COMPRESS_H_

+ 701 - 0
utils/exporters/utf8-r104/src/mesh.h

@@ -0,0 +1,701 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#ifndef WEBGL_LOADER_MESH_H_
+#define WEBGL_LOADER_MESH_H_
+
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base.h"
+#include "bounds.h"
+#include "stream.h"
+#include "utf8.h"
+
+// A short list of floats, useful for parsing a single vector
+// attribute.
+class ShortFloatList {
+ public:
+  // MeshLab can create position attributes with
+  // color coordinates like: v x y z r g b
+  static const size_t kMaxNumFloats = 6;
+  ShortFloatList()
+      : size_(0)
+  {
+    clear();
+  }
+
+  void clear() {
+    for (size_t i = 0; i < kMaxNumFloats; ++i) {
+      a_[i] = 0.f;
+    }
+  }
+
+  // Parse up to kMaxNumFloats from C string.
+  // TODO: this should instead return endptr, since size
+  // is recoverable.
+  size_t ParseLine(const char* line) {
+    for (size_ = 0; size_ != kMaxNumFloats; ++size_) {
+      char* endptr = NULL;
+      a_[size_] = strtof(line, &endptr);
+      if (endptr == NULL || line == endptr) break;
+      line = endptr;
+    }
+    return size_;
+  }
+
+  float operator[](size_t idx) const {
+    return a_[idx];
+  }
+
+  void AppendTo(AttribList* attribs) const {
+    AppendNTo(attribs, size_);
+  }
+
+  void AppendNTo(AttribList* attribs, const size_t sz) const {
+    attribs->insert(attribs->end(), a_, a_ + sz);
+  }
+
+  bool empty() const { return size_ == 0; }
+
+  size_t size() const { return size_; }
+ private:
+  float a_[kMaxNumFloats];
+  size_t size_;
+};
+
+class IndexFlattener {
+ public:
+  explicit IndexFlattener(size_t num_positions)
+      : count_(0),
+        table_(num_positions) {
+  }
+
+  int count() const { return count_; }
+
+  void reserve(size_t size) {
+    table_.reserve(size);
+  }
+
+  // Returns a pair of: < flattened index, newly inserted >.
+  std::pair<int, bool> GetFlattenedIndex(int position_index,
+                                         int texcoord_index,
+                                         int normal_index) {
+    if (position_index >= static_cast<int>(table_.size())) {
+      table_.resize(position_index + 1);
+    }
+    // First, optimistically look up position_index in the table.
+    IndexType& index = table_[position_index];
+    if (index.position_or_flat == kIndexUnknown) {
+      // This is the first time we've seen this position in the table,
+      // so fill it. Since the table is indexed by position, we can
+      // use the position_or_flat_index field to store the flat index.
+      const int flat_index = count_++;
+      index.position_or_flat = flat_index;
+      index.texcoord = texcoord_index;
+      index.normal = normal_index;
+      return std::make_pair(flat_index, true);
+    } else if (index.position_or_flat == kIndexNotInTable) {
+      // There are multiple flattened indices at this position index,
+      // so resort to the map.
+      return GetFlattenedIndexFromMap(position_index,
+                                      texcoord_index,
+                                      normal_index);
+    } else if (index.texcoord == texcoord_index &&
+               index.normal == normal_index) {
+      // The other indices match, so we can use the value cached in
+      // the table.
+      return std::make_pair(index.position_or_flat, false);
+    }
+    // The other indices don't match, so we mark this table entry,
+    // and insert both the old and new indices into the map.
+    const IndexType old_index(position_index, index.texcoord, index.normal);
+    map_.insert(std::make_pair(old_index, index.position_or_flat));
+    index.position_or_flat = kIndexNotInTable;
+    const IndexType new_index(position_index, texcoord_index, normal_index);
+    const int flat_index = count_++;
+    map_.insert(std::make_pair(new_index, flat_index));
+    return std::make_pair(flat_index, true);
+  }
+ private:
+  std::pair<int, bool> GetFlattenedIndexFromMap(int position_index,
+                                                int texcoord_index,
+                                                int normal_index) {
+    IndexType index(position_index, texcoord_index, normal_index);
+    MapType::iterator iter = map_.lower_bound(index);
+    if (iter == map_.end() || iter->first != index) {
+      const int flat_index = count_++;
+      map_.insert(iter, std::make_pair(index, flat_index));
+      return std::make_pair(flat_index, true);
+    } else {
+      return std::make_pair(iter->second, false);
+    }
+  }
+
+  static const int kIndexUnknown = -1;
+  static const int kIndexNotInTable = -2;
+
+  struct IndexType {
+    IndexType()
+        : position_or_flat(kIndexUnknown),
+          texcoord(kIndexUnknown),
+          normal(kIndexUnknown)
+    { }
+
+    IndexType(int position_index, int texcoord_index, int normal_index)
+        : position_or_flat(position_index),
+          texcoord(texcoord_index),
+          normal(normal_index)
+    { }
+
+    // I'm being tricky/lazy here. The table_ stores the flattened
+    // index in the first field, since it is indexed by position. The
+    // map_ stores position and uses this struct as a key to lookup the
+    // flattened index.
+    int position_or_flat;
+    int texcoord;
+    int normal;
+
+    // An ordering for std::map.
+    bool operator<(const IndexType& that) const {
+      if (position_or_flat == that.position_or_flat) {
+        if (texcoord == that.texcoord) {
+          return normal < that.normal;
+        } else {
+          return texcoord < that.texcoord;
+        }
+      } else {
+        return position_or_flat < that.position_or_flat;
+      }
+    }
+
+    bool operator==(const IndexType& that) const {
+      return position_or_flat == that.position_or_flat &&
+          texcoord == that.texcoord && normal == that.normal;
+    }
+
+    bool operator!=(const IndexType& that) const {
+      return !operator==(that);
+    }
+  };
+  typedef std::map<IndexType, int> MapType;
+
+  int count_;
+  std::vector<IndexType> table_;
+  MapType map_;
+};
+
+static inline size_t positionDim() { return 3; }
+static inline size_t texcoordDim() { return 2; }
+static inline size_t normalDim() { return 3; }
+
+// TODO(wonchun): Make a c'tor to properly initialize.
+struct GroupStart {
+  size_t offset;  // offset into draw_mesh_.indices.
+  unsigned int group_line;
+  int min_index, max_index;  // range into attribs.
+  webgl_loader::Bounds bounds;
+};
+
+class DrawBatch {
+ public:
+  DrawBatch()
+      : flattener_(0),
+        current_group_line_(0xFFFFFFFF) {
+  }
+
+  const std::vector<GroupStart>& group_starts() const {
+    return group_starts_;
+  }
+
+  void Init(AttribList* positions, AttribList* texcoords, AttribList* normals) {
+    positions_ = positions;
+    texcoords_ = texcoords;
+    normals_ = normals;
+    flattener_.reserve(1024);
+  }
+
+  void AddTriangle(unsigned int group_line, int* indices) {
+    if (group_line != current_group_line_) {
+      current_group_line_ = group_line;
+      GroupStart group_start;
+      group_start.offset = draw_mesh_.indices.size();
+      group_start.group_line = group_line;
+      group_start.min_index = INT_MAX;
+      group_start.max_index = INT_MIN;
+      group_start.bounds.Clear();
+      group_starts_.push_back(group_start);
+    }
+    GroupStart& group = group_starts_.back();
+    for (size_t i = 0; i < 9; i += 3) {
+      // .OBJ files use 1-based indexing.
+      const int position_index = indices[i + 0] - 1;
+      const int texcoord_index = indices[i + 1] - 1;
+      const int normal_index = indices[i + 2] - 1;
+      const std::pair<int, bool> flattened = flattener_.GetFlattenedIndex(
+          position_index, texcoord_index, normal_index);
+      const int flat_index = flattened.first;
+      CHECK(flat_index >= 0);
+      draw_mesh_.indices.push_back(flat_index);
+      if (flattened.second) {
+        // This is a new index. Keep track of index ranges and vertex
+        // bounds.
+        if (flat_index > group.max_index) {
+          group.max_index = flat_index;
+        }
+        if (flat_index < group.min_index) {
+          group.min_index = flat_index;
+        }
+        const size_t new_loc = draw_mesh_.attribs.size();
+        CHECK(8*size_t(flat_index) == new_loc);
+        for (size_t i = 0; i < positionDim(); ++i) {
+          draw_mesh_.attribs.push_back(
+              positions_->at(positionDim() * position_index + i));
+        }
+        if (texcoord_index == -1) {
+          for (size_t i = 0; i < texcoordDim(); ++i) {
+            draw_mesh_.attribs.push_back(0);
+          }
+        } else {
+          for (size_t i = 0; i < texcoordDim(); ++i) {
+            draw_mesh_.attribs.push_back(
+                texcoords_->at(texcoordDim() * texcoord_index + i));
+          }
+        }
+        if (normal_index == -1) {
+          for (size_t i = 0; i < normalDim(); ++i) {
+            draw_mesh_.attribs.push_back(0);
+          }
+        } else {
+          for (size_t i = 0; i < normalDim(); ++i) {
+            draw_mesh_.attribs.push_back(
+                normals_->at(normalDim() * normal_index + i));
+          }
+        }
+        // TODO: is the covariance body useful for anything?
+        group.bounds.EncloseAttrib(&draw_mesh_.attribs[new_loc]);
+      }
+    }
+  }
+
+  const DrawMesh& draw_mesh() const {
+    return draw_mesh_;
+  }
+ private:
+  AttribList* positions_, *texcoords_, *normals_;
+  DrawMesh draw_mesh_;
+  IndexFlattener flattener_;
+  unsigned int current_group_line_;
+  std::vector<GroupStart> group_starts_;
+};
+
+struct Material {
+  std::string name;
+  float Kd[3];
+  std::string map_Kd;
+
+  void DumpJson(FILE* out = stdout) const {
+    fprintf(out, "    \"%s\": { ", name.c_str());
+    if (map_Kd.empty()) {
+      fprintf(out, "\"Kd\": [%hu, %hu, %hu] }",
+              Quantize(Kd[0], 0, 1, 255),
+              Quantize(Kd[1], 0, 1, 255),
+              Quantize(Kd[2], 0, 1, 255));
+    } else {
+      fprintf(out, "\"map_Kd\": \"%s\" }", map_Kd.c_str());
+    }
+  }
+};
+
+typedef std::vector<Material> MaterialList;
+
+class WavefrontMtlFile {
+ public:
+  explicit WavefrontMtlFile(FILE* fp) {
+    ParseFile(fp);
+  }
+
+  const MaterialList& materials() const {
+    return materials_;
+  }
+
+ private:
+  // TODO: factor this parsing stuff out.
+  void ParseFile(FILE* fp) {
+    // TODO: don't use a fixed-size buffer.
+    const size_t kLineBufferSize = 256;
+    char buffer[kLineBufferSize];
+    unsigned int line_num = 1;
+    while (fgets(buffer, kLineBufferSize, fp) != NULL) {
+      char* stripped = StripLeadingWhitespace(buffer);
+      TerminateAtNewlineOrComment(stripped);
+      ParseLine(stripped, line_num++);
+    }
+  }
+
+  void ParseLine(const char* line, unsigned int line_num) {
+    switch (*line) {
+      case 'K':
+        ParseColor(line + 1, line_num);
+        break;
+      case 'm':
+        if (0 == strncmp(line + 1, "ap_Kd", 5)) {
+          ParseMapKd(line + 6, line_num);
+        }
+        break;
+      case 'n':
+        if (0 == strncmp(line + 1, "ewmtl", 5)) {
+          ParseNewmtl(line + 6, line_num);
+        }
+      default:
+        break;
+    }
+  }
+
+  void ParseColor(const char* line, unsigned int line_num) {
+    switch (*line) {
+      case 'd': {
+        ShortFloatList floats;
+        floats.ParseLine(line + 1);
+        float* Kd = current_->Kd;
+        Kd[0] = floats[0];
+        Kd[1] = floats[1];
+        Kd[2] = floats[2];
+        break;
+      }
+      default:
+        break;
+    }
+  }
+
+  void ParseMapKd(const char* line, unsigned int line_num) {
+    current_->map_Kd = StripLeadingWhitespace(line);
+  }
+
+  void ParseNewmtl(const char* line, unsigned int line_num) {
+    materials_.push_back(Material());
+    current_ = &materials_.back();
+    ToLower(StripLeadingWhitespace(line), &current_->name);
+  }
+
+  Material* current_;
+  MaterialList materials_;
+};
+
+typedef std::map<std::string, DrawBatch> MaterialBatches;
+
+// TODO: consider splitting this into a low-level parser and a high-level
+// object.
+class WavefrontObjFile {
+ public:
+  explicit WavefrontObjFile(FILE* fp) {
+    current_batch_ = &material_batches_[""];
+    current_batch_->Init(&positions_, &texcoords_, &normals_);
+    current_group_line_ = 0;
+    line_to_groups_.insert(std::make_pair(0, "default"));
+    ParseFile(fp);
+  }
+
+  const MaterialList& materials() const {
+    return materials_;
+  }
+
+  const MaterialBatches& material_batches() const {
+    return material_batches_;
+  }
+
+  const std::string& LineToGroup(unsigned int line) const {
+    typedef LineToGroups::const_iterator Iterator;
+    typedef std::pair<Iterator, Iterator> EqualRange;
+    EqualRange equal_range = line_to_groups_.equal_range(line);
+    const std::string* best_group = NULL;
+    int best_count = 0;
+    for (Iterator iter = equal_range.first; iter != equal_range.second;
+         ++iter) {
+      const std::string& group = iter->second;
+      const int count = group_counts_.find(group)->second;
+      if (!best_group || (count < best_count)) {
+        best_group = &group;
+        best_count = count;
+      }
+    }
+    if (!best_group) {
+      ErrorLine("no suitable group found", line);
+    }
+    return *best_group;
+  }
+
+  void DumpDebug() const {
+    printf("positions size: " PRIuS "\n"
+	   "texcoords size: " PRIuS "\n"
+	   "normals size: " PRIuS "\n",
+           positions_.size(), texcoords_.size(), normals_.size());
+  }
+ private:
+  WavefrontObjFile() { }  // For testing.
+
+  void ParseFile(FILE* fp) {
+    // TODO: don't use a fixed-size buffer.
+    const size_t kLineBufferSize = 256;
+    char buffer[kLineBufferSize] = { 0 };
+    unsigned int line_num = 1;
+    while (fgets(buffer, kLineBufferSize, fp) != NULL) {
+      char* stripped = StripLeadingWhitespace(buffer);
+      TerminateAtNewlineOrComment(stripped);
+      ParseLine(stripped, line_num++);
+    }
+  }
+
+  void ParseLine(const char* line, unsigned int line_num) {
+    switch (*line) {
+      case 'v':
+        ParseAttrib(line + 1, line_num);
+        break;
+      case 'f':
+        ParseFace(line + 1, line_num);
+        break;
+      case 'g':
+        if (isspace(line[1])) {
+          ParseGroup(line + 2, line_num);
+        } else {
+          goto unknown;
+        }
+        break;
+      case '\0':
+      case '#':
+        break;  // Do nothing for comments or blank lines.
+      case 'p':
+        WarnLine("point unsupported", line_num);
+        break;
+      case 'l':
+        WarnLine("line unsupported", line_num);
+        break;
+      case 'u':
+        if (0 == strncmp(line + 1, "semtl", 5)) {
+          ParseUsemtl(line + 6, line_num);
+        } else {
+          goto unknown;
+        }
+        break;
+      case 'm':
+        if (0 == strncmp(line + 1, "tllib", 5)) {
+          ParseMtllib(line + 6, line_num);
+        } else {
+          goto unknown;
+        }
+        break;
+      case 's':
+        ParseSmoothingGroup(line + 1, line_num);
+        break;
+      unknown:
+      default:
+        WarnLine("unknown keyword", line_num);
+        break;
+    }
+  }
+
+  void ParseAttrib(const char* line, unsigned int line_num) {
+    ShortFloatList floats;
+    floats.ParseLine(line + 1);
+    if (isspace(*line)) {
+      ParsePosition(floats, line_num);
+    } else if (*line == 't') {
+      ParseTexCoord(floats, line_num);
+    } else if (*line == 'n') {
+      ParseNormal(floats, line_num);
+    } else {
+      WarnLine("unknown attribute format", line_num);
+    }
+  }
+
+  void ParsePosition(const ShortFloatList& floats, unsigned int line_num) {
+    if (floats.size() != positionDim() &&
+        floats.size() != 6) {  // ignore r g b for now.
+      ErrorLine("bad position", line_num);
+    }
+    floats.AppendNTo(&positions_, positionDim());
+  }
+
+  void ParseTexCoord(const ShortFloatList& floats, unsigned int line_num) {
+    if ((floats.size() < 1) || (floats.size() > 3)) {
+      // TODO: correctly handle 3-D texcoords intead of just
+      // truncating.
+      ErrorLine("bad texcoord", line_num);
+    }
+    floats.AppendNTo(&texcoords_, texcoordDim());
+  }
+
+  void ParseNormal(const ShortFloatList& floats, unsigned int line_num) {
+    if (floats.size() != normalDim()) {
+      ErrorLine("bad normal", line_num);
+    }
+    // Normalize to avoid out-of-bounds quantization. This should be
+    // optional, in case someone wants to be using the normal magnitude as
+    // something meaningful.
+    const float x = floats[0];
+    const float y = floats[1];
+    const float z = floats[2];
+    const float scale = 1.0/sqrt(x*x + y*y + z*z);
+    if (isfinite(scale)) {
+      normals_.push_back(scale * x);
+      normals_.push_back(scale * y);
+      normals_.push_back(scale * z);
+    } else {
+      normals_.push_back(0);
+      normals_.push_back(0);
+      normals_.push_back(0);
+    }
+  }
+
+  // Parses faces and converts to triangle fans. This is not a
+  // particularly good tesselation in general case, but it is really
+  // simple, and is perfectly fine for triangles and quads.
+  void ParseFace(const char* line, unsigned int line_num) {
+    // Also handle face outlines as faces.
+    if (*line == 'o') ++line;
+
+    // TODO: instead of storing these indices as-is, it might make
+    // sense to flatten them right away. This can reduce memory
+    // consumption and improve access locality, especially since .OBJ
+    // face indices are so needlessly large.
+    int indices[9] = { 0 };
+    // The first index acts as the pivot for the triangle fan.
+    line = ParseIndices(line, line_num, indices + 0, indices + 1, indices + 2);
+    if (line == NULL) {
+      ErrorLine("bad first index", line_num);
+    }
+    line = ParseIndices(line, line_num, indices + 3, indices + 4, indices + 5);
+    if (line == NULL) {
+      ErrorLine("bad second index", line_num);
+    }
+    // After the first two indices, each index introduces a new
+    // triangle to the fan.
+    while ((line = ParseIndices(line, line_num,
+                                indices + 6, indices + 7, indices + 8))) {
+      current_batch_->AddTriangle(current_group_line_, indices);
+      // The most recent vertex is reused for the next triangle.
+      indices[3] = indices[6];
+      indices[4] = indices[7];
+      indices[5] = indices[8];
+      indices[6] = indices[7] = indices[8] = 0;
+    }
+  }
+
+  // Parse a single group of indices, separated by slashes ('/').
+  // TODO: convert negative indices (that is, relative to the end of
+  // the current vertex positions) to more conventional positive
+  // indices.
+  const char* ParseIndices(const char* line, unsigned int line_num,
+                           int* position_index, int* texcoord_index,
+                           int* normal_index) {
+    const char* endptr = NULL;
+    *position_index = strtoint(line, &endptr);
+    if (*position_index == 0) {
+      return NULL;
+    }
+    if (endptr != NULL && *endptr == '/') {
+      *texcoord_index = strtoint(endptr + 1, &endptr);
+    } else {
+      *texcoord_index = *normal_index = 0;
+    }
+    if (endptr != NULL && *endptr == '/') {
+      *normal_index = strtoint(endptr + 1, &endptr);
+    } else {
+      *normal_index = 0;
+    }
+    return endptr;
+  }
+
+  // .OBJ files can specify multiple groups for a set of faces. This
+  // implementation finds the "most unique" group for a set of faces
+  // and uses that for the batch. In the first pass, we use the line
+  // number of the "g" command to tag the faces. Afterwards, after we
+  // collect group populations, we can go back and give them real
+  // names.
+  void ParseGroup(const char* line, unsigned int line_num) {
+    std::string token;
+    while ((line = ConsumeFirstToken(line, &token))) {
+      ToLowerInplace(&token);
+      group_counts_[token]++;
+      line_to_groups_.insert(std::make_pair(line_num, token));
+    }
+    current_group_line_ = line_num;
+  }
+
+  void ParseSmoothingGroup(const char* line, unsigned int line_num) {
+    static bool once = true;
+    if (once) {
+      WarnLine("s ignored", line_num);
+      once = false;
+    }
+  }
+
+  void ParseMtllib(const char* line, unsigned int line_num) {
+    FILE* fp = fopen(StripLeadingWhitespace(line), "r");
+    if (!fp) {
+      WarnLine("mtllib not found", line_num);
+      return;
+    }
+    WavefrontMtlFile mtlfile(fp);
+    fclose(fp);
+    materials_ = mtlfile.materials();
+    for (size_t i = 0; i < materials_.size(); ++i) {
+      DrawBatch& draw_batch = material_batches_[materials_[i].name];
+      draw_batch.Init(&positions_, &texcoords_, &normals_);
+    }
+  }
+
+  void ParseUsemtl(const char* line, unsigned int line_num) {
+    std::string usemtl;
+    ToLower(StripLeadingWhitespace(line), &usemtl);
+    MaterialBatches::iterator iter = material_batches_.find(usemtl);
+    if (iter == material_batches_.end()) {
+      ErrorLine("material not found", line_num);
+    }
+    current_batch_ = &iter->second;
+  }
+
+  void WarnLine(const char* why, unsigned int line_num) const {
+    fprintf(stderr, "WARNING: %s at line %u\n", why, line_num);
+  }
+
+  void ErrorLine(const char* why, unsigned int line_num) const {
+    fprintf(stderr, "ERROR: %s at line %u\n", why, line_num);
+    exit(-1);
+  }
+
+  AttribList positions_;
+  AttribList texcoords_;
+  AttribList normals_;
+  MaterialList materials_;
+
+  // Currently, batch by texture (i.e. map_Kd).
+  MaterialBatches material_batches_;
+  DrawBatch* current_batch_;
+
+  typedef std::multimap<unsigned int, std::string> LineToGroups;
+  LineToGroups line_to_groups_;
+  std::map<std::string, int> group_counts_;
+  unsigned int current_group_line_;
+};
+
+#endif  // WEBGL_LOADER_MESH_H_

+ 135 - 0
utils/exporters/utf8-r104/src/obj2utf8.cc

@@ -0,0 +1,135 @@
+#if 0  // A cute trick to making this .cc self-building from shell.
+g++ $0 -O2 -Wall -Werror -o `basename $0 .cc`;
+exit;
+#endif
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#include "bounds.h"
+#include "compress.h"
+#include "mesh.h"
+#include "optimize.h"
+#include "stream.h"
+
+int main(int argc, const char* argv[]) {
+  FILE* json_out = stdout;
+  if (argc != 3 && argc != 4) {
+    fprintf(stderr, "Usage: %s in.obj out.utf8\n\n"
+            "\tCompress in.obj to out.utf8 and writes JS to STDOUT.\n\n",
+            argv[0]);
+    return -1;
+  } else if (argc == 4) {
+    json_out = fopen(argv[3], "w");
+    CHECK(json_out != NULL);
+  }
+
+  FILE* fp = fopen(argv[1], "r");
+  WavefrontObjFile obj(fp);
+  fclose(fp);
+
+  fputs("{\n  \"materials\": {\n", json_out);
+  const MaterialList& materials = obj.materials();
+  for (size_t i = 0; i < materials.size(); ++i) {
+    materials[i].DumpJson(json_out);
+    const bool last = i == materials.size() - 1;
+    fputs(",\n" + last, json_out);
+  }
+  fputs("  },\n", json_out);
+  
+  const MaterialBatches& batches = obj.material_batches();
+
+  // Pass 1: compute bounds.
+  webgl_loader::Bounds bounds;
+  bounds.Clear();
+  for (MaterialBatches::const_iterator iter = batches.begin();
+       iter != batches.end(); ++iter) {
+    const DrawBatch& draw_batch = iter->second;
+    bounds.Enclose(draw_batch.draw_mesh().attribs);
+  }
+  webgl_loader::BoundsParams bounds_params = 
+      webgl_loader::BoundsParams::FromBounds(bounds);
+  fputs("  \"decodeParams\": ", json_out);
+  bounds_params.DumpJson(json_out);
+  fputs(", \"urls\": {\n", json_out);
+  // Pass 2: quantize, optimize, compress, report.
+  FILE* utf8_out_fp = fopen(argv[2], "wb");
+  CHECK(utf8_out_fp != NULL);
+  fprintf(json_out, "    \"%s\": [\n", argv[2]);
+  webgl_loader::FileSink utf8_sink(utf8_out_fp);
+  size_t offset = 0;
+  MaterialBatches::const_iterator iter = batches.begin();
+  while (iter != batches.end()) {
+    const DrawMesh& draw_mesh = iter->second.draw_mesh();
+    if (draw_mesh.indices.empty()) {
+      ++iter;
+      continue;
+    }
+    QuantizedAttribList quantized_attribs;
+    webgl_loader::AttribsToQuantizedAttribs(draw_mesh.attribs, bounds_params,
+					    &quantized_attribs);
+    VertexOptimizer vertex_optimizer(quantized_attribs);
+    const std::vector<GroupStart>& group_starts = iter->second.group_starts();
+    WebGLMeshList webgl_meshes;
+    std::vector<size_t> group_lengths;
+    for (size_t i = 1; i < group_starts.size(); ++i) {
+      const size_t here = group_starts[i-1].offset;
+      const size_t length = group_starts[i].offset - here;
+      group_lengths.push_back(length);
+      vertex_optimizer.AddTriangles(&draw_mesh.indices[here], length,
+                                    &webgl_meshes);
+    }
+    const size_t here = group_starts.back().offset;
+    const size_t length = draw_mesh.indices.size() - here;
+    CHECK(length % 3 == 0);
+    group_lengths.push_back(length);
+    vertex_optimizer.AddTriangles(&draw_mesh.indices[here], length,
+                                  &webgl_meshes);
+
+    std::vector<std::string> material;
+    std::vector<size_t> attrib_start, attrib_length, index_start, index_length;
+    for (size_t i = 0; i < webgl_meshes.size(); ++i) {
+      const size_t num_attribs = webgl_meshes[i].attribs.size();
+      const size_t num_indices = webgl_meshes[i].indices.size();
+      CHECK(num_attribs % 8 == 0);
+      CHECK(num_indices % 3 == 0);
+      webgl_loader::CompressQuantizedAttribsToUtf8(webgl_meshes[i].attribs, 
+						   &utf8_sink);
+      webgl_loader::CompressIndicesToUtf8(webgl_meshes[i].indices, &utf8_sink);
+      material.push_back(iter->first);
+      attrib_start.push_back(offset);
+      attrib_length.push_back(num_attribs / 8);
+      index_start.push_back(offset + num_attribs);
+      index_length.push_back(num_indices / 3);
+      offset += num_attribs + num_indices;
+    }
+    for (size_t i = 0; i < webgl_meshes.size(); ++i) {
+      fprintf(json_out,
+              "      { \"material\": \"%s\",\n"
+              "        \"attribRange\": [" PRIuS ", " PRIuS "],\n"
+              "        \"indexRange\": [" PRIuS ", " PRIuS "]\n"
+              "      }",
+              material[i].c_str(),
+              attrib_start[i], attrib_length[i],
+              index_start[i], index_length[i]);
+      if (i != webgl_meshes.size() - 1) {
+        fputs(",\n", json_out);
+      }
+    }
+    const bool last = (++iter == batches.end());
+    fputs(",\n" + last, json_out);
+  }
+  fputs("    ]\n", json_out);
+  fputs("  }\n}", json_out);
+  return 0;
+}

+ 138 - 0
utils/exporters/utf8-r104/src/obj2utf8x.cc

@@ -0,0 +1,138 @@
+#if 0  // A cute trick to making this .cc self-building from shell.
+g++ $0 -O2 -Wall -Werror -o `basename $0 .cc`;
+exit;
+#endif
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#include "bounds.h"
+#include "compress.h"
+#include "mesh.h"
+#include "optimize.h"
+#include "stream.h"
+
+int main(int argc, const char* argv[]) {
+  FILE* json_out = stdout;
+  if (argc != 3 && argc != 4) {
+    fprintf(stderr, "Usage: %s in.obj out.utf8\n\n"
+            "\tCompress in.obj to out.utf8 and writes JS to STDOUT.\n\n",
+            argv[0]);
+    return -1;
+  } else if (argc == 4) {
+    json_out = fopen(argv[3], "w");
+    CHECK(json_out != NULL);
+  }
+
+  FILE* fp = fopen(argv[1], "r");
+  WavefrontObjFile obj(fp);
+  fclose(fp);
+
+  fputs("{\n  \"materials\": {\n", json_out);
+  const MaterialList& materials = obj.materials();
+  for (size_t i = 0; i < materials.size(); ++i) {
+    materials[i].DumpJson(json_out);
+    const bool last = i == materials.size() - 1;
+    fputs(",\n" + last, json_out);
+  }
+  fputs("  },\n", json_out);
+  
+  const MaterialBatches& batches = obj.material_batches();
+
+  // Pass 1: compute bounds.
+  webgl_loader::Bounds bounds;
+  bounds.Clear();
+  for (MaterialBatches::const_iterator iter = batches.begin();
+       iter != batches.end(); ++iter) {
+    const DrawBatch& draw_batch = iter->second;
+    bounds.Enclose(draw_batch.draw_mesh().attribs);
+  }
+  webgl_loader::BoundsParams bounds_params = 
+      webgl_loader::BoundsParams::FromBounds(bounds);
+  fputs("  \"decodeParams\": ", json_out);
+  bounds_params.DumpJson(json_out);
+  fputs(",\n  \"urls\": {\n", json_out);
+  // Pass 2: quantize, optimize, compress, report.
+  FILE* utf8_out_fp = fopen(argv[2], "wb");
+  CHECK(utf8_out_fp != NULL);
+  fprintf(json_out, "    \"%s\": [\n", argv[2]);
+  webgl_loader::FileSink utf8_sink(utf8_out_fp);
+  size_t offset = 0;
+  MaterialBatches::const_iterator iter = batches.begin();
+  while (iter != batches.end()) {
+    const DrawMesh& draw_mesh = iter->second.draw_mesh();
+    if (draw_mesh.indices.empty()) {
+      ++iter;
+      continue;
+    }
+    QuantizedAttribList quantized_attribs;
+    webgl_loader::AttribsToQuantizedAttribs(draw_mesh.attribs, bounds_params,
+					    &quantized_attribs);
+    VertexOptimizer vertex_optimizer(quantized_attribs);
+    const std::vector<GroupStart>& group_starts = iter->second.group_starts();
+    WebGLMeshList webgl_meshes;
+    std::vector<size_t> group_lengths;
+    for (size_t i = 1; i < group_starts.size(); ++i) {
+      const size_t here = group_starts[i-1].offset;
+      const size_t length = group_starts[i].offset - here;
+      group_lengths.push_back(length);
+      vertex_optimizer.AddTriangles(&draw_mesh.indices[here], length,
+                                    &webgl_meshes);
+    }
+    const size_t here = group_starts.back().offset;
+    const size_t length = draw_mesh.indices.size() - here;
+    CHECK(length % 3 == 0);
+    group_lengths.push_back(length);
+    vertex_optimizer.AddTriangles(&draw_mesh.indices[here], length,
+                                  &webgl_meshes);
+
+    std::vector<std::string> material;
+    // TODO: is this buffering still necessary?
+    std::vector<size_t> attrib_start, attrib_length, 
+        code_start, code_length, num_tris;
+    for (size_t i = 0; i < webgl_meshes.size(); ++i) {
+      const size_t num_attribs = webgl_meshes[i].attribs.size();
+      const size_t num_indices = webgl_meshes[i].indices.size();
+      CHECK(num_attribs % 8 == 0);
+      CHECK(num_indices % 3 == 0);
+      webgl_loader::EdgeCachingCompressor compressor(webgl_meshes[i].attribs,
+                                                     webgl_meshes[i].indices);
+      compressor.Compress(&utf8_sink);
+      material.push_back(iter->first);
+      attrib_start.push_back(offset);
+      attrib_length.push_back(num_attribs / 8);
+      code_start.push_back(offset + num_attribs);
+      code_length.push_back(compressor.codes().size());
+      num_tris.push_back(num_indices / 3);
+      offset += num_attribs + compressor.codes().size();
+    }
+    for (size_t i = 0; i < webgl_meshes.size(); ++i) {
+      fprintf(json_out,
+              "      { \"material\": \"%s\",\n"
+              "        \"attribRange\": [" PRIuS ", " PRIuS "],\n"
+              "        \"codeRange\": [" PRIuS ", " PRIuS ", " PRIuS "]\n"
+              "      }",
+              material[i].c_str(),
+              attrib_start[i], attrib_length[i],
+              code_start[i], code_length[i], num_tris[i]);
+      if (i != webgl_meshes.size() - 1) {
+        fputs(",\n", json_out);
+      }
+    }
+    const bool last = (++iter == batches.end());
+    fputs(",\n" + last, json_out);
+  }
+  fputs("    ]\n", json_out);
+  fputs("  }\n}", json_out);
+  return 0;
+}

+ 165 - 0
utils/exporters/utf8-r104/src/objcompress.cc

@@ -0,0 +1,165 @@
+#if 0  // A cute trick to making this .cc self-building from shell.
+g++ $0 -O2 -Wall -Werror -o `basename $0 .cc`;
+exit;
+#endif
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#include "bounds.h"
+#include "compress.h"
+#include "mesh.h"
+#include "optimize.h"
+#include "stream.h"
+
+int main(int argc, const char* argv[]) {
+  if (argc != 3) {
+    fprintf(stderr, "Usage: %s in.obj out.utf8\n\n"
+            "\tCompress in.obj to out.utf8 and writes JS to STDOUT.\n\n",
+            argv[0]);
+    return -1;
+  }
+  FILE* fp = fopen(argv[1], "r");
+  WavefrontObjFile obj(fp);
+  fclose(fp);
+
+  printf("MODELS[\'%s\'] = {\n", StripLeadingDir(argv[1]));
+  puts("  materials: {");
+  const MaterialList& materials = obj.materials();
+  for (size_t i = 0; i < materials.size(); ++i) {
+    materials[i].DumpJson();
+  }
+  puts("  },");
+  
+  const MaterialBatches& batches = obj.material_batches();
+
+  // Pass 1: compute bounds.
+  webgl_loader::Bounds bounds;
+  bounds.Clear();
+  for (MaterialBatches::const_iterator iter = batches.begin();
+       iter != batches.end(); ++iter) {
+    const DrawBatch& draw_batch = iter->second;
+    bounds.Enclose(draw_batch.draw_mesh().attribs);
+  }
+  webgl_loader::BoundsParams bounds_params = 
+    webgl_loader::BoundsParams::FromBounds(bounds);
+  printf("  decodeParams: ");
+  bounds_params.DumpJson();
+
+  puts("  urls: {");
+  std::vector<char> utf8;
+  webgl_loader::VectorSink sink(&utf8);
+  // Pass 2: quantize, optimize, compress, report.
+  for (MaterialBatches::const_iterator iter = batches.begin();
+       iter != batches.end(); ++iter) {
+    size_t offset = 0;
+    utf8.clear();
+    const DrawMesh& draw_mesh = iter->second.draw_mesh();
+    if (draw_mesh.indices.empty()) continue;
+    
+    QuantizedAttribList quantized_attribs;
+    webgl_loader::AttribsToQuantizedAttribs(draw_mesh.attribs, bounds_params,
+					    &quantized_attribs);
+    VertexOptimizer vertex_optimizer(quantized_attribs);
+    const std::vector<GroupStart>& group_starts = iter->second.group_starts();
+    WebGLMeshList webgl_meshes;
+    std::vector<size_t> group_lengths;
+    for (size_t i = 1; i < group_starts.size(); ++i) {
+      const size_t here = group_starts[i-1].offset;
+      const size_t length = group_starts[i].offset - here;
+      group_lengths.push_back(length);
+      vertex_optimizer.AddTriangles(&draw_mesh.indices[here], length,
+                                    &webgl_meshes);
+    }
+    const size_t here = group_starts.back().offset;
+    const size_t length = draw_mesh.indices.size() - here;
+    const bool divisible_by_3 = length % 3 == 0;
+    CHECK(divisible_by_3);
+    group_lengths.push_back(length);
+    vertex_optimizer.AddTriangles(&draw_mesh.indices[here], length,
+                                  &webgl_meshes);
+
+    std::vector<std::string> material;
+    std::vector<size_t> attrib_start, attrib_length, index_start, index_length;
+    for (size_t i = 0; i < webgl_meshes.size(); ++i) {
+      const size_t num_attribs = webgl_meshes[i].attribs.size();
+      const size_t num_indices = webgl_meshes[i].indices.size();
+      const bool kBadSizes = num_attribs % 8 || num_indices % 3;
+      CHECK(!kBadSizes);
+      webgl_loader::CompressQuantizedAttribsToUtf8(webgl_meshes[i].attribs, 
+						   &sink);
+      webgl_loader::CompressIndicesToUtf8(webgl_meshes[i].indices, &sink);
+      material.push_back(iter->first);
+      attrib_start.push_back(offset);
+      attrib_length.push_back(num_attribs / 8);
+      index_start.push_back(offset + num_attribs);
+      index_length.push_back(num_indices / 3);
+      offset += num_attribs + num_indices;
+    }
+    const uint32 hash = SimpleHash(&utf8[0], utf8.size());
+    char buf[9] = { '\0' };
+    ToHex(hash, buf);
+    // TODO: this needs to handle paths.
+    std::string out_fn = std::string(buf) + "." + argv[2];
+    FILE* out_fp = fopen(out_fn.c_str(), "wb");
+    printf("    \'%s\': [\n", out_fn.c_str());
+    size_t group_index = 0;
+    for (size_t i = 0; i < webgl_meshes.size(); ++i) {
+      printf("      { material: \'%s\',\n"
+             "        attribRange: [" PRIuS ", " PRIuS "],\n"
+             "        indexRange: [" PRIuS ", " PRIuS "],\n"
+             "        bboxes: " PRIuS ",\n"
+             "        names: [",
+             material[i].c_str(),
+             attrib_start[i], attrib_length[i],
+             index_start[i], index_length[i],
+             offset);
+      std::vector<size_t> buffered_lengths;
+      size_t group_start = 0;
+      while (group_index < group_lengths.size()) {
+        printf("\'%s\', ",
+               obj.LineToGroup(group_starts[group_index].group_line).c_str());
+        const size_t group_length = group_lengths[group_index];
+        const size_t next_start = group_start + group_length;
+        const size_t webgl_index_length = webgl_meshes[i].indices.size();
+        // TODO: bbox info is better placed at the head of the file,
+        // perhaps transposed. Also, when a group gets split between
+        // batches, the bbox gets stored twice.
+	webgl_loader::CompressAABBToUtf8(group_starts[group_index].bounds,
+					 bounds_params, &sink);
+        offset += 6;
+        if (next_start < webgl_index_length) {
+          buffered_lengths.push_back(group_length);
+          group_start = next_start;
+          ++group_index;
+        } else {
+          const size_t fits = webgl_index_length - group_start;
+          buffered_lengths.push_back(fits);
+          group_start = 0;
+          group_lengths[group_index] -= fits;
+          break;
+        }
+      }
+      printf("],\n        lengths: [");
+      for (size_t k = 0; k < buffered_lengths.size(); ++k) {
+        printf(PRIuS ", ", buffered_lengths[k]);
+      }
+      puts("],\n      },");
+    }
+    fwrite(&utf8[0], 1, utf8.size(), out_fp);
+    fclose(out_fp);
+    puts("    ],");
+  }
+  puts("  }\n};");
+  return 0;
+}

+ 273 - 0
utils/exporters/utf8-r104/src/optimize.h

@@ -0,0 +1,273 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#ifndef WEBGL_LOADER_OPTIMIZE_H_
+#define WEBGL_LOADER_OPTIMIZE_H_
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+
+// TODO: since most vertices are part of 6 faces, you can optimize
+// this by using a small inline buffer.
+typedef std::vector<int> FaceList;
+
+// Linear-Speed Vertex Cache Optimisation, via:
+// http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
+class VertexOptimizer {
+ public:
+  struct TriangleData {
+    bool active;  // true iff triangle has not been optimized and emitted.
+    // TODO: eliminate some wasted computation by using this cache.
+    // float score;
+  };
+
+  VertexOptimizer(const QuantizedAttribList& attribs)
+      : attribs_(attribs),
+        per_vertex_(attribs_.size() / 8),
+        next_unused_index_(0)
+  {
+    // The cache has an extra slot allocated to simplify the logic in
+    // InsertIndexToCache.
+    for (unsigned int i = 0; i < kCacheSize + 1; ++i) {
+      cache_[i] = kUnknownIndex;
+    }
+
+    // Initialize per-vertex state.
+    for (size_t i = 0; i < per_vertex_.size(); ++i) {
+      VertexData& vertex_data = per_vertex_[i];
+      vertex_data.cache_tag = kCacheSize;
+      vertex_data.output_index = kMaxOutputIndex;
+    }
+  }
+
+  void AddTriangles(const int* indices, size_t length,
+                    WebGLMeshList* meshes) {
+    std::vector<TriangleData> per_tri(length / 3);
+
+    // Loop through the triangles, updating vertex->face lists.
+    for (size_t i = 0; i < per_tri.size(); ++i) {
+      per_tri[i].active = true;
+      per_vertex_[indices[3*i + 0]].faces.push_back(i);
+      per_vertex_[indices[3*i + 1]].faces.push_back(i);
+      per_vertex_[indices[3*i + 2]].faces.push_back(i);
+    }
+
+    // TODO: with index bounds, no need to recompute everything.
+    // Compute initial vertex scores.
+    for (size_t i = 0; i < per_vertex_.size(); ++i) {
+      VertexData& vertex_data = per_vertex_[i];
+      vertex_data.cache_tag = kCacheSize;
+      vertex_data.output_index = kMaxOutputIndex;
+      vertex_data.UpdateScore();
+    }
+
+    // Prepare output.
+    if (meshes->empty()) {
+      meshes->push_back(WebGLMesh());
+    }
+    WebGLMesh* mesh = &meshes->back();
+
+    // Consume indices, one triangle at a time.
+    for (size_t c = 0; c < per_tri.size(); ++c) {
+      const int best_triangle = FindBestTriangle(indices, per_tri);
+      per_tri[best_triangle].active = false;
+
+      // Iterate through triangle indices.
+      for (size_t i = 0; i < 3; ++i) {
+        const int index = indices[3*best_triangle + i];
+        VertexData& vertex_data = per_vertex_[index];
+        vertex_data.RemoveFace(best_triangle);
+      
+        InsertIndexToCache(index);
+        const int cached_output_index = per_vertex_[index].output_index;
+        // Have we seen this index before?
+        if (cached_output_index != kMaxOutputIndex) {
+          mesh->indices.push_back(cached_output_index);
+          continue;
+        }
+        // The first time we see an index, not only do we increment
+        // next_unused_index_ counter, but we must also copy the
+        // corresponding attributes.  TODO: do quantization here?
+        per_vertex_[index].output_index = next_unused_index_;
+        for (size_t j = 0; j < 8; ++j) {
+          mesh->attribs.push_back(attribs_[8*index + j]);
+        }
+        mesh->indices.push_back(next_unused_index_++);
+      }
+      // Check if there is room for another triangle.
+      if (next_unused_index_ > kMaxOutputIndex - 3) {
+        // Is it worth figuring out which other triangles can be added
+        // given the verties already added? Then, perhaps
+        // re-optimizing?
+        next_unused_index_ = 0;
+        meshes->push_back(WebGLMesh());
+        mesh = &meshes->back();
+        for (size_t i = 0; i <= kCacheSize; ++i) {
+          cache_[i] = kUnknownIndex;
+        }
+        for (size_t i = 0; i < per_vertex_.size(); ++i) {
+          per_vertex_[i].output_index = kMaxOutputIndex;
+        }
+      }
+    }
+  }
+ private:
+  static const int kUnknownIndex = -1;
+  static const uint16 kMaxOutputIndex = 0xD800;
+  static const size_t kCacheSize = 32;  // Does larger improve compression?
+
+  struct VertexData {
+    // Should this also update scores for incident triangles?
+    void UpdateScore() {
+      const size_t active_tris = faces.size();
+      if (active_tris <= 0) {
+        score = -1.f;
+        return;
+      }
+      // TODO: build initial score table.
+      if (cache_tag < 3) {
+        // The most recent triangle should has a fixed score to
+        // discourage generating nothing but really long strips. If we
+        // want strips, we should use a different optimizer.
+        const float kLastTriScore = 0.75f;
+        score = kLastTriScore;
+      } else if (cache_tag < kCacheSize) {
+        // Points for being recently used.
+        const float kScale = 1.f / (kCacheSize - 3);
+        const float kCacheDecayPower = 1.5f;
+        score = powf(1.f - kScale * (cache_tag - 3), kCacheDecayPower);
+      } else {
+        // Not in cache.
+        score = 0.f;
+      }
+
+      // Bonus points for having a low number of tris still to use the
+      // vert, so we get rid of lone verts quickly.
+      const float kValenceBoostScale = 2.0f;
+      const float kValenceBoostPower = 0.5f;
+      // rsqrt?
+      const float valence_boost = powf(active_tris, -kValenceBoostPower);
+      score += valence_boost * kValenceBoostScale;
+    }
+
+    // TODO: this assumes that "tri" is in the list!
+    void RemoveFace(int tri) {
+      FaceList::iterator face = faces.begin();
+      while (*face != tri) ++face;
+      *face = faces.back();
+      faces.pop_back();
+    }
+
+    FaceList faces;
+    unsigned int cache_tag;  // kCacheSize means not in cache.
+    float score;
+    uint16 output_index;
+  };
+
+  int FindBestTriangle(const int* indices,
+                       const std::vector<TriangleData>& per_tri) {
+    float best_score = -HUGE_VALF;
+    int best_triangle = -1;
+
+    // The trick to making this algorithm run in linear time (with
+    // respect to the vertices) is to only scan the triangles incident
+    // on the simulated cache for the next triangle. It is an
+    // approximation, but the score is heuristic. Anyway, most of the
+    // time the best triangle will be found this way.
+    for (size_t i = 0; i < kCacheSize; ++i) {
+      if (cache_[i] == kUnknownIndex) {
+        break;
+      }
+      const VertexData& vertex_data = per_vertex_[cache_[i]];
+      for (size_t j = 0; j < vertex_data.faces.size(); ++j) {
+        const int tri_index = vertex_data.faces[j];
+        if (per_tri[tri_index].active) {
+          const float score =
+              per_vertex_[indices[3*tri_index + 0]].score +
+              per_vertex_[indices[3*tri_index + 1]].score +
+              per_vertex_[indices[3*tri_index + 2]].score;
+          if (score > best_score) {
+            best_score = score;
+            best_triangle = tri_index;
+          }
+        }
+      }
+    }
+    // TODO: keep a range of active triangles to make the slow scan a
+    // little faster. Does this ever happen?
+    if (best_triangle == -1) {
+      // If no triangles can be found through the cache (e.g. for the
+      // first triangle) go through all the active triangles and find
+      // the best one.
+      for (size_t i = 0; i < per_tri.size(); ++i) {
+        if (per_tri[i].active) {
+          const float score =
+              per_vertex_[indices[3*i + 0]].score +
+              per_vertex_[indices[3*i + 1]].score +
+              per_vertex_[indices[3*i + 2]].score;
+          if (score > best_score) {
+            best_score = score;
+            best_triangle = i;
+          }
+        }
+      }
+      CHECK(-1 != best_triangle);
+    }
+    return best_triangle;
+  }
+
+  // TODO: faster to update an entire triangle.
+  // This also updates the vertex scores!
+  void InsertIndexToCache(int index) {
+    // Find how recently the vertex was used.
+    const unsigned int cache_tag = per_vertex_[index].cache_tag;
+
+    // Don't do anything if the vertex is already at the head of the
+    // LRU list.
+    if (cache_tag == 0) return;
+
+    // Loop through the cache, inserting the index at the front, and
+    // bubbling down to where the index was originally found. If the
+    // index was not originally in the cache, then it claims to be at
+    // the (kCacheSize + 1)th entry, and we use an extra slot to make
+    // that case simpler.
+    int to_insert = index;
+    for (unsigned int i = 0; i <= cache_tag; ++i) {
+      const int current_index = cache_[i];
+
+      // Update cross references between the entry of the cache and
+      // the per-vertex data.
+      cache_[i] = to_insert;
+      per_vertex_[to_insert].cache_tag = i;
+      per_vertex_[to_insert].UpdateScore();
+      
+      // No need to continue if we find an empty entry.
+      if (current_index == kUnknownIndex) {
+        break;
+      }
+      
+      to_insert = current_index;
+    }
+  }
+
+  const QuantizedAttribList& attribs_;
+  std::vector<VertexData> per_vertex_;
+  int cache_[kCacheSize + 1];
+  uint16 next_unused_index_;
+};
+
+#endif  // WEBGL_LOADER_OPTIMIZE_H_

+ 272 - 0
utils/exporters/utf8-r104/src/stream.h

@@ -0,0 +1,272 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#ifndef WEBGL_LOADER_STREAM_H_
+#define WEBGL_LOADER_STREAM_H_
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+#include "base.h"
+
+namespace webgl_loader {
+
+// An abstract interface to allow appending bytes to various streams.
+class ByteSinkInterface {
+ public:
+  virtual void Put(char c) = 0;
+  virtual size_t PutN(const char* data, size_t len) = 0;
+  virtual ~ByteSinkInterface() { }
+
+ protected:
+  ByteSinkInterface() { }
+
+ private:
+  // Disallow copy and assignment.
+  ByteSinkInterface(const ByteSinkInterface&);
+  void operator=(const ByteSinkInterface&);
+};
+
+// None of the concrete implementations actually own the backing data.
+// They should be safe to copy.
+
+class NullSink : public ByteSinkInterface {
+ public:
+  NullSink() { }
+
+  virtual void Put(char) { }
+  
+  virtual size_t PutN(const char*, size_t len) { return len; }
+};
+
+class FileSink : public ByteSinkInterface {
+ public:
+  // |fp| is unowned and must not be NULL.
+  explicit FileSink(FILE* fp)
+    : fp_(fp) {
+  }
+
+  virtual void Put(char c) {
+    PutChar(c, fp_);
+  }
+
+  virtual size_t PutN(const char* data, size_t len) {
+    return fwrite(data, 1, len, fp_);
+  }
+
+ private:
+  FILE *fp_;  // unowned.
+};
+
+class VectorSink : public ByteSinkInterface {
+ public:
+  // |vec| is unowned and must not be NULL.
+  explicit VectorSink(std::vector<char>* vec)
+    : vec_(vec) {
+  }
+  
+  virtual void Put(char c) {
+    vec_->push_back(c);
+  }
+
+  virtual size_t PutN(const char* data, size_t len) {
+    vec_->insert(vec_->end(), data, data + len);
+    return len;
+  }
+
+ private:
+  std::vector<char>* vec_;  // unowned.
+};
+
+class StringSink : public ByteSinkInterface {
+ public:
+  // |str| is unowned and must not be NULL.
+  explicit StringSink(std::string* str)
+    : str_(str) {
+    DCHECK(str != NULL);
+  }
+
+  virtual void Put(char c) {
+    str_->push_back(c);
+  }
+
+  virtual size_t PutN(const char* data, size_t len) {
+    str_->append(data, len);
+    return len;
+  }
+
+ private:
+  std::string* str_;  // unowned.
+};
+
+class ByteHistogramSink : public ByteSinkInterface {
+ public:
+  // |sink| in unowned and must not be NULL.
+  explicit ByteHistogramSink(ByteSinkInterface* sink)
+      : sink_(sink) {
+    memset(histo_, 0, sizeof(histo_));
+  }
+
+  virtual void Put(char c) {
+    histo_[static_cast<uint8>(c)]++;
+    sink_->Put(c);
+  }
+
+  virtual size_t PutN(const char* data, size_t len) {
+    const char* const end = data + len;
+    for (const char* iter = data; iter != end; ++iter) {
+      histo_[static_cast<uint8>(*iter)]++;
+    }
+    return sink_->PutN(data, len);
+  }
+
+  const size_t* histo() const {
+    return histo_;
+  }
+
+ private:
+  size_t histo_[256];
+  ByteSinkInterface* sink_;  // unowned.
+};
+
+// TODO: does it make sense to have a global enum? How should 
+// new BufferedInput implementations define new error codes? 
+enum ErrorCode {
+  kNoError = 0, 
+  kEndOfFile = 1,
+  kFileError = 2,  // TODO: translate errno.
+};
+
+// Adapted from ryg's BufferedStream abstraction:
+// http://fgiesen.wordpress.com/2011/11/21/buffer-centric-io/
+class BufferedInput {
+ public:
+  typedef ErrorCode (*Refiller)(BufferedInput*);
+
+  BufferedInput(Refiller refiller = RefillZeroes)
+      : cursor(NULL),
+        begin_(NULL),
+        end_(NULL),
+        refiller_(refiller),
+        error_(kNoError) {
+  }
+
+  // InitFromMemory.
+  BufferedInput(const char* data, size_t length)
+      : cursor(data),
+        begin_(data),
+        end_(data + length),
+        refiller_(RefillEndOfFile),
+        error_(kNoError) {
+  }
+
+  const char* begin() const {
+    return begin_;
+  }
+
+  const char* end() const {
+    return end_;
+  }
+
+  const char* cursor;
+
+  ErrorCode error() const {
+    DCHECK(begin() <= cursor);
+    DCHECK(cursor <= end());
+    return error_;
+  }
+
+  ErrorCode Refill() {
+    DCHECK(begin() <= cursor);
+    DCHECK(cursor <= end());
+    if (cursor == end()) {
+      error_ = refiller_(this);
+    }
+    return error_;
+  }
+
+ protected:
+  static ErrorCode RefillZeroes(BufferedInput* bi) {
+    static const char kZeroes[64] = { 0 };
+    bi->cursor = kZeroes;
+    bi->begin_ = kZeroes;
+    bi->end_ = kZeroes + sizeof(kZeroes);
+    return bi->error_;
+  }
+
+  static ErrorCode RefillEndOfFile(BufferedInput* bi) {
+    return bi->fail(kEndOfFile);
+  }
+
+  ErrorCode fail(ErrorCode why) {
+    error_ = why;
+    refiller_ = RefillZeroes;
+    return Refill();
+  }
+
+  const char* begin_;
+  const char* end_;
+  Refiller refiller_;
+  ErrorCode error_;
+
+ private:
+  // Disallow copy and assign.
+  BufferedInput(const BufferedInput&);
+  void operator=(const BufferedInput&);
+};
+
+class BufferedInputStream : public BufferedInput {
+ public:
+  BufferedInputStream(FILE* fp, char* buf, size_t size)
+      : BufferedInput(RefillFread),
+        fp_(fp),
+        buf_(buf),
+        size_(size) {
+    DCHECK(buf != NULL);
+    // Disable buffering since we're doing it ourselves.
+    // TODO check error.
+    setvbuf(fp_, NULL, _IONBF, 0);
+    cursor = buf;
+    begin_ = buf;
+    end_ = buf;
+  }
+ protected:
+  // TODO: figure out how to automate this casting pattern.
+  static ErrorCode RefillFread(BufferedInput* bi) {
+    return static_cast<BufferedInputStream*>(bi)->DoRefillFread();
+  }
+ private:
+  ErrorCode DoRefillFread() {
+    const size_t bytes_read = fread(buf_, 1, size_, fp_);
+    cursor = begin_;
+    end_ = begin_ + bytes_read;
+    if (bytes_read < size_) {
+      if (feof(fp_)) {
+        refiller_ = RefillEndOfFile;
+      } else if (ferror(fp_)) {
+        return fail(kFileError);
+      }
+    }
+    return kNoError;
+  }
+
+  FILE* fp_;
+  char* buf_;
+  size_t size_;
+};
+
+}  // namespace webgl_loader
+
+#endif  // WEBGL_LOADER_STREAM_H_

+ 61 - 0
utils/exporters/utf8-r104/src/utf8.h

@@ -0,0 +1,61 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#ifndef WEBGL_LOADER_UTF8_H_
+#define WEBGL_LOADER_UTF8_H_
+
+#include "base.h"
+#include "stream.h"
+
+namespace webgl_loader {
+
+const uint8 kUtf8MoreBytesPrefix = 0x80;
+const uint8 kUtf8TwoBytePrefix = 0xC0;
+const uint8 kUtf8ThreeBytePrefix = 0xE0;
+
+const uint16 kUtf8TwoByteLimit = 0x0800;
+const uint16 kUtf8SurrogatePairStart = 0xD800;
+const uint16 kUtf8SurrogatePairNum = 0x0800;
+const uint16 kUtf8EncodableEnd = 0x10000 - kUtf8SurrogatePairNum;
+
+const uint16 kUtf8MoreBytesMask = 0x3F;
+
+bool Uint16ToUtf8(uint16 word, ByteSinkInterface* sink) {
+  if (word < 0x80) {
+    sink->Put(static_cast<char>(word));
+  } else if (word < kUtf8TwoByteLimit) {
+    sink->Put(static_cast<char>(kUtf8TwoBytePrefix + (word >> 6)));
+    sink->Put(static_cast<char>(kUtf8MoreBytesPrefix +
+				(word & kUtf8MoreBytesMask)));
+  } else if (word < kUtf8EncodableEnd) {
+    // We can only encode 65535 - 2048 values because of illegal UTF-8
+    // characters, such as surrogate pairs in [0xD800, 0xDFFF].
+    if (word >= kUtf8SurrogatePairStart) {
+      // Shift the result to avoid the surrogate pair range.
+      word += kUtf8SurrogatePairNum;
+    }
+    sink->Put(static_cast<char>(kUtf8ThreeBytePrefix + (word >> 12)));
+    sink->Put(static_cast<char>(kUtf8MoreBytesPrefix +
+				((word >> 6) & kUtf8MoreBytesMask)));
+    sink->Put(static_cast<char>(kUtf8MoreBytesPrefix +
+				(word & kUtf8MoreBytesMask)));
+  } else {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace webgl_loader
+
+#endif  // WEBGL_LOADER_UTF8_H_