プログラムソースリスト集 C言語


【 掲載プログラム 】

★ 当月ネット接続時間算出←新出

★ 長いファイル名を一括変更(BorlandC)

★ 複数ファイルの名の先頭に文字を加える(2)
(これもプログラミングだけの話)

★ ディスクのクラスタのサイズを求める
(これは珍しくプログラミングだけの話)

★ 流線形物体表面圧力分布 (本編では引数と戻り値にポインタを使っているが、構造体を使う方法もある 2002.10.10)

★ バット打撃時の衝撃のない打点を計算する
★ 物体立体視

※掲載ソースリスト内の
/* から */ までの間はコメント文であり、何の処理もしないというCコンパイラの決まりに則って、プログラミングの簡単な注釈を加えている。
――――――――――――

★ 当月ネット接続時間算出

■内容
接続時間の気になるダイヤルアップ接続の場合です。応用するとダイヤルアップ接続の人には便利だと思います。

通常のネット接続ソフトには、接続内容を追加記録し続けるログファイル(拡張子logやdat)をクライアントPCのハードディスク内に保存する機能が付いています。

このプログラムは、わざわざネット接続をしてプロバイダーHPの接続時間の確認ページに行くことなく、オフラインのまま直接自分のPC内のログファイルを使ってその月の接続時間が分かるものです。

自分は@niftyアシスタントを使っています。@niftyアシスタントは通常のインストールでCドライブの
\Program Flies\@nifty assistantディレクトリ(フォルダ)に
userlog.dat
というファイル名でログファイルを保存しています。
記録すべきイベント(接続と中断など)が発生した時には、このログファイルに以下のようなデータが追加されていきます。
[@niftyアシスタント例]
2.3*.2.8*, 接続,2003/1/1,23:35:22,"0 053 640-*****",0,"Conexant  HCF V90 56K Data Fax PCI Modem","ICD*****"

ログファイルで以下の事を確かめます。
@ログファイルの中で各イベントについての複数データの区切り文字は何か。

…区切り文字が空白なら、
sscanf()が使えるが、それ以外なら、strchr(),strtok(),memcpy(),strcpy()を使用しなければいけない。

Aログファイルの中に欲しいデータ(→*1)がどこにあるか
(*1)各イベント(接続と中断など)、月、時刻

…配列変数に必要なデータを入れていきます。

B何というイベントがどんな動作を表しているか
[@niftyアシスタント例]接続 接続中止 接続失敗 AP更新 切断 切断* 自動切断
…if()による分岐の条件内容が決まります。

C時刻データの形式はどうか
[@niftyアシスタント例]
6:06:45 , 16:06:45

従って@ABCを知ればプログラムの流れと、使う命令が自ずと分かって自分でプログラムを作ることができます。
■実行
net.c としてコンパイルすれば、
DOS窓などからnet と打込んでリターンキーだけです。

[ソースリスト]

/*
net.c
====当月ネットアクセス時間算出=====
■文字列中のコンマを区切りとして読み込む工夫
gets()は復帰改行だけ、sscanf()は空白と復帰改行が区切り印のため。
使っている strtok(log,",") は発見位置を'\0'に置換する以外、sscanf(log,"%[^,]",anystr) と同じ動きをする。
(strtok()の返りはsscanf()内のanystr)

strtok()は繰り返すので返り値は保存されない。その為のmemcpy()。(strcpy()は1番目データしか保存されない)*/

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<direct.h>/* to get dir by getcwd()*/
#include<time.h>/* to get month at that time by time() and localtime()*/
#include <stdlib.h>/* to get file size by stat()*/

#define N     160
#define dN    20

struct   Srct_time
 {  int time[3];
    char day[10];
  };
 
struct Srct_time  Srct;

struct Srct_time exec(char dat[][N>>1])/*  *dat[] だとコンパイル警告出る*/
{
int               i, posi=0;
char              cT[3][3]={""};

/*以下は時刻の入った
dat[3]に
"6:06:45"と"16:06:45"
のように1桁と2桁がある為の処理*/
sscanf(dat[3],"%[^:]",cT[0]);
if(strlen(cT[0])==2) posi=1;
strncpy(cT[1],dat[3]+2+posi,2);
strncpy(cT[2],dat[3]+5+posi,2);

/*年月日データを文字列のまま代入*/
strncpy(Srct.day,dat[2],strlen(dat[2]));

/*時刻データを代入*/
for(i=0;i<3;i++)
  Srct.time[i]=atoi(cT[i]);
     /*[0]:hours, [1]:minutes, [2]:seconds*/

return(Srct);
}


.


void proc(void)
{
 int           j, month ,clen  ,n=1;
unsigned int  hour,mint,secs;
 long          total=0;
 char   smth[5]  ,log[N],dat[8][N>>1], 
        *cache   ,*strp;

 time_t *t=0, lot=time(t);
                /* time_t is type-long */

 FILE *fp=fopen("userlog.dat", "r");

 struct tm        *STm;
 struct Srct_time  On, Off;

/*以下の3命令によって文字変数smthに本プログラム実行時の月([例] 2月)を
/2/
の形で代入する*/
STm=localtime(&lot);
month=STm->tm_mon +1;/* "+1" is because of putting int(0-11) in tm_mon */
sprintf(smth ,"%s%d%s", "/",month,"/");/* int型数値をそのまま文字に置き換えている*/
printf("%s",smth);

while(feof(fp)==0)
 {
 memset(dat,'\0',sizeof(dat));
 fgets(log,N,fp);
 for(j=0;j<8;j++)
    {
    strp=strchr(log,',');
    cache=strtok(log  ,",") ;
    clen=strlen(cache);
    memcpy(dat[j]    , cache  , clen);
    strcpy(log , strp+1);/*次のdat[j+1]への代入の為、dat[j]内に入ったデータをlog[]から削除*/
     }

 
 if(strstr(dat[2],smth)!=NULL)
    {
       if(strcmp(dat[1],"接続")==0)
       {
         printf("\n\nnum:%d\t月一致\n",n++);
         On=exec(dat);
         printf("始 [%02d:%02d:%02d]\t",On.time[0],On.time[1],On.time[2]);
        }
    if(strstr(dat[1],"切断")!=NULL)/*動作内容のデータdat[1]内に"切断"の文字があれば以下の処理*/
       { 
         Off=exec(dat); 
         printf("了 [%02d:%02d:%02d]\t",Off.time[0],Off.time[1],Off.time[2]);
         if(strcmp(Off.day,On.day)>0)
    Off.time[0]+=24;/*1日またいだ時の処理…時刻に24を加える*/

         for(j=0;j<3;j++)
            total+=pow(60,2-j)*(Off.time[j]-On.time[j]);
         }
    }
for(j=0;j<8;j++) printf("%s_",dat[j]);

 }
 hour=total/3600;
 mint=total/60-60*hour;
 secs=total-3600*hour-60*mint;
 printf("\n今月( %d 月)、現在までのネット接続時間は\n",month);
 printf("\t%2d 時間 %2d 分 %2d 秒\n",hour,mint,secs);
 fcloseall(); 
 return;
}

