{ ROTATE.PAS } { Rotating textured surface. Coded by Mike Shirobokov(MSH) aka Mad Max / Queue members. You can do anything with this code until this comments remain unchanged. Bugs corrected by Alex Grischenko } {$G+,A-,V-,X+} {$M 16384,0,16384} uses Crt, Objects, Memory, VgaGraph; { unit code at the end of program } const { Try to play with this constants } RotateSteps = {64*5}65*10; AngleStep = {3}1; MoveStep = {10}1; ScaleStep : Real = 0.02; type TBPoint = record X,Y: { Byte} Integer; end; TPointArray = array[ 1..500 ] of TBPoint; TRotateApp = object(TGraphApplication) StartTime, FramesNumber:LongInt; {Texture: TImage;} X,Y : Integer; WSX,WSY: Integer; WSXR, WSYR : Real; Angle : Integer; Size : TPoint; CurPage: Integer; Texture: TImage; constructor Init; procedure Run; virtual; destructor Done; virtual; procedure Draw; virtual; procedure FlipPage; virtual; procedure Rotate( AngleStep: Integer ); procedure Move( DeltaX, DeltaY: Integer ); procedure Scale( Factor: Real ); procedure Update; end; var Pal: TRGBPalette; Time: LongInt absolute $0:$46C; procedure TRotateApp.FlipPage; begin CurPage := 1-CurPage; ShowPage(1-CurPage); end; constructor TRotateApp.Init; var I, J: Integer; begin if not inherited Init(True) or not Texture.Load( ParamStr(1) ) then Fail; SetPalette( Texture.Palette ); X := 0; Y := 0; WSX := 240; WSY := 360; WSXR := WSX; WSYR := WSY; Angle := 0; Size.X := HRes div 2; Size.Y := VRes div 2; FramesNumber := 0; StartTime := Time; { asm mov ax,13h; int 10h; end;} system.move (Texture.Data^,Screen,64000); SetPalette( Texture.Palette ); { readkey;} end; procedure TRotateApp.Rotate( AngleStep: Integer ); begin Inc( Angle, AngleStep ); Angle := Angle mod RotateSteps; end; procedure TRotateApp.Move( DeltaX, DeltaY: Integer ); begin Inc( X, DeltaX ); Inc( Y, DeltaY ); end; procedure TRotateApp.Scale( Factor: Real ); begin WSXR := WSXR*Factor; WSX := Round(WSXR); WSYR := WSYR*Factor; WSY := Round(WSYR); end; procedure TRotateApp.Update; begin Move( MoveStep, MoveStep ); Rotate(AngleStep); Scale(1+ScaleStep); if (WSY >= 2000) or (WSY<=100) then ScaleStep := -ScaleStep; end; procedure TRotateApp.Draw; var I : Integer; Border, LineBuf: TPointArray; BorderLen: Integer; X1RN,X1LN, Y1RN,Y1LN, X2RN,X2LN, Y2RN,Y2LN, X1R,X1L, Y1R,Y1L, X2R,X2L, Y2R,Y2L, XL,YL: Integer; { This function can be heavly optimized but I'm too lazy to do absoletely meaningless things :-) } function BuildLine( var Buffer: TPointArray; X1,Y1, X2,Y2: Integer; Len: Integer ): Integer; var I: Word; XStep, YStep: LongInt; begin XStep := (LongInt(X2-X1) shl 16) div Len; YStep := (LongInt(Y2-Y1) shl 16) div Len; for I := 1 to Len do begin Buffer[I].X := Integer( ((XStep*I) shr 16) - ((XStep*(I-1)) shr 16) ); Buffer[I].Y := Integer( ((YStep*I) shr 16) - ((YStep*(I-1)) shr 16) ); end; end; procedure DrawPicLine( var Buffer; BitPlane: Integer; StartX, StartY: Integer; Len: Integer; var LineBuf ); var PD : Pointer; begin PD := Texture.Data; { pointer to unpacked screen image } Port[$3C4] := 2; if BitPlane = 0 then Port[$3C5] := 3 else Port[$3C5] := 12; asm push ds mov bx,[StartX] { bx = StartX } mov dx,[StartY] { dx = StartY } les di,Buffer { ES:DI = @Screen } add di,VPageLen/2-Hres/4 { calc target page } mov cx,Len { Drawing buffer length } lds si,PD { DS:SI = pointer to data } push bp { store BP } mov bp,word ptr LineBuf { BP = offset LineBuf } cld @loop: PUSH DX MOV AX,320 MUL DX { AX = StartY*320 } POP DX PUSH BX ADD BX,AX mov al,[bx+SI] POP BX stosb sub di,HRes/4+1{ add di,hres-1} add BX,[bp] ADD bp,2 add DX,[bp] ADD bp,2 { CMP BX,320 JB @@1 XOR BX,BX @@1: CMP DX,200 JB @@2 XOR DX,DX @@2:} loop @loop pop bp pop ds end; end; begin { Just imagine what can be if the next 8 lines would be more complex. I'm working around it. } { (X1L,Y1L) (X2R,Y1R) +---------------+ | | | | | | +---------------+ (X2L,Y2L) (X2R,Y2R) (X1LN,Y1LN) (X2RN,Y1RN) +---------------+ | | | | | | +---------------+ (X2LN,Y2LN) (X2RN,Y2RN) } X1L := 0; Y1L := 0; X2L := 0; Y2L := WSY; X1R := WSX; Y1R := 0; X2R := WSX; Y2R := WSY; { I call Cos and Sin instead of using tables!? Yeah, I do. So what? See comments near BuildLine ;-) } { I just rotate the rectangle corners, but why I do no more? } X1RN := Round( (X1R*Cos(2*Pi/RotateSteps*Angle)+Y1R*Sin(2*Pi/RotateSteps*Angle)) ); Y1RN := Round( (Y1R*Cos(2*Pi/RotateSteps*Angle)-X1R*Sin(2*Pi/RotateSteps*Angle)) ); X1LN := Round( (X1L*Cos(2*Pi/RotateSteps*Angle)+Y1L*Sin(2*Pi/RotateSteps*Angle)) ); Y1LN := Round( (Y1L*Cos(2*Pi/RotateSteps*Angle)-X1L*Sin(2*Pi/RotateSteps*Angle)) ); X2RN := Round( (X2R*Cos(2*Pi/RotateSteps*Angle)+Y2R*Sin(2*Pi/RotateSteps*Angle)) ); Y2RN := Round( (Y2R*Cos(2*Pi/RotateSteps*Angle)-X2R*Sin(2*Pi/RotateSteps*Angle)) ); X2LN := Round( (X2L*Cos(2*Pi/RotateSteps*Angle)+Y2L*Sin(2*Pi/RotateSteps*Angle)) ); Y2LN := Round( (Y2L*Cos(2*Pi/RotateSteps*Angle)-X2L*Sin(2*Pi/RotateSteps*Angle)) ); XL := X+X1LN; YL := Y+Y1LN; BuildLine( Border, XL,YL, X+X2LN,Y+Y2LN, Size.X ); BuildLine( LineBuf, 0, 0, X1RN-X1LN, Y1RN-Y1LN, Size.Y ); { The only thing that can be optimized is the loop below. I think it should be completely in asm. } for I := 1 to Size.X do begin DrawPicLine( PBuffer(@Screen)^[CurPage*VPageLen+(I-1) shr 1], (I-1) {mod 2} and 1, XL, YL, Size.Y, LineBuf ); { Inc( XL, Border[I].X ); Inc( YL, Border[I].Y ); } asm mov di,I shl di,2 mov ax,word ptr border[di]-4 add XL,ax mov ax,word ptr Border[di]-4+2 add YL,ax end; end; end; procedure TRotateApp.Run; var C: Char; begin repeat if KeyPressed then begin C := ReadKey; if C = #0 then C := ReadKey; case C of #72: Move(0,-10); #80: Move(0,-10); #75: Move(-10,0); #77: Move(10,0); #81: Rotate(1); #79: Rotate(-1); '+': Scale(1+ScaleStep); '-': Scale(1-ScaleStep); #27: Exit; end; end; Draw; { You can comment out the line below and do all transformation yourself } Update; FlipPage; Inc( FramesNumber ); until False; end; destructor TRotateApp.Done; begin inherited Done; WriteLn( 'Frames per second = ', (FramesNumber / ((Time-StartTime)*0.055) ):5:2 ); end; var RotateApp: TRotateApp; begin if not RotateApp.Init then Exit; RotateApp.Run; RotateApp.Done; end. {--------------------- UNIT CODE NEEDED HERE -------------------- } { VGA graphics unit. Coded by Mike Shirobokov(MSH) aka Mad Max / Queue members. This this the very small part of my gfx unit. I leave only functions used by RotateApp. Bugs corrected by Alex Grischenko } unit VGAGraph; interface uses Objects, Memory; const HRes = 360; VRes = 320; VPageLen = HRes*VRes div 4; { HRes = 320; VRes=200; Vpagelen=0;} type PBuffer = ^TBuffer; TBuffer = array[ 0..65534 ] of Byte; PScreenBuffer = ^TScreenBuffer; TScreenBuffer = array[ 0..199, 0..319 ] of Byte; TRGBPalette = array[ 0..255 ] of record R,G,B: Byte; end; PImage = ^TImage; TImage = object( TObject ) Size: TPoint; Palette: TRGBPalette; Data: PBuffer; constructor Load( Name: String ); { This procedures are now killed. If you need them just write me or see old mail from me. procedure Show( Origin: TPoint; var Buffer ); procedure ShowRect( Origin: TPoint; NewSize: TPoint; var Buffer ); } destructor Done; virtual; end; PGraphApplication = ^TGraphApplication; TGraphApplication = object( TObject ) constructor Init( ModeX : Boolean ); procedure Run; virtual; destructor Done; virtual; end; var Screen: TScreenBuffer absolute $A000:0; procedure SetPalette( var Pal: TRGBPalette ); procedure Set360x240Mode; procedure ShowPage( Page: Integer ); implementation uses PCX; constructor TImage.Load( Name: String ); var S: TDosStream; I: Integer; P: OldPCXPicture; Len: Word; begin inherited Init; P.Init( Name ); if P.Status <> pcxOK then begin P.Done; Fail; end; Size.X := P.H.XMax - P.H.XMin + 1; Size.Y := P.H.YMax - P.H.YMin + 1; { I use DOS memory allocation 'cuz GetMem can't allocate 64K Even thru DPMI. :-( GetMem( Data, Word(Size.X) * Size.Y ); } Len := Word((LongInt(Size.X)*Size.Y+15) div 16); LEN:=65536 DIV 16; asm mov ah,48h mov bx,Len int 21h jnc @mem_ok xor ax,ax @mem_ok: mov word ptr es:[di].Data+2,ax xor ax,ax mov word ptr es:[di].Data,ax end; if Data = nil then begin P.Done; Fail; end; fillchar(Data^,len*16-1,0); Move( P.Pal, Palette, SizeOf(Palette) ); for I := 0 to 255 do begin Palette[I].R := Palette[I].R shr 2; Palette[I].G := Palette[I].G shr 2; Palette[I].B := Palette[I].B shr 2; end; for I := 0 to Size.Y-1 do P.ReadLine( Data^[ Word(Size.X)*I ] ); P.Done; end; destructor TImage.Done; begin { FreeMem( Data, Word(Size.X)*Size.Y ); } asm mov ah,49h mov ax,word ptr es:[di].Data+2 mov es,ax int 21h end; inherited Done; end; constructor TGraphApplication.Init( ModeX : Boolean ); begin Set360x240Mode end; procedure TGraphApplication.Run; begin Abstract; end; destructor TGraphApplication.Done; begin asm mov ax,3h int 10h end; end; procedure SetPalette( var Pal: TRGBPalette ); var I : Integer; begin for I := 0 to 255 do begin Port[$3C8] := I; Port[$3C9] := Pal[I].R; Port[$3C9] := Pal[I].G; Port[$3C9] := Pal[I].B; end; end; { Modified from public-domain mode set code by John Bridges. } const SC_INDEX = $03c4; {Sequence Controller Index} CRTC_INDEX = $03d4; {CRT Controller Index} MISC_OUTPUT = $03c2; {Miscellaneous Output register} { Index/data pairs for CRT Controller registers that differ between mode 13h and mode X. } CRT_PARM_LENGTH = 17; CRTParms : array [1..CRT_PARM_LENGTH] of Word = ( $6B00, { Horz total } $5901, { Horz Displayed } $5A02, { Start Horz Blanking } $8E03, { End Horz Blanking } $5E04, { Start H Sync } $8A05, { End H Sync } $0d06, {vertical total} $3e07, {overflow (bit 8 of vertical counts)} $ea10, {v sync start} $8c11, {v sync end and protect cr0-cr7} $df12, {vertical displayed} $e715, {v blank start} $0616, {v blank end} $4209, {cell height (2 to double-scan)} $0014, {turn off dword mode} $e317, {turn on byte mode} $2D13 {90 bytes per line} ); procedure Set360x240Mode; begin asm mov ax,13h {let the BIOS set standard 256-color} int 10h {mode (320x200 linear)} mov dx,SC_INDEX mov ax,0604h out dx,ax {disable chain4 mode} mov ax,0100h out dx,ax {synchronous reset while switching clocks} mov dx,MISC_OUTPUT mov al,0E7h out dx,al {select 28 MHz dot clock & 60 Hz scanning rate} mov dx,SC_INDEX mov ax,0300h out dx,ax {undo reset (restart sequencer)} mov dx,CRTC_INDEX {reprogram the CRT Controller} mov al,11h {VSync End reg contains register write} out dx,al {protect bit} inc dx {CRT Controller Data register} in al,dx {get current VSync End register setting} and al,7fh {remove write protect on various} out dx,al {CRTC registers} dec dx {CRT Controller Index} cld mov si,offset CRTParms {point to CRT parameter table} mov cx,CRT_PARM_LENGTH {# of table entries} @SetCRTParmsLoop: lodsw {get the next CRT Index/Data pair} out dx,ax {set the next CRT Index/Data pair} push cx mov cx,1000 @loop: loop @loop pop cx loop @SetCRTParmsLoop mov dx,SC_INDEX mov ax,0f02h out dx,ax {enable writes to all four planes} mov ax,$A000{now clear all display memory, 8 pixels} mov es,ax {at a time} sub di,di {point ES:DI to display memory} sub ax,ax {clear to zero-value pixels} mov cx,VRes*HRes/4/2 {# of words in display memory} rep stosw {clear all of display memory} end; end; procedure ShowPage( Page: Integer ); begin asm mov ax,VPageLen mul word ptr Page mov bx,ax mov dx,3d4h mov al,0ch mov ah,bh out dx,ax mov dx,3d4h mov al,0dh mov ah,bl out dx,ax { Uncomment this waiting for retrace if you see flickering } { mov dx,3dah @@1: in al,dx test al,00001000b jz @@1 @@2: in al,dx test al,00001000b jnz @@2 } end; end; End. { -------------------------- UNIT CODE NEEDED HERE -------------} { 256 color PCX bitmaps handling unit. NewPCXPicture object are removed to reduce traffic. If you need it just contact me or dig in old mail from me. Coded by Mike Shirobokov(MSH) aka Mad Max / Queue Members. Free sourceware. } unit PCX; interface uses Objects; type TRGBPalette = array[ 0..255 ] of record R,G,B: Byte; end; PCXHeader = record Creator, Version, Encoding, Bits: Byte; XMin, YMin, XMax, YMax, HRes, VRes: Integer; Palette: array [ 1..48 ] of Byte; VMode, Planes: Byte; BytesPerLine, PaletteInfo, SHRes, SVRes: Word; Dummy: array [0..53] of Byte; end; const pcxOK = 0; pcxInvalidType = 1; pcxNoFile = 2; type OldPCXPicture = object H: PCXHeader; S: TBufStream; Pal: TRGBPalette; Status: Integer; constructor Init( AFileName: String ); procedure ReadLine( var Buffer ); function ErrorText: String; destructor Done; end; { NewPCXPicture = object H: PCXHeader; S: TBufStream; Pal: TRGBPalette; constructor Init( AFileName: String; HSize: Integer ); procedure WriteLine( var Buffer ); destructor Done; end; } implementation type GetByteFunc = function: Byte; ByteArr = array [0..65534] of Byte; PByte = ^ByteArr; procedure UnpackString( GetByte: GetByteFunc; var Dest; Size: Integer ); var DestPtr: PByte; Count: Integer; B: Byte; I: Integer; begin DestPtr := @Dest; Count := 0; while Count < Size do begin B := GetByte; if B < $C0 then begin DestPtr^[Count] := B; Inc(Count); end else begin DestPtr^[Count] := GetByte; for I := 0 to B-$C1 do DestPtr^[Count+I] := DestPtr^[Count]; Inc( Count, I+1 ); end; end; end; constructor OldPCXPicture.Init( AFileName: String ); begin S.Init( AFileName, stOpenRead, 2048 ); if S.Status <> stOk then begin Status := pcxNoFile; Exit; end; S.Read( H, SizeOf(H) ); if (H.Planes <> 1) or (H.Encoding <> 1) or (H.Bits <> 8 ) then begin Status := pcxInvalidType; Exit; end; S.Seek( S.GetSize - SizeOf(Pal) ); S.Read( Pal, SizeOf(Pal) ); S.Seek( SizeOf(H) ); Status := pcxOK; end; var __GetS__: PStream; function Get: Byte; far; var B: Byte; begin __GetS__^.Read( B, 1 ); Get := B; end; procedure OldPCXPicture.ReadLine( var Buffer ); begin __GetS__ := @S; UnpackString( Get, Buffer, H.BytesPerLine ); end; function OldPCXPicture.ErrorText: String; begin case Status of pcxOK: ErrorText := 'No errors'; pcxNoFile: ErrorText := 'Can''t open file'; pcxInvalidType: ErrorText := 'Only 8 bit PCXs are supported'; end; end; destructor OldPCXPicture.Done; begin S.Done; end; end.