0

I am making a 3D game using SFML. I want to use normals to check if the triangle need to be draw in a terrain (triangle) mesh.
Here is my code:

vec3d line1, line2, normal;
line1.x = terrain.tris[i].p[0].x - terrain.tris[i].p[1].x;
line1.y = terrain.tris[i].p[0].y - terrain.tris[i].p[1].y;
line1.z = terrain.tris[i].p[0].z - terrain.tris[i].p[1].z;

line2.x = terrain.tris[i].p[1].x - terrain.tris[i].p[2].x;
line2.y = terrain.tris[i].p[1].y - terrain.tris[i].p[2].y;
line2.z = terrain.tris[i].p[1].z - terrain.tris[i].p[2].z;

normal.x = line1.y * line2.z - line1.z * line2.y;
normal.y = line1.z * line2.x - line1.x * line2.z;
normal.z = line1.x * line2.y - line1.y * line2.x;
vec3d vCameraRay = Vector_Sub(terrain.tris[i].p[0], cam.pos);
if (Vector_DotProduct(normal, vCameraRay) < 0.0f){
    do_something();
}

Vector_DotProduct:

float Vector_DotProduct(vec3d& v1, vec3d& v2) {
    return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}

Vector_sub:

vec3d Vector_Sub(vec3d& v1, vec3d& v2) {
    return { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z };
}

And the vec3d is just a struct that contains

float x, y, z;

But whenever I run the program, I always get this problem
The triangles that should be displayed was considered "Not visable" by my program(The normal of it is wrong), but the calculation seems right to me!
The code for producing triangle grid:

for (int i = 0; i < 2; i++) {
    for (int y = 0; y < wds; y++) {
        for (int x = 0; x < wds; x++) {
            if (x + 1 < wds && y + 1 < wds) {
                vec3d point[3];
                switch (i) {
                case 0:
                    point[0] = { (float)(y + 1) * scl + p.x, height * h[y + 1][x + 1], (float)(x + 1) * scl + p.z };
                    point[1] = { (float)y * scl + p.x, height * h[y][x], (float)x * scl + p.z };
                    point[2] = { (float)y * scl + p.x, height * h[y][x + 1], (float)(x + 1) * scl + p.z };
                    break;
                case 1:
                    point[0] = { (float)(y + 1) * scl + p.x, height * h[y + 1][x + 1], (float)(x + 1) * scl + p.z };
                    point[2] = { (float)y * scl + p.x, height * h[y][x], (float)x * scl + p.z };
                    point[1] = { (float)(y + 1) * scl + p.x, height * h[y + 1][x], (float)x * scl + p.z };
                    break;
                };
                triangle out = { point[0], point[1], point[2] };
                tris.push_back(out);
            }
        }
    }
}

The wds is for the size of the grid(side length), the scl is the size of per grid, the h is the height map(two dimentions), and p is the position of the upper left corner.

My 3D point to camera point code:

float mx = p.x - pos.x;
float my = p.y - pos.y;
float mz = p.z - pos.z;
float dx = cos(rot.y) * (sin(rot.z) * my + cos(rot.z) * mx) - sin(rot.y) * mz;
float dy = sin(rot.x) * (cos(rot.y) * mz + sin(rot.y) * (sin(rot.z) * my + cos(rot.z) * mx)) + cos(rot.x) * (cos(rot.z) * my + sin(rot.z) * mx);
float dz = cos(rot.x) * (cos(rot.y) * mz + sin(rot.y) * (sin(rot.z) * my + cos(rot.z) * mx)) - sin(rot.x) * (cos(rot.z) * my + sin(rot.z) * mx);
return { dx, dy, dz };

The rot is the rotation of the camera, p is the position of the camera, and the pos is the 3D point I want to transfer to camera point.
I have been working on this problem for almost a week, but nothing seems to work.
It will be a lot of help if you guys can find the problem. Thanks in advance!

Full Code

Init.h:

#ifndef _INIT_H_
#define _INIT_H_
#define WIDTH 1200
#define HEIGHT 800
#endif

Noise.h: noice function

#pragma once
#ifndef _NOISE_H_
#define _NOISE_H_
extern int primeIndex;
extern int  numOctaves;

