1. วิเคราะห์การหมุนของเข็มนาฬิกา
การหมุนของเข็มนาฬิกาเป็นงานในส่วนของ animation เพราะการวาดเข็มจะเปลี่ยนไปเมื่อเวลาเปลี่ยน การหมุนของเข็มนาวินาที,นาทีและชั่วโมงใช้หลักการเดียวกัน ต่างกันที่ความเร็วเเชิงมุม การวิเคราะห์นี้จะช่วยทำให้เราทราบว่าในแต่ละหน่วยเวลาจะต้องทำการวาดเข็มนาฬิกาอย่างไร
รูปที่ 1 |
การหมุนของเข็มวินาที
การหมุน 1 รอบหมายถึงเวลาเดินไป 60 วินาทีหรือ 1 นาที คิดเป็นค่าของมุมคือ \( 2\pi \) ดังนั้นเวลา 1 วินาทีคิดเป็นค่าของมุมเป็น
\[ \text{angle of one second } = \frac{2\pi}{60} = \frac{\pi}{30} \tag{1.0} \]การหมุนของเข็มนาที
เหมือนกับการหมุนของเข็มวินาที แต่เปลี่ยนหน่วยเวลา 1 รอบหมายถึงเวลาเดินไป 60 นาทีหรือ 1 ชั่วโมง
\[ \text{angle of one minute } = \frac{2\pi}{60} = \frac{\pi}{30} \tag{1.1} \]การหมุนของเข็มชั่วโมง
การหมุน 1 รอบหมายถึงเวลาเดินไป 12 ชั่วโมง คิดเป็นค่าของมุมคือ \( 2\pi \) ดังนั้นเวลา 1 ชั่วโมงคิดเป็นค่าของมุมเป็น
\[ \text{angle of one hour } = \frac{2\pi}{12} = \frac{\pi}{6} \tag{1.2} \]2. การแปลงค่าของมุมไปสู่ coordinates
ขั้นตอนนี้เป็นการหา coordinates ที่จะนำไปใช้เป็นจุดปลายในการวาดเข็มนาฬิกาทั้ง 3 เส้น (ดูรายละเอียดจากตอนที่ 1) จาก (1.0),(1.1) และ (1.2) บอกถึงการได้มาซึ่งมุมของแต่ละเข็มที่จะกวดไปเมื่อเวลาผ่านไป 1 หน่วยเวลา จะนำค่าเหล่านี้ไปใช้งานได้ต้องทำการเปลี่ยนให้เป็น coordinates ที่ตรงตามชุดคำสั่งต้องการก่อน โดยอาศัยการแปลงจากระบบ polar coordinates ไปสู่่ cartesian coordinates
ถ้า \( r \) แทนความยาวของเข็มนาฬิกาที่ต้องการคำนวณ และ \(\theta \) ดังนี้
\[ \begin{align*} x &= rcos(\theta) \\ y &= rsin(\theta) \\ \end{align*} \]หรือ ถ้าให้ t คือเวลาที่ผ่านไป เช่น 5 วินาที, 10 นาที จะได้
\[ \begin{align*} x_t &= rcos(t\theta) \tag{1.3} \\ y_t &= rsin(t\theta) \tag{1.4}\\ \end{align*} \]แต่ raylib การเริ่มนับมุมที่ 0 radian เริ่มจากเส้นแนวนอน (X+ axis) แล้ววัดมุมตามเข็มนาฬิกา ในขณะที่การนับเวลาจะเริ่มนับจากแนวตั้ง (Y+ axis) ดังนั้นค่ามุมที่คำนวณได้ ก่อนนำไปใช้คำนวณต่อต้องนำเอา \( \frac{\pi}{2}\) ไปลบออกเสียก่อน
ดังนั้น (1.3),(1.4) จะต้องเชียนใหม่เป็น
\[ \begin{align*} x_t &= rcos(t\theta - \frac{\pi}{2}) \tag{1.5} \\ y_t &= rsin(t\theta - \frac{\pi}{2}) \tag{1.6}\\ \end{align*} \]รูปที่ 2 |
เมื่อนำเอาค่ามุมของ 1 หน่วยเวลาของวินาที,นาทีและชั่วโมงมาแทนค่า สามารถสรุปเป็นสูตรคำนวณสำหรับตำแหน่งของเข็มทั้งสามดังนี้
» coordinate ของปลายเข็มวินาที
\[ \begin{align*} x_s &= rcos(s \cdot \frac{\pi}{30} - \frac{\pi}{2}) \tag{1.7} \\ y_s &= rsin(s \cdot \frac{\pi}{30} - \frac{\pi}{2}) \tag{1.8}\\ \end{align*} \]» coordinate ของปลายเข็มนาที
\[ \begin{align*} x_m &= rcos(m \cdot \frac{\pi}{30} - \frac{\pi}{2}) \tag{1.9} \\ y_m &= rsin(m \cdot \frac{\pi}{30} - \frac{\pi}{2}) \tag{1.10}\\ \end{align*} \]» coordinate ของปลายเข็มชั่วโมง
\[ \begin{align*} x_h &= rcos(h \cdot \frac{\pi}{6} - \frac{\pi}{2}) \tag{1.11} \\ y_h &= rsin(h \cdot \frac{\pi}{6} - \frac{\pi}{2}) \tag{1.12}\\ \end{align*} \](1.7) ถึง (1.12) จะยังไม่สามารถนำไปใช้งานได้ ยังขาดขั้นตอนการย้ายตำแหน่ง (translation) เพราะจุดกำเนิดของนาฬิกาไม่ได้อยู่ที่ (0,0) แต่ย้ายไปที่ (cx,cy) ดังนั้นค่า coordinates ที่คำนวณได้ต้องย้ายตำแหน่งตามไปด้วย โดยการนำค่าของ cx และ cy รวมเข้าไปด้วย ดังนี้
3. การหาค่าของเวลา และการแปลงค่าไปสู่ coordinates
Raylib ใช้ชุดคำสั่ง SetTargetFPS(fps) เพื่อใช้กำหนดอัตราเร็วในการวาดใน 1 วินาที หมายความว่าสามารถคำนวณเวลาได้อัตราเร็วนี้
\[ \begin{align*} \text{frames rate} &= \frac{\text{number of frames}}{time} \\\\ \therefore time &= \frac{\text{number of frames}}{\text{frames rate}} \tag{2.0}\\ \end{align*} \]เช่น ถ้ากำหนดให้ fps = 20 และจำนวน frames ที่วาดไปแล้วคือ 1200 frames จะได้เวลาที่ใช้ไปคือ
\[ \begin{align*} \text{seconds} &= \frac{1200}{20} = 60 \\\\ \text{minutes} &= \frac{1}{60} \times \frac{1200}{20} = 10 \\\\ \text{hours} &= \frac{1}{360} \times \frac{1200}{20} = \frac{1}{60} \\\\ \end{align*} \]ค่าที่ได้จากการคำนวณนี้จะนำไปใช้แทนค่าให้กับตัวแปร s,m,และ h ใน (1.13) - (1.18) ถ้ากำหนดให้ cx = 100, cy = 100 และ r = 10 จะได้
\[ \begin{align*} x_s &= 10 \cdot cos(60 \cdot \frac{\pi}{30} - \frac{\pi}{2}) + 100 \\ y_s &= 10 \cdot sin(60 \cdot \frac{\pi}{30} - \frac{\pi}{2}) + 100 \\\\ x_m &= 10 \cdot cos(10 \cdot \frac{\pi}{30} - \frac{\pi}{2}) + 100 \\ y_m &= 10 \cdot sin(10 \cdot \frac{\pi}{30} - \frac{\pi}{2}) + 100 \\\\ x_h &= 10 \cdot cos(\frac{1}{60} \cdot \frac{\pi}{6} - \frac{\pi}{2}) + 100 \\ y_h &= 10 \cdot sin(\frac{1}{60} \cdot \frac{\pi}{6} - \frac{\pi}{2}) + 100 \\\\ x_s &= -10.7 + 100 = 89.3 \\ y_s &= -10.0 + 100 = 90.0\\\\ x_m &= 8.7 + 100 = 108.7\\ y_m &= -5.0 + 100 = 95.0 \\\\ x_h &= 0.087 + 100 = 100.087 \\ y_h &= -10.0 + 100 = 90.0\\\\ \end{align*} \]นั่นคือการวาดภาพ frame ที่ 1200 หมายถึงเป็นเวลา HH:MM:SS = 00:10:60 หรือ 00:11:00 และจุดปลายของเข็มวินาที,นาทีและชั่วโมงจะเป็น (89,90),(109,95),(100,90) ตามลำดับ
เขียน Code เพื่อทำ animation
เนื้อหาในตอนนี้จะมีผลต่อการปรับแต่ง code จากตอนที่ 1 ใน 3 functions คือ draw_sec_arm(),draw_min_arm() และ draw_hrs_arm() โดยต้องส่งค่า parameter ที่บอกเวลาให้กับทั้ง 3 function เพื่อนำไปคำนวณ coordinates ใหม่
void draw_sec_arm(int tick){
float thickness = 3.0f;
float ang = (tick * M_PI/30 - M_PI/2);
Vector2 start = {(float)cx,(float)cy};
Vector2 end;
end.x = (float)(cx + SEC_ARM_LEN * cos(ang));
end.y = (float)(cy + SEC_ARM_LEN * sin(ang));
DrawLineEx(start,end,thickness,RED);
}
void draw_min_arm(int tick){
float thickness = 7.0f;
float ang = (tick * M_PI/30 - M_PI/2);
Vector2 start = {(float)cx,(float)cy};
Vector2 end;
end.x = (float)(cx + SEC_MIN_LEN * cos(ang));
end.y = (float)(cy + SEC_MIN_LEN * sin(ang));
DrawLineEx(start,end,thickness,BLUE);
}
void draw_hrs_arm(int tick){
float thickness = 11.0f;
float ang = (tick * M_PI/6 - M_PI/2);
Vector2 start = {(float)cx,(float)cy};
Vector2 end;
end.x = (float)(cx + SEC_HRS_LEN * cos(ang));
end.y = (float)(cy + SEC_HRS_LEN * sin(ang));
DrawLineEx(start,end,thickness,DARKPURPLE);
}
cos(),sin() เป็น function ทาง trigonometry และ M_PI เป็นค่าคงที่ของ \( \pi \) ที่ถูกประกาศไว้ใน math.h
4. การแปลงค่าลำดับของ frame ไปเป็นหน่วยของเวลา
ใน workshop เราใช้หน่วยเวลา 3 หน่วยคือ วินาที, นาที และชั่วโมง ดังนั้นค่าลำดับของ frame ที่ได้จะต้องถูกแปลงไปเป็นหน่วยของเวลาทั้ง 3 หน่วย
จาก (2.0) ค่าของตัวแปร time จะมีหน่วยเป็น วินาที เนื่องจากหน่วยของ frame rate คือ frames per second จะได้ว่า
\[ \begin{align*} \text{seconds} &= \frac{\text{number of frames}}{\text{frames rate}} \\\\ \text{minutes} &= \frac{1}{60} \times \text{seconds} \\\\ \text{hours} &= \frac{1}{60} \times \text{minutes}\\\\ \end{align*} \]จะเห็นได้ว่าความสัมพันธ์ระหว่างค่าของเวลาทั้ง 3 หน่วยมีลักษณะเดียวกับการทำงานของเฟืองทด
รูปที่ 3 |
เมื่อจำนวน frame ที่วาดไปแล้วมีจำนวนเท่ากับ fps ก็จะทำให้ค่าของ second เพิ่มขึ้น 1
เมื่อจำนวน second มีจำนวนเท่ากับ 60 ก็จะทำให้ค่าของ minute เพิ่มขึ้น 1
เมื่อจำนวน minute มีจำนวนเท่ากับ 60 ก็จะทำให้ค่าของ hour เพิ่มขึ้น 1
เขียน Code เพื่อคำนวณเวลา
const int FPS = 20;
int second = 0;
int minute = 0;
int hour = 0;
int frame_count = 0;
while(!WindowShouldClose()){
if(frame_count < FPS){
frame_count+=1;
}else{
second+=1;//increase one second
if(second > 59){
minute+=1; // increase one minute
if(minute > 59){
hour+=1; // increase one hour
}
}
}
...
}
ข้อจำกัดจาก code ข้างบนคือตัวแปร frame_count จะเพิ่มจำนวนไปเรื่อยๆ ตราบที่โปรแกรมยังทำงาน ในทางปฏิบัติแล้วไม่ควรทำ เพราะหน่วนความจำของคอมพิวเตอร์มีจำกัดไม่สามารถเพิ่มค่าได้อย่างไม่มีจำกัด ดังนั้นต้องมีเงื่อนไขในการล้างค่าที่ใช้ไปแล้ว ซึ่งก็คือการเดินทางครบรอบของเข็มนาฬิกา เมื่อเข็มเดินทางครบ 1 รอบก็ทำการล้างค่าที่มีอยู่แล้วเริ่มรอบการทำงานใหม่ code ที่ได้ควรจะเป็น
const int FPS = 20;
int second = 0;
int minute = 0;
int hour = 0;
int frame_count = 0;
while(!WindowShouldClose()){
if(frame_count < FPS){
frame_count+=1;
}else{
frame_count = 0; // clean up
second+=1;//increase one second
if(second > 60){
second =0; //clean up
minute+=1; //increase one minute
if(minute > 60){
minute = 0; //clean up
hour+=1; // increase one hour
if(hour > 12){
hour = 0; //clean up
}
}
}
}
...
}
Complete code
#include "math.h"
#include "raylib.h"
/ constants :
//clock
const int WIN_WIDTH=400;
const int WIN_HEIGHT=400;
const int cx = (int)(WIN_WIDTH / 2);
const int cy = (int)(WIN_HEIGHT / 2);
// arms
const int SEC_ARM_LEN = 70;
const int MIN_ARM_LEN = 60;
const int HRS_ARM_LEN = 50;
const int FPS = 20;
int second = 0;
int minute = 0;
int hour = 0;
int frame_count = 0;
// functions
void draw_clock();
void draw_sec_arm(int tick);
void draw_min_arm(int tick);
void draw_hrs_arm(int tick);
int main(void){
InitWindow(SCR_WIDTH,SCR_HEIGHT,"Clock");
SetTargetFPS(FPS);
while(!WindowShouldClose()){
if(frame_count < FPS){
frame_count+=1;
}else{
frame_count = 0; // clean up
second+=1;//increase one second
if(second > 60){
second =0; //clean up
minute+=1; //increase one minute
if(minute > 60){
minute = 0; //clean up
hour+=1; // increase one hour
if(hour > 12){
hour = 0; //clean up
}
}
}
}
ClearBackground(WHITE);
draw_hrs_arm(hour);
draw_min_arm(minute);
draw_sec_arm(second);
draw_clock();
EndDrawing();
}
CloseWindow();
return 0;
}
void draw_clock(){
float thickness = 5.0f; // thickness of the ring
float inrad = 100.0f; // inner radius
float ourad = inrad + thickness; // outter radius
float stang = 0.0f; // start angle
float enang = 360.0f; // end angle
float seg = 360.0f; // segments
Vector2 cnt = {(int)cx,(int)cy}; // cast cx,cy to Vector2
DrawRing(cnt,inrad,ourad,stang,enang,seg,BLUE); // draw blue clock
DrawCircle(cx,cy,10,BLACK); // draw black circle with radius 10 pixels on the middle of the clock
}
void draw_sec_arm(int tick){
float thickness = 3.0f;
float ang = (tick * M_PI/30 - M_PI/2);
Vector2 start = {(float)cx,(float)cy};
Vector2 end;
end.x = (float)(cx + SEC_ARM_LEN * cos(ang));
end.y = (float)(cy + SEC_ARM_LEN * sin(ang));
DrawLineEx(start,end,thickness,RED);
}
void draw_min_arm(int tick){
float thickness = 7.0f;
float ang = (tick * M_PI/30 - M_PI/2);
Vector2 start = {(float)cx,(float)cy};
Vector2 end;
end.x = (float)(cx + MIN_ARM_LEN * cos(ang));
end.y = (float)(cy + MIN_ARM_LEN * sin(ang));
DrawLineEx(start,end,thickness,BLUE);
}
void draw_hrs_arm(int tick){
float thickness = 11.0f;
float ang = (tick * M_PI/6 - M_PI/2);
Vector2 start = {(float)cx,(float)cy};
Vector2 end;
end.x = (float)(cx + HRS_ARM_LEN * cos(ang));
end.y = (float)(cy + HRS_ARM_LEN * sin(ang));
DrawLineEx(start,end,thickness,DARKPURPLE);
}
Home work
ค้นคว้าหาวิธีนำเอาค่าของเวลาจากระบบปฏิบัติการมาใช้เป็นค่าเริ่มต้นสำหรับ เพื่อให้นาฬิกานี้สามารถใช้บอกเวลาได้จริง
ความคิดเห็น
แสดงความคิดเห็น