.

main()
{
char     dir[40]={"\\progra~1\\@nifty~1"},
  pushdir[40];

getcwd(pushdir, 40);

while(chdir(dir)!=0)
  {
   printf("ディレクトリ指定が誤っている\n");
   printf("一度、ディレクトリ名を入れてくれよ..>");
   gets(dir);
}

proc();
if(chdir(pushdir)==0)
        printf("めでたく終了\n");

return;
}
――――――――――――

★長いファイル名を一括変更(BorlandC)

■<renl.c>摘要
●本プログラムはファイル[例]<*.smp>の名の先頭に任意の文字列を追加する。

●拡張子は実行時に任意に指定できる。

●ファイル名の全長は255バイト(半角で255字)まで付けられる。

■コンパイルまでの手続き
●本ソースファイルは拡張子 c として保存する。その名を仮に<renl.c>とする。

●実行ファイル<renl.exe>の作成…本ソースファイル<renl.c>をコンパイルする。その時<wildargs.obj>とリンクする。(意味説明は下の「■ワイルドカード'*'を使う」に)

■<renl.exe>の実行
対象拡張子のファイル([例]:拡張子がsmpのもの) のあるディレクトリにDOSコマンドのcdによってカレントを移す。
[例]<.smp>の置かれている場所が
 d:\ ならば、DOSコマンドラインで
d:
cd \Borland\Bcc55

とする

DOSコマンドライン上から

renl *.smp
と打つ。

■<renl.exe>内部の流れ
●DOSコマンドのdir実行して得られる各smpファイルの情報を<fileinfo.dat>という新ファイルに入れる。

●<fileinfo.dat>を開き、各smpファイルの6番目の情報(元の長いままのファイル名)を取り込む。

●文字列の入力を待つ。
●入力の終わった文字列をファイル名の先頭に付けて新ファイル名とする。

■コンパイラ動作のための初期設定
BorlandCコンパイラbcc32.exe の実行前には LsiCと同様に
@コンパイラのパス(たいてい\〜\bin)を set path= で追加設定
A環境設定ファイル<bcc32.cfg>の設定
という初期設定が必要です。詳細は
○付属の<readme.txt>

○http://www.neco.nu/gohodoji/bccfaq.html
「BorlandC++Compiler5.5FAQ」

等を参考にして下さい。



■ワイルドカード'*'を使う
BorlandCによってコンパイルした実行ファイルで、ワイルドカード'*'を使いたいなら、実行ファイル作成時に<wildargs.obj>というオブジェクトファイルをリンクする必要がある。その方法は以下の通り。


まず、カレントディレクトリを<renl.c>の場所に合わせる。具体的には、
<renl.c>の置かれている場所が
 C:\Borland\Bcc55 ならば、DOSコマンドラインで
c:
cd \Borland\Bcc55

とするのである。

それから、コンパイルとリンクをする。
<wildargs.obj>が
 C:\Borland\Bcc55\lib
にあるとすると、

bcc32 renl.c lib\wildargs.obj

でコンパイルとリンクを問題なく終了することができる。

[ソースファイル]
BorlandCソースではコメントは/* */で括るか、//をコメント文の先頭に置く(一般的な決まり)

[ソースリスト]

#include <wildargs.h>//for using wild-card ;'*', on DOS_command_line    

#include<stdio.h>
#include <stdlib.h>//for system()
#include <string.h>//for strcpy()etc. 
#include <conio.h> //for getche()
   
#define Lngth 256

void proce(int arc);
void newname(char *old,char *pre);

char *strtailget(char *str, int width){      return(strncpy(str,str+strlen(str)-width,width+1));}
char *strprecut(char *str, int width){
return(strncpy(str,str+width,strlen(str)-width+1));}
//この自前関数strprecut()は「実行後間違ったと気づいた時」(@)の修正用。
//だから本ソース内では未使用。
//先頭にm文字付けて@の時、
//自前関数newname()内の strcat(new,old);を
//strcat(new,strprecut(old,m)); とだけすればよい。


main(int argc,char *argv[])
{
int   lenext;
char prf[Lngth],
     ext[5],
     com[40],
     *comnd[2]={ "dir *"
               , " > fileinfo.dat"};
printf("ファイル改名(先頭付加)プログラム\n");

sscanf(argv[1],"%[^.]",prf);//1つ目の改名対象ファイルのベース名の入ったアドレスを変数prfに入れる

lenext=strlen(argv[1])-strlen(prf);//lenext は'.'と拡張子を合わせた文字列の長さ(バイト数)

strcpy(ext,strtailget(argv[1],lenext));/*変数extに".<拡張子>"
[例]".smp"
という文字列の入ったアドレスを入れる*/

sprintf(com,"%s%s%s",comnd[0],ext,comnd[1]);

printf(">%s\n",com);

//system("d:")として、カレントドライブをここでいくら変えようとしても、実行すると元のままである。

system(com);//dir *.smp > fileinfo.dat を実行する。(smp は[例]であり、renl実行時に renl *.smp と指定する)
proce(argc);/*自前関数*/
return;//BCC では不可欠
}


void proce(int arc)
{ int  i=0,j,exec=0;
  char data[6][Lngth],
       pre[32],
       key='$';

 FILE *fp0=fopen("fileinfo.dat","r");
 
printf("名の先頭に付ける文字列は(字数任意)?..>");
gets(pre);
printf("実行するんかい ? (y/n)..>");

while(key=='$')// $はダミーだから何でもいい
   {
    key=getche();
    if(key=='y'||key=='Y')
         exec=1;
   }

if(exec==1)
{
 printf("\n");
 while(i++<5)
   fgets(data[0], 60, fp0);

 i=0;
 while(++i<arc)
  {
   for(j=0;j<6;j++)
     fscanf(fp0,"%s",data[j]);
   printf("count:%3d  ",i);
   newname(data[5],pre);/*自前関数*/ 
  }
}
fcloseall();
return;
}


void newname(char *old,char *pre)
{int  lenall=strlen(old);
 char new[Lngth];
 sprintf(new,"%s%s",pre,old);
 if(lenall<Lngth)
   {
      printf("oldname: %s\tnewname: %s\n",old,new);
      rename(old,new);//Cのコマンド。DOSコマンドのrenは使っていない
      
   }
return;
}

★複数ファイルの名の先頭に文字を加える (2)

■訂正
>"DOS コマンドの ren はワイルドカード「*」は使えません。"

これはまちがい。
拡張子以前の名前(ベースファイル名)が8字以内なら使えます。

[例]ren gr*.* dtr*.* 
で、カレントディレクトリ内の、 gr で始まる複数ファイルは dtr で始まるファイルに一括でそれぞれ改名できます。
[注]予め、カレント内のファイル名に目を通していないと、思いがけないファイルまで名が変ってしまうので上手に使いましょう。