extern double persistence;
extern int primes[10][3];
#endif
#include <math.h>

float Noise(int i, int x, int y);

float SmoothedNoise(int i, int x, int y);

float Interpolate(float a, float b, float x);

float InterpolatedNoise(int i, float x, float y);

float noise(float x, float y);

Noise.cpp:

#include "Noise.h"
int primeIndex = 0;
int numOctaves = 7;

double persistence = 0.5;
int primes[10][3] = {
    { 995615039, 600173719, 701464987 },
    { 831731269, 162318869, 136250887 },
    { 174329291, 946737083, 245679977 },
    { 362489573, 795918041, 350777237 },
    { 457025711, 880830799, 909678923 },
    { 787070341, 177340217, 593320781 },
    { 405493717, 291031019, 391950901 },
    { 458904767, 676625681, 424452397 },
    { 531736441, 939683957, 810651871 },
    { 997169939, 842027887, 423882827 }
};

float Noise(int i, int x, int y) {
    int n = x + y * 57;
    n = (n << 13) ^ n;
    int a = primes[i][0], b = primes[i][1], c = primes[i][2];
    int t = (n * (n * n * a + b) + c) & 0x7fffffff;
    return 1.0 - (float)(t) / 1073741824.0;
}

float SmoothedNoise(int i, int x, int y) {
    float corners = (Noise(i, x - 1, y - 1) + Noise(i, x + 1, y - 1) +
        Noise(i, x - 1, y + 1) + Noise(i, x + 1, y + 1)) / 16,
        sides = (Noise(i, x - 1, y) + Noise(i, x + 1, y) + Noise(i, x, y - 1) +
            Noise(i, x, y + 1)) / 8,
        center = Noise(i, x, y) / 4;
    return corners + sides + center;
}

float Interpolate(float a, float b, float x) {
    float ft = x * 3.1415927,
        f = (1 - cos(ft)) * 0.5;
    return  a * (1 - f) + b * f;
}

float InterpolatedNoise(int i, float x, float y) {
    int integer_X = x;
    float fractional_X = x - integer_X;
    int integer_Y = y;
    float fractional_Y = y - integer_Y;

    float v1 = SmoothedNoise(i, integer_X, integer_Y),
        v2 = SmoothedNoise(i, integer_X + 1, integer_Y),
        v3 = SmoothedNoise(i, integer_X, integer_Y + 1),
        v4 = SmoothedNoise(i, integer_X + 1, integer_Y + 1),
        i1 = Interpolate(v1, v2, fractional_X),
        i2 = Interpolate(v3, v4, fractional_X);
    return Interpolate(i1, i2, fractional_Y);
}

float noise(float x, float y) {
    float total = 0,
        frequency = pow(2, numOctaves),
        amplitude = 1;
    for (int i = 0; i < numOctaves; ++i) {
        frequency /= 2;
        amplitude *= persistence;
        total += InterpolatedNoise((primeIndex + i) % 10,
            x / frequency, y / frequency) * amplitude;
    }
    return total / frequency;
}

Struct.h:

#pragma once
#ifndef _STRUCT_H_
#define _STRUCT_H_
#endif
#include <vector>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <list>
#include "Init.h"

struct vec3d {
    float x = 0;
    float y = 0;
    float z = 0;
};

struct vec2d {
    float x = 0;
    float y = 0;
};

struct triangle {
    vec3d p[3];
    int color[3] = { 255, 255, 255 };
    vec3d normal;
};

Terrain.h: terrain generation

#pragma once
#ifndef _TERRAIN_H_
#define _TERRAIN_H_
#endif

#include <vector>
#include "Struct.h"
#include "Noise.h"

#define wds 50
#define scl 20
#define width 1000
#define height 120

struct Terrain {
public:
    std::vector<triangle> tris;
    vec3d p = { -width / 2, 0.0f, -width / 2 };
    float h[wds][wds];

    void triangle_Strip();
};

Terrain.cpp:

#include "Terrain.h"
void Terrain::make_value() {
    for (int y = 0; y < wds; y++) {
        for (int x = 0; x < wds; x++) {
            int a = abs(p.z / scl + x), b = abs(p.x / scl + y);
            h[y][x] = noise(a, b) * 30;
        }
    }
}

