0
$\begingroup$

I have asked this question on ru.stackoverflow.com and have no valid answers.

Specification:

  1. Coordinate system
  2. Cube spins around its center on all three axes.
  3. Turn angle is not known previously.
  4. Parallel projection calculated by:

x`` = x;

y`` = y + z / 4; 5. Per unit of time only 3 sides, closest to the projection plane, are visible.

Question:

How to determine which are the closest sides?

I'm going this way: take pairs of sides left and right, front and back, top and bottom, then determine closest and farthest points in (Z) axis, determine the equation of a line, take the second farthest point in (Z) axis, and substitute into this equation - so determine the inclination of the side. Based on this choose which side is closer. I want to draw only the visible faces.

This code don't work:

// Получаем параллельную проекцию кубика на плоскость экрана
function getParallelProjection(arr) {

var i, j, k;

var arr_new = [];

// Попар��о работаем с гранями
// Левая и правая
arr_new[0] = getSideParallelProjection(arr[0], arr[1], arr[6]);
// Задняя и передняя
arr_new[1] = getSideParallelProjection(arr[2], arr[3], arr[6]);
// Верхняя и нижняя
arr_new[2] = getSideParallelProjection(arr[4], arr[5], arr[6]);

return arr_new;
}

// Параллельная проекция грани
function getSideParallelProjection(side1, side2, cubeCenter) {

    // Сначала выясним какая пара нам попалась
    var centralProjection1 = getPointParallelProjection(side1[4]);
    var centralProjection2 = getPointParallelProjection(side2[4]);

    var coordinate = {
        x:Math.abs(side1[4].x - side2[4].x),
        y:Math.abs(side1[4].y - side2[4].y),
        z:Math.abs(side1[4].z - side2[4].z)};

    var i;

    // Найдем дальнюю (1) и ближнюю (2) от экрана точки
    // по оси (Z) и посчитаем их проекции
    var point1 = {z:10000},
    point2 = {z:-10000},
    point3 = {z:10000};

    for (i = 0; i < 4; i++) {
        if (point1.z > side1[i].z) {
            point1 = side1[i];
        }
        if (point1.z == side1[i].z
            && point1.x > side1[i].x) {
            point1 = side1[i];
        }
        if (point1.z == side1[i].z
            && point1.x == side1[i].x
            && point1.y < side1[i].y) {
            point1 = side1[i];
        }
        if (point2.z < side1[i].z) {
            point2 = side1[i];
        }
        if (point2.z == side1[i].z
            && point2.x < side1[i].x) {
            point2 = side1[i];
        }
        if (point2.z == side1[i].z
            && point2.x == side1[i].x
            && point2.y > side1[i].y) {
            point2 = side1[i];
        }
    }

    for (i = 0; i < 4; i++) {
        if (point3.z > side1[i].z
            && point1 != side1[i]) {
            point3 = side1[i];
        }
        if (point3.z == side1[i].z
            && point3.x > side1[i].x
            && point1 != side1[i]) {
            point3 = side1[i];
        }
        if (point3.z == side1[i].z
            && point3.x == side1[i].x
            && point3.y < side1[i].y
            && point1 != side1[i]) {
            point3 = side1[i];
        }
    }

    var direction = (point3.x - point1.x) / (point2.x - point1.x)
                  - (point3.y - point1.y) / (point2.y - point1.y);
    var projection = [];

    // (X) Правая и левая грани
    if (coordinate.x > coordinate.y && coordinate.x > coordinate.z) {

        point1 = getPointParallelProjection(point1);
        point2 = getPointParallelProjection(point2);
        point3 = getPointParallelProjection(point3);

        var direction = (point3.x - point1.x) / (point2.x - point1.x)
                      - (point3.y - point1.y) / (point2.y - point1.y);

        var selected;

        if (point1.x >= point2.x) {
            if (centralProjection1.x >= centralProjection2.x) {
                selected = side1;
            } else {
                selected = side2;
            }
        } else {
            if (centralProjection1.x <= centralProjection2.x) {
                selected = side1;
            } else {
                selected = side2;
            }
        }

        if (selected.length < 6) {
            for (i = 0; i < 4; i++) {
                projection[i] = getPointParallelProjection(selected[i]);
            }
            projection[4] = 'rgb(200,200,0)';
        }
    }

    // (Z) Передняя и задняя грани
    else if (coordinate.z > coordinate.x && coordinate.z > coordinate.y) {

        var selected;

        if (point1.x >= point2.x && direction < 0) {
            if (centralProjection1.x >= centralProjection2.x) {
                selected = side1;
            } else {
                selected = side2;
            }
        } else {
            if (centralProjection1.x <= centralProjection2.x) {
                selected = side1;
            } else {
                selected = side2;
            }
        }

        if (selected.length < 6) {
            for (i = 0; i < 4; i++) {
                projection[i] = getPointParallelProjection(selected[i]);
            }
            projection[4] = 'rgb(200,0,0)';
        }
    }

    // (Y) Верхняя и нижняя грани
    else {

        var selected;

        if (point1.y <= point2.y) {
            if (centralProjection1.y <= centralProjection2.y) {
                selected = side1;
            } else {
                selected = side2;
            }
        } else {
            if (centralProjection1.y >= centralProjection2.y) {
                selected = side1;
            } else {
                selected = side2;
            }
        }

        if (selected.length < 6) {
            for (i = 0; i < 4; i++) {
                projection[i] = getPointParallelProjection(selected[i]);
            }
            projection[4] = 'rgb(0,0,0)';
        }
    }

    return projection;
}