■追記@
プ ログラムの実行(=ソフトを動かせる)とは、結局、RAM(一般にメモリと言う)にHDやCD-ROM,FD,MO等の記憶装置のデータを取り込み、 CPUがRAM内の数値の読み書きを繰り返すことです。RAMの内部の1つの番地には、それぞれ1バイトずつのデータ(数値)が入っています。
Cでは変数をchar宣言することで、1つの番地に入った1バイトずつの値を扱う(読み書きする)ことができます。

■追記A
ソース内で自前関数strmid()を使った
reg=strmid(fn,1,len-1);
は文字列配列変数fnに入っている文字列の1番目から最後の1字を除いた文字までを納めた番地をchar型ポインタregに入れる、という意味なので、Cに元々備わっている関数strncpy()を使って、

reg=strncpy(fn,fn+1,len-1);

とした方がいいと思います。こう替えたら、ソースからstrmid()の宣言文と定義文を削ることができます。strmid()も間違ってはいないが、できるだけ標準搭載の関数を使うのがいいので。
短縮できる関数は他にもあって、

strcpy(new,"h");
 strcat(new,fn);


sprintf(new,"h%s",fn);


strcpy(rename,"ren ");
 strcat(rename,arv);
 strcat(rename," ");
 strcat(rename,new);

sprintf(rename,"ren %s %s",arv,new);
にできます。

■追記B
これはソース内にはありませんが、
strncpy(a,b+4,3);
などとしたときの b+4 の意味を説明します。
半角1文字は1バイト、全角1文字は2バイトの値でコンピュータ内部で決められています。
例えば

char a[]="abcdだな"
, b[]="次はopqrかなあ、気になるなあ";
strcpy(a,b+4);

を実行すると、aには

opqrかな\0

という文字が入っています。ちなみに文字列終了コード\0は特殊文字で、これで1バイトの大きさです。
上の文字列をprintf()で表示すると

opqrかな

と出ます。
さて、一連の動作を説明します。

ま ずchar 宣言と変数定義で、メモリ内部の適当な番地からaには8+1=9バイト、bには14+1=15バイトの領域を連続して確保し、次の strcpy()でbに当てられた番地の先頭から4バイト後の番地に納められた値'o'から後をaに入れる、ということをしているのです。

■追記C
Windows95以降、文章のように長いファイル名も扱えるようになりました。
但しDOS内ではファイル名は最長<[8字].[3字]>計12文字の短い名(エリアス名と言うらしい)
が自動で作られて、それを扱います。例えば
<長い長ーい名前.abc>
というファイルは
<長い長~1.abc>
で操作することになります。この ~1 は数値テールと呼ぶそうで、自動で付きます。
DOSではこの<[8字以内].[3字以内]>の12字内のエリアス名だけを扱います。LsiCはDOS上で動くため、専用のフリーライブラリ<lfnl111.lzh>を入れない限り、エリアス名だけしか扱えませんが、
他のもっと一般的なコンパイラでは、元の長いままの名が使えます。(確認:BorlandC++Compiler)
関数rename()を使うと、元の名も新しい名も、256字以内(含文字列終端字'\0')なら、そのまま変えられます。rename()はBorlandC++では<stdio.h>に定義されています。
そのような長い名のままで扱えるコンパイラにかけるのなら、プログラム内で扱うファイル名の長さを気にせずプログラムを作れます。元のエリアス名が12字の場合の手続きも不要となるので、かなり簡単になります。

"BorlandC++Compiler"(←これもフリーコンパイラ
入手先http://www.borland.co.jp/cppbuilder/freecompiler/
から入手可能[注] "〃 Builder"とは異なる)

■予告
次回(2002.11.30)はBorlandCを使って、ファイル名を長いままで変更するブログラムを載せます。
――――――――――――

★ 複数ファイルの名の先頭に文字を加える(1)

ネットなどから入手したファイル(例として本編ではjpgファイル)が同名のことがあります。その場で改名するよりも、重複した時に別のフォルダに入れておいてから、後で一括に改名する方が効率的です。
DOS コマンドの ren はワイルドカード「*」は使えません。一括処理ができないのです。
そこで本プログラムを作り、一括改名を自動で行えるようにしたのです。

※  なお、DOS では通常では拡張子を除いたファイル名は半角8文字までしか扱いませんが、長いままのファイル名を扱いたい時には、 Vectorから<lfnl111.lzh>を入手すれば可能になります。但し<lfnl111.lzh>はLsiC上でのみ使 用可能です。

[ソースリスト]

/* renh.c
コンパイルしたexeファイルを<a>ディレクトリにコピーして実行。
コマンド:renh *.jpg
■概要
 ファイル情報を入れた<flname.dat>を使って<jpg>ファイル名の先頭に「h」(何でも可)
を付ける。
 
 ※<a>ディレクトリ : 名変更する<jpg>ファイルのあるディレクトリ
*/
#include <stdio.h>
#include <stdlib.h>/* system()のため */
#include <string.h>/* strcpy(),strcat()のため */
void   proce(int arc, char *arv[]);
void   newname(char *arv);
char  *strmid(char *str, int start, int len);

void main(int argc, char *argv[])
{
 system("dir *.jpg /o:n > flname.dat");
/* <flname.dat>というファイルを作り、カレントディレクトリ内の全jpgファイル情報を入れる。 
main()の引数 argv[] は名前順なのでdosコマンド dir も名前順並べ替えオプション /o:n を使用*/

 proce(argc,argv);/*自前関数*/
 return;
 }

void proce(int arc, char *arv[])
{ int  fnum, count=0,  cnt=1;
  char    data[7][60]={} ;
 FILE *fp0=fopen("flname.dat","r");

 while(++count<=5)
   fgets(data[0], 60, fp0);
/* 処理に必要なデータは fgets() で扱われる場合、6番目の文字列からになるので、ここでファイルのアクセスポイントを6番目に持って来ている。
fgets() は「\n」までを1文字列として扱う。
fopen() で使われるファイルは、ファイルの先頭から順番にしかデータを扱えないので、ファイルへのアクセスポイントの移動は先頭から順番に繰り返さないといけない。
ファイルfp0("flname.dat"のこと)には必要なデータまでに先頭に5つ
「\n」(ASCIIコード:0x0D0A
…0x0D は「復帰」0x0A は「改行」)が入っている。*/
   
for(fnum=1;fnum<arc;fnum++)/*<ファイル数>=<arc-1>*/

for(count=1;count<=6;count++)/*dirコマンドでは1ファイルにつき6個の情報ある。それがfp0に入っている*/

fscanf(fp0,"%s",data[count]);
/*fscanf はfp0のアクセス点から空白又は「\n」まで読み込む*/

printf("num:%d count:%d ",fnum,cnt++);
newname(arv[fnum]);/*自前関数*/
  }
}

