├── projection_logo.png ├── .gitignore ├── README.md ├── Projection ├── keywords.txt ├── examples │ ├── HelloCube │ │ └── HelloCube.ino │ ├── HelloCubeFaster │ │ └── HelloCubeFaster.ino │ ├── GaussianPlot │ │ └── GaussianPlot.ino │ └── ModelFromOBJFile │ │ └── ModelFromOBJFile.ino ├── Projection.h ├── ObjConverter.html └── Projection.cpp └── LICENSE /projection_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menehune23/projection/HEAD/projection_logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # projection 2 | ![Projection logo](projection_logo.png) 3 | 4 | A simple 3D-2D projection library for small screens on Arduino 5 | 6 | ## getting started 7 | To get started: 8 | * [Download this project](https://github.com/menehune23/projection/archive/master.zip). Once downloaded, you can unzip it and import the "Projection" subfolder into your Arduino environment. It contains well-documented library source files as well as example code for MicroView. 9 | * Or, [clone this project](github-mac://openRepo/https://github.com/menehune23/projection). 10 | 11 | ## working with OBJ files 12 | In the "Projection" folder is a simple converter tool, _ObjConverter.html_, that will convert a simple OBJ file to C++ code that you can copy into your source file. It should run in most modern browsers (no internet required). See the example "ModelFromOBJFile" to see how to work with the generated code. 13 | 14 | For a free 3D modeling program that can export to the OBJ format, see Blender. 15 | -------------------------------------------------------------------------------- /Projection/keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For Projection 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | Camera KEYWORD1 10 | Transform KEYWORD1 11 | point2 KEYWORD1 12 | point3 KEYWORD1 13 | line2 KEYWORD1 14 | line3 KEYWORD1 15 | PROJ_MODE KEYWORD1 16 | 17 | 18 | ####################################### 19 | # Methods and Functions (KEYWORD2) 20 | ####################################### 21 | 22 | project KEYWORD2 23 | 24 | 25 | ####################################### 26 | # Constants (LITERAL1) 27 | ####################################### 28 | 29 | PROJ_PERSPECTIVE LITERAL1 30 | PROJ_ORTHO LITERAL1 31 | RAD_TO_DEG LITERAL1 32 | DEG_TO_RAD LITERAL1 33 | 34 | 35 | ####################################### 36 | # Member Variables (LITERAL2) 37 | ####################################### 38 | 39 | projMode LITERAL2 40 | transform LITERAL2 41 | focalDistPx LITERAL2 42 | nearDist LITERAL2 43 | farDist LITERAL2 44 | orthoViewWidth LITERAL2 45 | screenWidth LITERAL2 46 | screenHeight LITERAL2 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 menehune23 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Projection/examples/HelloCube/HelloCube.ino: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////// 2 | // HelloCube 3 | // by Andrew Meyer, Mar 2015 4 | // 5 | // Demonstrates drawing a simple cube on a 6 | // MicroView ( http://sfe.io/p12923 ). 7 | /////////////////////////////////////////////// 8 | 9 | #include 10 | #include 11 | 12 | #define MIN_SCALE 0.1 13 | #define MAX_SCALE 1.5 14 | #define UPDATE_DELAY 0 15 | 16 | // Cube properties to keep track of 17 | float roll, pitch, yaw; 18 | float scale = MIN_SCALE; 19 | float scaleStep = 0.015; 20 | Transform cubeTrans; 21 | 22 | // Cube vertices 23 | point3 cubeVerts[] = { 24 | { 1, 1, 1 }, 25 | { 1, -1, 1 }, 26 | { -1, -1, 1 }, 27 | { -1, 1, 1 }, 28 | { 1, 1, -1 }, 29 | { 1, -1, -1 }, 30 | { -1, -1, -1 }, 31 | { -1, 1, -1 } 32 | }; 33 | 34 | // Cube lines 35 | line3 cubeLines[] = { 36 | { cubeVerts[0], cubeVerts[1] }, 37 | { cubeVerts[1], cubeVerts[2] }, 38 | { cubeVerts[2], cubeVerts[3] }, 39 | { cubeVerts[3], cubeVerts[0] }, 40 | { cubeVerts[4], cubeVerts[5] }, 41 | { cubeVerts[5], cubeVerts[6] }, 42 | { cubeVerts[6], cubeVerts[7] }, 43 | { cubeVerts[7], cubeVerts[4] }, 44 | { cubeVerts[0], cubeVerts[4] }, 45 | { cubeVerts[1], cubeVerts[5] }, 46 | { cubeVerts[2], cubeVerts[6] }, 47 | { cubeVerts[3], cubeVerts[7] }, 48 | }; 49 | 50 | // Create a camera 51 | Camera cam(LCDWIDTH, LCDHEIGHT); 52 | 53 | void setup() 54 | { 55 | // Start MicroView 56 | uView.begin(); 57 | 58 | // Camera starts out at origin, looking along world +Y axis (its own +Z axis). 59 | // Set camera back a few units so cube will be in view. 60 | cam.transform.y = -5; 61 | 62 | // Uncomment these two lines for orthographic projection 63 | //cam.projMode = PROJ_ORTHO; 64 | //cam.orthoViewWidth = 3.0; 65 | } 66 | 67 | void loop() 68 | { 69 | uView.clear(PAGE); 70 | 71 | updateCube(); 72 | drawCube(); 73 | 74 | uView.display(); 75 | delay(UPDATE_DELAY); 76 | } 77 | 78 | void updateCube() 79 | { 80 | // Update scale 81 | 82 | scale += scaleStep; 83 | 84 | if (scale < MIN_SCALE) 85 | { 86 | scale = MIN_SCALE; 87 | scaleStep *= -1; 88 | } 89 | else if (scale > MAX_SCALE) 90 | { 91 | scale = MAX_SCALE; 92 | scaleStep *= -1; 93 | } 94 | 95 | // Update cube transform 96 | cubeTrans = Transform(roll += 2, pitch, yaw += 2, scale, scale, scale, 0, 0, 0); 97 | } 98 | 99 | void drawCube() 100 | { 101 | for (byte i = 0; i < 12; i++) 102 | { 103 | // Project line to screen after applying cube transform 104 | line2 line = cam.project(cubeTrans * cubeLines[i]); 105 | 106 | // Draw if not clipped completely 107 | if (!isnan(line.p0.x)) 108 | { 109 | uView.line(line.p0.x, line.p0.y, line.p1.x, line.p1.y); 110 | } 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /Projection/examples/HelloCubeFaster/HelloCubeFaster.ino: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////// 2 | // HelloCubeFaster 3 | // by Andrew Meyer, Mar 2015 4 | // 5 | // Demonstrates drawing a simple cube on a 6 | // Microview ( http://sfe.io/p12923 ). 7 | // 8 | // Removes redundancy by applying cube's 9 | // transform to vertices before forming lines. 10 | /////////////////////////////////////////////// 11 | 12 | #include 13 | #include 14 | 15 | #define MIN_SCALE 0.1 16 | #define MAX_SCALE 1.5 17 | #define UPDATE_DELAY 0 18 | 19 | // Cube properties to keep track of 20 | float roll, pitch, yaw; 21 | float scale = MIN_SCALE; 22 | float scaleStep = 0.015; 23 | Transform cubeTrans; 24 | 25 | // Cube vertices 26 | point3 cubeVerts[] = { 27 | { 1, 1, 1 }, 28 | { 1, -1, 1 }, 29 | { -1, -1, 1 }, 30 | { -1, 1, 1 }, 31 | { 1, 1, -1 }, 32 | { 1, -1, -1 }, 33 | { -1, -1, -1 }, 34 | { -1, 1, -1 } 35 | }; 36 | 37 | // Each pair of indices denotes endpoints 38 | // from cubeVerts above 39 | byte cubeLineIndices[] = { 40 | 0, 1, // Top square 41 | 1, 2, 42 | 2, 3, 43 | 3, 0, 44 | 45 | 4, 5, // Bottom square 46 | 5, 6, 47 | 6, 7, 48 | 7, 4, 49 | 50 | 0, 4, // Sides 51 | 1, 5, 52 | 2, 6, 53 | 3, 7 54 | }; 55 | 56 | // Create a camera 57 | Camera cam(LCDWIDTH, LCDHEIGHT); 58 | 59 | void setup() 60 | { 61 | // Start MicroView 62 | uView.begin(); 63 | 64 | // Camera starts out at origin, looking along world +Y axis (its own +Z axis). 65 | // Set camera back a few units so cube will be in view. 66 | cam.transform.y = -5; 67 | 68 | // Uncomment these two lines for orthographic projection 69 | //cam.projMode = PROJ_ORTHO; 70 | //cam.orthoViewWidth = 3.0; 71 | } 72 | 73 | void loop() 74 | { 75 | uView.clear(PAGE); 76 | 77 | updateCube(); 78 | drawCube(); 79 | 80 | uView.display(); 81 | delay(UPDATE_DELAY); 82 | } 83 | 84 | void updateCube() 85 | { 86 | // Update scale 87 | 88 | scale += scaleStep; 89 | 90 | if (scale < MIN_SCALE) 91 | { 92 | scale = MIN_SCALE; 93 | scaleStep *= -1; 94 | } 95 | else if (scale > MAX_SCALE) 96 | { 97 | scale = MAX_SCALE; 98 | scaleStep *= -1; 99 | } 100 | 101 | // Update cube transform 102 | cubeTrans = Transform(roll += 2, pitch, yaw += 2, scale, scale, scale, 0, 0, 0); 103 | } 104 | 105 | void drawCube() 106 | { 107 | point3 verts[8]; 108 | 109 | // Apply cube transform to vertices 110 | for (byte i = 0; i < 8; i++) 111 | { 112 | verts[i] = cubeTrans * cubeVerts[i]; 113 | } 114 | 115 | // Draw projected cube lines 116 | for (byte i = 0; i < 24; i += 2) 117 | { 118 | // Get line to project 119 | line3 cubeLine = { 120 | verts[cubeLineIndices[i]], 121 | verts[cubeLineIndices[i + 1]] 122 | }; 123 | 124 | // Project line to screen 125 | line2 line = cam.project(cubeLine); 126 | 127 | // Draw if not clipped completely 128 | if (!isnan(line.p0.x)) 129 | { 130 | uView.line(line.p0.x, line.p0.y, line.p1.x, line.p1.y); 131 | } 132 | } 133 | } 134 | 135 | -------------------------------------------------------------------------------- /Projection/examples/GaussianPlot/GaussianPlot.ino: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////// 2 | // GaussianPlot 3 | // by Andrew Meyer, May 2015 4 | // 5 | // Demonstrates drawing a 2D Gaussian function 6 | // on a MicroView ( http://sfe.io/p12923 ). 7 | /////////////////////////////////////////////// 8 | 9 | #include 10 | #include 11 | 12 | #define UPDATE_DELAY 0 13 | 14 | // Transform for plot 15 | Transform plotTrans; 16 | float angle = 0; 17 | 18 | // Array of plot points 19 | point3 **points; 20 | unsigned int numPtsX, numPtsY; 21 | 22 | // Create a camera 23 | Camera cam(LCDWIDTH, LCDHEIGHT); 24 | 25 | void setup() 26 | { 27 | // Start MicroView 28 | uView.begin(); 29 | 30 | // Set camera back and up a few units and angle downward. 31 | // 32 | // Note that camera's projection axis is its own Z+ axis, 33 | // so an x-angle of -90 deg aligns it with the world Y+ 34 | // axis, and less than -90 will angle the camera's 35 | // projection axis further downward. 36 | cam.transform = Transform(-90 - 10, 0, 0, 1, 1, 1, 0, -7, 2); 37 | 38 | // Uncomment these two lines for orthographic projection 39 | //cam.projMode = PROJ_ORTHO; 40 | //cam.orthoViewWidth = 3.0; 41 | 42 | // Compute the points in the Gaussian plot 43 | computePoints(); 44 | } 45 | 46 | void loop() 47 | { 48 | uView.clear(PAGE); 49 | 50 | // Update plot transform and draw points 51 | plotTrans = Transform(0, 0, angle += 2, 1, 1, 3, 0, 0, 0); 52 | drawPoints(); 53 | 54 | uView.display(); 55 | delay(UPDATE_DELAY); 56 | } 57 | 58 | void computePoints() 59 | { 60 | float ux = 0, uy = 0; // Means 61 | float sx = 0.5, sy = 0.7; // Standard deviations 62 | 63 | // Plotting options 64 | float minX = -3, maxX = 3; 65 | float minY = -3, maxY = 3; 66 | float xStep = 0.75, yStep = 0.75; 67 | 68 | numPtsX = (maxX - minX) / xStep; 69 | numPtsY = (maxY - minY) / yStep; 70 | 71 | points = new point3*[numPtsX]; 72 | 73 | if (points != NULL) 74 | { 75 | for (unsigned int n = 0; n < numPtsX; n++) 76 | { 77 | // Try to allocate next row of points 78 | points[n] = new point3[numPtsY]; 79 | 80 | if (points[n] != NULL) 81 | { 82 | for (unsigned int m = 0; m < numPtsY; m++) 83 | { 84 | // Compute Gaussian point 85 | float x = (n * xStep) + minX; 86 | float y = (m * yStep) + minY; 87 | float z = gauss(x, y, ux, uy, sx, sy); 88 | 89 | points[n][m] = { x, y, z }; 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | float gauss(float x, float y, float ux, float uy, float sx, float sy) 97 | { 98 | return exp(-( 99 | ((x - ux) * (x - ux) / (2 * sx * sx)) + 100 | ((y - uy) * (y - uy) / (2 * sy * sy)))) 101 | / (2 * PI * sx * sy); 102 | } 103 | 104 | void drawPoints() 105 | { 106 | for (unsigned int n = 0; n < numPtsX; n++) 107 | { 108 | // Check for low memory 109 | if (points == NULL || points[n] == NULL) 110 | { 111 | uView.clear(PAGE); 112 | uView.setCursor(0, 0); 113 | uView.print("Out of\nmemory!\n\nIncrease\nX, Y step\nsize"); 114 | } 115 | else 116 | { 117 | for (unsigned int m = 0; m < numPtsY; m++) 118 | { 119 | // Transform and project 3D point 120 | point2 pt = cam.project(plotTrans * points[n][m]); 121 | 122 | // Draw point if not clipped 123 | if (!isnan(pt.x)) 124 | { 125 | uView.pixel(pt.x, pt.y); 126 | } 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Projection/Projection.h: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // Projection library for Arduino 3 | // Created March 2105 by Andrew Meyer 4 | // 5 | // NOTES: 6 | // - Coordinate system is a right-handed, Z-up system 7 | // - 3D rotations follow ZYX Euler angle convention (yaw-pitch-roll) 8 | // - Angles are assumed to be in degrees 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | #ifndef PROJECTION_H 12 | #define PROJECTION_H 13 | 14 | #include 15 | 16 | // Macro to convert an angle from degrees to radians 17 | #define DEG_TO_RAD(deg) deg * 0.017453 18 | 19 | // Macro to convert an angle from radians to degrees 20 | #define RAD_TO_DEG(rad) rad * 57.29578 21 | 22 | // Camera projection mode 23 | enum PROJ_MODE { 24 | PROJ_PERSPECTIVE, // Perspective projection 25 | PROJ_ORTHO // Orthographic projection 26 | }; 27 | 28 | // A simple 2D point structure 29 | struct point2 30 | { 31 | float x, y; 32 | }; 33 | 34 | // A simple 3D point structure 35 | struct point3 36 | { 37 | float x, y, z; 38 | }; 39 | 40 | // A simple 2D line structure 41 | struct line2 42 | { 43 | point2 p0, p1; 44 | }; 45 | 46 | // A simple 3D line structure 47 | struct line3 48 | { 49 | point3 p0, p1; 50 | }; 51 | 52 | // A 4x4 homogeneous transform of block-matrix form: 53 | // 54 | // H = [ M B ] 55 | // [ 0 1 ] 56 | // 57 | // where M is a 3x3 transformation matrix, usually representing 58 | // rotation and scaling, and B is a 3x1 translation vector 59 | class Transform 60 | { 61 | public: 62 | // Constructs a default Transform equivalent to the 4x4 63 | // identity matrix 64 | Transform(); 65 | 66 | // Constructs a Transfrom from given matrix and 67 | // translation values 68 | Transform(float mat00, float mat01, float mat02, 69 | float mat10, float mat11, float mat12, 70 | float mat20, float mat21, float mat22, 71 | float posX, float posY, float posZ); 72 | 73 | // Constructs a Transform with a specified rotation, 74 | // scale, and translation 75 | Transform(float angleX, float angleY, float angleZ, 76 | float scaleX, float scaleY, float scaleZ, 77 | float posX, float posY, float posZ); 78 | 79 | // Computes the inverse of a Transform 80 | Transform inverse() const; 81 | 82 | // Multiplies two Transforms 83 | Transform operator *(const Transform &rhs) const; 84 | 85 | // Transforms a 3D point 86 | point3 operator *(const point3 &point) const; 87 | 88 | // Transforms a 3D line 89 | line3 operator *(const line3 &line) const; 90 | 91 | // Elements of Transform's matrix 92 | float m00, m01, m02; 93 | float m10, m11, m12; 94 | float m20, m21, m22; 95 | 96 | // Translation components of Transform 97 | float x, y, z; 98 | }; 99 | 100 | // A projection camera for projecting points and lines 101 | // from 3D to 2D 102 | class Camera 103 | { 104 | public: 105 | // Constructs a default camera at world origin, with its 106 | // +Z axis looking along the world +Y axis 107 | Camera(); 108 | 109 | // Constructs a camera given display dimensions, at world origin, 110 | // with its +Z axis looking along the world +Y axis 111 | Camera(int displayWidth, int displayHeight); 112 | 113 | // Projects a 3D point onto 2D screen, with optional clipping to 114 | // screen bounds and rounding to nearest pixel 115 | point2 project(const point3 &point, bool clip = true, bool round = true) const; 116 | 117 | // Projects a 3D line onto 2D screen, with optional clipping to 118 | // screen bounds and rounding to nearest pixel 119 | line2 project(const line3 &line, bool clip = true, bool round = true) const; 120 | 121 | PROJ_MODE projMode; // Projection mode 122 | Transform transform; // Transform of camera relative to world 123 | float focalDistPx; // Focal distance, in pixels 124 | float nearDist; // Near clipping distance 125 | float farDist; // Far clipping distance 126 | float orthoViewWidth; // Viewing width for orthographic mode 127 | int screenWidth; // Width of display 128 | int screenHeight; // Height of display 129 | 130 | private: 131 | static constexpr const float MIN_NEAR_DIST = 0.1; 132 | static float getLineT(float bound, float start, float end); 133 | 134 | point2 getImageCoords(const point3 &camPoint) const; 135 | byte outcode(const point3 &point, float near, float far) const; 136 | byte outcode(const point2 &point) const; 137 | void clipLine(line3 &line) const; 138 | void clipLine(line2 &line) const; 139 | }; 140 | 141 | #endif // PROJECTION_H -------------------------------------------------------------------------------- /Projection/examples/ModelFromOBJFile/ModelFromOBJFile.ino: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////// 2 | // ModelFromOBJFile 3 | // by Andrew Meyer, April 2015 4 | // 5 | // Demonstrates drawing a simple OBJ model on a 6 | // Microview ( http://sfe.io/p12923 ). 7 | // 8 | // Removes redundancy by applying model's 9 | // transform to vertices before forming lines. 10 | /////////////////////////////////////////////// 11 | 12 | #include 13 | #include 14 | 15 | #define UPDATE_DELAY 0 16 | 17 | // Model properties to keep track of 18 | float angle = -90; 19 | Transform modelTrans; 20 | 21 | // vertices and lineIndices code was generated 22 | // from the ObjConverter tool. 23 | // Run ObjConverter.html in a browser -- no 24 | // internet required! 25 | 26 | // Model vertices 27 | const byte NUM_VERTICES = 28; 28 | point3 vertices[] = { 29 | { -1.036572, 1.036572, 0.303021 }, 30 | { 0.466702, -0.357184, 0.258869 }, 31 | { -1.036572, -0.466702, 0.303021 }, 32 | { 0.533960, -1.182932, -0.246472 }, 33 | { -0.585337, -1.182932, -0.246472 }, 34 | { 0.533960, -1.182932, -0.028611 }, 35 | { -0.585337, -1.182932, -0.028611 }, 36 | { 1.033760, -1.851447, -0.434040 }, 37 | { 0.605000, -1.851447, -0.434040 }, 38 | { 1.296128, 0.574044, -0.421914 }, 39 | { 1.296128, 0.574044, -0.188807 }, 40 | { -1.036572, -0.466702, -0.303021 }, 41 | { 0.466702, -0.357184, -0.258869 }, 42 | { 0.466702, 0.927055, -0.258869 }, 43 | { -1.036572, 1.036572, -0.303021 }, 44 | { 0.466702, 0.927055, 0.258869 }, 45 | { 0.533960, 1.752803, -0.246472 }, 46 | { -0.585337, 1.752803, -0.246472 }, 47 | { 0.533960, 1.752803, -0.028611 }, 48 | { -0.585337, 1.752803, -0.028611 }, 49 | { 1.033760, 2.421317, -0.434040 }, 50 | { 0.605000, 2.421317, -0.434040 }, 51 | { 1.296128, -0.004174, -0.421914 }, 52 | { 1.296128, -0.004174, -0.188807 }, 53 | { -1.296128, 0.926559, 0.205740 }, 54 | { -1.296128, -0.356688, 0.205740 }, 55 | { -1.296128, -0.356688, -0.205740 }, 56 | { -1.296128, 0.926559, -0.205740 } 57 | }; 58 | 59 | // Model line indices 60 | // Each pair of indices defines a line 61 | const byte NUM_INDICES = 108; 62 | byte lineIndices[] = { 63 | 0, 15, 64 | 15, 13, 65 | 13, 14, 66 | 14, 0, 67 | 4, 3, 68 | 3, 7, 69 | 7, 8, 70 | 8, 4, 71 | 1, 2, 72 | 2, 6, 73 | 6, 5, 74 | 5, 1, 75 | 1, 15, 76 | 0, 2, 77 | 12, 13, 78 | 13, 9, 79 | 9, 22, 80 | 22, 12, 81 | 11, 12, 82 | 12, 3, 83 | 4, 11, 84 | 14, 17, 85 | 17, 16, 86 | 16, 13, 87 | 2, 11, 88 | 4, 6, 89 | 11, 14, 90 | 17, 21, 91 | 21, 20, 92 | 20, 16, 93 | 16, 18, 94 | 18, 15, 95 | 12, 1, 96 | 5, 3, 97 | 10, 23, 98 | 23, 22, 99 | 9, 10, 100 | 23, 1, 101 | 0, 19, 102 | 19, 17, 103 | 10, 15, 104 | 18, 19, 105 | 14, 27, 106 | 27, 24, 107 | 24, 0, 108 | 26, 25, 109 | 25, 24, 110 | 27, 26, 111 | 2, 25, 112 | 26, 11, 113 | 8, 6, 114 | 7, 5, 115 | 19, 21, 116 | 18, 20 117 | }; 118 | 119 | /////////////////////////////////////////// 120 | 121 | // Create a camera 122 | Camera cam(LCDWIDTH, LCDHEIGHT); 123 | 124 | void setup() 125 | { 126 | // Start MicroView 127 | uView.begin(); 128 | 129 | // Camera starts out at origin, looking along world +Y axis (its own +Z axis). 130 | // Set camera back a few units so model will be in view. 131 | cam.transform.y = -5; 132 | 133 | // Uncomment these two lines for orthographic projection 134 | //cam.projMode = PROJ_ORTHO; 135 | //cam.orthoViewWidth = 3.0; 136 | } 137 | 138 | void loop() 139 | { 140 | uView.clear(PAGE); 141 | 142 | updateModel(); 143 | drawModel(); 144 | 145 | uView.display(); 146 | delay(UPDATE_DELAY); 147 | } 148 | 149 | void updateModel() 150 | { 151 | // Update model transform 152 | modelTrans = Transform(0, 5, angle += 5, 1, 1, 1, 0, 0, 0); 153 | } 154 | 155 | void drawModel() 156 | { 157 | point3 transVerts[NUM_VERTICES]; 158 | 159 | // Apply model transform to vertices 160 | for (byte i = 0; i < NUM_VERTICES; i++) 161 | { 162 | transVerts[i] = modelTrans * vertices[i]; 163 | } 164 | 165 | // Draw projected model lines 166 | for (byte i = 0; i < NUM_INDICES; i += 2) 167 | { 168 | // Get line to project 169 | line3 modelLine = { 170 | transVerts[lineIndices[i]], 171 | transVerts[lineIndices[i + 1]] 172 | }; 173 | 174 | // Project line to screen 175 | line2 line = cam.project(modelLine); 176 | 177 | // Draw if not clipped completely 178 | if (!isnan(line.p0.x)) 179 | { 180 | uView.line(line.p0.x, line.p0.y, line.p1.x, line.p1.y); 181 | } 182 | } 183 | } 184 | 185 | -------------------------------------------------------------------------------- /Projection/ObjConverter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 54 | 55 | 56 | 57 |