// Параллельная проекция точки
function getPointParallelProjection(point) {
return {
    x:point.x,
    y:point.y + point.z / 4};
}
<canvas id="animation" width="300" height="300">
    <p>Your browser does not support Canvas</p>
</canvas>
$\endgroup$
2
  • $\begingroup$ The three visible faces aren't necessarily closer because the projection biases to +z. $\endgroup$
    – Dan Hulme
    Commented Aug 29, 2017 at 13:36
  • $\begingroup$ The visible faces are the faces that have cos(theta) < 0 where theta is the angle between the faces normal and cameras direction. The dot product. You can use the cross product to get the faces normal. That doesn't tell you anything about how close the faces are to the cameras plane. You can use the GJK algorithm to find the minimum distance between the plane and cube. $\endgroup$ Commented Aug 30, 2017 at 7:08

2 Answers 2

0
$\begingroup$

According to your comment, your goal is to avoid drawing the invisible faces. There is no reason to find the closest 3 sides of the cube and draw them, because it is possible that only one or tho sides are visible.

Finding the exact distance from a polygon in 3D space is computationally expensive. Instead of doing it, I would like to suggest a better method:

Use face normals. A normal is a vector, which is perpendicular to a given surface at a given point.

If you calculate the normal vector (and the points of the polygon) in world space, you can decide using a simple dot product, if the polygon is facing towards the camera:

bool is_visisble = 0 < dot(
    transformMat4(face.normal, world_mat_it), // normal in world space
    // vec from face midpoint to the camera position
    sub(
        eye, // camera position
        transformMat4(face.midpoint, world_mat) // Face midpoint in world space
    )
)

Here you can find a renderer written is javascript, which uses this method: https://jsfiddle.net/deadmanswitch/sw22s4fs/

If there are more complex objects in the scene, then you should order the faces by the distance between the midpoint and the camera, and draw the farthest ones first, to avoid rendering farther objects over closer ones.

$\endgroup$
0
0
$\begingroup$

OK. This is the answer:

function getParallelProjection(arr) {
    var i, j, k;

    var projected = [];
    var projection;

    for (i = 0; i < 6; i++) {
        projection = [];
        if (arr[i].length < 6) {
            for (j = 0; j < 4; j++) {
                projection[j] = getPointParallelProjection(arr[i][j]);
            }
        }
        projected[i] = projection;
    }

    return projected.filter(isClockwise);
}

function isClockwise(point_array) {
    if (point_array.length < 3) return false;
    var p0 = point_array[0];
    var p1 = point_array[1];
    var p2 = point_array[2];
    return (p1.x-p0.x)*(p2.y-p0.y) - (p2.x-p0.x)*(p1.y-p0.y) > 0;
}

All the magic that we check how the faces are drawn. After the vector product we check the order in wich projected vertices goes. If it is counterclockwise - so this set of vertices is discarded.

$\endgroup$