void newname(char *arv)
{
int  i, len=strlen(arv);
char rename[]={"ren 12345678.aaa 12345678.bbb"}
 , new[13]={}
 , fn[]={"12345678.jpg"}
 , *reg="12345678.jpg"
;
/* 各char変数の中身は文字列の長さを確保する為のダミー */

strcpy(fn,arv);
for(i=0;i<13;i++)
  new[i]='\0';
/*「\0」は文字列終了コード。ポインタで文字列を扱う時には「\0」ないと限りなく取り込んでエラーとなる。なお、「"」 で挟んだ文字列には自動で「\0」が付く。*/


if(len>12)
/*名が8文字の時の処理。
dosではファイル名は8文字以下。拡張子の3文字と「.」を合わせて12文字まで。
ここで先頭に1文字付けた後が8文字以下になるようにしている*/
 {  
 reg=strmid(fn,1,len-1);
 for(i=0;i<len-1;i++)
   fn[i]=*(reg+i);
 fn[len-1]='\0';
 }
 
 strcpy(new,"h");
 strcat(new,fn);

 strcpy(rename,"ren ");
 strcat(rename,arv);
 strcat(rename," ");
 strcat(rename,new);

 printf("command:%s\n",rename);
 system(rename);  
 return;
}

/* 文字列の一部を抽出する */
char *strmid(char *str, int start, int len){
int i;
char *after="0123456789012";
/*文字列はダミー*/

 for(i=0; i<len; i++)
   *(after+i)=*(str+i+start-1);
 *(after+len)='\0';
 return(after);
 }
――――――――――――

★ディスクのクラスタのサイズを求める

この欄ではOSがwindowsであることを前提にして話を進めています。
クラスタという言葉は"scandisk.log"の中身を見ると出ています。
例えば

 「ファイルシステム
         2チェーン中に 2個の破損クラスタがありました.
         破損クラスタは空き領域として再生されました.
  クラスタスキャン
         テストは行われませんでした」
等と出ています。2個のクラスタて何だろう、と思う人は以下を見てください。

クラスタ(:cluster=房) はOSが各ディスクにアクセスする時の論理的な最小単位です。たとえ1バイトの大きさのファイルでも、1クラスタ分の領域を占有している為、小サイズのファイルをたくさん作ると、ディスクの領域を無駄遣いする事になります。

1個のクラスタのサイズは各ディスクで初期化の時に決定されています。
自分のPCのクラスタが何バイトか知りたい時は、以下の事をやってください。

<やる事1>
エクスプローラ(ファイル管理ユーティリティソフト)のフォルダ窓にある'D:'ドライブのアイコンを右クリック(マウスINDボタン押す)してプロパティを左クリック(Selectボタン押す)します。
'D:'ドライブの使用領域と空き領域サイズが表示されます。
まずこの数値を憶えておいてください。

<やる事2>
メモ帳などエディタで1文字だけ打って新ファイルとして保存します。

<やる事3>
どれだけ空きが減ったか、<やる事1>を繰り返します。

<確かめ>
自分のPCでは、上の事をやれば、空き領域が4096バイト減っています。
この4096=4kバイトが1つのクラスタのサイズです。
保存したファイルのサイズは3バイトです(:DOSプロンプトで"dir"コマンド使う)。

本プログラムでは、こうした長い面倒な手続きを経ずにクラスタサイズを求めます。

[ソースリスト]

/*  指定ドライブのクラスタサイズをバイト数で表示  */
#include <dos.h>/*_dos_getdiskfree()使う為*/
#include <stdio.h>
#include <conio.h>/*geche()使う為*/

struct diskfree_t  Drinf;
/*  構造体 Drinf をdiskfree_t という型枠で宣言したという意。宣言場所はmain()関数の外か、自前関 数getin()内にする。Drinfは得られた情報の格納場所である。diskfree_t については下記<解説>参照  */

void getin(int drive_No);/* 自前中枢関数 */

void checkanddo(char key);/* 自前関数check()を呼ぶ為の前手続き */

void check(int ikey);/* 自前関数 */

main()
{
char    key=NULL;

printf("指定ドライブのクラスタサイズ表示\n");
printf("カレント:[return]  A:[a]  B:[b]  C:[c]  D:[d]  G:[g]\n");
printf("大文字、小文字のどちらでもいい\n");
printf("Exit:[Q]\n");
while(key!='q' && key!='Q')
 {

/* 押したキーのASCII文字を文字変数keyに入れる。エコーバック(押したキーに対応するASCII文字の画面表示)も行う */
key=getche();
printf("\n");

/* リターンキー(CR:キャリッジリターン、ASCIIコード13 )押せばカレントドライブ情報を獲得*/
if((int)key==13){
 getin(0);continue;}

/* そうでなければ本来の手続き */
checkanddo(key);
  }

return;
}

/* 自前関数check()を呼ぶ為の前手続き */
void checkanddo( char key)
{
/*  アルファベット大文字のASCIIコードは0x41(16進数の41のC言語表現。数学的表現は"41h"等)、小文字は0x61から始まるから、キーを 押せば、ドライブ番号(下記<解説>参照)に正しく対応した数値が、大文字ならikey1に、小文字ならikey2に入る。
*/
int     ikey1=key-0x40,
        ikey2=key-0x60;
check(ikey1);
check(ikey2);
return;
}

/* 自前関数 */
void check(int ikey)
{
/* ドライブ番号(変数名ikey)が1から7なら自前中枢関数getin()を呼んでドライブ番号を渡す */
if(ikey>0 && ikey<8) getin(ikey);
return;
}

/* 自前中枢関数 */
void getin(int drive_No)
{
unsigned int    byt_sect,sect_cl;
_dos_getdiskfree(drive_No,  &Drinf);
byt_sect=Drinf.bytes_per_sector;
sect_cl=Drinf.sectors_per_cluster/8;
printf("セクタ  :  %8d バイト \n",  byt_sect);
/* セクタとはディスクドライブの物理的な最小単位 */
printf("クラスタ:  %8d セクタ \n",  sect_cl);
printf("クラスタ:  %8ld バイト\n", (long)byt_sect*(long)sect_cl);
/*数値が大きいので、byt_sect、sect_clの2の変数は、long型に一時的な型変換をしている。もしunsigned (int) 型のままなら、間違った値が表示される。
なぜこのようなややこしい処置が必要なのか。それはdiskfree_t型構造体が下記<解説>のような既定の定義があるから。元々、各メンバ(構造体内の各変数)がlong型なら、このキャスト(型変換)は不要なのに。
 */

return;
}

<解説>

以下はlsic86.man(Cユーザーズマニュアル用テキストファイル)より抜粋

■関数_dos_getdiskfree()

unsigned
_dos_getdiskfree( unsigned device,    struct  diskfree_t *diskspace);

_dos_getdiskfree ()はMS-DOSシステムコール0x36を実行し、ディスクドライブdeviceの情報を得ます。deviceはカレントドライブは0、A:は1、B: は2...と指定します(「ドライブ番号」と呼ぶ事にする)。得られた情報は*diskspaceにセットされます。
可搬性:MS-DOS (ANSI-Cに対応していない)

■diskfree_t型構造体

diskfree_t型構造体は<dos.h>で次のように定義されています。
struct  diskfree_t  {
unsigned total_clusters;.....(1)      
unsigned avail_clusters; ....(2)
unsigned sectors_per_cluster;(3)  
unsigned bytes_per_sector;...(4)
 };
