โปรแกรมตัวอย่าง การวาดและหมุน จานคำนวณ 360 องศา (Delphi) วันที่ 15/06/2009 22:00:22
ได้เคยกล่าวถึง การเขียนโปรแกรม คำนวณดาว โดยการดึงโมดูล Swiss Ephemeris มาใช้ แต่ลำพังสมผุสดาวที่ถูกต้องคงใช้ประโยชน์ได้เพียงหาตำแหน่งดาวมาลงจานหมุนจริง โปรแกรมจะสมบูรณ์ยิ่งขึ้นหากเราสามารถสร้าง จานคำนวณ ลงตำแหน่งดาว และทำให้ผู้ใช้งานสามารถหมุนจานได้เหมือนจริง ซึ่งในส่วนของการวาด จานคำนวณ นั้น ผมได้แนวคิดเบื้องต้นจากหนังสือเรื่อง Learn Programming and Visual Basic 2.0 with John Socha (First Authurised Asian Edition-1993) บทที่ 7 ในตัวอย่างโปรแกรมนาฬิกา ส่วนการใช้เมาส์ลาก จานคำนวณ นั้น ได้แนวคิดเบื้องต้นจากการทบทวนความรู้ในวิชาตรีโกณมิติสมัยเรียนมัธยมครับ
ในที่นี้จะขอกล่าวถึงแต่การวาดและหมุน จานคำนวณ 360 องศาเท่านั้น และเป็น จานคำนวณ แบบคร่าวๆ เหมือนในโปรแกรม Virgo04 การสร้างจานคำนวณแบบใน Virgo05-06 ทั้ง360 องศา และ 90 องศา รวมถึงการลงตำแหน่งดาวที่จะให้ถึงขนาดว่าดาวไม่ทับซ้อนกันนั้น เป็นเรื่องที่สลับซับซ้อนเกินกว่าที่จะอธิบายให้กระชับได้ จึงของดไว้ก่อน และต้องขออธิบายเฉพาะการเขียนโปรแกรมด้วย Delphi เท่านั้น เชื่อว่าคงเพียงพอที่จะเป็นแนวทางให้ผู้มีความรู้และความมุ่งมั่นสามารถนำไปประยุกต์ใช้ได้ตามสมควร
วิธีการวาด จานคำนวณ
ใน Delphi ผมจะ กำหนดขนาด Form ไว้พอประมาณ นำคอมโพเน้น TImage มาวางในฟอร์ม กำหนดขนาดไว้ที่ 389*389 pixels ใน Timage นี้จะมี Property ชื่อ Canvas ซึ่งมีคำสั่งในการวาดรูปต่างๆ อยู่แล้ว การวาดรูปหรือลากเส้นต่างๆ จะกำหนดพิกัดโดยให้ค่า X เป็นพิกัดแนวนอน หรือวัดจากซ้ายไปขวา และ Y เป็นพิกัดแนวตั้ง หรือวัดจากบนลงล่าง เมื่อเขียนค่าพิกัดจะใช้ตัวเลขเป็นคู่ โดยตัวแรกเป็น X ตัวถัดมาเป็น Y เช่น จุด (5,7) คือจุดที่ห่างจากขอบซ้ายของภาพ 5 จุด และห่างจากขอบบนของภาพ 7 จุด เป็นต้น
ในการวาดรูปวงกลม/วงรี จะใช้คำสั่ง Ellipse โดยมีรูปแบบ :
Ellipse(X1, Y1, X2, Y2: Integer);
ซึ่งเป็นการวาดวงกลม/วงรีภายในขอบเขตสี่เหลี่ยมที่มองไม่เห็น (เว้นแต่จะวาดสี่เหลี่ยมตามพิกัดดังกล่าวรอไว้ก่อนแล้ว) โดย X1, Y1 เป็นพิกัดของมุมซ้ายบน และ X2, Y2 เป็นพิกัดของมุมขวาล่างของสี่เหลี่ยมที่บรรจุวงกลม/วงรีที่เราวาดขึ้น ซึ่งถ้ามันเป็นสี่เหลี่ยมจตุรัส (ระยะห่าง X1 ถึง X2 เท่ากับระยะห่าง Y1 ถึง Y2 ) เราก็จะได้วงกลม มิฉะนั้นก็จะเป็นวงรีไป
แต่การวาดวงกลมในสามัญสำนึกของคนทั่วไป รวมถึงในโปรแกรมนี้ เราจะต้องกำหนดจุดศูนย์กลางของวงกลม แล้ววาดวงกลมที่มีค่ารัศมีแตกต่างกันออกไปให้ซ้อนเป็นชั้นๆ ในตอนเริ่มต้นโปรแกรมจึงเริ่มด้วยการหาค่าพิกัดจุดกึ่งกลางของ TImage แล้วเมื่อจะวาดวงกลมก็หาค่ามุมบนขวาและล่างซ้ายโดยเอาค่าจุดกึ่งกลางนี้ลบหรือบวกด้วยค่ารัศมีแล้วแต่กรณี
ในส่วนของการลากเส้นต่างๆ ในจานคำนวณ ได้แก่ เส้นแบ่งราศี/เรือนชะตา และเส้นขีดองศาทั้งหลาย ซึ่งเป็นการลากเส้นจากขอบจานไปยังจุดศูนย์กลาง (หรือจะคิดกลับกันว่าจากจุดศูนย์กลางไปยังขอบจานก็ตามแต่) ใน TCanvas จะมีคำสั่งในการลากเส้นอยู่แล้ว คือ LineTo ซึ่งมีรูปแบบ LineTo(X, Y: Integer); โดยก่อนใช้จะต้องใช้คำสั่ง MoveTo ในการกำหนดจุดเริ่มต้นการลากเส้นก่อน การลากเส้นในแนวตั้งหรือแนวนอนโดยทำมุม 0,90,180 และ 270 องศากับจุดศูนย์กลางเป็นเรื่องง่าย เพราะจุดศูนย์กลางก็หาจากการเอากรอบสี่เหลี่ยมจตุรัสของเราหารครึ่งทั้งพิกัด X และ Y ปลายเส้นอีกข้างหนึ่ง ก็จะมีพิกัด X หรือ Y เท่ากับพิกัดของจุดศูนย์กลางอยู่แล้ว พิกัดอีกอันหนึ่งก็หาจากการบวกลบง่ายๆ
แต่พอจะต้องลากเส้นที่เอียงเป็นมุมต่างๆ คราวนี้ก็ต้องอาศัยการคำนวณตรีโกณมิติเพิ่มขึ้นมา ที่ยุ่งขึ้นมาอีกหน่อย คือ ภาษาที่ใช้ในการเขียนโปรแกรมไม่ว่าภาษาใดๆ ส่วนใหญ่จะวัดมุมเป็นหน่วย เรเดียน (Radian) แต่ในโหราศาสตร์เราใช้หน่วยเป็นองศา จึงต้องมีการแปลงค่าเป็นพัลวัน สรุปเอาง่ายๆ ว่า
-พิกัด X ของปลายเส้น หาจาก พิกัด X จุดศูนย์กลาง ลบด้วยค่า รัศมีคุณค่า Sin ของมุม (วัดทวนเข็มนาฬิกา ซึ่งตรงกับการวัดมุมของโหราศาสตร์อยู่แล้ว)
-พิกัด Y หาจาก พิกัด X จุดศูนย์กลาง ลบด้วยค่า รัศมีคุณค่า Cos ของมุม
ตัวอย่างจากเช่น เมื่อค่ารัศมีเท่ากับ 140 มีตัวแปร ChartCenter เก็บค่าของจุดศูนย์กลาง และ SubIndex เก็บค่าความเอียงของมุมไว้แล้ว เมื่อเอาค่ามุมมาคูณด้วย pi หาร 180 ก็จะเป็นค่าเรเดียนที่ใช้กับฟังก์ชัน Sin และ Cos ได้ เขียนเป็นโค้ดได้ดังนี้
X1 := ChartCenter-Trunc(140*Sin(SubIndex*pi/180));
Y1 := ChartCenter-Trunc(140*Cos(SubIndex*pi/180));
การหมุน จานคำนวณ ด้วยเมาส์
ต้องขอท้าวความย้อนไปนิดนึงว่า ในโปรแกรมของผมจะเก็บค่าของดัชนีจานคำนวณไว้ในตัวแปรชื่อ Index เมื่อผู้ใช้คลิกและลากเมาส์ก็จะตรวจสอบว่าตำแหน่งของเมาส์นั้น มีพิกัด X Y เท่าไหร่ อยู่ในอาณาบริเวณของจานคำนวณหรือเปล่า ซึ่งถ้าค่า X หรือ Y ตัวใดตัวหนึ่งเท่ากับค่า X หรือ Y ของจุดศูนย์กลางอยู่แล้ว แสดงว่าเป็นเส้นตรงในแนวตั้งหรือแนวนอนที่เราสามารถหาค่าด้วยการบวกลบธรรมดา แต่ถ้าไม่ใช่ จะเป็นเส้นเอียง เราจะต้องจินตนาการว่าได้เกิดสามเหลี่ยมมุมฉากขึ้นมา โดยมีเส้นสมมติที่ลากจากตำแหน่งของเมาส์ไปยังจุดศูนย์กลางเป็นเส้นด้านตรงข้ามมุมฉาก แล้วมีเส้นสมมติอีกสองเส้นที่ลากออกจากจุดศูนย์กลางและจุดตำแหน่งเมาส์ในแนวตั้งและแนวนอน ไปบรรจบกันเป็นมุมฉากที่ใดที่หนึ่ง คราวนี้ก็จะหาระยะทางจากเมาส์ถึงจุดศุนย์กลางด้วยสูตรที่ว่า
Z 2 = X 2 + Y 2
“ค่ายกกำลังสองของด้านตรงข้ามมุมฉาก มีค่าเท่ากับผลรวมของค่ายกกำลังสองของด้านประกอบมุมฉากทั้งสอง”
ขอให้ดูโค้ดประกอบนะครับ เมื่อได้ค่ายกกำลังสองของด้านตรงข้ามมุมฉาก นำมาถอดแสควร์รูทก็จะได้ค่าความยาวจริง เขียนเป็นโค้ดปาสกาลได้ประมาณนี้
FindDistance := Trunc(Sqrt(sqr(X-ChartCenter)+sqr(y-ChartCenter)));
เมื่อเทียบดูแล้วถ้าอยู่ในอาณาบริเวณของจานหมุนก็ผ่านขั้นแรก
ทีนี้พอเราจะหาค่ามุมของเส้นสมมติที่ลากจากตำแหน่งเมาส์ไปยังจุดศูนย์กลาง ซึ่งหาได้จากฟังก์ชัน Arctan2 เขียนเป็นโค้ดได้ ดังนี้
RotateAngle1 := ArcTan2((X-ChartCenter),(y-ChartCenter))*180/pi;
ส่วนถ้าจะถามว่า ArcTan2 คืออะไร ทำไมมีทั้ง ArcTan กับ ArcTan2 นี่ คนรู้ตรีโกณงูๆ ปลาๆ แบบผมคงตอบเองไม่ได้ แต่จะขดคัดลอกคำอธิบายจาก Help File ของ Delphi มาอธิบายประกอบ ดังนี้
ArcTan function
ArcTan calculates the arctangent of the given number.
Unit
System
Category
trigonometry routines
function ArcTan(X: Extended): Extended;
Description
Calculate other trigonometric functions using Sin, Cos, and ArcTan in the following expressions:
Tan(x) = Sin(x) / Cos(x)
ArcSin(x) = ArcTan (x/sqrt (1-sqr (x)))
ArcCos(x) = ArcTan (sqrt (1-sqr (x)) /x)
ArcTan2 function
ArcTan2 calculates the arctangent angle and quadrant of the given number.
Unit
Math
Category
trigonometry routines
function ArcTan2(Y, X: Extended): Extended;
Description
The ArcTan2 function calculates ArcTan(Y/X), and returns an angle in the correct quadrant. The values of X and Y must be between -264 and 264. In addition, the value of X can’t be 0. The return value will fall in the range from -Pi to Pi radians.
ค่ามุมนี้ ผมจะหานับตั้งแต่เริ่มกดปุ่มเมาส์ซ้ายค้างไว้ แล้วเลื่อนเมาส์เรื่อยไป ได้ผลต่างของมุมเท่าไหร่ก็เอาไปบวกหรือลบกับค่า Index ที่มีอยู่เดิม แล้วสั่งวาดจานคำนวณใหม่ทุกระยะ จนกว่าผู้ใช้จะปล่อยนิ้วจากปุ่มเมาส์ซ้าย
รายละเอียดทั้งหมดอยู่ใน Source Code ข้างล่าง หรือ Download ได้ที่ลิงค์ข้างท้าย ทั้งนี้ยินดีรับทราบปัญหาหรือข้อคิดเห็น เพิ่มเติมจากนักพัฒนาโปรแกรมทุกท่าน
Source Code สำหรับการสร้าง Form
object Form1: TForm1
Left = 345
Top = 126
BorderIcons = [biSystemMenu, biMinimize]
BorderStyle = bsSingle
Caption = 'ตัวอย่างการวาดและหมุนจานคำนวณ 360 องศา'
ClientHeight = 438
ClientWidth = 391
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
Position = poDesktopCenter
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 14
object Image1: TImage
Left = 0
Top = 0
Width = 389
Height = 389
OnMouseDown = Image1MouseDown
OnMouseMove = Image1MouseMove
OnMouseUp = Image1MouseUp
end
object Label1: TLabel
Left = 19
Top = 400
Width = 85
Height = 14
Caption = 'ค่าดัชนี = 90 องศา'
end
object BitBtn1: TBitBtn
Left = 302
Top = 396
Width = 80
Height = 27
TabOrder = 0
Kind = bkClose
end
end
Source Code สำหรับโปรแกรม
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, Math, StdCtrls, Buttons;
type
TForm1 = class(TForm)
Image1: TImage;
BitBtn1: TBitBtn;
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure Image1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
ChartSize, ChartCenter, X1,X2,Y1,Y2 : Integer;
Index, SubIndex, RotateAngle1, RotateAngle2 : Real;
DialRotate : Boolean;
Function FindDistance(x,y : Integer): Integer;
Function Normal360(x : Real):Real;
Procedure DrawDial360;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
Function TForm1.Normal360(x : Real):Real;
begin
Repeat
if X<0 then X := X+360;
if X>=360 then X := X-360;
until (X>=0)and(X<360);
Result := X;
end;
Function TForm1.FindDistance(x,y : Integer): Integer;
begin
if X= ChartCenter then FindDistance := abs(y-ChartCenter) else if y= ChartCenter then FindDistance := abs(X-ChartCenter)
else FindDistance := Trunc(Sqrt(sqr(X-ChartCenter)+sqr(y-ChartCenter)));
end;
Procedure TForm1.DrawDial360;
Var I,E : Integer;
begin
With Image1.Canvas do begin
Pen.Color := RGB ( 0, 0, 85);
Image1.Canvas.Brush.Style := bsSolid;
Rectangle( 0, 0,ChartSize,ChartSize);
Pen.Color := RGB (255, 0,64);
Image1.Canvas.Brush.Style := bsClear;
Ellipse(ChartCenter-140, ChartCenter-140, ChartCenter+140, ChartCenter+140);
Pen.Color := ClPurple;
Ellipse(ChartCenter-120, ChartCenter-120, ChartCenter+120, ChartCenter+120);
Ellipse(ChartCenter-10, ChartCenter-10, ChartCenter+10, ChartCenter+10);
for I:=1 to 12 do begin // 12 Main Lines
SubIndex := Index+(I*30); Normal360(SubIndex);
X1 := ChartCenter-Trunc(140*Sin(SubIndex*pi/180));
Y1 := ChartCenter-Trunc(140*Cos(SubIndex*pi/180));
X2 := ChartCenter-Trunc(10*Sin(SubIndex*pi/180));
Y2 := ChartCenter-Trunc(10*Cos(SubIndex*pi/180));
Case I of
3 : Pen.Color := RGB(0,0,255); // First House
12 : Pen.Color := RGB(255,0,0); // Tenth House
else Pen.Color := ClPurple;
end;
Moveto(X1,Y1);
Lineto(X2,Y2);
if I in [3,6,9,12] then begin // Arrow on Square Point
X2 := ChartCenter-Trunc(120*Sin((SubIndex+5)*pi/180));
Y2 := ChartCenter-Trunc(120*Cos((SubIndex+5)*pi/180));
Moveto(X1,Y1); Lineto(X2,Y2);
X2 := ChartCenter-Trunc(120*Sin((SubIndex-5)*pi/180));
Y2 := ChartCenter-Trunc(120*Cos((SubIndex-5)*pi/180));
Moveto(X1,Y1); Lineto(X2,Y2);
end;
end;
Pen.Color := RGB(255,0,64);
for I:=0 to 3 do begin // Arrow at 4 of 45 Degree
SubIndex := Index+45+(I*90); Normal360(SubIndex);
X1 := ChartCenter-Trunc(140*Sin(SubIndex*pi/180));
Y1 := ChartCenter-Trunc(140*Cos(SubIndex*pi/180));
X2 := ChartCenter-Trunc(120*Sin(SubIndex*pi/180));
Y2 := ChartCenter-Trunc(120*Cos(SubIndex*pi/180));
Moveto(X1,Y1);
Lineto(X2,Y2);
X2 := ChartCenter-Trunc(120*Sin((SubIndex+2)*pi/180));
Y2 := ChartCenter-Trunc(120*Cos((SubIndex+2)*pi/180));
Moveto(X1,Y1); Lineto(X2,Y2);
X2 := ChartCenter-Trunc(120*Sin((SubIndex-2)*pi/180));
Y2 := ChartCenter-Trunc(120*Cos((SubIndex-2)*pi/180));
Moveto(X1,Y1); Lineto(X2,Y2);
end;
for I:=1 to 71 do // Every 5 Degree
if I in [6,9,12,18,24,27,30,36,42,45,48,54,60,63,66] then else
begin
SubIndex := Index+(I*5); Normal360(SubIndex);
if (I mod 3)=0 then E:=125 else E:=130;
X1 := ChartCenter-Trunc(140*Sin(SubIndex*pi/180));
Y1 := ChartCenter-Trunc(140*Cos(SubIndex*pi/180));
X2 := ChartCenter-Trunc(E*Sin(SubIndex*pi/180));
Y2 := ChartCenter-Trunc(E*Cos(SubIndex*pi/180));
Moveto(X1,Y1);
Lineto(X2,Y2);
end;
for I := 1 to 359 do // Mini Lines Every Degree
if (I mod 5) = 0 then else
begin
SubIndex := Index+I; Normal360(SubIndex);
X1 := ChartCenter-Trunc(140*Sin(SubIndex*pi/180));
Y1 := ChartCenter-Trunc(140*Cos(SubIndex*pi/180));
X2 := ChartCenter-Trunc(135*Sin(SubIndex*pi/180));
Y2 := ChartCenter-Trunc(135*Cos(SubIndex*pi/180));
Moveto(X1,Y1);
Lineto(X2,Y2);
end;
end; // With
Label1.Caption := 'ค่าดัชนี = '+FloatToStr(index)+' องศา';
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ChartSize := Image1.Width;
ChartCenter := Round(ChartSize/2);
Index := 90;
DrawDial360;
end;
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if FindDistance(x,y) in [10..140] then begin
DialRotate := True;
RotateAngle1 := ArcTan2((X-ChartCenter),(y-ChartCenter))*180/pi;
Screen.Cursor := crHandPoint;
Image1.Cursor := Screen.Cursor;
end;
end;
procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if DialRotate and (FindDistance(x,y) in [10..140]) then begin
RotateAngle2 := ArcTan2((X-ChartCenter),(y-ChartCenter))*180/pi;
Screen.Cursor := crHandPoint;
Image1.Cursor := Screen.Cursor;
Index := Index+(RotateAngle2-RotateAngle1);
Index := Normal360(Index);
DrawDial360;
RotateAngle1 := RotateAngle2;
end;
end;
procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
If DialRotate then begin
DialRotate := False;
Screen.Cursor := crDefault;
Image1.Cursor := Screen.Cursor;
end;
end;
end.
เพิ่มเติม 15 ธันวาคม 2549
ดาวน์โหลด Source Code
สินค้าที่เกี่ยวข้องจาก ร้านค้าออนไลน์
|