void Terrain::triangle_Strip() {
    tris.clear();
    for (int i = 0; i < 2; i++) {
        for (int y = 0; y < wds; y++) {
            for (int x = 0; x < wds; x++) {
                if (x + 1 < wds && y + 1 < wds) {
                    vec3d point[3];
                    switch (i) {
                    case 0:
                        point[0] = { (float)(y + 1) * scl + p.x, height * h[y + 1][x + 1], (float)(x + 1) * scl + p.z };
                        point[1] = { (float)y * scl + p.x, height * h[y][x], (float)x * scl + p.z };
                        point[2] = { (float)y * scl + p.x, height * h[y][x + 1], (float)(x + 1) * scl + p.z };
                        break;
                    case 1:
                        point[0] = { (float)(y + 1) * scl + p.x, height * h[y + 1][x + 1], (float)(x + 1) * scl + p.z };
                        point[2] = { (float)y * scl + p.x, height * h[y][x], (float)x * scl + p.z };
                        point[1] = { (float)(y + 1) * scl + p.x, height * h[y + 1][x], (float)x * scl + p.z };
                        break;
                    };
                    triangle out = { point[0], point[1], point[2] };
                    tris.push_back(out);
                }
            }
        }
    }
}

Camera.h: camera class, get3dcoord which is get camera point, get2dcoord which is get screen point

#pragma once
#ifndef _CAMERA_H_
#define _CAMERA_H_
#endif


#include "Mat.h"
#include "Init.h"
#include "Struct.h"

class Cam {
public:
    vec3d pos;
    vec3d rot;
    float fov;
    float speed;

    Cam(vec3d p, vec3d r, float f, float s);
    vec3d get3dcoord(vec3d p);

    vec3d get2dcoord(vec3d p);
};

Camera.cpp:

#include "Camera.h"

Cam::Cam(vec3d p, vec3d r, float f, float s) {
    pos = p;
    rot = r;
    fov = f;
    speed = s;
}

vec3d Cam::get3dcoord(vec3d p) {
    float mx = p.x - pos.x;
    float my = p.y - pos.y;
    float mz = p.z - pos.z;
    float dx = cos(rot.y) * (sin(rot.z) * my + cos(rot.z) * mx) - sin(rot.y) * mz;
    float dy = sin(rot.x) * (cos(rot.y) * mz + sin(rot.y) * (sin(rot.z) * my + cos(rot.z) * mx)) + cos(rot.x) * (cos(rot.z) * my + sin(rot.z) * mx);
    float dz = cos(rot.x) * (cos(rot.y) * mz + sin(rot.y) * (sin(rot.z) * my + cos(rot.z) * mx)) - sin(rot.x) * (cos(rot.z) * my + sin(rot.z) * mx);
    return { dx, dy, dz };
}

vec3d Cam::get2dcoord(vec3d p) {
    float e = (float)tan(fov / 2) * (float)(WIDTH / 2);
    float x = (WIDTH / 2) + (e * p.x) / p.z;
    float y = (HEIGHT / 2) + (e * p.y) / p.z;
    return { x, y, 0 };
}

3D engine.h: main

#pragma once
#ifndef _3D_ENGINE_H_
#define _3D_ENGINE_H_
#endif
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <iostream>
#include <stdlib.h>
#include <sstream>
#include <list>
#include "Struct.h"
#include "Camera.h"
#include "Init.h"
#include "Noise.h"
#include "Terrain.h"


#define endl "\n"

void draw_triangle(vec3d p1, vec3d p2, vec3d p3, int color[]);
vec3d Vector_Sub(vec3d& v1, vec3d& v2);
float Vector_DotProduct(vec3d& v1, vec3d& v2);

3D engine.cpp:

#include "3D engine.h"

sf::RenderWindow window(sf::VideoMode(WIDTH, HEIGHT), "3D game in progress");