(1)総クラスタ数
(2)使用可能クラスタ数
(3)クラスタあたりのセクタ数
(4)セクタあたりのバイト数   

本プログラムではこのうち(2)(3)を使っている。

――――――――――――

★流線形物体表面圧力分布

■圧力
 流体分野で圧力とは、重力や蒸気圧等による静圧(大気圧、水圧等)と、流体が動いている(又は流体中で物体が動いている)時に物体に作用する動圧に分けられ、単に圧力と言う時は、静圧の事を事を言う場合が多いようです。
 実在流体は、流れが物体表面から剥がれ、渦(とそれによって引き起こされる圧力抵抗)を生むのが普通です。
流れの中の物体に働く抵抗には、表面と流体の間の摩擦抵抗と、流れの剥がれで物体前後間に圧力差が生まれる圧力抵抗の2種類があります。
物体が円柱で、軸を流れに垂直に置いたならば、全抵抗中の98%が圧力抵抗です。剥がれまで考慮すると、粘性を考慮したナビエストークス方程式を扱うことになり、解析的に解くことはもはや不可能です。数値解法の差分法を持ち出す必要が出て来ます。こうなると大変です。
 翼表面の速度分布を近似計算で求める方法もありますが、それにも翼形に条件があり、反りと厚みが小さい場合に限られます。条件を満たさなければ、計算値と実測値は懸隔します。
 ここで流線形が活躍します。
 流線形ならば剥がれを考える必要はなくなり、物体表面の速度が解析的に近似計算でき、速度が求まれば、ベルヌーイの方程式(*2)より、圧力が求められるのです。

■ジューコフスキ変換
円は特殊な写像(ジューコフスキ変換(*1))によって、流線形に近い形に変形出来ます。
 流線形の定義は、「流れの剥がれ(渦)が生まれにくい形状」です。
 このプログラムでは、この疑似流線形を断面に持つ物体が、定常(時間に依らない)一様流(一様とは、場所に依らないこと。流体は水を想定)中に置かれた時の、物体表面の圧力分布を理論計算して図示します。

■ジューコフスキ(Joukowski)変換後の物体形状及びその表面上の圧力計算

右に流れる一様流の速さをUcとおく。

速度を
 (u,v)
とする。
速さ V は
V=√(u^2+v^2)
つまり

V^2=u^2+v^2

圧力を
 Pとすると
流体を非粘性(流れが剥がれて渦が生まれるという事が無い)非圧縮(場所時間によらず流体の密度ρ一定)
として外力が働かないとすると、

P+ρ・V^2/2

は、一つの流線(物体断面形状も流線の一つ)の上では一定の値をとる。第1項を静圧、第2項を動圧、式全体を総圧と呼ぶ...(ベルヌーイの定理)
上式から、速度と圧力のどちらか得られれば、他方が求められる。
複素数を使った数学的解析では、速度が容易に算出されるので、それを用いて圧力を導き出すことができる。
比較が簡単にできるように、
Pを無次元化して

P=(Uc^2-V^2)/Uc^2

と定義することができる。

u,v の求め方

i:虚数
i=√(-1)
として複素領域で考察する。
複素数
z は全て
z=x+iy
の形に集約できる。
ここで
xを実部Re(z)
yを虚部Im(z)
と呼ぶ。

複素平面の変換で
ζ→ζ'→Z
を考える。
ζ平面に原点中心、半径Rdの円を置くと、その式は
ζ[円]=Rd・exp(iθ)

(0<=θ<=2π)
ここで
exp(iθ)=cosθ+i・sinθ

ζ'はζ内の図形を実軸(横)方向に-p,虚軸(縦)方向にq平行移動したもので、
ξ=-p+iq
とすると下記のように定義される。
ζ'=ζ+ξ

Cプログラム上では
(p,q)=(ct[0],ct[1])

ζ'→Zはジューコフスキ変換で、式は
z=ζ'+k^2/ζ'


exp(-iα)
=cos(-α)+i・sin(-α)
=cosα-i・sinα

α:一様流と実軸の角度
tanβ=q/√(Rd^2-q^2)
k=-p+√(Rd^2-q^2)
とすると
u-iv
=Uc*exp(-iα)*{1-Rd^2/ζ^2+i2Rd・sin(α+β)}/(1-k^2/ζ’^2) 

上式=ξ
とおくと、

u=Re(ξ)
v=-Im(ξ)
今は物体表面上だけ考えているので
パラメータθ(0<=θ<=2π)を導入すると
式中のζは
ζ[円]=Rd・exp(iθ)
ζ’は
ζ’[円]=ξ+Rd・exp(iθ)
として計算できる。

■下記ソースリストの説明
自作関数が全て
「double *」 の型なのは、複素数の実部と虚部の2個の値を一度に返して、複素数の演算の煩雑さを省く為の一工夫である。
具体的には、
「 tg_pl(multi_pl(div_pl(),),); 」のように引数に関数を次々入れてまとめることができる。
「  void 」 型の関数の方がプログラミング上の工夫は不要で作り易い。その代わり、入れ子の形にはまとめられず、ソースリストがか なり冗長で見づらくなる。又、この場合、事前に式変形の必要があり、その結果、手間は手計算と変わりがなくなる。

【注意点】
<1>. 関数がポインタなので引数と返値も全てポインタに統一して宣言する必要がある。
但しポインタで宣言した引数部分に配列変数を使用しても支障はない。
<2>. 各関数の内部変数は配列変数で宣言するのが原則。
但し使用する関数の返りを代入する変数( 〈例〉a=jkw(); の a )はポインタにする。配列を使うとエラーとなる。
<3>. 関数の返値を代入する変数のポインタは、その関数の1番目の引数に等しい。
つまり複数の関数の第1引数に同じ変数が使われる時には、返値を別の変数へ退避する事が必要になる。

外部変数やstatic変数を使ってみたが、うまくいかなくて、その過程で上述の事が分かった。しかし、代わりにこのようなヘンテコな形になった(哀!)。

main()関数内を見てみよう。
「 a20=around(rg0,Rd);」…(1)式
を例にとると、a20 とrg0 は便宜上、名前を変えているだけで、アドレスは同じ。従って下方の
「 ans3=around(rg2,ck);」…(2)式
でrg2 の代わりにrg0 を前処理せずに使用すると、値を残しておきたいa20 も連動してans3 と等しくなってしまう。
だから、この場合は
<対処1>.他の第一引数で未使用の変数rg2[] を使う(本リストの方法)
<対処2>.
double ans20[2]; など新たに宣言して(1)式と(2)式の間に
「 for(j=0;j<2;j++) ans20[j]=*(a20+j);」…(3)式
という操作を一つ入れる。これは配列変数ans20[j]をレジスタのように使って一時保管している訳である。

プログラムの実行

exeファイルをクリックすると、一様流角度、基準円中心座標の値を聞いてくるので順に入力します。
結果は物体表面の位置(x,y)、その位置の圧力の順に
jkwski_p.dat
というファイルに出力されます。画面にも出力されて終了します。
後は表計算ソフトでjkwski_p.datを開けば表ができ、グラフが作れます。
なお、jkwski_p.datはexeファイルと同じディレクトリに作られます。

