-1
$\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 pane, are visible.

Question:

How to determine which are the closest sides?

I'am 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 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.

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$
  • $\begingroup$ You might get better answers if you cut down your code to the minimum necessary to understand the problem. There's a lot here to do with rotating and "falling" that seems peripheral to the question, and nobody is going to read this much source code. What do you mean by the "three closest sides"? Do you mean the three visible faces? These aren't necessarily closer because the projection biases to +z. $\endgroup$ – Dan Hulme Aug 29 '17 at 13:36
  • $\begingroup$ Cropped unnecessary code. Yes, I mean 3 visible faces. How to determine them? $\endgroup$ – Drakonoved Aug 29 '17 at 13:52
  • $\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$ – Andrew Wilson Aug 30 '17 at 7:08
  • $\begingroup$ What is your goal? Do you want to draw the visible faces only? $\endgroup$ – Iter Ator Aug 30 '17 at 7:26
  • $\begingroup$ Yes. That is the goal. Why to draw invisible faces with no transparancy? $\endgroup$ – Drakonoved Aug 30 '17 at 7:45
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$
  • $\begingroup$ Thanks for your answer. But this solution is suitable for perspective projection. I'am using parallel projection. $\endgroup$ – Drakonoved Aug 30 '17 at 12:59
  • $\begingroup$ If I change this line var P = mat4.perspective(mat4.create(), Math.PI/2.0, width/height, 0.1, 100); in your code snippet to this line: var P = mat4.ortho(mat4.create(), -5,5,-5,5,-5,5); so I have the same result which I originally had - faces blink. $\endgroup$ – Drakonoved Aug 30 '17 at 14:26
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$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.