//--------------------------------
//             Water
//
//    (C) 1995 Kimmo Roimela
//
// You may use this code for your
// own productions as you wish,
// but partial credit would be
// appreciated.
//--------------------------------

// Oh, and this ISN'T supposed to be an example of good coding,
// I just quickly converted this from another program of mine...

#include <conio.h>
#include <dos.h>
#include <iostream.h>
#include <mem.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>

#define	MAXCOL		256

typedef unsigned char byte;

const long XRes	= 320,
	   YRes = 200;

unsigned VGA_SEG = 0xA000;
byte far *const VGABase = (byte far*) MK_FP(VGA_SEG, 0);

byte far *buffer1, *buffer2, *buffer3;
unsigned buffer1_SEG, buffer2_SEG, buffer3_SEG;

const long PL = XRes / 4;

void init(void);
void cleanup(void);
void drawframe(unsigned buffer_SEG);
void setpalette(void);

extern "C" void far wave(unsigned dest, unsigned src, int x, int y, int width, int height);

//----------------------------------------------------------


void main(void)
{
    // Initialize graphics & memory.
    init();

    // Initialize the frame rate calculation.
    clock_t start, end;
    long frames = 0;

    // Loop with the effects on until Esc is pressed.
    while(!(kbhit() && getch() == 27))
    {
	drawframe(buffer2_SEG);
	wave(buffer1_SEG, buffer2_SEG, 2, 2, 316, 196);
	drawframe(buffer1_SEG);
	wave(buffer2_SEG, buffer1_SEG, 2, 2, 316, 196);
    }

    // Start the clock.
    start = clock();

    // Continue without the effects.
    while(!(kbhit() && getch() == 27))
    {
	wave(buffer1_SEG, buffer2_SEG, 2, 2, 316, 196);
	frames++;
	wave(buffer2_SEG, buffer1_SEG, 2, 2, 316, 196);
	frames++;
    }

    // Get the end time.
    end = clock();

    // Clean up, display frame rate.
    cleanup();

    cout << CLK_TCK * long(frames) / (end - start) << " frames/second.\n";
}

//------------------------------------------------------
// init
//
// Reserves memory, initializes tables & graphics.
//
void init(void)
{
    // Make sure the buffer size doesn't exceed segment size.
    long memreq = XRes * YRes + 16;
    if (memreq > 65536L)
    {
      cerr << "Buffer memory requirement exceeds segment size.\n";
      exit(0);
    }

    // Reserve buffers.
    if (!(buffer1 = new far byte[memreq]))
    {
	cerr << "Buffer memory allocation failed.\n";
	exit(0);
    }

    if (!(buffer2 = new far byte[memreq]))
    {
	cerr << "Buffer memory allocation failed.\n";
	exit(0);
    }

    if (!(buffer3 = new far byte[memreq]))
    {
	cerr << "Velocity table allocation failed.\n";
	exit(0);
    }

    // Get direct segment addresses to the buffers.
    unsigned long seg = FP_SEG(buffer1),
		  off = FP_OFF(buffer1);
    buffer1_SEG = (seg * 16 + off + 15) / 16;

    seg = FP_SEG(buffer2);
    off = FP_OFF(buffer2);
    buffer2_SEG = (seg * 16 + off + 15) / 16;

    seg = FP_SEG(buffer3);
    off = FP_OFF(buffer3);
    buffer3_SEG = (seg * 16 + off + 15) / 16;

    // Clear video & buffer memory
    _fmemset(VGABase, 0, XRes * YRes);
    _fmemset(buffer1, 128, XRes * YRes + 16);
    _fmemset(buffer2, 128, XRes * YRes + 16);
    _fmemset(buffer3, 0, XRes * YRes + 16);

    randomize();

    // Switch to graphics mode
    asm {
	mov ah,0
	mov al,0x13
	int 0x10
    }

    // Set the palette
    setpalette();
}

//------------------------------------------------------
// cleanup
//
// Init in reverse.
//
void cleanup(void)
{
    // Free reserved memory
    delete[] buffer1;
    delete[] buffer2;
    delete[] buffer3;

    // Restore text mode
    asm {
	mov ah,0
	mov al,3
	int 0x10
    }
}