OBJ to C++ Converter

58 | Upload an OBJ file to convert it to C++ code for use with the Arduino Projection library 59 |
60 | Requires a modern browser with HTML 5 compatibility 61 |
62 |
63 | 64 |
65 |
66 | 67 |
68 |
69 |
70 | 71 |
72 | 85 |
86 |
87 |
88 | 89 | 90 |
91 | 92 |
93 | Click text area to select code 94 |
95 | 96 |
97 | 98 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /Projection/Projection.cpp: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // Projection library for Arduino 3 | // Created March 2105 by Andrew Meyer 4 | // 5 | // NOTES: 6 | // - Coordinate system is a right-handed, Z-up system 7 | // - 3D rotations follow ZYX Euler angle convention (yaw-pitch-roll) 8 | // - Angles are assumed to be in degrees 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | #include 12 | 13 | Transform::Transform() 14 | : m00(1), m01(0), m02(0), 15 | m10(0), m11(1), m12(0), 16 | m20(0), m21(0), m22(1), 17 | x(0), y(0), z(0) 18 | { } 19 | 20 | Transform::Transform(float mat00, float mat01, float mat02, 21 | float mat10, float mat11, float mat12, 22 | float mat20, float mat21, float mat22, 23 | float posX, float posY, float posZ) 24 | : m00(mat00), m01(mat01), m02(mat02), 25 | m10(mat10), m11(mat11), m12(mat12), 26 | m20(mat20), m21(mat21), m22(mat22), 27 | x(posX), y(posY), z(posZ) 28 | { } 29 | 30 | Transform::Transform(float angleX, float angleY, float angleZ, 31 | float scaleX, float scaleY, float scaleZ, 32 | float posX, float posY, float posZ) 33 | : x(posX), y(posY), z(posZ) 34 | { 35 | float sx = sin(DEG_TO_RAD(angleX)); 36 | float sy = sin(DEG_TO_RAD(angleY)); 37 | float sz = sin(DEG_TO_RAD(angleZ)); 38 | float cx = cos(DEG_TO_RAD(angleX)); 39 | float cy = cos(DEG_TO_RAD(angleY)); 40 | float cz = cos(DEG_TO_RAD(angleZ)); 41 | 42 | m00 = scaleX * cy * cz; 43 | m01 = scaleY * (sx * sy * cz - cx * sz); 44 | m02 = scaleZ * (sx * sz + cx * sy * cz); 45 | 46 | m10 = scaleX * cy * sz; 47 | m11 = scaleY * (sx * sy * sz + cx * cz); 48 | m12 = scaleZ * (cx * sy * sz - sx * cz); 49 | 50 | m20 = scaleX * -sy; 51 | m21 = scaleY * sx * cy; 52 | m22 = scaleZ * cx * cy; 53 | } 54 | 55 | Transform Transform::inverse() const 56 | { 57 | // Compute N = inv(M) 58 | float n00 = (m11 * m22) - (m12 * m21); 59 | float n01 = (m02 * m21) - (m01 * m22); 60 | float n02 = (m01 * m12) - (m02 * m11); 61 | float n10 = (m12 * m20) - (m10 * m22); 62 | float n11 = (m00 * m22) - (m02 * m20); 63 | float n12 = (m02 * m10) - (m00 * m12); 64 | float n20 = (m10 * m21) - (m11 * m20); 65 | float n21 = (m01 * m20) - (m00 * m21); 66 | float n22 = (m00 * m11) - (m01 * m10); 67 | 68 | float det = (m02 * n20) + (m01 * n10) + (m00 * n00); 69 | 70 | n00 /= det; n01 /= det; n02 /= det; 71 | n10 /= det; n11 /= det; n12 /= det; 72 | n20 /= det; n21 /= det; n22 /= det; 73 | 74 | // Compute R = -N * B 75 | float r0 = -((n00 * x) + (n01 * y) + (n02 * z)); 76 | float r1 = -((n10 * x) + (n11 * y) + (n12 * z)); 77 | float r2 = -((n20 * x) + (n21 * y) + (n22 * z)); 78 | 79 | // Return inverse as [ N R ] 80 | // [ 0 1 ] 81 | return Transform(n00, n01, n02, 82 | n10, n11, n12, 83 | n20, n21, n22, 84 | r0, r1, r2); 85 | } 86 | 87 | Transform Transform::operator *(const Transform &rhs) const 88 | { 89 | return Transform(m00 * rhs.m00 + m01 * rhs.m10 + m02 * rhs.m20, 90 | m00 * rhs.m01 + m01 * rhs.m11 + m02 * rhs.m21, 91 | m00 * rhs.m02 + m01 * rhs.m12 + m02 * rhs.m22, 92 | 93 | m10 * rhs.m00 + m11 * rhs.m10 + m12 * rhs.m20, 94 | m10 * rhs.m01 + m11 * rhs.m11 + m12 * rhs.m21, 95 | m10 * rhs.m02 + m11 * rhs.m12 + m12 * rhs.m22, 96 | 97 | m20 * rhs.m00 + m21 * rhs.m10 + m22 * rhs.m20, 98 | m20 * rhs.m01 + m21 * rhs.m11 + m22 * rhs.m21, 99 | m20 * rhs.m02 + m21 * rhs.m12 + m22 * rhs.m22, 100 | 101 | m00 * rhs.x + m01 * rhs.y + m02 * rhs.z + x, 102 | m10 * rhs.x + m11 * rhs.y + m12 * rhs.z + y, 103 | m20 * rhs.x + m21 * rhs.y + m22 * rhs.z + z); 104 | } 105 | 106 | point3 Transform::operator *(const point3 &rhs) const 107 | { 108 | return { m00 * rhs.x + m01 * rhs.y + m02 * rhs.z + x, 109 | m10 * rhs.x + m11 * rhs.y + m12 * rhs.z + y, 110 | m20 * rhs.x + m21 * rhs.y + m22 * rhs.z + z }; 111 | } 112 | 113 | line3 Transform::operator *(const line3 &line) const 114 | { 115 | return { (*this) * line.p0, (*this) * line.p1 }; 116 | } 117 | 118 | Camera::Camera() 119 | : transform(Transform(-90, 0, 0, 1, 1, 1, 0, 0, 0)) 120 | { } 121 | 122 | Camera::Camera(int displayWidth, int displayHeight) 123 | : screenWidth(displayWidth), screenHeight(displayHeight), projMode(PROJ_PERSPECTIVE), 124 | focalDistPx(75), nearDist(MIN_NEAR_DIST), farDist(1000.0), 125 | transform(Transform(-90, 0, 0, 1, 1, 1, 0, 0, 0)) 126 | { } 127 | 128 | point2 Camera::project(const point3 &point, bool clip /* = true */, bool round /* = true */) const 129 | { 130 | point2 ret; 131 | point3 camPoint = transform.inverse() * point; // Transform point to camera space 132 | 133 | if (clip) 134 | { 135 | // Clip to near/far 3D planes 136 | 137 | float near = max(nearDist, MIN_NEAR_DIST); 138 | float far = max(farDist, near); 139 | 140 | if (camPoint.z < near || camPoint.z > far) 141 | { 142 | return { NAN, NAN }; 143 | } 144 | } 145 | 146 | // Project to screen 147 | ret = getImageCoords(camPoint); 148 | 149 | if (clip) 150 | { 151 | // Clip to 2D viewport 152 | 153 | if (ret.x < 0 || ret.x > (screenWidth - 1) || 154 | ret.y < 0 || ret.y > (screenHeight - 1)) 155 | { 156 | return { NAN, NAN }; 157 | } 158 | } 159 | 160 | if (round) 161 | { 162 | ret = { round(ret.x), round(ret.y) }; 163 | } 164 | 165 | return ret; 166 | } 167 | 168 | line2 Camera::project(const line3 &line, bool clip /* = true */, bool round /* = true */) const 169 | { 170 | line2 ret; 171 | Transform worldToCam = transform.inverse(); 172 | line3 camLine = { worldToCam * line.p0, worldToCam * line.p1 }; // Transform line to camera space 173 | 174 | if (clip) 175 | { 176 | // Clip line to near/far 3D planes 177 | 178 | clipLine(camLine); 179 | 180 | if (isnan(camLine.p0.x)) 181 | { 182 | return { { NAN, NAN }, { NAN, NAN } }; 183 | } 184 | } 185 | 186 | // Project to screen 187 | ret = { getImageCoords(camLine.p0), getImageCoords(camLine.p1) }; 188 | 189 | if (clip) 190 | { 191 | // Clip line to 2D viewport 192 | 193 | clipLine(ret); 194 | 195 | if (isnan(ret.p0.x)) 196 | { 197 | return { { NAN, NAN }, { NAN, NAN } }; 198 | } 199 | } 200 | 201 | if (round) 202 | { 203 | ret = { { round(ret.p0.x), round(ret.p0.y) }, 204 | { round(ret.p1.x), round(ret.p1.y) } }; 205 | } 206 | 207 | return ret; 208 | } 209 | 210 | float Camera::getLineT(float bound, float start, float end) 211 | { 212 | return (bound - start) / (end - start); 213 | } 214 | 215 | point2 Camera::getImageCoords(const point3 &camPoint) const 216 | { 217 | if (projMode == PROJ_PERSPECTIVE) 218 | { 219 | return { (focalDistPx * camPoint.x) / camPoint.z + screenWidth / 2, 220 | (focalDistPx * camPoint.y) / camPoint.z + screenHeight / 2 }; 221 | } 222 | else 223 | { 224 | return { (screenWidth * camPoint.x) / orthoViewWidth + screenWidth / 2, 225 | (screenWidth * camPoint.y) / orthoViewWidth + screenHeight / 2 }; 226 | } 227 | } 228 | 229 | byte Camera::outcode(const point3 &point, float near, float far) const 230 | { 231 | byte code = 0; 232 | 233 | bitWrite(code, 0, point.z < near); 234 | bitWrite(code, 1, point.z > far); 235 | 236 | return code; 237 | } 238 | 239 | byte Camera::outcode(const point2 &point) const 240 | { 241 | byte code = 0; 242 | 243 | bitWrite(code, 0, point.x < 0); 244 | bitWrite(code, 1, point.x > (screenWidth - 1)); 245 | bitWrite(code, 2, point.y < 0); 246 | bitWrite(code, 3, point.y > (screenHeight - 1)); 247 | 248 | return code; 249 | } 250 | 251 | void Camera::clipLine(line3 &line) const 252 | { 253 | bool done = false; 254 | float near = max(nearDist, MIN_NEAR_DIST); 255 | float far = max(farDist, near); 256 | 257 | do 258 | { 259 | byte code0 = outcode(line.p0, near, far); 260 | byte code1 = outcode(line.p1, near, far); 261 | 262 | if ((code0 | code1) == 0) 263 | { 264 | // Trivially accept 265 | done = true; 266 | } 267 | else if ((code0 & code1) != 0) 268 | { 269 | // Trivially reject 270 | line = { { NAN, NAN, NAN }, { NAN, NAN, NAN } }; 271 | done = true; 272 | } 273 | else 274 | { 275 | // Clip one end 276 | 277 | byte code; 278 | point3 *point; 279 | 280 | if (code0) 281 | { 282 | code = code0; 283 | point = &line.p0; 284 | } 285 | else 286 | { 287 | code = code1; 288 | point = &line.p1; 289 | } 290 | 291 | // Compute point of intersection with clipping plane 292 | 293 | float bound = bitRead(code, 0) ? near : far; 294 | float t = getLineT(bound, line.p0.z, line.p1.z); 295 | 296 | point->x = line.p0.x + t * (line.p1.x - line.p0.x); 297 | point->y = line.p0.y + t * (line.p1.y - line.p0.y); 298 | point->z = bound; 299 | } 300 | } while (!done); 301 | } 302 | 303 | void Camera::clipLine(line2 &line) const 304 | { 305 | bool done = false; 306 | 307 | do 308 | { 309 | byte code0 = outcode(line.p0); 310 | byte code1 = outcode(line.p1); 311 | 312 | if ((code0 | code1) == 0) 313 | { 314 | // Trivially accept 315 | done = true; 316 | } 317 | else if ((code0 & code1) != 0) 318 | { 319 | // Trivially reject 320 | line = { { NAN, NAN }, { NAN, NAN } }; 321 | done = true; 322 | } 323 | else 324 | { 325 | // Clip one end 326 | 327 | byte code; 328 | point2 *point; 329 | 330 | if (code0) 331 | { 332 | code = code0; 333 | point = &line.p0; 334 | } 335 | else 336 | { 337 | code = code1; 338 | point = &line.p1; 339 | } 340 | 341 | // Compute point of intersection with clipping edge 342 | 343 | if (bitRead(code, 0)) // x < 0 344 | { 345 | float t = getLineT(0, line.p0.x, line.p1.x); 346 | 347 | point->x = 0; 348 | point->y = line.p0.y + t * (line.p1.y - line.p0.y); 349 | } 350 | else if (bitRead(code, 1)) // x > (screenWidth - 1) 351 | { 352 | float t = getLineT(screenWidth - 1, line.p0.x, line.p1.x); 353 | 354 | point->x = screenWidth - 1; 355 | point->y = line.p0.y + t * (line.p1.y - line.p0.y); 356 | } 357 | else if (bitRead(code, 2)) // y < 0 358 | { 359 | float t = getLineT(0, line.p0.y, line.p1.y); 360 | 361 | point->x = line.p0.x + t * (line.p1.x - line.p0.x); 362 | point->y = 0; 363 | } 364 | else // y > (screenHeight - 1) 365 | { 366 | float t = getLineT(screenHeight - 1, line.p0.y, line.p1.y); 367 | 368 | point->x = line.p0.x + t * (line.p1.x - line.p0.x); 369 | point->y = screenHeight - 1; 370 | } 371 | } 372 | } while (!done); 373 | } 374 | --------------------------------------------------------------------------------