This project contains the following files (right-click files you'd like to download):
perspproj.cppgeometry.hxtree.obj//[header]
// This program renders a wireframe image of a 3D object whose description is stored
// in the program itself. The result is stored to a SVG file. To draw an image
// of each triangle making up that object, we project the vertices of each triangle
// onto the screen using perspective projection (effectively transforming the vertices
// world coordinates to 2D pixel coordinates). Triangles are stored in the SVG
// file by connecting their respective vertices to each other with lines.
//[/header]
//[compile]
// Download the perspproj.cpp and geometry.h files to the same folder.
// Open a shell/terminal, and run the following command where the files are saved:
//
// c++ perspproj.cpp -o perspproj -std=c++11
//
// Run with: ./perspproj. Open the file ./proj.svg in any Internet browser to see
// the result.
//[/compile]
//[ignore]
// Copyright (C) 2012 www.scratchapixel.com
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//[/ignore]
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <cmath>
#include "geometry.h"
//[comment]
// List of vertices making up the object
//[/comment]
const Vec3f verts[146] = {
{ 0, 39.034, 0}, { 0.76212, 36.843, 0},
{ 3, 36.604, 0}, { 1, 35.604, 0},
{ 2.0162, 33.382, 0}, { 0, 34.541, 0},
{ -2.0162, 33.382, 0}, { -1, 35.604, 0},
{ -3, 36.604, 0}, { -0.76212, 36.843, 0},
{-0.040181, 34.31, 0}, { 3.2778, 30.464, 0},
{-0.040181, 30.464, 0}, {-0.028749, 30.464, 0},
{ 3.2778, 30.464, 0}, { 1.2722, 29.197, 0},
{ 1.2722, 29.197, 0}, {-0.028703, 29.197, 0},
{ 1.2722, 29.197, 0}, { 5.2778, 25.398, 0},
{ -0.02865, 25.398, 0}, { 1.2722, 29.197, 0},
{ 5.2778, 25.398, 0}, { 3.3322, 24.099, 0},
{-0.028683, 24.099, 0}, { 7.1957, 20.299, 0},
{ -0.02861, 20.299, 0}, { 5.2778, 19.065, 0},
{-0.028663, 18.984, 0}, { 9.2778, 15.265, 0},
{-0.028571, 15.185, 0}, { 9.2778, 15.265, 0},
{ 7.3772, 13.999, 0}, {-0.028625, 13.901, 0},
{ 9.2778, 15.265, 0}, { 12.278, 8.9323, 0},
{-0.028771, 8.9742, 0}, { 12.278, 8.9323, 0},
{ 10.278, 7.6657, 0}, {-0.028592, 7.6552, 0},
{ 15.278, 2.5994, 0}, {-0.028775, 2.6077, 0},
{ 15.278, 2.5994, 0}, { 13.278, 1.3329, 0},
{-0.028727, 1.2617, 0}, { 18.278, -3.7334, 0},
{ 18.278, -3.7334, 0}, { 2.2722, -1.2003, 0},
{-0.028727, -1.3098, 0}, { 4.2722, -5, 0},
{ 4.2722, -5, 0}, {-0.028727, -5, 0},
{ -3.3582, 30.464, 0}, { -3.3582, 30.464, 0},
{ -1.3526, 29.197, 0}, { -1.3526, 29.197, 0},
{ -1.3526, 29.197, 0}, { -5.3582, 25.398, 0},
{ -1.3526, 29.197, 0}, { -5.3582, 25.398, 0},
{ -3.4126, 24.099, 0}, { -7.276, 20.299, 0},
{ -5.3582, 19.065, 0}, { -9.3582, 15.265, 0},
{ -9.3582, 15.265, 0}, { -7.4575, 13.999, 0},
{ -9.3582, 15.265, 0}, { -12.358, 8.9323, 0},
{ -12.358, 8.9323, 0}, { -10.358, 7.6657, 0},
{ -15.358, 2.5994, 0}, { -15.358, 2.5994, 0},
{ -13.358, 1.3329, 0}, { -18.358, -3.7334, 0},
{ -18.358, -3.7334, 0}, { -2.3526, -1.2003, 0},
{ -4.3526, -5, 0}, { -4.3526, -5, 0},
{ 0, 34.31, 0.040181}, { 0, 30.464, -3.2778},
{ 0, 30.464, 0.040181}, { 0, 30.464, 0.028749},
{ 0, 30.464, -3.2778}, { 0, 29.197, -1.2722},
{ 0, 29.197, -1.2722}, { 0, 29.197, 0.028703},
{ 0, 29.197, -1.2722}, { 0, 25.398, -5.2778},
{ 0, 25.398, 0.02865}, { 0, 29.197, -1.2722},
{ 0, 25.398, -5.2778}, { 0, 24.099, -3.3322},
{ 0, 24.099, 0.028683}, { 0, 20.299, -7.1957},
{ 0, 20.299, 0.02861}, { 0, 19.065, -5.2778},
{ 0, 18.984, 0.028663}, { 0, 15.265, -9.2778},
{ 0, 15.185, 0.028571}, { 0, 15.265, -9.2778},
{ 0, 13.999, -7.3772}, { 0, 13.901, 0.028625},
{ 0, 15.265, -9.2778}, { 0, 8.9323, -12.278},
{ 0, 8.9742, 0.028771}, { 0, 8.9323, -12.278},
{ 0, 7.6657, -10.278}, { 0, 7.6552, 0.028592},
{ 0, 2.5994, -15.278}, { 0, 2.6077, 0.028775},
{ 0, 2.5994, -15.278}, { 0, 1.3329, -13.278},
{ 0, 1.2617, 0.028727}, { 0, -3.7334, -18.278},
{ 0, -3.7334, -18.278}, { 0, -1.2003, -2.2722},
{ 0, -1.3098, 0.028727}, { 0, -5, -4.2722},
{ 0, -5, -4.2722}, { 0, -5, 0.028727},
{ 0, 30.464, 3.3582}, { 0, 30.464, 3.3582},
{ 0, 29.197, 1.3526}, { 0, 29.197, 1.3526},
{ 0, 29.197, 1.3526}, { 0, 25.398, 5.3582},
{ 0, 29.197, 1.3526}, { 0, 25.398, 5.3582},
{ 0, 24.099, 3.4126}, { 0, 20.299, 7.276},
{ 0, 19.065, 5.3582}, { 0, 15.265, 9.3582},
{ 0, 15.265, 9.3582}, { 0, 13.999, 7.4575},
{ 0, 15.265, 9.3582}, { 0, 8.9323, 12.358},
{ 0, 8.9323, 12.358}, { 0, 7.6657, 10.358},
{ 0, 2.5994, 15.358}, { 0, 2.5994, 15.358},
{ 0, 1.3329, 13.358}, { 0, -3.7334, 18.358},
{ 0, -3.7334, 18.358}, { 0, -1.2003, 2.3526},
{ 0, -5, 4.3526}, { 0, -5, 4.3526}
};
const uint32_t numTris = 128;
//[comment]
// Triangle index array. A triangle has 3 vertices. Each successive group of 3
// integers in this array represent the positions of the vertices in the vertex
// array making up one triangle of that object. For example, the first 3 integers
// from this array, 8/7/9 represent the positions of the vertices making up the
// the first triangle. You can access these vertices with the following code:
//
// verts[8]; /* first vertex */
//
// verts[7]; /* second vertex */
//
// verts[9]; /* third vertex */
//
// 6/5/5 are the positions of the vertices in the vertex array making up the second
// triangle, and so on.
// To find the indices of the n-th triangle, use the following code:
//
// tris[n * 3]; /* index of the first vertex in the verts array */
//
// tris[n * 3 + 1]; /* index of the second vertexin the verts array */
//
// tris[n * 3 + 2]; /* index of the third vertex in the verts array */
//[/comment]
const uint32_t tris[numTris * 3] = {
8, 7, 9, 6, 5, 7, 4, 3, 5, 2, 1, 3, 0, 9, 1,
5, 3, 7, 7, 3, 9, 9, 3, 1, 10, 12, 11, 13, 15, 14,
15, 13, 16, 13, 17, 16, 18, 20, 19, 17, 20, 21, 20, 23, 22,
20, 24, 23, 23, 26, 25, 24, 26, 23, 26, 27, 25, 26, 28, 27,
27, 30, 29, 28, 30, 27, 30, 32, 31, 30, 33, 32, 27, 30, 34,
32, 36, 35, 33, 36, 32, 36, 38, 37, 36, 39, 38, 38, 41, 40,
39, 41, 38, 41, 43, 42, 41, 44, 43, 44, 45, 43, 44, 47, 46,
44, 48, 47, 48, 49, 47, 48, 51, 50, 10, 52, 12, 13, 53, 54,
55, 17, 54, 13, 54, 17, 56, 57, 20, 17, 58, 20, 20, 59, 60,
20, 60, 24, 60, 61, 26, 24, 60, 26, 26, 61, 62, 26, 62, 28,
62, 63, 30, 28, 62, 30, 30, 64, 65, 30, 65, 33, 62, 66, 30,
65, 67, 36, 33, 65, 36, 36, 68, 69, 36, 69, 39, 69, 70, 41,
39, 69, 41, 41, 71, 72, 41, 72, 44, 44, 72, 73, 44, 74, 75,
44, 75, 48, 48, 75, 76, 48, 77, 51, 78, 80, 79, 81, 83, 82,
83, 81, 84, 81, 85, 84, 86, 88, 87, 85, 88, 89, 88, 91, 90,
88, 92, 91, 91, 94, 93, 92, 94, 91, 94, 95, 93, 94, 96, 95,
95, 98, 97, 96, 98, 95, 98, 100, 99, 98, 101, 100, 95, 98, 102,
100, 104, 103, 101, 104, 100, 104, 106, 105, 104, 107, 106, 106, 109, 108,
107, 109, 106, 109, 111, 110, 109, 112, 111, 112, 113, 111, 112, 115, 114,
112, 116, 115, 116, 117, 115, 116, 119, 118, 78, 120, 80, 81, 121, 122,
123, 85, 122, 81, 122, 85, 124, 125, 88, 85, 126, 88, 88, 127, 128,
88, 128, 92, 128, 129, 94, 92, 128, 94, 94, 129, 130, 94, 130, 96,
130, 131, 98, 96, 130, 98, 98, 132, 133, 98, 133, 101, 130, 134, 98,
133, 135, 104, 101, 133, 104, 104, 136, 137, 104, 137, 107, 137, 138, 109,
107, 137, 109, 109, 139, 140, 109, 140, 112, 112, 140, 141, 112, 142, 143,
112, 143, 116, 116, 143, 144, 116, 145, 119
};
//[comment]
// Compute the 2D pixel coordinates of a point defined in world space. This function
// requires the point original world coordinates of course, the world-to-camera
// matrix (which you can get from computing the inverse of the camera-to-world matrix,
// the matrix transforming the camera), the canvas dimension and the image width and
// height in pixels.
//
// Note that we don't check in this version of the function if the point is visible
// or not. If the absolute value of of any of the screen coordinates are greater
// that their respective maximum value (the canvas width for the x-coordinate,
// and the canvas height for the y-coordinate) then the point is not visible.
// When a SVG file is displayed to the screen, the application displaying the content
// of the file clips points and lines which are not visible. Thus we can store lines or point
// in the file whether they are visible or not. The final clipping will be done when the
// image is displayed to the screen.
//[/comment]
void computePixelCoordinates(
const Vec3f pWorld,
Vec2i &pRaster,
const Matrix44f &worldToCamera,
const float &canvasWidth,
const float &canvasHeight,
const uint32_t &imageWidth,
const uint32_t &imageHeight
)
{
Vec3f pCamera;
worldToCamera.multVecMatrix(pWorld, pCamera);
Vec2f pScreen;
pScreen.x = pCamera.x / -pCamera.z;
pScreen.y = pCamera.y / -pCamera.z;
Vec2f pNDC;
pNDC.x = (pScreen.x + canvasWidth * 0.5) / canvasWidth;
pNDC.y = (pScreen.y + canvasHeight * 0.5) / canvasHeight;
pRaster.x = (int)(pNDC.x * imageWidth);
pRaster.y = (int)((1 - pNDC.y) * imageHeight);
}
int main(int argc, char **argv)
{
std::ofstream ofs;
//[comment]
// We will export the result to a SVG file.
//[/comment]
ofs.open("./proj.svg");
ofs << "<svg version=\"1.1\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns=\"http://www.w3.org/2000/svg\" height=\"512\" width=\"512\">" << std::endl;
//[comment]
// We exported the camera matrix from Maya. We need to compute its inverse,
// which is the matrix used in the computePixelCoordinates() function.
//[/comment]
Matrix44f cameraToWorld(0.871214, 0, -0.490904, 0, -0.192902, 0.919559, -0.342346, 0, 0.451415, 0.392953, 0.801132, 0, 14.777467, 29.361945, 27.993464, 1);
Matrix44f worldToCamera = cameraToWorld.inverse();
std::cerr << worldToCamera << std::endl;
float canvasWidth = 2, canvasHeight = 2;
uint32_t imageWidth = 512, imageHeight = 512;
//[comment]
// Loop over all the triangles making up the object, get the 3 vertices
// making the current triangle, convert the 3 vertices wolrd coordinates
// to 2D pixel coordinates using the computePixelCoordinates function.
// Then finally store the result as 3 lines connecting the projected
// vertices of the current triangle to each other.
//[/comment]
for (uint32_t i = 0; i < numTris; ++i) {
const Vec3f &v0World = verts[tris[i * 3]];
const Vec3f &v1World = verts[tris[i * 3 + 1]];
const Vec3f &v2World = verts[tris[i * 3 + 2]];
Vec2i v0Raster, v1Raster, v2Raster;
computePixelCoordinates(v0World, v0Raster, worldToCamera, canvasWidth, canvasHeight, imageWidth, imageHeight);
computePixelCoordinates(v1World, v1Raster, worldToCamera, canvasWidth, canvasHeight, imageWidth, imageHeight);
computePixelCoordinates(v2World, v2Raster, worldToCamera, canvasWidth, canvasHeight, imageWidth, imageHeight);
std::cerr << v0Raster << ", " << v1Raster << ", " << v2Raster << std::endl;
ofs << "<line x1=\"" << v0Raster.x << "\" y1=\"" << v0Raster.y << "\" x2=\"" << v1Raster.x << "\" y2=\"" << v1Raster.y << "\" style=\"stroke:rgb(0,0,0);stroke-width:1\" />\n";
ofs << "<line x1=\"" << v1Raster.x << "\" y1=\"" << v1Raster.y << "\" x2=\"" << v2Raster.x << "\" y2=\"" << v2Raster.y << "\" style=\"stroke:rgb(0,0,0);stroke-width:1\" />\n";
ofs << "<line x1=\"" << v2Raster.x << "\" y1=\"" << v2Raster.y << "\" x2=\"" << v0Raster.x << "\" y2=\"" << v0Raster.y << "\" style=\"stroke:rgb(0,0,0);stroke-width:1\" />\n";
}
ofs << "</svg>\n";
ofs.close();
return 0;
}