[ソースリスト]

#include <stdio.h>
#include <math.h>
#define  Rd  10    /* 物体基準円半径 */
#define  Uc  10     /* 一様流速 */
#define  Nq  32      /* 物体基準円分割数 */
#define  Th_n  2.0*PI/Nq
/* 実数(real)の平方(2乗) */
double sqr(double a)
{  return(a*a); }

 /* 共役複素数(虚部符号反転) */
double  *tg_pl(double *a)
{
a[1]*=-1;
 return(a);
}

/* 複素数 a の逆数 1/a */ 
double  *rcp_pl(double *a)
{
int i;
double m, *c=tg_pl(a);
m=1/(sqr(a[0])+sqr(a[1]));
for(i=0; i<2; i++) c[i]*=m;
for(i=0; i<2; i++) a[i]=c[i];
return(a);
}

/* 2複素数の積 ab */
double  *multi_pl(double *a,double *b )
{int i;
double c[2];
c[0]=a[0]*b[0]-a[1]*b[1];
c[1]=a[0]*b[1]+a[1]*b[0];
for(i=0; i<2; i++) a[i]=c[i];
return(a);
}


/*2複素数の商  a/b */
double  *div_pl(double *a, double *b )
{
return(multi_pl(a,rcp_pl(b)));
}

/* 複素数(complex) a の平方 aa */
double  *sqr_pl(double *a)
{
int i;
double c[2];
for(i=0; i<2; i++) c[i]=a[i];
return(multi_pl(a,c));
}

/* 2複素数の和 a+b */
double  *add_pl(double  *a,double  *b)
{
int i;
for(i=0; i<2; i++) a[i]+=b[i];
return(a);
}

/*  1-k^2/a^2   k:real  a:complex  */
double *around(double *a, double k)
{
int j;
double c[2], *d=rcp_pl(sqr_pl(a));
for(j=0; j<2; j++)  c[j]=*(d+j)*(-sqr(k));
c[0]+=1;
for(j=0; j<2; j++)  a[j]=c[j];
return(a);
}

/* Joukowski- Changing a+k^2/a  */
double *jkw(double *a, double k)
{
int i;
double c[2], *g;
for(i=0; i<2; i++) c[i]=*(a+i);
g=rcp_pl(a);
for(i=0; i<2; i++) c[i]+=*(g+i)*sqr(k);
for(i=0; i<2; i++)  a[i]=c[i];
return(a);
}

main()
{
int    i;
double alfa, beta, thta=0, ck, prs;
double centr[2], cirline[2], ans1[2],
rg0[2], rg1[2], rg2[2];
double *obj, *a20, *ans2, *ans3, *ans;

/*
*ans(又はans[])が求める速度ベクトル u=*ans=ans[0],v=*(ans+1)=ans[1] */

FILE   *p=fopen("jkwski_p.dat","w");
printf("一様流角度 [deg] ? ");
scanf("%lf",&alfa);
alfa*=PI/180;
printf("基準円中心実軸座標 (-)? ");
scanf("%lf",centr);
printf("基準円中心虚軸座標   ? ");
scanf("%lf",centr+1);

beta=atan(centr[1]/sqrt(sqr(Rd)-sqr(centr[1])));
ck=-centr[0]+sqrt(sqr(Rd)-sqr(centr[1]));


for(i=0; i<=Nq ; i++) {
cirline[0]=-centr[0]+Rd*cos(thta);
cirline[1]=centr[1]+Rd*sin(thta);
obj=jkw(cirline,ck);

ans1[0]=Uc*cos(-alfa);
ans1[1]=Uc*sin(-alfa);

rg0[0]=Rd*cos(thta);
rg0[1]=Rd*sin(thta); 
a20=around(rg0,Rd);

rg1[0]=0;
rg1[1]=2*Rd*sin(alfa+beta);
rg2[0]=Rd*cos(thta);
rg2[1]=Rd*sin(thta);
ans2=add_pl(a20,div_pl(rg1,rg2));

rg2[0]=-centr[0]+Rd*cos(thta);
rg2[1]=centr[1]+Rd*sin(thta);
ans3=around(rg2,ck);

ans=tg_pl(multi_pl(ans1,div_pl(ans2,ans3)));
prs=(sqr(Uc)-sqr(ans[0])-sqr(ans[1]))/sqr(Uc);

printf("%2d  %11.3e %11.3e %11.3e \n",i, obj[0],obj[1],prs);

fprintf(p, "%7.3f,%7.3f,%7.3f,\n",obj[0],obj[1], prs);

thta+=Th_n;
 }
fcloseall();
return;
}


――――――――――――

・・

■あとがき

 本プログラムの難点は、物体形状が任意でなく、変換式に則った形状しか表現できない点です。
 しかしパラメータの物体基準円中心座標は自在に決められるので、変換後には円に近い形から、翼形、湾曲板形、滴下形等、出現する形は多様で、圧力分布の特徴や圧力抵抗の大きさを知ることが出来ます。

  本文書で流体に興味を持った人は、下の参考文献の 1)〜3)の一読を勧めます。
 1)と3)は煙や渦や回転円筒(カップや洗濯槽)内や堰(せき)等の具体的事例に即した流れの説明も豊富で工学分野に入ります。2)は現象を理論考察した、物理学の分野に属します。
< 参考文献 > 
1)  谷一郎,流れ学(第3版),岩波全書 (1988).
2)  今井功,流体力学,岩波全書 (1988).
3)  植松時雄,水力学(第2版),産業図書 (1988).
4)  神戸勉,偏微分方程式,講談社 (1989), P9-13,24-29.
5)  遠木幸成 偏,基礎課程 解析概論,学術図書出版社 (1986),P42-43,46-50.
6)  斉藤・松田・砂川,物理学への道(下),学術図書出版社 (1985),付録A,付録B.
7)  山本邦夫,力学,学術図書出版社 (1987),p1-6.
8)  吉川・松井・石井,機械の力学,コロナ社 (1988),P1-16.


以下の項目については、上記各文献の下記ページを参照して下さい。
なお、ここでは、ベクトル、複素数、スカラー、偏微分、といった数学的内容の説明は省略致します。参考文献の 4)〜8) 等を渉猟してみて下さい。


◆(*1)等角写像(ジューコフスキ変換)
1) p78-79.
2) p78-80.

◆(*2)ベルヌイの方程式
1) p15-21.
2) p36-38.
3) p38-39.
4) P24-29.

◆複素速度ポテンシャル 
1) p70-75.
2) p57,63-67,70.

◆一般(円断面)物体表面の速度分布、圧力分布 
1) p25-27,163-165.
2) p72,83.
3) p179-180.

◆翼(流線形の典型)の圧力分布
1) p233-236.

以上

★バット打撃時の衝撃のない打点を計算する

