I'm trying to draw a 3d image that displays a ripple:
function myFunc(x, y) {
let zRipple =
Math.pow(2, -0.005 * (Math.abs(x) + Math.abs(y))) *
Math.cos(((x * x + y * y) * 2 * pi) / 180 / width) *
height;
return zRipple;
}
width and height here are constants that define a drawing area and are equal to 200 in my tests.
My approach is based on what I recall from an article that I read 30 years ago and trying to recall now.
The idea is to:
split the whole drawing board into the 10-pixel grid
for each 'cell' of the grid, draw a line to the nearest cell along the Y- and the X-axis' (step=10, ds=0.0
for (let x3 = width; x3 >= - width; x3 -= step) { for (let y3 = -height; y3 <= height; y3 += step) { for (let s = 0; s < step; s += ds) { let x = x3 + s; if (x < width) { let z3 = myFunc(x, y3); drawPixel3d(x, y3, z3); } } for (let s = 0; s < step; s += ds) { let y = y3 + s; if (y < height) { let z3 = myFunc(x3, y); drawPixel3d(x3, y, z3); } } } } }
Here is how I convert 3d coordinates to 2d:
function drawPixel3d(x3, y3, z3) {
let x2 = (x3 + y3) * Math.sin((60 * pi) / 180);
let y2 = z3 - ((x3 - y3) * Math.sin((30 * pi) / 180)) / 4;
drawPixel(x2, y2);
}
As you see from the image below, I get a decent graphic, but there is a problem: I draw ALL dots, not only those, that are VISIBLE.
Question: How do I check if any pixel needs to be displayed or not?
From what I can recall in that article, we should follow the approach:
- start drawing from the front part of the scene (which I believe I do, the closest to the viewer or screen if dot with coordinates (width, -height)
- for each pixel column - remember the 'Z' coordinate and only draw the new pixel if its Z-coordinate is bigger than the last recorded one
To achieve this I've modified my 'drawPixel3d' method:
function drawPixel3d(x3, y3, z3) {
let x2 = (x3 + y3) * Math.sin((60 * pi) / 180);
let y2 = z3 - ((x3 - y3) * Math.sin((30 * pi) / 180)) / 4;
let n = Math.round(x2);
let visible = false;
if (zs[n] === undefined) {
zs[n] = z3;
visible = true;
} else {
if (z3 > zs[n]) {
visible = z3 > zs[n];
zs[n] = z3;
}
}
if (visible) drawPixel(x2, y2);
}
But the result is not expected:
What do I do wrong? Or an alternative question: how to draw a simple 3d graphic?
Thanks!
P.S. The last piece of the program (that illustrates inversion of Y-coordinate during actual drawing):
function drawPixel(x: number, y: number) {
ctx.fillRect(cX + x, cY - y, 1, 1); // TS-way to draw pixel on canvas is to draw a rectangle
} // cX and cY are coordinates of the center of the drawing canvas
P.P.S. I have an idea of the algorithmic solution, so added an 'algorithm' tag: maybe someone from this community can help?




I got the idea of the solution: start drawing from the point nearest to the observer but for every combination of x2 and y2 coordinates draw the pixel only once and only when it is visible (never draw points behind others)... The only problem is that I don't draw EVERY point of the surface, I only draw a surface grid with 10 points step. As a result, part of the surface will be visible in 'between' the grid cells.
Another idea is to calculate distance from every drawing point of the surface to the observer and make sure to draw only that point that is visible of the surface that is CLOSEST to the observer... but how?