P = ( x , y , z )
\ begin {aligned} X & = (1,0,0) \\ Y & = (0,1,0) \\ Z & = (0,0,1) \ end {aligned}
\ {O; X, Y, Z \}
P=O+aX+bY+cZ
\ begin {aligned} (2,3,4) & = (2,0,0) + (0,3,0) + (0,0,4) \\ & = (0,0,0) + (2.0.0) + (0.3.0) + (0.0.4) \\ = (0.0.0) + 2 (1.0.0) + 3 (0.1, 0) + 4 (0,0,1) \\ & = O + 2X + 3Y + 4Z \\ end {aligned}
X=Y timesZ
(a,b,c) times(d,e,f)=(bf−ce,cd−af,ae−bd)
Point
class and the Vector
class. If we were going to create our own engine based on this information, we would need to take other important steps in creating these classes (mainly related to optimizing and working with existing APIs), but we will omit this for the sake of simplification. Point Class { Variables: num tuple[3]; //(x,y,z) Operators: Point AddVectorToPoint(Vector); Point SubtractVectorFromPoint(Vector); Vector SubtractPointFromPoint(Point); Function: // API, // drawPoint; }
Vector Class { Variables: num tuple[3]; //(x,y,z) Operators: Vector AddVectorToVector(Vector); Vector SubtractVectorFromVector(Vector); }
main { var point1 = new Point(1,2,1); var point2 = new Point(0,4,4); var vector1 = new Vector(2,0,0); var vector2; point1.drawPoint(); // (1,2,1) point2.drawPoint(); // (0,4,4) vector2 = point1.subtractPointFromPoint(point2); vector1 = vector1.addVectorToVector(vector2); point1.addVectorToPoint(vector1); point1.drawPoint(); // (4,0,-2) point2.subtractVectorFromPoint(vector2); point2.drawPoint(); // (-1,6,7) }
B=f(a)
\ begin {bmatrix} b_ {0} \\ b_ {1} \\ b_ {2} \ end {bmatrix} = \ begin {bmatrix} f_ {00} & f_ {01} & f_ {02} \\ f_ {10} & f_ {11} & f_ {12} \\ f_ {20} & f_ {21} & f_ {22} \ end {bmatrix} \ begin {bmatrix} a_ {0} \\ a_ {1} \ \ a_ {2} \ end {bmatrix}
beginbmatrixb0b1b2 endbmatrix= beginbmatrixf00a0+f01a1+f02a2f10a0+f11a1+f12a2f20a0+f21a1+f22a2 endbmatrix
\ begin {bmatrix} cos \ theta & -sin \ theta & 0 \\ sin \ theta & cos \ theta & 0 \\ 0 & 0 & 1 \\ \ end {bmatrix}
\ begin {bmatrix} cos \ theta & 0 & sin \ theta \\ 0 & 1 & 0 \\ -sin \ theta & 0 & cos \ theta \ end {bmatrix}
\ begin {bmatrix} 1 & 0 & 0 \\ 0 & cos \ theta & -sin \ theta \\ 0 & sin \ theta & cos \ theta \ end {bmatrix}
\ begin {aligned} \ begin {bmatrix} b_ {0} \\ b_ {1} \\ b_ {2} \ end {bmatrix} & = \ begin {bmatrix} cos \ frac {\ pi} {2} & -sin \ frac {\ pi} {2} & 0 \\ sin \ frac {\ pi} {2} & cos \ frac {\ pi} {2} & 0 \\ 0 & 0 & 1 \ end {bmatrix} \ begin {bmatrix} a_ {0} \\ a_ {1} \\ a_ {2} \ end {bmatrix} \\ & = \ begin {bmatrix} cos \ frac {\ pi} {2} a_ {0} + -sin \ frac {\ pi} {2} a_ {1} + 0a_ {2} \\ sin \ frac {\ pi} {2} a_ {0} + cos \ frac {\ pi} {2} a_ {1 } + 0a_ {2} \\ 0a_ {0} + 0a_ {1} + 1a_ {2} \ end {bmatrix} \\ & = \ begin {bmatrix} 0a_ {0} + -1a_ {1} + 0a_ {2 } \\ 1a_ {0} + 0a_ {1} + 0a_ {2} \\ 0a_ {0} + 0a_ {1} + 1a_ {2} \ end {bmatrix} \\ & = \ begin {bmatrix} -a_ { 1} \\ a_ {0} \\ a_ {2} \ end {bmatrix} \ end {aligned}
Vector
class. One must rotate the vector around the XY plane, the other around YZ, and the third around XZ. At the input, the functions should receive the required number of degrees of rotation, and return the vector at the output.\ begin {bmatrix} s0 & 0 & 0 \\ 0 & s1 & 0 \\ 0 & 0 & s2 \ end {bmatrix}
\ begin {aligned} \ begin {bmatrix} b_ {0} \\ b_ {1} \\ b_ {2} \ end {bmatrix} & = \ begin {bmatrix} s0 & 0 & 0 \\ 0 & s1 & 0 \\ 0 & 0 & s2 \ end {bmatrix} \ begin {bmatrix} a_ {0} \\ a_ {1} \\ a_ {2} \ end {bmatrix} \\ & = \ begin {bmatrix} 2 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \ end {bmatrix} \ begin {bmatrix} a_ {0} \\ a_ {1} \\ a_ {2} \ end {bmatrix} \\ & = \ begin {bmatrix} 2a_ {0} + 0a_ {1} + 0a_ {2} \\ 0a_ {0} + 1a_ {1} + 0a_ {2} \\ 0a_ {0} + 0a_ {1} + 1a_ {2} \ end {bmatrix} \\ & = \ begin {bmatrix} 2a_ {0} \\ a_ {1} \\ a_ {2} \ end {bmatrix} \ end {aligned}
y0 = x0 * s0; y1 = x1*s1; y2 = x2*s2
).Point
class. We will call it setPointToPoint()
, it will simply set the position of the current point at the point that is passed to it. At the entrance she will receive a point and not return anything. Point Class { Variables: num tuple[3]; //(x,y,z) Operators: Point AddVectorToPoint(Vector); Point SubtractVectorFromPoint(Vector); Vector SubtractPointFromPoint(Point); // Null SetPointToPoint(Point); Functions: // API drawPoint; } Vector Class { Variables: num tuple[3]; //(x,y,z) Operators: Vector AddVectorToVector(Vector); Vector SubtractVectorFromVector(Vector); Vector RotateXY(degrees); Vector RotateYZ(degrees); Vector RotateXZ(degrees); Vector Scale(s0,s1,s2); }
main{ // API // ( ) // 100 Point Array pointArray[100]; for (int x = 0; x < pointArray.length; x++) { // pointArray[x].tuple = [random(0,screenWidth), random(0,screenHeight), random(0,desiredDepth)); } // function redrawScreen() { // API ClearTheScreen(); for (int x = 0; x < pointArray.length; x++) { // pointArray[x].drawPoint(); } } // escape, while (esc != pressed) { // if (key('d') == pressed) { redrawScreen(); } if (key('a') == pressed) { // Point origin = new Point(0,0,0); Vector tempVector; for (int x = 0; x < pointArray.length; x++) { // tempVector = pointArray[x].subtractPointFromPoint(origin); // , pointArray[x].setPointToPoint(origin); // pointArray[x].addVectorToPoint(tempVector.scale(0.5,0.5,0.5)); } redrawScreen(); } if(key('s') == pressed) { // Point origin = new Point(0,0,0); Vector tempVector; for (int x = 0; x < pointArray.length; x++) { // tempVector = pointArray[x].subtractPointFromPoint(origin); // , pointArray[x].setPointToPoint(origin); // pointArray[x].addVectorToPoint(tempVector.scale(2.0,2.0,2.0)); } redrawScreen(); } if(key('r') == pressed) { // Point origin = new Point(0,0,0); Vector tempVector; for (int x = 0; x < pointArray.length; x++) { // tempVector = pointArray[x].subtractPointFromPoint(origin); // , pointArray[x].setPointToPoint(origin); // pointArray[x].addVectorToPoint(tempVector.rotateXY(15)); } redrawScreen(); } } }
0
(where the camera is installed) will use a randomly selected value of 100
). Camera Class { Vars: int minX, maxX; // X int minY, maxY; //. . Y int minZ, maxZ; //. . Z }
Renderer
class, cut the points by the camera system, save the ones you want to draw in the array, and then send the array to the renderer’s draw()
function. Camera Class { Vars: int minX, maxX; // X int minY, maxY; // Y int minZ, maxZ; // Z array objectsInWorld; // Functions: null drawScene(); // , }
main{ // API // ( ) var camera = new Camera(); // camera.objectsInWorld[100]; // 100 // camera.minX = 0; camera.maxX = screenWidth; camera.minY = 0; camera.maxY = screenHeight; camera.minZ = 0; camera.maxZ = 100; for(int x = 0; x < camera.objectsInWorld.length; x++) { // camera.objectsInWorld[x].tuple = [random(-200,1000), random(-200,1000), random(-100,200)); } function redrawScreenWithoutCulling() // { ClearTheScreen(); // API for(int x = 0; x < camera.objectsInWorld.length; x++) { camera.objectsInWorld[x].drawPoint(); // } } while(esc != pressed) // { if(key('d') == pressed) { redrawScreenWithoutCulling(); } if(key('c') == pressed) { camera.drawScene(); } if(key('a') == pressed) { Point origin = new Point(0,0,0); Vector tempVector; for(int x = 0; x < camera.objectsInWorld.length; x++) { // tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); // , camera.objectsInWorld[x].setPointToPoint(origin); // camera.objectsInWorld[x].addVectorToPoint(tempVector.scale(0.5,0.5,0.5)); } } if(key('s') == pressed) { Point origin = new Point(0,0,0); //create the space's origin as a point Vector tempVector; for(int x = 0; x < camera.objectsInWorld.length; x++) { // tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); // , camera.objectsInWorld[x].setPointToPoint(origin); // camera.objectsInWorld[x].addVectorToPoint(tempVector.scale(2.0,2.0,2.0)); } } if(key('r') == pressed) { Point origin = new Point(0,0,0); //create the space's origin as a point Vector tempVector; for(int x = 0; x < camera.objectsInWorld.length; x++) { // tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); // , camera.objectsInWorld[x].setPointToPoint(origin); // camera.objectsInWorld[x].addVectorToPoint(tempVector.rotateXY(15)); } } } }
sx
, sy
and error detection (the mathematical definition is given below). LineSegment Class { Variables: int startX, startY; // int endX, endY; // Function: array returnPointsInSegment; // , }
LineSegment
, then it is enough to apply the corresponding transformation to the starting and ending points LineSegment
, and then pass them back to the class. All points between the institutes will be processed when drawing itself LineSegment
, because the Bresenham algorithm requires only the start and end points to search for all subsequent points.LineSegment
in an existing engine, we need to add a function to the class draw()
, so I refused to use the function returnPointsInSegment
. This function will return an array of all points that are in the line segment, which will allow us to draw and cut the segment conveniently.returnPointsInSegment()
will look something like this (in javascript): function returnPointsInSegment() { // var pointArray = new Array(); // var x0 = this.startX; var y0 = this.startY; var x1 = this.endX; var y1 = this.endY; // , var dx = Math.abs(x1-x0); var dy = Math.abs(y1-y0); var sx = (x0 & x1) ? 1 : -1; // x var sy = (y0 & y1) ? 1 : -1; // y var err = dx-dy; // // pointArray.push(new Point(x0,y0)); // while(!((x0 == x1) && (y0 == y1))) { var e2 = err * 2; // // , ( ) if(e2 => -dy) { err -= dy; x0 += sx; } if(e2 < dx) { err += dx; y0 += sy; } // pointArray.push(new Point(x0, y0)); } return pointArray; }
if
, for example, like this: // if (class type == Point) { // } else if (class type == LineSegment) { var segmentArray = LineSegment.returnPointsInSegment(); // , , }
Circle Class { Variables: int centerX, centerY; // int radius; // Function: array returnPointsInCircle; // , Circle }
returnPointsInCircle()
will behave in the same way as a class function LineSegment
, returning an array of points so that the camera can render and cut them off. This allows the engine to handle many different forms, each of which needs to make only minor changes.returnPointsInCircle()
(in javascript): function returnPointsInCircle() { // var pointArray = new Array(); // , var f = 1 - radius; // ( ) var ddFx = 1; // x var ddFy = -2 * this.radius; // y var x = 0; var y = this.radius; // , // pointArray.push(new Point(this.centerX, this.centerY + this.radius)); pointArray.push(new Point(this.centerX, this.centerY - this.radius)); pointArray.push(new Point(this.centerX + this.radius, this.centerY)); pointArray.push(new Point(this.centerX - this.radius, this.centerY)); while(x < y) { if(f >= 0) { y--; ddFy += 2; f += ddFy; } x++; ddFx += 2; f += ddFx; // pointArray.push(new Point(x0 + x, y0 + y)); pointArray.push(new Point(x0 - x, y0 + y)); pointArray.push(new Point(x0 + x, y0 - y)); pointArray.push(new Point(x0 - x, y0 - y)); pointArray.push(new Point(x0 + y, y0 + x)); pointArray.push(new Point(x0 - y, y0 + x)); pointArray.push(new Point(x0 + y, y0 - x)); pointArray.push(new Point(x0 - y, y0 - x)); } return pointArray; }
if
to the main draw loop, and these circles will be fully integrated into the code! // if(class type == point) { // } else if(class type == LineSegment) { var segmentArray = LineSegment.returnPointsInSegment(); //loop through points in the array, drawing and culling them as we have previously } else if(class type == Circle) { var circleArray = Circle.returnPointsInCircle(); // , , }
main{ // API // ( ) var camera = new Camera(); // camera.objectsInWorld[]; // 100 // camera.minX = 0; camera.maxX = screenWidth; camera.minY = 0; camera.maxY = screenHeight; camera.minZ = 0; camera.maxZ = 100; while(key != esc) { if(mouseClick) { // camera.objectsInWorld.push(new Circle(mouse.x,mouse.y,random(3,10)); // camera.drawScene(); } } }
Triangle
and Quad
we will actively use the class LineSegment
.Triangle
in the engine is quite simple, especially due to the use of the class LineSegment
, in which all rasterization will occur. This class allows you to assign three points and draw line segments between them to create a closed triangle. Triangle Class { Variables: // int Point1X, Point1Y; int Point2X, Point2Y; int Point3X, Point3Y; Function: array returnPointsInTriangle; // }
LineSegment
you can write the following function returnPointsInTriangle()
: function returnPointsInTriangle() { array PointsToReturn; // // PointsToReturn.push(new LineSegment(this.Point1X, this.Point1Y, this.Point2X, this.Point2Y)); PointsToReturn.push(new LineSegment(this.Point2X, this.Point2Y, this.Point3X, this.Point3Y)); PointsToReturn.push(new LineSegment(this.Point3X, this.Point3Y, this.Point1X, this.Point1Y)); return(PointsToReturn); }
LineSegment
, so we just have to sequentially connect the segments together to create more complex shapes. This allows us to easily create much more complex polygons (polygons) on the screen simply by adding new ones LineSegment
(and storing more points in the class itself).Triangle
. With another set of points, the class of the quadrilateral will look like this: Quad Class { Variables: int Point1X, Point1Y; // int Point2X, Point2Y; int Point3X, Point3Y; int Point4X, Point4Y; Function: array returnPointsInQuad; // }
returnPointsInQuad
, like this: function returnPointsInQuad() { array PointsToReturn; // // PointsToReturn.push(new LineSegment(this.Point1X, this.Point1Y, this.Point2X, this.Point2Y)); PointsToReturn.push(new LineSegment(this.Point2X, this.Point2Y, this.Point3X, this.Point3Y)); PointsToReturn.push(new LineSegment(this.Point3X, this.Point3Y, this.Point4X, this.Point4Y)); PointsToReturn.push(new LineSegment(this.Point4X, this.Point4Y, this.Point1X, this.Point1Y)); return(PointsToReturn); }
Polygon Class { Variables: array Points; // Function: array returnPointsInPolygon; //, }
returnPointsInPolygon()
, which may look something like this: function returnPointsInPolygon { array PointsToReturn; // // ( ) for(int x = 0; x < this.Points.length; x+=2) { if( ) { // PointsToReturn.push(new LineSegment(this.Points[x], this.Points[x+1], this.Points[x+2], this.Points[x+3])); } else if( ) { // PointsToReturn.push(new LineSegment(this.Points[x-2], this.Points[x-1], this.Points[0], this.Points[1])); } } // return PointsToReturn; }
main{ // API // ( ) var camera = new Camera(); // camera.objectsInWorld[]; // // camera.minX = 0; camera.maxX = screenWidth; camera.minY = 0; camera.maxY = screenHeight; camera.minZ = 0; camera.maxZ = 100; //c var threeSides = new Array(100,100,100,50,50,50); var fourSides = new Array(points in here); var fiveSides = new Array(points in here); var sixSides = new Array(points in here); var sevenSides = new Array(points in here); var eightSides = new Array(points in here); var nineSides = new Array(points in here); var tenSides = new Array(points in here); // var sidesArray = new Array(threeSides, fourSides, fiveSides, sixSides, sevenSides, eightSides, nineSides, tenSides); // var polygonPoints = 3; // var polygon = new Polygon(sidesArray[0][0], sidesArray[0][1], sidesArray[0][2], sidesArray[0][3], sidesArray[0][4], sidesArray[0][5],); // camera.drawScene(); // escape while(key != esc) { if(key pressed == 'a') { // 3 if(polygonPoints != 3) { // polygonPoints--; // , } // camera.drawScene(); } else if(key pressed == 's') { // 10 if(polygonPoints != 10) { // polygonPoints++; // , } // camera.drawScene(); } } }
Point
and Vector
(engine building blocks).Camera
(sets the scope and cuts off points outside the screen).Point
. This will allow each point to have its own color, which will greatly simplify the calculations of lighting and shading (at least for a person - sometimes this engine code is less effective). When calculating the illumination and shading of the scene, we can create a function with a list of points and process all of them taking into account the distance to the light source in order to change their color accordingly.255,0,0
) or hexadecimal form ( 0xFF0000
or #FF0000
). We will use the decimal format, because it is much easier to work with it. In addition, if your graphics API uses hexadecimal values, then it most likely has a function for converting decimal values to hexadecimal values, that is, this will not be a problem.red
, blue
and green
. So far, nothing incomprehensible happens, but here’s how the sketch of our class should now look Point
: Point Class { Variables: num tuple[3]; //(x,y,z) num red, green, blue; // r, g, b Operators: Point AddVectorToPoint(Vector); Point SubtractVectorFromPoint(Vector); Vector SubtractPointFromPoint(Point); Null SetPointToPoint(Point); Functions: drawPoint; // }
object.setColor(red, green, blue)
object.setColor(toHex(red,green,blue))
toHex()
(again, in different API the names of the functions will be different), converting the RGB value into a hexadecimal value, so that you do not have to do it manually. lineSegment::constructor(startX, startY, endX, endY, red, green, blue) { this.startX = startX; this.startY = startY; this.endX = endX; this.endY = endY; this.red = red; this.green = green; this.blue = blue; }
function returnPointsInSegment() { // var pointArray = new Array(); // var x0 = this.startX; var y0 = this.startY; var x1 = this.endX; var y1 = this.endY; // , var dx = Math.abs(x1-x0); var dy = Math.abs(y1-y0); var sx = (x0 & x1) ? 1 : -1; // x var sy = (y0 & y1) ? 1 : -1; // y var err = dx-dy; // // pointArray.push(new Point(x0,y0,this.red,this.green,this.blue)); // while(!((x0 == x1) && (y0 == y1))) { var e2 = err * 2; // // , ( ) if(e2 => -dy) { err -= dy; x0 += sx; } if(e2 < dx) { err += dx; y0 += sy; } // pointArray.push(new Point(x0, y0,this.red,this.green,this.blue)); } return pointArray; }
r,g,b
). We will create a program that uses all this huge amount of colors. main{ // API // ( ) var camera = new Camera(); // // camera.minX = 0; camera.maxX = screenWidth; camera.minY = 0; camera.maxY = screenHeight; camera.minZ = 0; camera.maxZ = 100; // , var red, green, blue; // while(key != esc) { if(key press = 'a') { if(red > 0) { red --; object.red = red; // } } if(key press = 'q') { if(red < 255) { red ++; object.red = red; // } } if(key press = 's') { if(green > 0) { green --; object.green = green; // } } if(key press = 'w') { if(green < 255) { green ++; object.green = green; // } } if(key press = 'd') { if(blue > 0) { blue --; object.blue = blue; // } } if(key press = 'e') { if(blue < 255) { blue ++; object.blue = blue; // } } } }
Point
and class Camera
. Here's what they look like: Point Class { Variables: num tuple[3]; //(x,y,z) Operators: Point AddVectorToPoint(Vector); Point SubtractVectorFromPoint(Vector); Vector SubtractPointFromPoint(Point); Null SetPointToPoint(Point); Functions: drawPoint; // } Camera Class { Vars: int minX, maxX; int minY, maxY; int minZ, maxZ; array objectsInWorld; // Functions: null drawScene(); // }
Lighting Class { Variables: num position[3]; //(x,y,z) num red = 255; //, r num green = 255; //, g num blue = 255; //, b string lightType = "point"; // num radius = 50; // }
drawScene()
: if(currentPoint.x >= (light.x - light.radius)){ // if(currentPoint.x <= (light.x + light.radius)){ // if(currentPoint.y >= (light.y - light.radius)){ // if(currentPoint.y <= (light.y + light.radius)){ // // (distance) // (percentage = distance / radius) point.red += (light.red * percentage); // , point.green += (light.green * percentage); // , point.blue += (light.blue * percentage); // , } } } }
main{ // API // ( ) var camera = new Camera(); // // camera.minX = 0; camera.maxX = screenWidth; camera.minY = 0; camera.maxY = screenHeight; camera.minZ = 0; camera.maxZ = 100; // while(key != esc) { if(mouseClick) { if(firstClick) { // } else { // } camera.drawScene(); } }
Source: https://habr.com/ru/post/334580/
All Articles