以下のリストを各種Cコンパイルすれば動くと思います。直ちに実行したい人は、下記のlzh ファイルをソフトライブラリVectorから入手してLHA.exeで展開して下さい。詳細は展開後の文書ファイルを読んで下さい。
以下のソースは、グラフィック表示のないもの(DOS/V用)です。整頓した訳ではないので、見にくく長たらしいかも知れませんが
物理計算をするのが目的なので、まあ、勘弁して下さい。



[ソースリスト]

#include <stdio.h>
#include <math.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define N 10
/*   バットの慣性モーメント、衝撃の中心(握り位置)より、ミートポイント算出 */

double sqr(double x);
double total(double *mass, int n);
double len_t(double *len);
void mass_sec(double den,  double *r,  double *len, double *mass);
double cal_center_grav(double *r,  double *len,  double *mass);
double  i_moment0(  double *r,  double  *len,  double *mass);


void proce(void)
{
int    i;
double  i_moment, i_m0,    meet_point;
double  mass[N+2], mass_total=0, center_grav, center_shock_from_grav;
double  d[]={32,78}, len[]={200,200,400}, den=1, grip=100;
/*  len[2].. チュウオウ ブ  */
double  r[2],  len_total;
                                               /* 半径-r  長さ-len  密度-den  */
char  key;

len_total=len_t(len);
puts("\x1b[2J"); /* clear screen */
printf ("密度 :%5.2f g/cm^3   小径 :%6.2f  mm   大径 :%6.2f mm   長さ :% 7.2f mm  O.K.? (y/n)\n",den,d[0],d[1],len_total);
scanf("%c%*c",&key);
if(key=='n' || key=='N') {
          printf(" input data  小径=? ");
         scanf("%lf",&d[0]);   
   printf(" input data  大径=? ");
         scanf("%lf",&d[1]);   
      printf(" input data  長さ(0)=? "); /* 手側円柱部 */
         scanf("%lf",&len[0]);
     printf(" input data  長さ(1)=? ");/* 中央広がり部 */
         scanf("%lf",&len[2]);
     printf(" input data  長さ(2)=? ");/* 打つ側円柱部 */
         scanf("%lf%*c",&len[1]);
            len_total=len_t(len);
  }
for(i=0; i<2; i++)    r[i]=d[i]/2;
mass_sec(den, r, len, mass);
mass_total=total(mass, N+2);
center_grav=cal_center_grav(r,len,mass)/mass_total;
i_m0=i_moment0(r,len,mass);
        i_moment=i_m0-mass_total*sqr(center_grav);
center_shock_from_grav=center_grav-grip;
meet_point=center_grav+i_moment/mass_total/center_shock_from_grav; 
printf ("質量 :%6.3f kg \n慣性モ−メント( 手部軸 ):\n%6.3f× 10^4 kg・mm^2 \n",         mass_total,  i_m0/10000);
printf("重心 :% 6.2f mm \n打点 :%6.2f 〜 %6.2f mm\n",     center_grav,  meet_point, len_total);
}



main()
{
char key;

for (;;) {
proce();
printf("end ? (y/n)\n");
scanf("%c%*c",&key);
if( key=='y' || key=='Y' )  break;
}
}



double len_t(double *len) {
int i;
double len_total=0;
for(i=0; i<3; i++)  len_total+=len[i];
return len_total;
}


double sqr(double x) {
return  x*x;
}


double total(double *mass, int n) {
int i;
double tl=0;
for(i=0; i<n; i++)
tl+=mass[i];
return tl;
}

void mass_sec(double den,  double *r,  double *len, double *mass) {
double r_x0, r_x1,  dx,  dr;
int    i;
  dx=len[2]/N;
  dr=(r[1]-r[0])/N;
  r_x0=r[0];
  r_x1=r[0]+dr;
for(i=0; i<2; i++) 
mass[i]=den*1.0e-6*PI*sqr(r[i])*len[i];
for(i=2; i<N+2; i++)  {
        mass[i]=den/3*1.0e-6*PI*dx*(sqr(r_x0)+sqr(r_x1)+r_x0*r_x1);
        r_x0=r_x1; r_x1+=dr;
}
}


double  cal_center_grav(double *r,  double *len,  double *mass) {
 double center=0.0, x,  xmean[2], dx;
 int i;
dx=len[2]/N;
x=len[0]+dx/2;
xmean[0]=len[0]/2; xmean[1]=len[0]+len[2]+len[1]/2;
for(i=0; i<2; i++)  center+=mass[i]*xmean[i];
for(i=2; i<N+2; i++) {
center+=mass[i]*x;
x+=dx;
}
return center;
}

double  i_moment0(  double *r,  double  *len,  double *mass) {
double i_m0=0, x, r_x, xmean[2];
double dx, dr;
int i;
  dx=len[2]/N;
  dr=(r[1]-r[0])/N;
x=len[0]+dx/2; r_x=r[0]+dr/2;
xmean[0]=len[0]/2; xmean[1]=len[0]+len[2]+len[1]/2;
for(i=0; i<2; i++)  i_m0+=mass[i]*((sqr(len[i])+3*sqr(r[i]))/12+sqr(xmean[i]));
for(i=2; i<N+2; i++) {
i_m0+=mass[i]*((sqr(dx)+3*sqr(r_x))/12+sqr(x));
x+=dx;  r_x+=dr;
}
return i_m0;
}


――――――――――――


★ 物体立体視

立体を画面上に表すプログラムです。
コンパイラは LsiC86 です。
そのままでは、グラフィック機能は準備されていないため、別にグラフィックライブラリを取り寄せ、組み込みました。
そのライブラリはPC98用である為、それ以外の機種ではグラフィック表示は出来ません。
プログラミングで最も大切な所はどこか。
それは手段です。
こ こではどのように3次元座標を2次元に正確に変換するかという事です。それさえ把握すれば、いかなる状況にも対応できます。自分で組む事も出来るのです。 プレタポルテよりもオートクチュールが面白くて価値もあると思います。作る側も使う側も。以下そう考えて見て欲しいです。

などと長たらし く言い訳書きました。何が言いたいか、要するに PC98 以外では、フル装備のコンパイラを自分で用意し、自分で作って下さい、と いう事です(何て冷たい(-_-;) )。グラフィック関連のヘッダファイル名とコマンド記述だけ変更すれば後は大丈夫と思います。

《 閑話休題 》
このブログラムで、基盤となる大切な考えは、
像を映す仮想スクリーンを球面として、視点を球の内側に、対象物体を球の外側に置くという仮定です。任意の物体をこのスクリーンに投影すれば、3次元座標を2次元にきれいに変換できます。「きれい」とは、「現実と同じ」という意味です。
C には便利な関数 atan2() が備わっているので、とても簡単な変換式で表す事ができます。Cも BASICも  atan() は返り値の範囲が±90°なので if と新たな変数を導入して余分な処理を加えないといけな い為、グラフィックの実用には不向きです。(BASIC の項 billiard.bas 参照)
3次元を2次元に変換する、本品根幹部の関数は
void  chg3_2(int v[ ][2], double ob[ ][3],  double sp[ ],  double ag[ ])

