دعونا نبني محرك رسومات ثلاثية الأبعاد: المثلثات النقطية والرباعية
() translation by (you can also view the original English article)
مرحبًا بك في الجزء الخامس من سلسلة Let Let's Build 3D Graphics Engine! في هذه المرة ، سنبني فصلين دراسيين جديدين للتنقيط: واحد للمثلثات والآخر للدرجات الرباعية الأساسية. بعد ذلك ، سنأخذ قطعًا من هاتين الفئتين ونجمع طبقة مضلعة نهائية وقوية.
نصيحة: هذا جزء من سلسلة ، لذلك إذا كنت ترغب في الحصول على أقصى استفادة منه ، فتأكد من قراءة البرامج التعليمية الأخرى التي سبقت هذا.
خلاصة
لقد قمنا ببناء بعض الشيء في محركنا حتى الآن! إليكم ما لدينا:
- فاصلات النقطة و المتجهات (وحدات البناء لمحركنا).
- وظائف التحول لنقاطنا.
- فئة الكاميرا (تحدد إطار العرض الخاص بنا ، وتعطّل النقاط خارج الشاشة).
- فئتان للتنقيط (شرائح الخط والدوائر).
في ما يلي مرجع سريع لجميع الفصول التي أنشأناها:
1 |
|
2 |
Point Class |
3 |
{
|
4 |
Variables:
|
5 |
num tuple[3]; //(x,y,z) |
6 |
Operators:
|
7 |
Point AddVectorToPoint(Vector); |
8 |
Point SubtractVectorFromPoint(Vector); |
9 |
Vector SubtractPointFromPoint(Point); |
10 |
Null SetPointToPoint(Point); |
11 |
Functions:
|
12 |
drawPoint; //draw a point at its position tuple |
13 |
}
|
14 |
|
15 |
Vector Class |
16 |
{
|
17 |
Variables:
|
18 |
num tuple[3]; //(x,y,z) |
19 |
Operators:
|
20 |
Vector AddVectorToVector(Vector); |
21 |
Vector SubtractVectorFromVector(Vector); |
22 |
Vector RotateXY(degrees); |
23 |
Vector RotateYZ(degrees); |
24 |
Vector RotateXZ(degrees); |
25 |
Vector Scale(s0,s1,s2); //receives a scaling 3-tuple, returns the scaled vector |
26 |
}
|
27 |
|
28 |
Camera Class |
29 |
{
|
30 |
Vars:
|
31 |
int minX, maxX; |
32 |
int minY, maxY; |
33 |
int minZ, maxZ; |
34 |
array objectsInWorld; //an array of all existent objects |
35 |
Functions:
|
36 |
null drawScene(); //draws all needed objects to the screen |
37 |
}
|
38 |
|
39 |
LineSegment Class |
40 |
{
|
41 |
Variables:
|
42 |
int startX, startY; //the starting point of our line segment |
43 |
int endX, endY; //the ending point of our line segment |
44 |
Function:
|
45 |
array returnPointsInSegment; //all points lying on this line segment |
46 |
}
|
سوف نعتمد بشكل كبير على فئة LineSegment
لإنشاء فئتي Triangle
و Quad
، لذا تأكد من إعادة التعرف عليها قبل الانتقال.
المثلثات النقطية



إن وضع فئة Triangle
للمحرّك بسيطة إلى حد ما ، خاصةً وأن فئة LineSegment
هي المكان الذي ستحدث فيه جميع عمليات التنقيط. سيسمح هذا الفصل بوضع ثلاث نقاط ، وسيرسم قطعة خط بينهما لإنشاء المثلث المكتمل.
يمكن أن يظهر المخطط الأساسي للفصل كالتالي:
1 |
|
2 |
Triangle Class |
3 |
{
|
4 |
Variables:
|
5 |
//co-ordinates for the three points of our triangles
|
6 |
int Point1X, Point1Y; |
7 |
int Point2X, Point2Y; |
8 |
int Point3X, Point3Y; |
9 |
Function:
|
10 |
array returnPointsInTriangle; //all points within the triangle's perimeter |
11 |
}
|
من أجل المعايير ، سنفترض أن النقاط الثلاث المعلنة ضمن مثلثنا هي في نمط عقارب الساعة.
باستخدام فئة LineSegment
، يمكننا إعداد وظيفة returnPointsInTriangle()
الخاصة بنا على النحو التالي:
1 |
|
2 |
function returnPointsInTriangle() |
3 |
{
|
4 |
array PointsToReturn; //create a temporary array to hold the triangle's points |
5 |
|
6 |
//Create three line segments and store their points in the array
|
7 |
PointsToReturn.push(new LineSegment(this.Point1X, this.Point1Y, this.Point2X, this.Point2Y)); |
8 |
PointsToReturn.push(new LineSegment(this.Point2X, this.Point2Y, this.Point3X, this.Point3Y)); |
9 |
PointsToReturn.push(new LineSegment(this.Point3X, this.Point3Y, this.Point1X, this.Point1Y)); |
10 |
|
11 |
return(PointsToReturn); |
12 |
}
|
ليس سيئا للغاية ، أليس كذلك؟ نظرًا لأن لدينا بالفعل الكثير من العمل الذي يتم إجراؤه ضمن فئة LineSegment
الخاصة بنا ، إلا أنه يجب علينا الاستمرار في توحيدهم معًا لإنشاء أشكال أكثر تعقيدًا. هذا يجعل من السهل إنشاء مضلعات أكثر تعقيدًا على الشاشة ، وذلك ببساطة عن طريق إضافة المزيد من LineSegments
(وتخزين المزيد من النقاط داخل الفئة نفسها).
بعد ذلك ، دعونا نلقي نظرة على كيفية إضافة المزيد من النقاط إلى هذا النظام من خلال إنشاء فئة مربعة.
الحصول على تربيعي بعيدا