const sf::Vector2i windowCenter(WIDTH / 2, HEIGHT / 2);
Cam cam({ 0.0f, -40.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, 90, 2.0f);
Terrain terrain;
sf::VertexArray TriangleToDraw(sf::Triangles);

void draw_triangle(vec3d p1, vec3d p2, vec3d p3, int color[]) {
    sf::VertexArray tri(sf::Triangles, 3);

    tri[0].position = sf::Vector2f(p1.x, p1.y);
    tri[1].position = sf::Vector2f(p2.x, p2.y);
    tri[2].position = sf::Vector2f(p3.x, p3.y);
    tri[0].color = sf::Color((int)color[0], (int)color[1], (int)color[2]);
    tri[1].color = sf::Color((int)color[0], (int)color[1], (int)color[2]);
    tri[2].color = sf::Color((int)color[0], (int)color[1], (int)color[2]);

    TriangleToDraw.append(tri[0]);
    TriangleToDraw.append(tri[1]);
    TriangleToDraw.append(tri[2]);
}
vec3d Vector_Sub(vec3d& v1, vec3d& v2) {
    return { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z };
}

float Vector_DotProduct(vec3d& v1, vec3d& v2) {
    return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}

int main() {
    window.setMouseCursorVisible(false);
    sf::Mouse::setPosition(windowCenter, window);
    terrain.make_value();
    terrain.triangle_Strip();
    while (window.isOpen()) {
        TriangleToDraw.clear();
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) {
                window.close();
            }
            if ((event.type == sf::Event::MouseLeft || event.type == sf::Event::MouseMoved) && sf::Mouse::getPosition(window) != windowCenter) {
                sf::Vector2i pos = sf::Mouse::getPosition(window);
                int x_a = pos.x;
                int y_a = pos.y;
                float movex = (float)(x_a - windowCenter.x) / 500.0f;
                float movey = (float)(y_a - windowCenter.y) / 500.0f;
                cam.rot.x -= movey;
                cam.rot.y += movex;
                sf::Mouse::setPosition(windowCenter, window);
            }
        }
        float x = sin(cam.rot.y) * cam.speed; float z = cos(cam.rot.y) * cam.speed;
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) { cam.pos.x -= x; cam.pos.z -= z; /*terrain.p.x -= x; terrain.p.z -= z;*/ }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) { cam.pos.x += x; cam.pos.z += z; /*terrain.p.x += x; terrain.p.z += z;*/ }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) { cam.pos.x += z; cam.pos.z -= x; /*terrain.p.x += z; terrain.p.z -= x;*/ }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) { cam.pos.x -= z; cam.pos.z += x; /*terrain.p.x -= z; terrain.p.z += x;*/ }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Space)) cam.pos.y += cam.speed;
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::LSHIFT)) cam.pos.y -= cam.speed;
        
        window.clear(sf::Color(0, 0, 0));
        std::vector<triangle> triangles;
        for (int i = 0, len = terrain.tris.size(); i < len; i++) {
            std::vector<vec3d> projected(3);
            for (int r = 0; r < 3; r++) projected[r] = cam.get3dcoord(terrain.tris[i].p[r]);
            vec3d line1, line2, normal;
            line1.x = projected[0].x - projected[1].x;
            line1.y = projected[0].y - projected[1].y;
            line1.z = projected[0].z - projected[1].z;

            line2.x = projected[1].x - projected[2].x;
            line2.y = projected[1].y - projected[2].y;
            line2.z = projected[1].z - projected[2].z;

            normal.x = line1.y * line2.z - line1.z * line2.y;
            normal.y = line1.z * line2.x - line1.x * line2.z;
            normal.z = line1.x * line2.y - line1.y * line2.x;

            float l = sqrtf(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);
            normal.x /= l; normal.y /= l; normal.z /= l;
            vec3d vCameraRay1 = Vector_Sub(projected[0], cam.pos);
            if (Vector_DotProduct(normal, vCameraRay1) < 0.0f && projected[0].z < 0.0f && projected[1].z < 0.0f && projected[2].z < 0.0f/*avoid points behind the camera to be projected*/) {
                vec3d light = { 0.0f, 0.0f, 1.0f };
                float lNormal = sqrtf(powf(light.x, 2) + powf(light.y, 2) + powf(light.z, 2));
                light.x /= lNormal; light.y /= lNormal; light.z /= lNormal;

                float dp = std::max(0.3f, Vector_DotProduct(light, normal));
                int c = 255  * dp;

                triangles.push_back({projected[0], projected[1], projected[2], {c, c, c}});
            }
        }
        std::sort(triangles.begin(), triangles.end(), [](triangle& t1, triangle& t2)
            {
                float z1 = (t1.p[0].z + t1.p[1].z + t1.p[2].z) / 3.0f;
                float z2 = (t2.p[0].z + t2.p[1].z + t2.p[2].z) / 3.0f;
                return z1 < z2;
            });
        for (triangle tri : triangles) {
            draw_triangle(cam.get2dcoord(tri.p[0]), cam.get2dcoord(tri.p[1]), cam.get2dcoord(tri.p[2]), tri.color);
        }
        window.draw(TriangleToDraw);
        window.display();
    }
    return 0;
}