です。

定数、変数の値を下手に置けば、ほんの少しの視点移動で物体の形が歪んで、原形を留めない程しっかり懸離れます。
それを抑えるポイントは、

・視点を原点に置かず、物体に寄せる
・球面スクリーンの曲率を抑える(=球半径を大きくする)

です。

その他、全体の中で重要な点は
・ main() 中の関数 getch(); の使い方
・ 関数から複数個の値を一度に返す方法
(・chg3_2() 中の静的変数型 static int の意義 … 現リストからは消滅、前試作品の副産物 )

といろいろあります。それぞれ追って記述していきます。

‥‥‥‥‥‥‥‥‥‥‥‥


■ プログラムの説明
glib.h はグラフィック用のヘッダファイル。
conio.h はキー割込み関数 getch() を使う為、
math.h は逆正接(アークタンジェント) atan2() を使う為、装填。
外部変数 dr の宣言と、関数 chg3_2() , mvrt_s() 内での使用部分は、視点が座標中心から外れた時の補正用。現在は未使用。

本品中使用のグラフィック関連の内部関数は
graphinit() setcolor() gcls() line() 

座標は、
・原点を球面スクリーンの中心に置く。

・配列要素の
0 が水平右方向:x
1 が垂直下方向:y
2 が水平奥方向:z
   と定義している。

変数名が含む意味
ang_ は角度
mov は移動距離
spos は視点位置
sline は視線向き(ベクトル)

キー割込みによる視点移動
2 は下
4 は左
6 は右
8 は上
0 は手前
7 は奥

キー割込みによる視点移動速さ(スカラー)
x は速くなる
z は遅くなる

キー割込みによる視線向き変化
← は左
→ は右
↑ は上
↓ は下
  (当たり前やて...)

上述のキーに一致したキーを押す度に上図の
赤い直方体の見え方がどんどん変わります。

その他詳細は適宜増補するつもりです。

‥‥‥‥‥‥‥‥‥‥‥‥

物体立体視 [ソースリスト]

#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "glib.h"

#define   RD  20    /* RD : radius of ball_screen  */
#define   XP  24   /* XP : expand view in monitor  */
#define   LR  0
#define   UD  1

void  chg3_2(int v[ ][2], double ob[ ][3],  double sp[ ],  double ag[ ] );
void  graph(int p[ ][2]);
void  mov_obj(double  obj[ ][3],  int mov[ ] );
void  mvrt_s(char k,  double *sp,  double  sp_m,  double *ag,   double  ag_m);

int dr=-1;
main(){
        int     pt[8][2]={ },  col=3;  
char    key=NULL;
double  spos_mov=16,  ang_mov=0.1;
double  ang_sline[2]={ };
double  obj1[ ][3]={{ 25,   25,  150},
                           { 25,  -25,  150},
                           {-25,  -25,  150},
                           {-25,   25,  150},
                           { 25,   25,  190},
                           { 25,  -25,  190},
                           {-25,  -25,  190},
                           {-25,   25,  190}};
double  spos[3]={ 0, 0, 5 };
int     mov1[3]={ }; 

puts("\x1b[2J"); /* clear screen */
puts("\x1b[>1h");/* funk-key off */
graphinit(); 
        setcolor(col);

chg3_2(pt, obj1, spos, ang_sline);
graph(pt);
printf(" move L&R on [4],[6] \n move U&D on [8],[2] \n");
printf(" move F&B on [7],[0] \n");
printf(" speed up   on [x] \n");
printf(" speed down on [z] \n");
printf(" rot  U&D on U&D-arrow-keys \n");
printf(" rot  L&R on L&R-arrow-keys \n");
printf("exit on [Q] \n");

while(key!='q' && key!='Q' && obj1[4][2]+mov1[2]-spos[2]>0.0 ) {
key=getch();
mvrt_s( key,  spos, spos_mov, ang_sline,  ang_mov);
mov_obj( obj1,  mov1 );
chg3_2(pt, obj1, spos, ang_sline);
gcls();
graph(pt);


}

}




void     graph(int p[ ][2])
{
int i;
for(i=0; i<3; i++){
line( p[i][0], p[i][1], p[i+1][0], p[i+1][1]);
line( p[i+4][0], p[i+4][1], p[i+5][0], p[i+5][1]); }
for(i=0; i<4; i++)
   line( p[i+4][0], p[i+4][1],  p[i][0], p[i][1] );
line( p[3][0], p[3][1],  p[0][0], p[0][1] );
line( p[7][0], p[7][1],  p[4][0], p[4][1] );
        return;
}


・・・

void  mvrt_s(char k,  double *sp,  double  sp_m,  double *ag,   double  ag_m)
{
int askey=k;
if(askey>=0x30) {
    switch(k) {
case '4': { sp[0]-=sp_m;  break; }
case '6': { sp[0]+=sp_m;  break; }
case '2': { sp[1]-=sp_m;  break; }
case '8': { sp[1]+=sp_m;  break; }
case '7': { sp[2]+=sp_m;  break; }
case '0': { sp[2]-=sp_m;  break; }
default:                      break; 
      }
    if(k=='z' || k=='Z') sp_m-=5;
    if(k=='x' || k=='X') sp_m+=5;
    if(sp_m<0.1) sp_m=0.1;
    return;
        }

                if(askey<=0x0f) {
    switch(askey) {
case 0x08: { ag[0]-=ag_m;  dr=LR; break; }
case 0x0c: { ag[0]+=ag_m;  dr=LR; break; }
case 0x0a: { ag[1]-=ag_m;  dr=UD; break; }
case 0x0b: { ag[1]+=ag_m;  dr=UD; break; }
default:         break; 
      }
    
}
        return;
}



void  chg3_2(int v[ ][2], double ob[ ][3],  double sp[ ],  double ag[ ])
  {
int    i;
/* double    a[8][2];*/

  for(i=0; i<8; i++)
 {
/*  if(dr!=-1) {
  a[i][0]= ob[i][2]  -sp[2];
  a[i][1]= ob[i][dr] -sp[dr];
  a[i][0]= cos(-ag[dr]) *a[i][0] -sin(-ag[dr]) *a[i][1];
  a[i][1]= sin(-ag[dr]) *a[i][0] +cos(-ag[dr]) *a[i][1];
  ob[i][dr]= a[i][1] +sp[dr];
  ob[i][2]=  a[i][0] +sp[2];
  dr=-1;
  }
*/
    v[i][0]=320+RD*XP *( atan2(ob[i][0]-sp[0], ob[i][2]-sp[2]) -ag[0] );
    v[i][1]=200-RD*XP *( atan2(ob[i][1]-sp[1], ob[i][2]-sp[2]) -ag[1] );
  }
return;
}


void  mov_obj(double  obj[ ][3],  int mov[ ] )
  {
int i, j;
for(j=0; j<8; j++)
for(i=0; i<3; i++)  obj[j][i]+=mov[i];
  return;
  }




前のページへ
戻る
トップページへ
トップ
次のページへ
次へ