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 |
--------------------------------------------------------------------------------