//------------------------------------------------------
// setpalette
//
void setpalette(void)
{
    int i, r, g, b;

    for(i = 0; i < MAXCOL; i++)
    {
	r = (i >= (MAXCOL * 3 / 4.0)) ? (i - MAXCOL * 3 / 4.0) * 4 / MAXCOL : 0;
	g = (i >= (MAXCOL / 2.0)) ? (i - (MAXCOL / 2.0)) / (MAXCOL / 2.0 / 64) : 0;
	b = i * 64 / MAXCOL;
	outportb(0x3C8, i);
	outportb(0x3C9, r);
	outportb(0x3C9, g);
	outportb(0x3C9, b);
    }
}

//------------------------------------------------------
// drawframe
//
void drawframe(unsigned buffer_SEG)
{
  static byte *base = (byte *) MK_FP(buffer_SEG, 0);
  static byte *vel = (byte *) MK_FP(buffer3_SEG, 0);
  static int i = 0;
  static unsigned offset;

// Uncomment the ones you want from the effects below //

  // Single pulse
/*  if (i == 0) {
    base[161 + 640 * 50] = 128;
    vel[161 + 640 * 50] = 31;
  }*/

  // One complete wave
/*  if (i / 10.0 < 2 * M_PI) {
    base[161 + 640 * 50] = 128 + 127 * sin(i / 10.0);
    vel[161 + 640 * 50] = 0;
  }*/

  // Half a wave
/*  if (i / 10.0 < M_PI) {
    base[161 + 640 * 50] = 128 + 127 * sin(i / 10.0);
    vel[161 + 640 * 50] = 0;
  }*/

  // Horizontal waves (may overflow after some time...)
/*  for (int y = 2; y < 98; y++) {
    base[161 + 640 * y] = 128 + 32 * sin(i / 16.0);
    vel[161 + 640 * y] = 0;
  }*/

  // Low-frequency vertical waves (an attempt at a standing wave)
/*  for (int x = 2; x < 158; x++) {
    base[2 * x + 640 * 50 + 1] = 128 + 32 * sin(i / 49.0);
    vel[2 * x + 640 * 50 + 1] = 0;
  }*/

  // More vertical waves
/*  for (int x = 2; x < 158; x++) {
    base[2 * x + 640 * 50 + 1] = 128 + 24 * sin(i / 12.25);
    vel[2 * x + 640 * 50 + 1] = 0;
  }*/

  // Single diagonal wave
/*  if (i / 6.0 < 2 * M_PI) {
    for (int y = 22; y < 78; y++) {
      base[2 * (y / 2 + 20) + 640 * y + 1] = 128 + 60 * sin(i / 6.0);
      vel[2 * (y / 2 + 20) + 640 * y + 1] = 0;
    }
  }*/

  // Interference
/*  base[121 + 640 * 40] = 128 + 127 * sin(i / 12.0);
  vel[121 + 640 * 40] = 0;
  base[201 + 640 * 60] = 128 + 127 * sin(i / 12.0);
  vel[201 + 640 * 60] = 0;*/

  // Moving peak
  offset = 2 * int(60 * sin(i / 130.0)) + 160 + 640 * int(40 * cos(i / 170.0) + 50) + 1;
  base[offset] = 128+80;
  vel[offset] = 0;

  // Moving vibrator
/*  offset = 2 * int(40 * sin(i / 360.0)) + 160 + 640 * int(40 * cos(i / 360.0) + 50) + 1;
  base[offset] = 128 + 127*sin(i/12.0);
  vel[offset] = 0;*/

  // Rain, sort of
/*  if (random(2000) < i) {
    offset = 2*random(160) + 640 * random(100) + 1;
    //base[offset] = 128;
    vel[offset] = random(24)-12;
  }*/

  // Something strange...
/*  offset = 2*random(160) + 640 * random(100) + 1;
  base[offset] = 128;
  vel[offset] = -random(24);*/

  i++;
}
