I wrote a program in which some points are expressed, expressed in three-dimensional coordinates, and which must be drawn in a two-dimensional canvas. To do this, I use perspective projection, uniform coordinates and similar triangles. However, my program does not work, and I really do not know why.
I followed two tutorials. I really understood the geometric definitions and properties that I read. However, my implementation does not work ... I will write links to both of these courses little by little to make your reading more comfortable :).
Overview: geometric reminders
Perspective projection is performed after this workflow (see these 2 courses - I wrote the appropriate links (HTML bindings later in this post):
Definition of drawing points expressed in accordance with the world coordinate system; Definition of a projection matrix, which is a transformation matrix that "converts" a point expressed in accordance with the world coordinate system to a point expressed in accordance with the camera coordinate system (NB: this matrix can also be understood as a camera)
The product of these points with this matrix (as defined in the corresponding part below): the product of these points leads to the transformation of these points into a camera coordinate system. Note that the points and matrix are expressed in 4D (the concept of homogeneous coordinates).
( ) , ( 4D-): 3D ( , )
: , ( ).
-,
, , . , -, .
.

Scastie ()
: X11 Scastie, , , .
https://scastie.scala-lang.org/2LQ1wSMBTWqQQ7hql35sOg
, ? , .
Ref.: self
val world_cube_points : Seq[Seq[Double]] = Seq(
Seq(0, 40, 0, 1),
Seq(0, 40, 10, 1),
Seq(0, 0, 0, 1),
Seq(0, 0, 10, 1),
Seq(20, 40, 0, 1),
Seq(20, 40, 10, 1),
Seq(20, 0, 0, 1),
Seq(20, 0, 10, 1)
)
()
Ref.: https://github.com/ssloy/tinyrenderer/wiki/Lesson-4:-Perspective-projection#time-to-work-in-full-3d
val matrix_world_to_camera : Matrix = new Matrix(Seq(
Seq(1, 0, 0, 0),
Seq(0, 1, 0, 0),
Seq(0, 0, 1, 0),
Seq(0, 0, -1, 1)
))
-, , : .
Ref.: https://github.com/ssloy/tinyrenderer/wiki/Lesson-4:-Perspective-projection#homogeneous-coordinates
class Matrix(val content : Seq[Seq[Double]]) {
def product(point : Seq[Double]) : Seq[Double] = {
(0 to 3).map(
i => content(i).zip(point).map(couple2 => couple2._1 * couple2._2).sum
)
}
}
,
Ref. 1/2: . " " https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point/mathematics-computing-2d-coordinates-of-3d-points
Ref. 2/2: https://github.com/ssloy/tinyrenderer/wiki/Lesson-4:-Perspective-projection#time-to-work-in-full-3d
NB: , ( : ).
class Projector {
def drawPointsOnCanvas(points : Seq[Seq[Double]]) : Seq[Seq[Double]] = {
points.map(point => {
point.map(coordinate => {
coordinate / -point(3)
}).dropRight(1)
})
}
}
, .
Ref.: . " " https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point/mathematics-computing-2d-coordinates-of-3d-points
import java.awt.Graphics
import javax.swing.JFrame
class Canvas(val drawn_points : Seq[Seq[Double]]) extends JFrame {
val CANVAS_WIDTH = 60
val CANVAS_HEIGHT = 60
val IMAGE_WIDTH = 55
val IMAGE_HEIGHT = 55
def display = {
setTitle("Perlin")
setSize(CANVAS_WIDTH, CANVAS_HEIGHT)
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
setVisible(true)
}
override def paint(graphics : Graphics): Unit = {
super.paint(graphics)
drawn_points.foreach(point => {
if(!(Math.abs(point.head) <= CANVAS_WIDTH / 2 || Math.abs(point(1)) <= CANVAS_HEIGHT / 2)) {
println("WARNING : the point (" + point.head + " ; " + point(1) + ") can't be drawn in this canvas.")
} else {
val normalized_drawn_point = Seq((point.head + (CANVAS_WIDTH / 2)) / CANVAS_WIDTH, (point(1) + (CANVAS_HEIGHT / 2)) / CANVAS_HEIGHT)
graphics.drawRect(normalized_drawn_point.head.toInt * IMAGE_WIDTH, (1 - normalized_drawn_point(1).toInt) * IMAGE_HEIGHT, 1, 1)
}
})
}
}
...
object Main {
def main(args : Array[String]) : Unit = {
val projector = new Projector()
val world_cube_points : Seq[Seq[Double]] = Seq(
Seq(0, 40, 0, 1),
Seq(0, 40, 10, 1),
Seq(0, 0, 0, 1),
Seq(0, 0, 10, 1),
Seq(20, 40, 0, 1),
Seq(20, 40, 10, 1),
Seq(20, 0, 0, 1),
Seq(20, 0, 10, 1)
)
val matrix_world_to_camera : Matrix = new Matrix(Seq(
Seq(1, 0, 0, 0),
Seq(0, 1, 0, 0),
Seq(0, 0, 1, 0),
Seq(0, 0, -1, 1)
))
val points_to_draw_on_canvas = projector.drawPointsOnCanvas(world_cube_points.map(point => {
matrix_world_to_camera.product(point)
}))
new Canvas(points_to_draw_on_canvas).display
}
}
? , , . , . , , () ...