前頁では、幾何要素/表記要素フィーチャの円について見ましたので次は円弧について見てみます。と、その前に、クリッピングについて考えておきます。クリッピングとは、対象画面からはみ出る部分をカットして、表示する図形だけを描画するための処理です。既に、点・線分をクリッピングする関数をまとめた UnitClipping.pas を含めていますが、少し追加しておきます。2点のクリッピングチェックと同様の、3点のクリッピングチェック、4点のクリッピングチェックを追加しておきます。
UnitClipping.pas |
function Clip3Point(x1,y1,x2,y2,x3,y3,wx1,wy1,wx2,wy2:double):Integer ;
function Clip4Point(x1,y1,x2,y2,x3,y3,x4,y4,wx1,wy1,wx2,wy2:double):Integer ;
function Clip1xyPoint(x,y,sx,sy,wx1,wy1,wx2,wy2:double):Integer ; |
まずは、点マーカから。
点マーカの No.3 dot は指定ドット半径の円に外接する正方形が画面範囲内にあるかどうかを判定、それ以外は、回転する2.5mm×倍率四方の正方形が画面範囲内にあるかどうかを判定、することにします。後者の場合、正方形が回転するという事は、その正方形の外接円を想定し、更にその外接円に外接する正方形(1辺が2.5mm×倍率×√2)を想定してチェックします。第1の粗チェックという訳です。その後は点マーカ内で線分描画用手続きを実行していますので、そちらでクリッピングされるであろう、という事にします。
UnitDataGraph.pas |
// 点マーカの表示
procedure DisplayTenMarker(lay,col,mc:integer;px,py,an,sc:double);
const
DOT_SIZE = 5 ; // 点マーカ<dot>の大きさ(半径値)
var
・・・
begin
// 点マーカ・クリッピングチェック
if (mc = 3) then begin
// dot
sz := DOT_SIZE/mm_dot ;
end
else begin
// dot以外
sz := 2.5*sc*Sqrt(2.0) ;
end;
if (Clip1xyPoint(px,py,sz,sz, WndX1,WndY1,WndX2,WndY2) = 2) then exit;
DisplayLayCol1(lay,col,c,cc); // 色
gp.G_SetWidth(1);
//
sz := 2.5/2.0*sc ;
・・・ |
次に「線分の表示」では、クリッピングのチェックではなく処理をして、得られる座標での描画を行うようにします。
UnitDataGraph.pas |
// 線分の表示・サブ1:実線
procedure DisplayLineSub1(px1,py1,px2,py2:double);
var
x1,y1,x2,y2 : integer ;
begin
if (ClipLine(px1,py1,px2,py2, WndX1,WndY1,WndX2,WndY2) = 2) then exit;
Z2D(x1,y1, px1,py1);
Z2D(x2,y2, px2,py2);
gp.G_Line(x1,y1,x2,y2);
end;
// 線分の表示・サブ2:線種指定
procedure DisplayLineSub2(l:integer;var i:integer;var d1:double;px1,py1,px2,py2:double);
var
x1,y1,x2,y2 : integer ;
a,sa,ca,d,p,wx1,wy1,wx2,wy2 : double ;
fl : Boolean ;
begin
if (ClipLine(px1,py1,px2,py2, WndX1,WndY1,WndX2,WndY2) = 2) then exit;
a := Angle(px2-px1, py2-py1);
d := Dist(px1,py1, px2,py2);
・・・ |
ベタですがこんな感じでしょうか。ClipLine()は引数の最初の4つの変数内容(px1,py1,px2,py2)を変更しますが、この「線分の表示・サブ」では変数px1,py1,px2,py2はローカル変数として処理し、呼び出し側には値を返しませんから影響はありません。
ただ、折線などの場合、線種パターン・ピッチを継続しますが、複数の線分全てが画面範囲内にある場合と、一部分が画面範囲外になった場合の表示が異なってしまいます。画面範囲外の部分は、線種パターン・ピッチを考慮しないためです。 これを防ぐには、画面範囲外の部分の計算を行って調整を行うか、或いは、線種パターン毎にクリッピング処理を行うかになりますが、後者の場合は描画速度がかなり低下するであろうという懸念はありますし、前者の場合も描画はしないだけで計算量が増すでしょう。見え方が変わる程度で実害はほとんど無いと思いますのでここではこのまま無視します。
次に、円です。円は内接多角形として描画しているので、上記でそのままクリッピングされるのですが、例えば、100角形状態の円が完全に画面範囲外である場合には、100回分の線分のクリッピング処理も時間的に無駄となりますから、円に外接する正方形(部分図以下の場合には変形されて平行四辺形状態になる可能性があります)での粗チェックを掛けておいた方がいいでしょう。
UnitDataGraph.pas |
// 円の表示
procedure DisplayCircle(lay,col,ltp,wid:integer;px,py,pr:double;t:TMatrix);
const
ARC_LENGTH = 15.0 ; // 補間線分長さ[dot相当値]
ARC_MIN = 8 ;
ARC_MAX = 360 ;
var
c,cc,l,w, i,j : integer ;
ww, d1, a,aa,n, x1,y1,x2,y2,x3,y3,x4,y4 : double ;
fl : Boolean ;
begin
// 円・クリッピングチェック
x1 := px-pr; y1 := py-pr; MtxXY(x1,y1, x1,y1, t);
x2 := px+pr; y2 := py-pr; MtxXY(x2,y2, x2,y2, t);
x3 := px+pr; y3 := py+pr; MtxXY(x3,y3, x3,y3, t);
x4 := px-pr; y4 := py+pr; MtxXY(x4,y4, x4,y4, t);
if (Clip4Point(x1,y1,x2,y2,x3,y3,x4,y4, WndX1,WndY1,WndX2,WndY2) = 2) then exit;
x1 := 0.0 ;
y1 := 0.0 ;
x2 := pr*Cos(0.25*Pi) ;
y2 := pr*Sin(0.25*Pi) ;
MtxXY(x1,y1, x1,y1, t);
・・・ |
CADソフトの場合、(たとえ一部でも)描画したかしなかったか、をデータとして登録しておき、端点交点要素検索などの際に、描画したものだけを対象として検索する、というような事をしたりしますが、煩雑になりますのでここでは処理していません。検索は、全データを対象にするのが簡単ですが、データ量が多くなってくると、検索に掛かる時間もどんどん増えていきます。特に、線分等の端点を拾う(スナップする)のに時間が掛かったりすると、利用者のストレスがたまってしまいます。
それでは、円弧について見ていきます。
幾何要素/表記要素|円弧 |
|
パラメータ | 型 | 説明 | 範囲 |
Layer | Int | レイヤコード |
|
Color | Int | 色コード |
|
Type | Int | 線種コード |
|
line_width | Int | 線幅コード |
|
Center_x | double | 中心X座標 |
double(64bits)の
範囲(有効桁15桁) |
Center_y | double | 中心Y座標 |
double(64bits)の
範囲(有効桁15桁) |
Radius | double | 半径 | 0<半径<1.0×1015 |
Direction | Int | 向きフラグ(0:反時計廻り,1:時計廻り) |
|
start_angle | double | 始角 | 0≦始角<360 |
end_angle | double | 終角 | 0≦終角<360 |
備考
・角度は水平右側が0度、単位は度とする。
・始角と終角に同一の角度は指定できない。 |
SXF Ver.3.1実装規約では線分等同様、円弧について何も無さそうです。
それでは円弧のデータ構造を決めます。
UnitData.pas |
type
・・・
TDataArc = record // 幾何要素/表記要素|円弧
exf : Boolean ; // 存在フラグ(True:有り False:無し)
Layer : Integer ; // レイヤ(1〜256)
Color : Integer ; // 色 (0:レイヤ色 1〜256)
Ltype : Integer ; // 線種 (0:レイヤ線種 1〜32)
line_width: Integer ; // 線幅 (0:レイヤ線幅 1〜16)
Center_x : double ; // 中心点X座標
Center_y : double ; // 中心点Y座標
Radius : double ; // 半径
Direction : Integer ; // 向き (0:反時計廻り,1:時計廻り)
start_angle : double ; // 始角[°]
end_angle : double ; // 終角[°]
end;
TFukugoZukeiDef = record // 構造化要素|複合図形定義
exf : Boolean ; // 存在フラグ(True:有り False:無し)
name : string ; // 複合図形名
Flag : Integer ; // 種別フラグ
cnt : Integer ; // 配置数
・・・
mArc : array of TDataArc; // 幾何要素|円弧
mArcN : Integer ; // 数
mFzk : array of TDataFukugoZukei; // 構造化要素|複合図形配置
mFzkN : Integer ; // 数
end;
TDataClass = class
public
{ Public 宣言 }
・・・
dArc : array of TDataArc; // 表記要素|円弧
dArcN: Integer ; // 数
・・・ |
データ追加用の関数は
function AddDataArc(s:string;lay,col,ltp,wid,dir:integer;cx,cy,cr,sa,ea:double) : Boolean; |
のようにします。線分等と同様、最初に「データ追加先の複合図形名(幾何要素) null:用紙へ追加(表記要素)」を指定するようにしています。
UnitData.pas |
// 円弧 データ項目の追加登録
function TDataClass.function AddDataArc(s:string;lay,col,ltp,wid,dir:integer;cx,cy,cr,sa,ea:double) : Boolean;
var
m : integer ;
begin
Result := False;
if (lay < 1) or (lay > zLayN) then lay := 1 ;
if (col < 0) or (col > zColN) then col := 0 ;
if (ltp < 0) or (ltp > zLtpN) then ltp := 0 ;
if (wid < 0) or (wid > zWidN) then wid := 0 ;
if (cr < LIMIT10) then exit ; // 半径が <= 0
if (dir <> 0) then dir := 1 ;
sa := SetAngle(sa);
ea := SetAngle(ea);
if (Abs(ea-sa) < LIMIT10) then exit ; // 始角 = 終角
if (s = '') then begin
// 用紙へ追加
if (AddDataOrder(-1,5,dArcN)) then begin
try
Inc(dArcN);
if ((dArcN mod 200) = 1) then SetLength(dArc, dArcN+199);
with dArc[dArcN-1] do begin
exf := True ;
Layer := lay ;
Color := col ;
Ltype := ltp ;
line_width := wid ;
Center_x := cx ;
Center_y := cy ;
Radius := cr ;
Direction := dir ;
start_angle := sa ;
end_angle := ea ;
end;
Result := True ;
AddLayerCnt(lay,col,ltp,wid,-1);
except
Dec(dArcN);
Dec(dOrdN);
end;
end;
end
else begin
m := FZukeiNameCheck(0,s);
if (m = 0) then exit ; // 追加先の複合図形名は無いのでエラー
with fDef[m-1] do begin
if (AddDataOrder(m,5,mArcN)) then begin
try
Inc(mArcN);
if ((mArcN mod 200) = 1) then SetLength(mArc, mArcN+199);
with mArc[mArcN-1] do begin
exf := True ;
Layer := lay ;
Color := col ;
Ltype := ltp ;
line_width := wid ;
Center_x := cx ;
Center_y := cy ;
Radius := cr ;
Direction := dir ;
start_angle := sa ;
end_angle := ea ;
end;
Result := True ;
AddLayerFCnt(lay,col,ltp,wid,-1);
except
Dec(mArcN);
Dec(mOrdN);
end;
end;
end;
end;
end; |
さて、これで円弧データの登録は出来るようになりました。
次に、円弧の描画についてですが、円の描画を利用します。
UnitDataGraph.pas |
// 円弧の表示
procedure DisplayArc(lay,col,ltp,wid,dir:integer;px,py,pr,sa,ea:double;t:TMatrix);
const
ARC_LENGTH = 15.0 ; // 補間線分長さ[dot相当値]
ARC_MIN = 8 ;
ARC_MAX = 360 ;
var
c,cc,l,w, i,j : integer ;
ww, d1, a,aa,n, x1,y1,x2,y2,x3,y3,x4,y4 : double ;
fl : Boolean ;
begin
// 円・クリッピングチェック
x1 := px-pr; y1 := py-pr; MtxXY(x1,y1, x1,y1, t);
x2 := px+pr; y2 := py-pr; MtxXY(x2,y2, x2,y2, t);
x3 := px+pr; y3 := py+pr; MtxXY(x3,y3, x3,y3, t);
x4 := px-pr; y4 := py+pr; MtxXY(x4,y4, x4,y4, t);
if (Clip4Point(x1,y1,x2,y2,x3,y3,x4,y4, WndX1,WndY1,WndX2,WndY2) = 2) then exit;
if (dir <> 0) then swapD(sa,ea);
aa := ea - sa ;
if (aa < 0.0) then aa := aa + 360.0 ;
x1 := 0.0 ;
y1 := 0.0 ;
x2 := pr*Cos(0.25*Pi) ;
y2 := pr*Sin(0.25*Pi) ;
MtxXY(x1,y1, x1,y1, t);
MtxXY(x2,y2, x2,y2, t);
d1 := Dist(x1,y1,x2,y2);
a := ARC_LENGTH / (d1 * mm_dot) ;
n := Int(aa/180.0*Pi/a) ;
if (n < ARC_MIN) then n := ARC_MIN ;
if (n > ARC_MAX) then n := ARC_MAX ;
a := aa/180.0*Pi / n ;
DisplayLayCol2(lay,col,ltp,wid, c,cc,l,w,ww); // 色・線種・線幅
//
fl := False ;
if (CData.zLtp[l-1].Segment = 0)
or(CData.zLtp[l-1].SpaceMax * mm_dot < 2.0)
or(CData.zLtp[l-1].SpaceMax * mm_dot < ww ) then
fl := True ;
i := 0 ;
d1 := 0.0 ;
aa := sa/180.0*Pi ;
x1 := px + pr*Cos(aa);
y1 := py + pr*Sin(aa);
MtxXY(x1,y1, x1,y1, t);
for j:=1 to Round(n) do begin
aa := aa + a;
x2 := px + pr*Cos(aa);
y2 := py + pr*Sin(aa);
MtxXY(x2,y2, x2,y2, t);
if (fl) then
DisplayLineSub1(x1,y1,x2,y2)
else
DisplayLineSub2(l,i,d1, x1,y1,x2,y2) ;
x1 := x2 ;
y1 := y2 ;
end;
end; |
最初のクリッピング粗チェックは、粗めのチェックですので、円として計算しています。円弧に応じて粗チェックを狭めるようにするのも良いですが意外と面倒です。それによってプログラムが煩雑になったり、計算による速度低下があってもうまくありませんので、ここでは手間を省いています。
向きフラグが時計廻りの場合、始角と終角を入れ替えています。始角と終角から円弧角(中心角;変数 aa)を算出し、それを何分割するかを求め、始角からスタートし円弧角分の線分補間による円弧の作図を行います。
あとは、全データ表示に、これを呼び出す部分を追加し、円弧データを追加する部分を記述してテスト実行をしてみます。
Unit1.pas |
・・・
// 円弧データ登録テスト
col := 1 ;
ltp := 1 ;
wid := 1 ;
for i:=1 to 10 do begin
lx1 := i*5 ;
CData.AddDataArc('', 1,col,ltp,wid,0, 80.0,0.0,lx1,0.0,180.0);
CData.AddDataArc('部分図A', 1,col,ltp,wid,1, 80.0,0.0,lx1,0.0,180.0);
Inc(col);
if (col > CData.zColN) then col := 1 ;
Inc(ltp);
if (ltp > CData.zLtpN) then ltp := 1 ;
Inc(wid);
if (wid > CData.zWidN) then wid := 1 ;
end;
・・・ |
円弧は、始角0°・終角180°のものを幾つか作図しています。用紙上のものは向きを0:反時計廻り、部分図Aのものは向きを1:時計廻り、として作図していますので、上図のようになっています。見て分かるとおり、部分図Aのものは、始角180°・終角0°・向き0:反時計廻り、としても同じように見えます。
それでは、ここまでのテストプログラムです。実行ファイル、gdiplus.dll、gdipフォルダは入っていません。ソースのみです。
|