لا يتطلب وضع فئة للتعامل مع الأجيال الرباعية سوى إضافة بعض الأشياء الإضافية إلى صفنا المثلث
. مع مجموعة أخرى من النقاط ، فإن صفنا الرباعي سيبدو هكذا:
1 |
|
2 |
Quad Class |
3 |
{
|
4 |
Variables:
|
5 |
int Point1X, Point1Y; //co-ordinates for the four points of our quadrilateral |
6 |
int Point2X, Point2Y; |
7 |
int Point3X, Point3Y; |
8 |
int Point4X, Point4Y; |
9 |
|
10 |
Function:
|
11 |
array returnPointsInQuad; //return all points within the quadrilateral |
12 |
}
|
ثم ، نقوم فقط بإضافة جزء السطر الإضافي إلى الدالة returnPointsInQuad
، مثل:
1 |
|
2 |
function returnPointsInQuad() |
3 |
{
|
4 |
array PointsToReturn; //create a temporary array to hold the quad's points |
5 |
|
6 |
//Create four line segments and store their points in the array
|
7 |
PointsToReturn.push(new LineSegment(this.Point1X, this.Point1Y, this.Point2X, this.Point2Y)); |
8 |
PointsToReturn.push(new LineSegment(this.Point2X, this.Point2Y, this.Point3X, this.Point3Y)); |
9 |
PointsToReturn.push(new LineSegment(this.Point3X, this.Point3Y, this.Point4X, this.Point4Y)); |
10 |
PointsToReturn.push(new LineSegment(this.Point4X, this.Point4Y, this.Point1X, this.Point1Y)); |
11 |
|
12 |
return(PointsToReturn); |
13 |
}
|
في حين أن بناء فصول جديدة كهذه أمرًا بسيطًا إلى حد ما ، فهناك طريقة أسهل بكثير لتغليف كل مضلعاتنا في فئة واحدة. باستخدام سحر الحلقات والمصفوفات ، يمكننا تجميع فئة المضلع التي يمكن أن تجعل أي شكل من الأشكال قد ترغب به تقريبًا!
أين لديك كل بوليس غون؟
لإنشاء فئة مضلع دائمًا ، نحتاج إلى القيام بأمرين. الأول هو نقل كل نقاطنا إلى مجموعة ، مما يمنحنا مخططًا دراسيًا مشابهًا لشيء مثل هذا:
1 |
|
2 |
Polygon Class |
3 |
{
|
4 |
Variables:
|
5 |
array Points; //holds all of the polygon's points in an array |
6 |
|
7 |
Function:
|
8 |
array returnPointsInPolygon; //an array holding all of the polygon's points |
9 |
}
|
والثاني هو استخدام حلقة للسماح لعدد غير مسمّى من مقاطع الخطوط بالعبور في وظيفة returnPointsInPolygon()
التي قد تبدو كالتالي:
1 |
|
2 |
function returnPointsInPolygon |
3 |
{
|
4 |
array PointsToReturn; //a temporary array to hold the polygon's points |
5 |
|
6 |
//loop through all points in the polygon, moving one co-ordinate pair at a time (by a step of two)
|
7 |
for(int x = 0; x < this.Points.length; x+=2) |
8 |
{
|
9 |
if(this is not the last point) |
10 |
{
|
11 |
//create a line segment between this point and the next one in the array
|
12 |
PointsToReturn.push(new LineSegment(this.Points[x], this.Points[x+1], this.Points[x+2], this.Points[x+3])); |
13 |
}
|
14 |
else if(this is the last point) |
15 |
{
|
16 |
//create a line segment between this point and the first point in the array
|
17 |
PointsToReturn.push(new LineSegment(this.Points[x-2], this.Points[x-1], this.Points[0], this.Points[1])); |
18 |
}
|
19 |
}
|
20 |
|
21 |
//return the array of points
|
22 |
return PointsToReturn; |
23 |
}
|
مع إضافة هذه الفئة إلى محركنا ، يمكننا الآن إنشاء أي شيء من المثلث إلى بعض المراجعين من 39 جانبًا مع نفس سطر الشفرة.
مضلع مضلع
للعب مع فئة مضلعاتنا الجديدة ، دعونا نجعل برنامجًا يوضح مدى وصوله. يتيح برنامجنا للمستخدم إمكانية إضافة أو إزالة جوانب من المضلع المعروض باستخدام الضغط على المفاتيح. وبالطبع ، سيكون علينا وضع حدود لعدد الجوانب التي يمكن أن يمتلكها مضلعنا ، نظرًا لأن وجود أقل من ثلاثة أطراف لن يجعله مضلعاً بعد الآن. لا يتعين علينا مراقبة الحدود العليا لمضلعنا لأنهم يجب أن يتوسعوا بشكل جيد. ومع ذلك ، سنقوم بتحديد المضلعات بحيث يكون لها عشرة جوانب كحد أقصى لأننا سنضع نقاطها الجديدة من داخل الشفرة.
يمكن تقسيم مواصفات برنامجنا إلى هذه الأجزاء الصغيرة:
- ارسم مضلعًا على الشاشة في البداية.
- عند الضغط على المفتاح 'a' ، قم بتخفيض عدد الجوانب على المضلع بمقدار 1.
- عندما يتم الضغط على مفتاح 's' ، قم بزيادة عدد الجوانب على المضلع بمقدار 1.
- منع عدد مضلع الجانبين من السقوط تحت 3.
- منع عدد مضلع الجوانب من الزيادة فوق 10.
لنلق نظرة على شكل الرمز الخاص بنا:
1 |
|
2 |
main{ |
3 |
//setup for your favorite Graphics API here
|
4 |
//setup for keyboard input (may not be required) here
|
5 |
|
6 |
var camera = new Camera(); //create an instance of our camera class |
7 |
camera.objectsInWorld[]; //initialize the camera's object array |
8 |
|
9 |
//set up the camera's view space
|
10 |
camera.minX = 0; |
11 |
camera.maxX = screenWidth; |
12 |
camera.minY = 0; |
13 |
camera.maxY = screenHeight; |
14 |
camera.minZ = 0; |
15 |
camera.maxZ = 100; |
16 |
|
17 |
//create an array of points for each polygon size
|
18 |
var threeSides = new Array(100,100,100,50,50,50); |
19 |
var fourSides = new Array(points in here); |
20 |
var fiveSides = new Array(points in here); |
21 |
var sixSides = new Array(points in here); |
22 |
var sevenSides = new Array(points in here); |
23 |
var eightSides = new Array(points in here); |
24 |
var nineSides = new Array(points in here); |
25 |
var tenSides = new Array(points in here); |
26 |
|
27 |
//store all of the arrays in another array for easier access
|
28 |
var sidesArray = new Array(threeSides, fourSides, fiveSides, sixSides, sevenSides, eightSides, nineSides, tenSides); |
29 |
|
30 |
//keep track of how many points the polygon currently has
|
31 |
var polygonPoints = 3; |
32 |
|
33 |
//create the initial polygon to be displayed
|
34 |
var polygon = new Polygon(sidesArray[0][0], sidesArray[0][1], sidesArray[0][2], sidesArray[0][3], sidesArray[0][4], sidesArray[0][5],); |
35 |
//draw the initial polygon to the screen
|
36 |
camera.drawScene(); |
37 |
|
38 |
//while the user has not pressed the escape key
|
39 |
while(key != esc) { |
40 |
if(key pressed == 'a') |
41 |
{
|
42 |
//if the polygon is not at risk of falling below 3
|
43 |
if(polygonPoints != 3) |
44 |
{
|
45 |
//reduce the number of points
|
46 |
polygonPoints--; |
47 |
//change the polygon to have the correct number of points
|
48 |
}
|
49 |
//redraw the scene
|
50 |
camera.drawScene(); |
51 |
}
|
52 |
else if(key pressed == 's') |
53 |
{
|
54 |
//if the polygon is not at risk of going above 10
|
55 |
if(polygonPoints != 10) |
56 |
{
|
57 |
//increase the number of points
|
58 |
polygonPoints++; |
59 |
//change the polygon to have the correct number of points
|
60 |
}
|
61 |
//redraw the scene
|
62 |
camera.drawScene(); |
63 |
}
|
64 |
}
|
65 |
}
|
يجب أن يسمح لك برنامجنا الصغير بتعديل المضلع على الشاشة الآن! تحقق من العرض. إذا كنت ترغب في تعزيز هذا البرنامج قليلاً ، فقد تحتاج إلى محاولة وضع قسم تغيير المضلع في شكل من أشكال الخوارزمية لتسهيل عملية القياس على نفسك. أنا غير متأكد من وجود واحد بالفعل ، ولكن إذا حدث ذلك ، فيمكنك بسهولة مضلع مقياس بلا حدود على يديك!
خاتمة
لدينا كمية كبيرة من التنقيط المضمنة في محركنا الآن ، مما يسمح لنا بإنشاء أي شكل قد نحتاج إليه (على الرغم من بعض فقط من خلال الجمع). في المرة القادمة سنبتعد عن رسم الأشكال والتحدث أكثر عن ممتلكاتهم. إذا كنت مهتمًا بإحضار بعض الألوان إلى شاشتك ، فاحرص على التحقق من الجزء التالي!