One of the triangle that had the wrong normal:

Normal: -0.08
vCameraRay: -588.2, 19.0, -662.5
Vector Dotproduct: -74.7
Triangle Point1: 19.03, -35.10, -75.69
Triangle Point2: -1.28, -27.57, -92.94
Triangle Point3: -0.96, -25.79, -71.35
Camera position: 2.20, 627.26, 0.03

One of the triangle that had the wrong Dot Product:

Normal: 0.59
vCameraRay: 468.41, 13.59, -634.75
Vector DotProduct: -55.05
Triangle Point1: 13.59, -7.29, -55.05
Triangle Point2: 19.19, 7.04, -37.72
Trianlge Point3: 0.00, 9.75, -28.36
Camera pos: 0.00, 627.45, 0.00
Eddie38
  • 31
  • 6
  • 3
    A [mcve] would be very helpful. The code provided is fragmented, and is not in a "copy, paste, compile, run" state. The issue may or may not be in the code snippets provided. Help us help you. – Eljay Jan 07 '22 at 15:11
  • first step would be to log, when `if (Vector_DotProduct(normal, vCameraRay) < 0.0f) { do_something();c}` is triggered, the `normal`, `vCameraRay` and `terrain.tris[i]` to see the values. – Jeffrey Jan 07 '22 at 15:41
  • 1
    This question is way too sprawling, *especially* considering the behemoth of code added as an answer (why?!). – gspr Jan 07 '22 at 15:55
  • @Eljay I have added it in the answer!(for the code is too long to add in the original post) – Eddie38 Jan 07 '22 at 15:56
  • There are 3 important parts to [mcve]. The **example** needs to be complete, so we can copy-and-paste it into a file, and compile it. The code needs to **reproduce** the problem. The code needs to be **minimal**. Are you sure all that code is needed to reproduce the issue? Seems gratuitously excessive. Perhaps this is a good time to learn [how to debug small programs](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/). – Eljay Jan 07 '22 at 16:39
  • @Eljay I have edit my code! If there're still too much code, plase let me know! – Eddie38 Jan 08 '22 at 01:55
  • @Jeffrey I've tried it, but I just can't fix it(can't spot where the problem is in the code) – Eddie38 Jan 08 '22 at 01:58
  • @Eddie38 ok, what are the `normal`, `vCameraRay` and `terrain.tris[i]` when the `if` triggers? – Jeffrey Jan 08 '22 at 18:21
  • @Jeffrey I have edited my question so that the value is at the bottom! If there is any more value you need, please let me know.(Both of the two triangle I post(the value at the bottom) is NOT shown when it is visible to the camera) – Eddie38 Jan 09 '22 at 09:45
  • Welcome to Stack Overflow! Just note that your question is quite enormous, and you've continued to add code in subsequent edits. Someone would have to go through quite a bit to figure out what you're doing here. you also haven't been specific about your problem - you included only a link to an external animation, saying that it's what your problem is. You'll need to edit your question and 1) reduce your question to absolute minimum necessary, and 2) explain precisely what your problem is, and what specific issue(s) you are having. – David Makogon Jan 09 '22 at 18:24
  • Please trim your code to make it easier to find your problem. Follow these guidelines to create a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Community Jan 15 '22 at 12:07
  • If you have any two sides of a triangle defined in vector form, simply obtaining the Cross-Product of the two sides will provide a vector that is normal to the plane of the triangle. A Dot product is a scalar-product and has no direction information. – David C. Rankin Jan 17 '22 at 15:18

0 Answers0