summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantin Käfer <github@kkaefer.com>2014-01-07 12:10:14 +0100
committerKonstantin Käfer <github@kkaefer.com>2014-01-07 12:10:14 +0100
commit44b43fe5a608ef0d2f83b2c99ec5b87c6e00f6e0 (patch)
treea65c62c85ee579e45ae30fd406a81bb7d9f03a0a
downloadqtlocation-mapboxgl-44b43fe5a608ef0d2f83b2c99ec5b87c6e00f6e0.tar.gz
glfw version of sample app
-rw-r--r--Makefile41
-rw-r--r--enable-emscripten2
-rw-r--r--include/llmr/geometry/linevertexbuffer.hpp26
-rw-r--r--include/llmr/llmr.hpp6
-rw-r--r--include/llmr/map/map.hpp34
-rw-r--r--include/llmr/map/tile.hpp35
-rw-r--r--include/llmr/map/transform.hpp22
-rw-r--r--include/llmr/platform/gl.hpp22
-rw-r--r--include/llmr/platform/platform.hpp24
-rw-r--r--include/llmr/renderer/painter.hpp55
-rw-r--r--include/llmr/renderer/shader-fill.hpp19
-rw-r--r--include/llmr/renderer/shader-line.hpp19
-rw-r--r--include/llmr/renderer/shader.hpp23
-rw-r--r--include/llmr/util/mat4.h41
-rw-r--r--src/geometry/linevertexbuffer.cpp33
-rw-r--r--src/map/map.cpp64
-rw-r--r--src/map/pbf.hpp178
-rw-r--r--src/map/tile.cpp138
-rw-r--r--src/map/transform.cpp21
-rw-r--r--src/renderer/painter.cpp226
-rw-r--r--src/renderer/shader-fill.cpp14
-rw-r--r--src/renderer/shader-line.cpp14
-rw-r--r--src/renderer/shader.cpp134
-rw-r--r--src/shader/fill.fragment.glsl5
-rw-r--r--src/shader/fill.vertex.glsl7
-rw-r--r--src/shader/line.fragment.glsl5
-rw-r--r--src/shader/line.vertex.glsl8
-rw-r--r--src/util/mat4.c160
28 files changed, 1376 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000..106055e55d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,41 @@
+CXXFLAGS = -Wall -Wextra -std=c++11 -stdlib=libc++ -fno-exceptions
+CPPFLAGS = -O3 -DDEBUG
+INCLUDE = -Iinclude
+
+
+
+SRCS += src/map/map.cpp
+SRCS += src/map/tile.cpp
+SRCS += src/map/transform.cpp
+SRCS += src/geometry/linevertexbuffer.cpp
+SRCS += src/renderer/painter.cpp
+SRCS += src/renderer/shader.cpp
+SRCS += src/renderer/shader-fill.cpp
+SRCS += src/renderer/shader-line.cpp
+SRCS += src/util/mat4.c
+
+SRCS += macosx/main.mm
+
+OBJS = $(patsubst %.mm,%.o,$(patsubst %.cpp,%.o,$(patsubst %.c,%.o,$(SRCS))))
+
+main: macosx
+
+library: $(OBJS)
+
+macosx: library
+ $(CXX) $(OBJS) $(INCLUDE) -lglfw3 -framework OpenGL -framework Foundation -o bin/macosx
+
+%.o: %.cpp
+ $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(INCLUDE) -c -o $@ $^
+
+%.o: %.mm
+ $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(INCLUDE) -c -o $@ $^
+
+%.o: %.c
+ $(CC) $(CPPFLAGS) $(INCLUDE) -c -o $@ $^
+
+clean:
+ rm -rf src/*/*.o
+ rm -rf bin/macosx
+
+.PHONY: macosx
diff --git a/enable-emscripten b/enable-emscripten
new file mode 100644
index 0000000000..d7feba1eab
--- /dev/null
+++ b/enable-emscripten
@@ -0,0 +1,2 @@
+#!/bin/sh
+export PATH="`emsdk active_path`:$PATH"
diff --git a/include/llmr/geometry/linevertexbuffer.hpp b/include/llmr/geometry/linevertexbuffer.hpp
new file mode 100644
index 0000000000..8d5c6b335c
--- /dev/null
+++ b/include/llmr/geometry/linevertexbuffer.hpp
@@ -0,0 +1,26 @@
+#ifndef LLMR_GEOMETRY_LINEVERTEXBUFFER
+#define LLMR_GEOMETRY_LINEVERTEXBUFFER
+
+#include "../platform/gl.hpp"
+
+#include <vector>
+
+namespace llmr {
+
+class linevertexbuffer {
+public:
+ linevertexbuffer();
+
+ void addDegenerate();
+ void addCoordinate(int16_t x, int16_t y);
+
+ uint32_t length();
+ void bind();
+private:
+ std::vector<uint16_t> array;
+ GLuint buffer;
+};
+
+}
+
+#endif
diff --git a/include/llmr/llmr.hpp b/include/llmr/llmr.hpp
new file mode 100644
index 0000000000..2d9b46b9c1
--- /dev/null
+++ b/include/llmr/llmr.hpp
@@ -0,0 +1,6 @@
+#ifndef LLMR_MAIN
+#define LLMR_MAIN
+
+#include "map/map.hpp"
+
+#endif
diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp
new file mode 100644
index 0000000000..e19236f12e
--- /dev/null
+++ b/include/llmr/map/map.hpp
@@ -0,0 +1,34 @@
+#ifndef LLMR_MAP_MAP
+#define LLMR_MAP_MAP
+
+#include "../platform/platform.hpp"
+#include "../renderer/painter.hpp"
+#include "transform.hpp"
+
+namespace llmr {
+
+class tile;
+
+class map {
+public:
+ map(class platform *platform, class painter *painter);
+ ~map();
+
+ /* callback */
+ bool render();
+ void moveBy(double dx, double dy);
+ void scaleBy(double ds, double cx, double cy);
+ void tileLoaded(tile *tile);
+ void tileFailed(tile *tile);
+
+private:
+ platform *platform;
+ painter *painter;
+ transform *transform;
+
+ std::vector<tile *> tiles;
+};
+
+}
+
+#endif
diff --git a/include/llmr/map/tile.hpp b/include/llmr/map/tile.hpp
new file mode 100644
index 0000000000..026810ace4
--- /dev/null
+++ b/include/llmr/map/tile.hpp
@@ -0,0 +1,35 @@
+#ifndef LLMR_MAP_TILE
+#define LLMR_MAP_TILE
+
+#include "../geometry/linevertexbuffer.hpp"
+
+#include <stdint.h>
+#include <vector>
+
+namespace llmr {
+
+class tile {
+public:
+ tile(int32_t z, int32_t x, int32_t y);
+
+ void setData(uint8_t *data, uint32_t bytes);
+ bool parse();
+ void parseLayer(const uint8_t *data, uint32_t bytes);
+ void parseFeature(const uint8_t *data, uint32_t bytes);
+ void loadGeometry(const uint8_t *data, uint32_t bytes);
+
+public:
+ const int32_t z, x, y;
+ bool loaded;
+ linevertexbuffer lineVertex;
+
+private:
+ // Source data
+ uint8_t *data;
+ uint32_t bytes;
+
+};
+
+}
+
+#endif
diff --git a/include/llmr/map/transform.hpp b/include/llmr/map/transform.hpp
new file mode 100644
index 0000000000..26a6683f20
--- /dev/null
+++ b/include/llmr/map/transform.hpp
@@ -0,0 +1,22 @@
+#ifndef LLMR_MAP_TRANSFORM
+#define LLMR_MAP_TRANSFORM
+
+namespace llmr {
+
+class tile;
+
+class transform {
+public:
+ transform();
+
+ void moveBy(double dx, double dy);
+ void scaleBy(double ds, double cx, double cy);
+
+public:
+ double x, y;
+ double scale;
+};
+
+}
+
+#endif
diff --git a/include/llmr/platform/gl.hpp b/include/llmr/platform/gl.hpp
new file mode 100644
index 0000000000..4fa0cf4775
--- /dev/null
+++ b/include/llmr/platform/gl.hpp
@@ -0,0 +1,22 @@
+#ifndef LLMR_RENDERER_GL
+#define LLMR_RENDERER_GL
+
+#ifdef __APPLE__
+ #include "TargetConditionals.h"
+ #if TARGET_OS_IPHONE
+ #include <OpenGLES/ES2/gl.h>
+ #include <OpenGLES/ES2/glext.h>
+ #elif TARGET_IPHONE_SIMULATOR
+ #include <OpenGLES/ES2/gl.h>
+ #include <OpenGLES/ES2/glext.h>
+ #elif TARGET_OS_MAC
+ #include <GLFW/glfw3.h>
+ #include <OpenGL/OpenGL.h>
+ #else
+ #error Unsupported Apple platform
+ #endif
+#else
+ #error Unsupported platform
+#endif
+
+#endif
diff --git a/include/llmr/platform/platform.hpp b/include/llmr/platform/platform.hpp
new file mode 100644
index 0000000000..46a1383c42
--- /dev/null
+++ b/include/llmr/platform/platform.hpp
@@ -0,0 +1,24 @@
+#ifndef LLMR_PLATFORM_IOS
+#define LLMR_PLATFORM_IOS
+
+namespace llmr {
+
+class tile;
+
+class platform {
+public:
+ platform(void *obj) : obj(obj) {}
+
+ const char *shaderSource(const char *name, const char *extension);
+ void restart();
+ void request(tile *tile);
+
+private:
+ void *obj;
+};
+
+}
+
+
+
+#endif
diff --git a/include/llmr/renderer/painter.hpp b/include/llmr/renderer/painter.hpp
new file mode 100644
index 0000000000..ac6012b0d0
--- /dev/null
+++ b/include/llmr/renderer/painter.hpp
@@ -0,0 +1,55 @@
+#ifndef LLMR_RENDERER_PAINTER
+#define LLMR_RENDERER_PAINTER
+
+#include "../platform/gl.hpp"
+
+#include "shader-fill.hpp"
+#include "shader-line.hpp"
+
+namespace llmr {
+
+class platform;
+class transform;
+class tile;
+
+class painter {
+public:
+ painter(class platform *platform);
+
+ void setup();
+ void teardown();
+
+ void resize(GLuint new_width, GLuint new_height);
+ void viewport();
+
+
+ void clear();
+ void render(tile *tile);
+
+ void switchShader(Shader *shader, float matrix[16]);
+ void setTransform(transform *transform);
+
+private:
+ void setupShaders();
+ void changeMatrix();
+
+public:
+
+private:
+ platform *platform;
+ transform *transform;
+ GLuint width, height;
+ GLfloat matrix[16];
+
+ Shader *currentShader;
+ FillShader *fillShader;
+ LineShader *lineShader;
+
+ GLuint vertexArray;
+ GLuint triangleVertexBuffer;
+ GLuint fillVertexBuffer;
+};
+
+}
+
+#endif
diff --git a/include/llmr/renderer/shader-fill.hpp b/include/llmr/renderer/shader-fill.hpp
new file mode 100644
index 0000000000..165266e4b5
--- /dev/null
+++ b/include/llmr/renderer/shader-fill.hpp
@@ -0,0 +1,19 @@
+#ifndef LLMR_RENDERER_SHADER_FILL
+#define LLMR_RENDERER_SHADER_FILL
+
+#include "shader.hpp"
+
+namespace llmr {
+
+class FillShader : public Shader {
+public:
+ FillShader(const GLchar *vertex, const GLchar *fragment);
+
+ GLint a_pos;
+ GLint u_matrix;
+ GLint u_color;
+};
+
+}
+
+#endif
diff --git a/include/llmr/renderer/shader-line.hpp b/include/llmr/renderer/shader-line.hpp
new file mode 100644
index 0000000000..60e27146f9
--- /dev/null
+++ b/include/llmr/renderer/shader-line.hpp
@@ -0,0 +1,19 @@
+#ifndef LLMR_RENDERER_SHADER_LINE
+#define LLMR_RENDERER_SHADER_LINE
+
+#include "shader.hpp"
+
+namespace llmr {
+
+class LineShader : public Shader {
+public:
+ LineShader(const GLchar *vertex, const GLchar *fragment);
+
+ GLint a_pos;
+ GLint u_matrix;
+ GLint u_color;
+};
+
+}
+
+#endif
diff --git a/include/llmr/renderer/shader.hpp b/include/llmr/renderer/shader.hpp
new file mode 100644
index 0000000000..d178a1d9c8
--- /dev/null
+++ b/include/llmr/renderer/shader.hpp
@@ -0,0 +1,23 @@
+#ifndef LLMR_RENDERER_SHADER
+#define LLMR_RENDERER_SHADER
+
+#include "../platform/gl.hpp"
+#include <vector>
+
+namespace llmr {
+
+class Shader {
+public:
+ Shader(const GLchar *vertex, const GLchar *fragment);
+ ~Shader();
+ bool valid;
+ GLuint program;
+ std::vector<GLuint> attributes;
+
+private:
+ bool compileShader(GLuint *shader, GLenum type, const GLchar *source);
+};
+
+}
+
+#endif
diff --git a/include/llmr/util/mat4.h b/include/llmr/util/mat4.h
new file mode 100644
index 0000000000..bdfc3b1715
--- /dev/null
+++ b/include/llmr/util/mat4.h
@@ -0,0 +1,41 @@
+// This is an incomplete port of http://glmatrix.net/
+//
+// Copyright (c) 2013 Brandon Jones, Colin MacKenzie IV
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the
+// use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not claim
+// that you wrote the original software. If you use this software in a
+// product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+
+#ifndef llmr_util_mat4_
+#define llmr_util_mat4_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void mat4_identity(float out[16]);
+void mat4_ortho(float out[16], float left, float right, float bottom, float top, float near, float far);
+void mat4_copy(float out[16], float a[16]);
+void mat4_translate(float out[16], float a[16], float x, float y, float z);
+void mat4_scale(float out[16], float a[16], float x, float y, float z);
+void mat4_multiply(float out[16], float a[16], float b[16]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/geometry/linevertexbuffer.cpp b/src/geometry/linevertexbuffer.cpp
new file mode 100644
index 0000000000..03cbdb79b1
--- /dev/null
+++ b/src/geometry/linevertexbuffer.cpp
@@ -0,0 +1,33 @@
+#include <llmr/geometry/linevertexbuffer.hpp>
+
+using namespace llmr;
+
+linevertexbuffer::linevertexbuffer()
+ : buffer(0) {
+}
+
+void linevertexbuffer::addDegenerate() {
+ array.push_back(32767);
+ array.push_back(0);
+}
+
+void linevertexbuffer::addCoordinate(int16_t x, int16_t y) {
+ array.push_back(x);
+ array.push_back(y);
+}
+
+uint32_t linevertexbuffer::length() {
+ // We store 2 coordinates per vertex
+ return array.size() / 2;
+}
+
+void linevertexbuffer::bind() {
+ if (buffer == 0) {
+ glGenBuffers(1, &buffer);
+ fprintf(stderr, "new buffer id: %d\n", buffer);
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ glBufferData(GL_ARRAY_BUFFER, array.size() * sizeof(uint16_t), array.data(), GL_STATIC_DRAW);
+ } else {
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ }
+}
diff --git a/src/map/map.cpp b/src/map/map.cpp
new file mode 100644
index 0000000000..397ba384d8
--- /dev/null
+++ b/src/map/map.cpp
@@ -0,0 +1,64 @@
+#include <llmr/map/map.hpp>
+#include <llmr/map/tile.hpp>
+
+#include <iostream>
+#include <thread>
+
+using namespace llmr;
+
+map::map(class platform *platform, class painter *painter)
+ : platform(platform),
+ painter(painter),
+ transform(new class transform()) {
+
+ painter->setTransform(transform);
+
+
+ tile *tile = new class tile(0, 0, 0);
+ tiles.push_back(tile);
+ platform->request(tile);
+
+}
+
+map::~map() {
+ delete transform;
+}
+
+
+void map::moveBy(double dx, double dy) {
+ transform->moveBy(dx, dy);
+ platform->restart();
+}
+
+void map::scaleBy(double ds, double cx, double cy) {
+ transform->scaleBy(ds, cx, cy);
+ platform->restart();
+}
+
+
+bool map::render() {
+ painter->clear();
+
+ for (tile *tile : tiles) {
+ // painter->viewport();
+ if (tile->loaded) {
+ painter->render(tile);
+ }
+ }
+
+ return false;
+}
+
+void map::tileLoaded(tile *tile) {
+ platform->restart();
+ // fprintf(stderr, "[%8zx] tile loaded %d/%d/%d\n",
+ // std::hash<std::thread::id>()(std::this_thread::get_id()),
+ // tile->z, tile->x, tile->y);
+ // schedule rerender
+}
+
+void map::tileFailed(tile *tile) {
+ fprintf(stderr, "[%8zx] tile failed to load %d/%d/%d\n",
+ std::hash<std::thread::id>()(std::this_thread::get_id()),
+ tile->z, tile->x, tile->y);
+} \ No newline at end of file
diff --git a/src/map/pbf.hpp b/src/map/pbf.hpp
new file mode 100644
index 0000000000..faf0aadf91
--- /dev/null
+++ b/src/map/pbf.hpp
@@ -0,0 +1,178 @@
+#pragma once
+
+/*
+ * Some parts are from upb - a minimalist implementation of protocol buffers.
+ *
+ * Copyright (c) 2008-2011 Google Inc. See LICENSE for details.
+ * Author: Josh Haberman <jhaberman@gmail.com>
+ */
+
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <string>
+// #include <cassert>
+
+namespace llmr {
+
+struct pbf {
+ inline pbf(const unsigned char *data, uint32_t length);
+ inline pbf(const char *data, uint32_t length);
+ // inline pbf(const std::string& buffer);
+
+ inline bool next();
+ inline uint64_t varint();
+ inline int64_t svarint();
+ inline std::string string();
+ inline float float32();
+ inline double float64();
+ inline int64_t int64();
+ inline bool boolean();
+
+ template <typename T, typename... Args>
+ inline T *message(const Args&... args);
+
+ inline void skip();
+ inline void skipValue(uint32_t val);
+ inline void skipBytes(uint32_t bytes);
+
+ const uint8_t *data;
+ const uint8_t *end;
+ uint32_t value;
+ uint32_t tag;
+};
+
+pbf::pbf(const unsigned char *data, uint32_t length)
+ : data(data),
+ end(data + length)
+{}
+
+pbf::pbf(const char *data, uint32_t length)
+ : data((const unsigned char *)data),
+ end((const unsigned char *)data + length)
+{}
+
+// pbf::pbf(const std::string& buffer)
+// : data((const unsigned char *)buffer.data()),
+// end((const unsigned char *)buffer.data() + buffer.size())
+// {}
+
+bool pbf::next()
+{
+ if (data < end) {
+ value = (uint32_t)varint();
+ tag = value >> 3;
+ return true;
+ }
+ return false;
+}
+
+
+uint64_t pbf::varint()
+{
+ uint8_t byte = 0x80;
+ uint64_t result = 0;
+ int bitpos;
+ for (bitpos = 0; bitpos < 70 && (byte & 0x80); bitpos += 7) {
+ if (data >= end) {
+ fprintf(stderr, "unterminated varint, unexpected end of buffer");
+ exit(1);
+ }
+ result |= ((uint64_t)(byte = *data) & 0x7F) << bitpos;
+
+ data++;
+ }
+ if (bitpos == 70 && (byte & 0x80)) {
+ fprintf(stderr, "unterminated varint (too long)");
+ exit(1);
+ }
+
+ return result;
+}
+
+int64_t pbf::svarint()
+{
+ uint64_t n = varint();
+ return (n >> 1) ^ -(int64_t)(n & 1);
+}
+
+std::string pbf::string()
+{
+ uint32_t bytes = (uint32_t)varint();
+ const char *string = (const char *)data;
+ skipBytes(bytes);
+ return std::string(string, bytes);
+}
+
+float pbf::float32()
+{
+ skipBytes(4);
+ float result;
+ memcpy(&result, data - 4, 4);
+ return result;
+}
+double pbf::float64()
+{
+ skipBytes(8);
+ double result;
+ memcpy(&result, data - 8, 8);
+ return result;
+}
+
+int64_t pbf::int64()
+{
+ return (int64_t)varint();
+}
+
+bool pbf::boolean()
+{
+ skipBytes(1);
+ return *(bool *)(data - 1);
+}
+
+
+template <typename T, typename... Args> T *pbf::message(const Args&... args)
+{
+ uint32_t bytes = (uint32_t)varint();
+ T *result = new T(data, bytes, args...);
+ skipBytes(bytes);
+ return result;
+}
+
+void pbf::skip()
+{
+ skipValue(value);
+}
+
+void pbf::skipValue(uint32_t val)
+{
+ switch (val & 0x7) {
+ case 0: // varint
+ varint();
+ break;
+ case 1: // 64 bit
+ skipBytes(8);
+ break;
+ case 2: // string/message
+ skipBytes((uint32_t)varint());
+ break;
+ case 5: // 32 bit
+ skipBytes(4);
+ break;
+ default:
+ fprintf(stderr, "cannot skip unknown type %d", val & 0x7);
+ exit(1);
+ }
+}
+
+void pbf::skipBytes(uint32_t bytes)
+{
+ data += bytes;
+ if (data > end) {
+ fprintf(stderr, "unexpected end of buffer");
+ exit(1);
+ }
+}
+
+} // end namespace llmr
+
diff --git a/src/map/tile.cpp b/src/map/tile.cpp
new file mode 100644
index 0000000000..a27f6e5ba8
--- /dev/null
+++ b/src/map/tile.cpp
@@ -0,0 +1,138 @@
+#include <llmr/map/tile.hpp>
+
+#include <stdint.h>
+// #include <iostream>
+#include <thread>
+
+#include "pbf.hpp"
+
+using namespace llmr;
+
+tile::tile(int32_t z, int32_t x, int32_t y)
+ : z(z),
+ x(x),
+ y(y),
+ loaded(false),
+ data(0),
+ bytes(0) {
+}
+
+void tile::setData(uint8_t *data, uint32_t bytes) {
+ this->data = (uint8_t *)malloc(bytes);
+ this->bytes = bytes;
+ memcpy(this->data, data, bytes);
+}
+
+bool tile::parse()
+{
+ fprintf(stderr, "[%8zx] parsing tile\n",
+ std::hash<std::thread::id>()(std::this_thread::get_id())
+ );
+
+ // try {
+ pbf tile(data, bytes);
+ while (tile.next()) {
+ if (tile.tag == 3) { // layer
+ uint32_t bytes = (uint32_t)tile.varint();
+ parseLayer(tile.data, bytes);
+ tile.skipBytes(bytes);
+ } else {
+ tile.skip();
+ }
+ }
+ // } catch (std::exception& ex) {
+ // std::cerr << ex.what();
+ // return false;
+ // }
+
+ loaded = true;
+ return true;
+}
+
+void tile::parseLayer(const uint8_t *data, uint32_t bytes) {
+ pbf layer(data, bytes);
+ std::string name;
+ while (layer.next()) {
+ if (layer.tag == 1) {
+ name = layer.string();
+ } else if (layer.tag == 2) {
+ uint32_t bytes = (uint32_t)layer.varint();
+ parseFeature(layer.data, bytes);
+ layer.skipBytes(bytes);
+ } else {
+ layer.skip();
+ }
+ }
+}
+
+void tile::parseFeature(const uint8_t *data, uint32_t bytes) {
+ pbf feature(data, bytes);
+ while (feature.next()) {
+ if (feature.tag == 1) {
+ uint64_t id = feature.varint();
+ } else if (feature.tag == 2) {
+ const uint8_t *tag_end = feature.data + feature.varint();
+ while (feature.data < tag_end) {
+ uint32_t key = (uint32_t)feature.varint();
+ uint32_t value = (uint32_t)feature.varint();
+ }
+ } else if (feature.tag == 3) {
+ uint32_t type = (uint32_t)feature.varint();
+ } else if (feature.tag == 4) {
+ uint32_t bytes = (uint32_t)feature.varint();
+ loadGeometry(feature.data, bytes);
+ feature.skipBytes(bytes);
+ } else {
+ feature.skip();
+ }
+ }
+}
+
+void tile::loadGeometry(const uint8_t *data, uint32_t bytes) {
+ pbf geometry(data, bytes);
+
+ uint32_t cmd = 1;
+ uint32_t length = 0;
+ int32_t x = 0, y = 0;
+
+ // var lines = [];
+ // var line = null;
+ int32_t ox = 0, oy = 0;
+
+ while (geometry.data < geometry.end) {
+ if (!length) {
+ uint32_t cmd_length = (uint32_t)geometry.varint();
+ cmd = cmd_length & 0x7;
+ length = cmd_length >> 3;
+ }
+
+ length--;
+
+ if (cmd == 1 || cmd == 2) {
+ x += geometry.svarint();
+ y += geometry.svarint();
+
+ if (cmd == 1) {
+ // moveTo
+ // fprintf(stderr, "[m %d/%d] ", x, y);
+ // degenerate vertex
+ lineVertex.addDegenerate();
+ ox = x;
+ oy = y;
+ } else {
+ // lineTo
+ // fprintf(stderr, "[l %d/%d] ", x, y);
+ }
+ lineVertex.addCoordinate(x, y);
+ } else if (cmd == 7) {
+ // closePolygon
+ // fprintf(stderr, "[c]\n");
+ lineVertex.addCoordinate(ox, oy);
+ } else {
+ // throw new Error('unknown command ' + cmd);
+ // throw std::runtime_error("unknown command");
+ fprintf(stderr, "unknown command");
+ exit(1);
+ }
+ }
+}
diff --git a/src/map/transform.cpp b/src/map/transform.cpp
new file mode 100644
index 0000000000..61d982192d
--- /dev/null
+++ b/src/map/transform.cpp
@@ -0,0 +1,21 @@
+#include <llmr/map/transform.hpp>
+
+using namespace llmr;
+
+transform::transform()
+ : x(0.0f),
+ y(0.0f),
+ scale(1.0f) {
+
+}
+
+void transform::moveBy(double dx, double dy) {
+ x += dx;
+ y += dy;
+}
+
+void transform::scaleBy(double ds, double cx, double cy) {
+ scale *= ds;
+ x = x * ds + cx * (1 - ds);
+ y = y * ds + cy * (1 - ds);
+}
diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp
new file mode 100644
index 0000000000..b43e92d5ea
--- /dev/null
+++ b/src/renderer/painter.cpp
@@ -0,0 +1,226 @@
+#include <llmr/renderer/painter.hpp>
+#include <iostream>
+#include <assert.h>
+#include <llmr/util/mat4.h>
+#include <llmr/platform/platform.hpp>
+#include <llmr/map/transform.hpp>
+#include <llmr/map/tile.hpp>
+
+using namespace llmr;
+
+#define BUFFER_OFFSET(i) ((char *)NULL + (i))
+
+GLfloat triangle_vertices[] = {
+ 100, 100,
+ 100, 300,
+ 150, 150
+};
+
+GLfloat fill_vertices[] = {
+ 0, 0,
+ 300, 0,
+ 0, 300,
+ 300, 300
+};
+
+
+painter::painter(class platform *platform)
+ : platform(platform),
+ transform(NULL),
+ width(0),
+ height(0),
+ currentShader(NULL),
+ fillShader(NULL) {
+
+}
+
+void painter::setTransform(class transform *transform) {
+ this->transform = transform;
+}
+
+
+void painter::setup() {
+ setupShaders();
+
+
+ assert(fillShader);
+
+
+
+ glGenBuffers(1, &triangleVertexBuffer);
+ glBindBuffer(GL_ARRAY_BUFFER, triangleVertexBuffer);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(triangle_vertices), triangle_vertices, GL_STATIC_DRAW);
+
+ glGenBuffers(1, &fillVertexBuffer);
+ glBindBuffer(GL_ARRAY_BUFFER, fillVertexBuffer);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(fill_vertices), fill_vertices, GL_STATIC_DRAW);
+
+
+ // glGenVertexArraysOES(1, &vertexArray);
+ // glBindVertexArrayOES(vertexArray);
+ // glBindVertexArrayOES(0);
+}
+
+void painter::setupShaders() {
+ const GLchar *vertexSource, *fragmentSource;
+
+ vertexSource = platform->shaderSource("fill", "vertex.glsl");
+ assert((vertexSource, "Missing fill vertex shader source"));
+ fragmentSource = platform->shaderSource("fill", "fragment.glsl");
+ assert((fragmentSource, "Missing fill fragment shader source"));
+
+ fillShader = new FillShader(vertexSource, fragmentSource);
+
+ vertexSource = platform->shaderSource("line", "vertex.glsl");
+ assert((vertexSource, "Missing line vertex shader source"));
+ fragmentSource = platform->shaderSource("line", "fragment.glsl");
+ assert((fragmentSource, "Missing line fragment shader source"));
+ lineShader = new LineShader(vertexSource, fragmentSource);
+}
+
+void painter::teardown() {
+ glDeleteBuffers(1, &triangleVertexBuffer);
+ glDeleteBuffers(1, &fillVertexBuffer);
+
+ // glDeleteVertexArraysOES(1, &vertexArray);
+
+ if (fillShader) {
+ delete fillShader;
+ fillShader = NULL;
+ }
+
+ if (lineShader) {
+ delete lineShader;
+ lineShader = NULL;
+ }
+
+}
+
+void painter::resize(GLuint new_width, GLuint new_height) {
+ if (width == new_width && height == new_height) {
+ return;
+ }
+
+ fprintf(stderr, "changing viewport size to %d/%d\n", new_width, new_height);
+ width = new_width;
+ height = new_height;
+}
+
+void painter::changeMatrix() {
+ assert(transform);
+
+ // Initialize projection matrix
+ float projMatrix[16];
+ mat4_ortho(projMatrix, 0, width, height, 0, 1, 10);
+
+ float mvMatrix[16];
+ mat4_identity(mvMatrix);
+ mat4_translate(mvMatrix, mvMatrix, transform->x, transform->y, -1);
+ mat4_scale(mvMatrix, mvMatrix, transform->scale / 8, transform->scale / 8, 1);
+
+ mat4_multiply(matrix, projMatrix, mvMatrix);
+}
+
+void painter::clear() {
+ glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+}
+
+void painter::render(tile *tile) {
+ changeMatrix();
+
+ glDisable(GL_STENCIL_TEST);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ // fprintf(stderr, "render tile\n");
+ switchShader(lineShader, matrix);
+ glUniformMatrix4fv(lineShader->u_matrix, 1, GL_FALSE, matrix);
+
+ // glEnable(GL_BLEND);
+
+ // glVertexAttribPointer(fillShader->a_pos, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
+ // glVertexAttribPointer(fillShader->a_pos, 2, GL_SHORT, GL_FALSE, 0, BUFFER_OFFSET(0));
+ // glBindBuffer(GL_ARRAY_BUFFER, triangleVertexBuffer);
+ tile->lineVertex.bind();
+ glVertexAttribPointer(lineShader->a_pos, 2, GL_SHORT, GL_FALSE, 0, BUFFER_OFFSET(0));
+ glUniform4f(lineShader->u_color, 0.0f, 0.0f, 0.0f, 1.0f);
+ glLineWidth(1.0f);
+ glDrawArrays(GL_LINE_STRIP, 0, tile->lineVertex.length());
+
+ // switchShader(fillShader, matrix);
+ // glBindBuffer(GL_ARRAY_BUFFER, triangleVertexBuffer);
+ // glVertexAttribPointer(fillShader->a_pos, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
+ // glUniform4f(fillShader->u_color, 0.0f, 1.0f, 0.0f, 1.0f);
+ // glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);
+
+
+
+
+ // glBindVertexArrayOES(vertexArray);
+ // switchShader(fillShader, matrix);
+ // glUniformMatrix4fv(fillShader->u_matrix, 1, GL_FALSE, matrix);
+
+ // glEnable(GL_STENCIL_TEST);
+
+ // Draw stencil mask.
+ // glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+ // glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
+ // glStencilFunc(GL_EQUAL, 0xFF, 0xFF);
+ // glStencilMask(0xFF);
+
+ // glBindBuffer(GL_ARRAY_BUFFER, triangleVertexBuffer);
+ // glVertexAttribPointer(fillShader->a_pos, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
+ // glUniform4f(fillShader->u_color, 0.0f, 1.0f, 0.0f, 1.0f);
+ // glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);
+
+
+ // // Draw covering fill.
+ // glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ // glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ // glStencilFunc(GL_EQUAL, 0xFF, 0xFF);
+
+ // glBindBuffer(GL_ARRAY_BUFFER, fillVertexBuffer);
+ // glVertexAttribPointer(fillShader->a_pos, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
+ // glUniform4f(fillShader->u_color, 1.0f, 0.0f, 0.0f, 1.0f);
+ // glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+}
+
+void painter::viewport() {
+
+}
+
+// Switches to a different shader program.
+void painter::switchShader(Shader *shader, float matrix[16]) {
+ if (currentShader != shader) {
+ glUseProgram(shader->program);
+
+ // Disable all attributes from the existing shader that aren't used in
+ // the new shader. Note: attribute indices are *not* program specific!
+ if (currentShader) {
+ // const std::vector<GLuint> &enabled = currentShader->attributes;
+ // const std::vector<GLuint> &required = shader->attributes;
+ // TODO
+
+ // for (var i = 0; i < enabled.length; i++) {
+ // if (required.indexOf(enabled[i]) < 0) {
+ // gl.disableVertexAttribArray(enabled[i]);
+ // }
+ // }
+
+ // // Enable all attributes for the new shader.
+ // for (var j = 0; j < required.length; j++) {
+ // if (enabled.indexOf(required[j]) < 0) {
+ // gl.enableVertexAttribArray(required[j]);
+ // }
+ // }
+ } else {
+ // Enable all attributes for the new shader.
+ for (GLuint index : shader->attributes) {
+ glEnableVertexAttribArray(index);
+ }
+ }
+ }
+
+ currentShader = shader;
+}
diff --git a/src/renderer/shader-fill.cpp b/src/renderer/shader-fill.cpp
new file mode 100644
index 0000000000..763c7e2fcf
--- /dev/null
+++ b/src/renderer/shader-fill.cpp
@@ -0,0 +1,14 @@
+#include <llmr/renderer/shader-fill.hpp>
+
+using namespace llmr;
+
+FillShader::FillShader(const GLchar *vertSource, const GLchar *fragSource)
+ : Shader(vertSource, fragSource) {
+ if (!valid) return;
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+ attributes.push_back(a_pos);
+
+ u_matrix = glGetUniformLocation(program, "u_matrix");
+ u_color = glGetUniformLocation(program, "u_color");
+}
diff --git a/src/renderer/shader-line.cpp b/src/renderer/shader-line.cpp
new file mode 100644
index 0000000000..2f3381bf5b
--- /dev/null
+++ b/src/renderer/shader-line.cpp
@@ -0,0 +1,14 @@
+#include <llmr/renderer/shader-line.hpp>
+
+using namespace llmr;
+
+LineShader::LineShader(const GLchar *vertSource, const GLchar *fragSource)
+ : Shader(vertSource, fragSource) {
+ if (!valid) return;
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+ attributes.push_back(a_pos);
+
+ u_matrix = glGetUniformLocation(program, "u_matrix");
+ u_color = glGetUniformLocation(program, "u_color");
+}
diff --git a/src/renderer/shader.cpp b/src/renderer/shader.cpp
new file mode 100644
index 0000000000..d05fb2a65f
--- /dev/null
+++ b/src/renderer/shader.cpp
@@ -0,0 +1,134 @@
+ #include <llmr/renderer/shader.hpp>
+
+#include <cstdlib>
+
+using namespace llmr;
+
+Shader::Shader(const GLchar *vertSource, const GLchar *fragSource)
+ : valid(false),
+ program(0) {
+
+ GLuint vertShader;
+ if (!compileShader(&vertShader, GL_VERTEX_SHADER, vertSource)) {
+ fprintf(stderr, "Vertex shader failed to compile: %s\n", vertSource);
+ return;
+ }
+
+ GLuint fragShader;
+ if (!compileShader(&fragShader, GL_FRAGMENT_SHADER, fragSource)) {
+ fprintf(stderr, "Fragment shader failed to compile: %s\n", fragSource);
+ return;
+ }
+
+ program = glCreateProgram();
+
+ // Attach shaders
+ glAttachShader(program, vertShader);
+ glAttachShader(program, fragShader);
+
+
+ {
+ // Link program
+ GLint status;
+ glLinkProgram(program);
+
+#if defined(DEBUG)
+ GLint logLength;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
+ if (logLength > 0) {
+ GLchar *log = (GLchar *)malloc(logLength);
+ glGetProgramInfoLog(program, logLength, &logLength, log);
+ fprintf(stderr, "Program link log:\n%s", log);
+ free(log);
+ }
+#endif
+
+ glGetProgramiv(program, GL_LINK_STATUS, &status);
+ if (status == 0) {
+ fprintf(stderr, "Program failed to link\n");
+ glDeleteShader(vertShader);
+ vertShader = 0;
+ glDeleteShader(fragShader);
+ fragShader = 0;
+ glDeleteProgram(program);
+ program = 0;
+ return;
+ }
+ }
+
+ {
+ // Validate program
+ GLint status;
+ glValidateProgram(program);
+
+#if defined(DEBUG)
+ GLint logLength;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
+ if (logLength > 0) {
+ GLchar *log = (GLchar *)malloc(logLength);
+ glGetProgramInfoLog(program, logLength, &logLength, log);
+ fprintf(stderr, "Program validate log:\n%s", log);
+ free(log);
+ }
+#endif
+
+ glGetProgramiv(program, GL_VALIDATE_STATUS, &status);
+ if (status == 0) {
+ fprintf(stderr, "Program failed to validate\n");
+ glDeleteShader(vertShader);
+ vertShader = 0;
+ glDeleteShader(fragShader);
+ fragShader = 0;
+ glDeleteProgram(program);
+ program = 0;
+ }
+
+ }
+
+ // Remove the compiled shaders; they are now part of the program.
+ glDetachShader(program, vertShader);
+ glDeleteShader(vertShader);
+ glDetachShader(program, fragShader);
+ glDeleteShader(fragShader);
+
+
+ fprintf(stderr, "successfully compiled shader\n");
+ valid = true;
+}
+
+
+bool Shader::compileShader(GLuint *shader, GLenum type, const GLchar *source) {
+ GLint status;
+
+ *shader = glCreateShader(type);
+ glShaderSource(*shader, 1, &source, NULL);
+ glCompileShader(*shader);
+
+#if defined(DEBUG)
+ GLint logLength;
+ glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
+ if (logLength > 0) {
+ GLchar *log = (GLchar *)malloc(logLength);
+ glGetShaderInfoLog(*shader, logLength, &logLength, log);
+ fprintf(stderr, "Shader compile log:\n%s", log);
+ free(log);
+ }
+#endif
+
+ glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
+ if (status == 0) {
+ glDeleteShader(*shader);
+ *shader = 0;
+ return false;
+ }
+
+ return true;
+}
+
+Shader::~Shader() {
+ if (program) {
+ glDeleteProgram(program);
+ program = 0;
+ valid = false;
+ }
+}
diff --git a/src/shader/fill.fragment.glsl b/src/shader/fill.fragment.glsl
new file mode 100644
index 0000000000..8df552c171
--- /dev/null
+++ b/src/shader/fill.fragment.glsl
@@ -0,0 +1,5 @@
+uniform vec4 u_color;
+
+void main() {
+ gl_FragColor = u_color;
+}
diff --git a/src/shader/fill.vertex.glsl b/src/shader/fill.vertex.glsl
new file mode 100644
index 0000000000..866c3cd2f3
--- /dev/null
+++ b/src/shader/fill.vertex.glsl
@@ -0,0 +1,7 @@
+attribute vec2 a_pos;
+
+uniform mat4 u_matrix;
+
+void main() {
+ gl_Position = u_matrix * vec4(a_pos, 0, 1);
+}
diff --git a/src/shader/line.fragment.glsl b/src/shader/line.fragment.glsl
new file mode 100644
index 0000000000..8df552c171
--- /dev/null
+++ b/src/shader/line.fragment.glsl
@@ -0,0 +1,5 @@
+uniform vec4 u_color;
+
+void main() {
+ gl_FragColor = u_color;
+}
diff --git a/src/shader/line.vertex.glsl b/src/shader/line.vertex.glsl
new file mode 100644
index 0000000000..547576757e
--- /dev/null
+++ b/src/shader/line.vertex.glsl
@@ -0,0 +1,8 @@
+attribute vec2 a_pos;
+
+uniform mat4 u_matrix;
+
+void main() {
+ float z = step(32767.0, a_pos.x) * 2.0;
+ gl_Position = u_matrix * vec4(a_pos, z, 1);
+ }
diff --git a/src/util/mat4.c b/src/util/mat4.c
new file mode 100644
index 0000000000..e3925ab3a1
--- /dev/null
+++ b/src/util/mat4.c
@@ -0,0 +1,160 @@
+// This is an incomplete port of http://glmatrix.net/
+//
+// Copyright (c) 2013 Brandon Jones, Colin MacKenzie IV
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the
+// use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not claim
+// that you wrote the original software. If you use this software in a
+// product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+
+#include <llmr/util/mat4.h>
+
+void mat4_identity(float out[16]) {
+ out[0] = 1.0f;
+ out[1] = 0.0f;
+ out[2] = 0.0f;
+ out[3] = 0.0f;
+ out[4] = 0.0f;
+ out[5] = 1.0f;
+ out[6] = 0.0f;
+ out[7] = 0.0f;
+ out[8] = 0.0f;
+ out[9] = 0.0f;
+ out[10] = 1.0f;
+ out[11] = 0.0f;
+ out[12] = 0.0f;
+ out[13] = 0.0f;
+ out[14] = 0.0f;
+ out[15] = 1.0f;
+}
+
+void mat4_ortho(float out[16], float left, float right, float bottom, float top, float near, float far) {
+ float lr = 1.0f / (left - right),
+ bt = 1.0f / (bottom - top),
+ nf = 1.0f / (near - far);
+ out[0] = -2.0f * lr;
+ out[1] = 0.0f;
+ out[2] = 0.0f;
+ out[3] = 0.0f;
+ out[4] = 0.0f;
+ out[5] = -2.0f * bt;
+ out[6] = 0.0f;
+ out[7] = 0.0f;
+ out[8] = 0.0f;
+ out[9] = 0.0f;
+ out[10] = 2.0f * nf;
+ out[11] = 0.0f;
+ out[12] = (left + right) * lr;
+ out[13] = (top + bottom) * bt;
+ out[14] = (far + near) * nf;
+ out[15] = 1.0f;
+}
+
+void mat4_copy(float out[16], float a[16]) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[8] = a[8];
+ out[9] = a[9];
+ out[10] = a[10];
+ out[11] = a[11];
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+}
+
+void mat4_translate(float out[16], float a[16], float x, float y, float z) {
+ float a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+ a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+ a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+ a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
+
+ out[0] = a00 + a03 * x;
+ out[1] = a01 + a03 * y;
+ out[2] = a02 + a03 * z;
+ out[3] = a03;
+
+ out[4] = a10 + a13 * x;
+ out[5] = a11 + a13 * y;
+ out[6] = a12 + a13 * z;
+ out[7] = a13;
+
+ out[8] = a20 + a23 * x;
+ out[9] = a21 + a23 * y;
+ out[10] = a22 + a23 * z;
+ out[11] = a23;
+ out[12] = a30 + a33 * x;
+ out[13] = a31 + a33 * y;
+ out[14] = a32 + a33 * z;
+ out[15] = a33;
+}
+
+void mat4_scale(float out[16], float a[16], float x, float y, float z) {
+ out[0] = a[0] * x;
+ out[1] = a[1] * x;
+ out[2] = a[2] * x;
+ out[3] = a[3] * x;
+ out[4] = a[4] * y;
+ out[5] = a[5] * y;
+ out[6] = a[6] * y;
+ out[7] = a[7] * y;
+ out[8] = a[8] * z;
+ out[9] = a[9] * z;
+ out[10] = a[10] * z;
+ out[11] = a[11] * z;
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+}
+
+void mat4_multiply(float out[16], float a[16], float b[16]) {
+ float a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+ a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+ a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+ a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
+
+ // Cache only the current line of the second matrix
+ float b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
+ out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+
+ b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
+ out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+
+ b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
+ out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+
+ b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
+ out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+}