3D Starfield

Pincha aquí para verlo en español

In previous tutorials 2D Starfield and Measuring times and optimizing 2D Starfield we saw how to program and optimize a star field effect in two dimensions, let us now make a simple effect of 3D star field.

In this effect we simulate that we travel in the cabin of a spaceship and move among the stars. Each of the stars will have a position in x, y and z (depth), what we do is move the position of every star in the z axis until it reaches 0 and then we start again. To paint on the screen, we calculate the 2D position of a star in 3D, which is really easy: x2D = x3D / z3D and y2d = y3D / z3D. Our screen is 160x200 in mode 0, but in 3D we will expand this range in the x and y of each star, because otherwise all the stars would be in the center of the screen until the z was very close to 0 .

This would be the complete source code 3Dstars01.c (for sdcc):

////////////////////////////////////////////////////////////////////////
// 3Dstars01.c
// 3D Star Field
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

unsigned char GetMode0PixelColorByte(unsigned char nColor, unsigned char nPixel)
{
  unsigned char nByte = 0;

  if(nPixel == 0)
  {
    nByte &= 85;

    if(nColor & 1)
      nByte |= 128;

    if(nColor & 2)
      nByte |= 8;

    if(nColor & 4)
      nByte |= 32;

    if(nColor & 8)
      nByte |= 2;
  }
  else
  {
    nByte &= 170;

    if(nColor & 1)
      nByte |= 64;

    if(nColor & 2)
      nByte |= 4;

    if(nColor & 4)
      nByte |= 16;

    if(nColor & 8)
      nByte |= 1;
  }

  return nByte;
}

unsigned char *GetLineAddress(unsigned char nLine)
{
  return (unsigned char *)0xC000 + ((nLine / 8) * 80) + ((nLine % 8) * 2048);
}

////////////////////////////////////////////////////////////////////////
unsigned char char1,char2,char3,char4;

unsigned int GetTime()
{
  unsigned int nTime = 0;

  __asm
    CALL #0xBD0D ;KL TIME PLEASE
    PUSH HL
    POP DE
    LD HL, #_char3
    LD (HL), D
    LD HL, #_char4
    LD (HL), E
  __endasm;

  nTime = (char3 << 8) + char4;

  return nTime;
}
////////////////////////////////////////////////////////////////////////

struct _tStar
{
  int nX;
  int nY;
  int nZ;
  int nVelocity;
  unsigned char nColor;
  unsigned char *pLastAddress;
};

#define STARS_NUM 25
struct _tStar aStars[STARS_NUM];

#define MAX_Z 64
#define ORIGIN_X 80
#define ORIGIN_Y 100

void main()
{
  unsigned int nFPS = 0;
  unsigned int nTimeLast = 0;
  unsigned char nStar = 0;
  struct _tStar *pStar = NULL;
  int nScreenX = 0;
  int nScreenY = 0;

  memset(aStars, 0, sizeof(aStars));
  
  //SCR_SET_MODE 0
  __asm
    ld a, #0
    call #0xBC0E
  __endasm;

  //PALETE
  __asm
    ld a, #0
    ld b, #0 ;black
    ld c, b
    call #0xBC32 ;SCR SET INK

    ld a, #1
    ld b, #12 ;Yellow
    ld c, b
    call #0xBC32 ;SCR SET INK

    ld a, #2
    ld b, #25 ;Pastel Yellow    
    ld c, b
    call #0xBC32 ;SCR SET INK

    ld a, #3
    ld b, #24 ;Bright Yellow
    ld c, b
    call #0xBC32 ;SCR SET INK
  __endasm;

  //SCR SET BORDER 0
  __asm
    ld b, #0 ;black
    ld c, b
    call #0xBC38
  __endasm;

  //Init
  for(nStar = 0; nStar < STARS_NUM; nStar++)
  {
    aStars[nStar].nX = rand() % 3200 - 1600;
    aStars[nStar].nY = rand() % 4000 - 2000;
    aStars[nStar].nZ = rand() % MAX_Z;
    aStars[nStar].nColor = GetMode0PixelColorByte(1 + rand() % 3, 0);
    aStars[nStar].nVelocity = 1 + rand() % 5;
    aStars[nStar].pLastAddress = NULL;
  }

  nTimeLast = GetTime();

  while(1)
  {
    for(nStar = 0; nStar < STARS_NUM; nStar++)
    {
      pStar = &aStars[nStar];

      //delete star
      if(pStar->pLastAddress != NULL)
        *pStar->pLastAddress = 0;

      //move star
      pStar->nZ -= pStar->nVelocity;
      
      //paint star
      nScreenX = ORIGIN_X + pStar->nX / (pStar->nZ > 0 ? pStar->nZ : 1);
      nScreenY = ORIGIN_Y + pStar->nY / (pStar->nZ > 0 ? pStar->nZ : 1);

      if(nScreenX >= 0 && nScreenX < 160 && nScreenY >= 0 && nScreenY < 200)
      {
        pStar->pLastAddress = GetLineAddress(nScreenY) + (nScreenX / 2);
        *pStar->pLastAddress = pStar->nColor;
      }
      else
        pStar->nZ = MAX_Z;

      if(pStar->nZ <= 0)
        pStar->nZ = MAX_Z;

      //printf("%d,%d (%d,%d,%d)\n\r", nScreenX, nScreenY, pStar->nX, pStar->nY, pStar->nZ);
    }

    nFPS++;

    if(GetTime() - nTimeLast >= 300)
    {
      //TXT SET CURSOR 0,0
      __asm
        ld h, #1
        ld l, #1
        call #0xBB75
      __endasm;

      printf("%u  ", nFPS);

      nTimeLast = GetTime();
      nFPS = 0;
    }
  }
}
////////////////////////////////////////////////////////////////////////

If you run on an emulator you would see something like this (much more fluid in the emulator):

gifff

As you can see, we got about 24 frames per second with 25 stars ... the truth is that 25 stars are few. As we saw in the previous tutorial, it's best to precalculate all we can. In this case we can precalculate everything, each star would have at most 64 positions, we can calculate them all to avoid making the operations and dedicate only to delete / paint. This would be the modified source code to precalculate all:

////////////////////////////////////////////////////////////////////////
// 3Dstars02.c
// 3D Star Field
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

unsigned char GetMode0PixelColorByte(unsigned char nColor, unsigned char nPixel)
{
  unsigned char nByte = 0;

  if(nPixel == 0)
  {
    nByte &= 85;

    if(nColor & 1)
      nByte |= 128;

    if(nColor & 2)
      nByte |= 8;

    if(nColor & 4)
      nByte |= 32;

    if(nColor & 8)
      nByte |= 2;
  }
  else
  {
    nByte &= 170;

    if(nColor & 1)
      nByte |= 64;

    if(nColor & 2)
      nByte |= 4;

    if(nColor & 4)
      nByte |= 16;

    if(nColor & 8)
      nByte |= 1;
  }

  return nByte;
}

unsigned char *GetLineAddress(unsigned char nLine)
{
  return (unsigned char *)0xC000 + ((nLine / 8) * 80) + ((nLine % 8) * 2048);
}

////////////////////////////////////////////////////////////////////////
unsigned char char1,char2,char3,char4;

unsigned int GetTime()
{
  unsigned int nTime = 0;

  __asm
    CALL #0xBD0D ;KL TIME PLEASE
    PUSH HL
    POP DE
    LD HL, #_char3
    LD (HL), D
    LD HL, #_char4
    LD (HL), E
  __endasm;

  nTime = (char3 << 8) + char4;

  return nTime;
}
////////////////////////////////////////////////////////////////////////

#define MAX_Z 64
#define ORIGIN_X 80
#define ORIGIN_Y 100

struct _tStar
{
  unsigned char nColor;
  unsigned char nActualAddress;
  unsigned char *aAddress[MAX_Z + 1];
};

#define STARS_NUM 25
struct _tStar aStars[STARS_NUM];

void main()
{
  unsigned int nFPS = 0;
  unsigned int nTimeLast = 0;
  unsigned char nStar = 0;
  unsigned char nAddress = 0;
  struct _tStar *pStar = NULL;
  int nScreenX = 0;
  int nScreenY = 0;

  memset(aStars, 0, sizeof(aStars));
  
  //SCR_SET_MODE 0
  __asm
    ld a, #0
    call #0xBC0E
  __endasm;

  //PALETE
  __asm
    ld a, #0
    ld b, #0 ;black
    ld c, b
    call #0xBC32 ;SCR SET INK

    ld a, #1
    ld b, #12 ;Yellow
    ld c, b
    call #0xBC32 ;SCR SET INK

    ld a, #2
    ld b, #25 ;Pastel Yellow    
    ld c, b
    call #0xBC32 ;SCR SET INK

    ld a, #3
    ld b, #24 ;Bright Yellow
    ld c, b
    call #0xBC32 ;SCR SET INK
  __endasm;

  //SCR SET BORDER 0
  __asm
    ld b, #0 ;black
    ld c, b
    call #0xBC38
  __endasm;

  //Init
  memset(aStars, 0, sizeof(aStars));

  for(nStar = 0; nStar < STARS_NUM; nStar++)
  {
    int nX = rand() % 3200 - 1600;
    int nY = rand() % 4000 - 2000;
    int nZ = MAX_Z;
    int nVelocity = 1 + rand() % 5;

    pStar = &aStars[nStar];
    pStar->nColor = GetMode0PixelColorByte(1 + rand() % 3, 0);
    
    for(nAddress = 0; nAddress < MAX_Z; nAddress++)
    {
      nScreenX = ORIGIN_X + nX / (nZ > 0 ? nZ : 1);
      nScreenY = ORIGIN_Y + nY / (nZ > 0 ? nZ : 1);

      if(nScreenX >= 0 && nScreenX < 160 && nScreenY >= 0 && nScreenY < 200)
      {
        pStar->aAddress[nAddress] = GetLineAddress(nScreenY) + (nScreenX / 2);
      }
      else
      {
        break;
      }

      nZ -= nVelocity;

      if(nZ < 0)
        break;
    }

    pStar->nActualAddress = rand() % nAddress;
  }

  nTimeLast = GetTime();

  while(1)
  {
    for(nStar = 0; nStar < STARS_NUM; nStar++)
    {
      pStar = &aStars[nStar];

      //delete star
      *pStar->aAddress[pStar->nActualAddress] = 0;

      //move star
      pStar->nActualAddress++;

      if(pStar->aAddress[pStar->nActualAddress] == NULL)
        pStar->nActualAddress = 0;
      
      //paint star
      *pStar->aAddress[pStar->nActualAddress] = pStar->nColor;
    }

    nFPS++;

    if(GetTime() - nTimeLast >= 300)
    {
      //TXT SET CURSOR 0,0
      __asm
        ld h, #1
        ld l, #1
        call #0xBB75
      __endasm;

      printf("%u  ", nFPS);

      nTimeLast = GetTime();
      nFPS = 0;
    }
  }
}
////////////////////////////////////////////////////////////////////////

With these optimizations, would improve from 24 frames per second up to 81 ... 25 stars are few, so we will generate a final version of the source code, eliminating the calculation and on-screen text frames per second and increasing the number of stars from 25 to 125, to get the effect be nicer. This would be the final source code:

////////////////////////////////////////////////////////////////////////
// 3Dstars03.c
// 3D Star Field
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

unsigned char GetMode0PixelColorByte(unsigned char nColor, unsigned char nPixel)
{
  unsigned char nByte = 0;

  if(nPixel == 0)
  {
    nByte &= 85;

    if(nColor & 1)
      nByte |= 128;

    if(nColor & 2)
      nByte |= 8;

    if(nColor & 4)
      nByte |= 32;

    if(nColor & 8)
      nByte |= 2;
  }
  else
  {
    nByte &= 170;

    if(nColor & 1)
      nByte |= 64;

    if(nColor & 2)
      nByte |= 4;

    if(nColor & 4)
      nByte |= 16;

    if(nColor & 8)
      nByte |= 1;
  }

  return nByte;
}

unsigned char *GetLineAddress(unsigned char nLine)
{
  return (unsigned char *)0xC000 + ((nLine / 8) * 80) + ((nLine % 8) * 2048);
}

#define MAX_Z 64
#define ORIGIN_X 80
#define ORIGIN_Y 100

struct _tStar
{
  unsigned char nColor;
  unsigned char nActualAddress;
  unsigned char *aAddress[MAX_Z + 1];
};

#define STARS_NUM 125
struct _tStar aStars[STARS_NUM];

void main()
{
  unsigned char nStar = 0;
  unsigned char nAddress = 0;
  struct _tStar *pStar = NULL;
  int nScreenX = 0;
  int nScreenY = 0;

  memset(aStars, 0, sizeof(aStars));
  
  //SCR_SET_MODE 0
  __asm
    ld a, #0
    call #0xBC0E
  __endasm;

  //PALETE
  __asm
    ld a, #0
    ld b, #0 ;black
    ld c, b
    call #0xBC32 ;SCR SET INK

    ld a, #1
    ld b, #12 ;Yellow
    ld c, b
    call #0xBC32 ;SCR SET INK

    ld a, #2
    ld b, #25 ;Pastel Yellow    
    ld c, b
    call #0xBC32 ;SCR SET INK

    ld a, #3
    ld b, #24 ;Bright Yellow
    ld c, b
    call #0xBC32 ;SCR SET INK
  __endasm;

  //SCR SET BORDER 0
  __asm
    ld b, #0 ;black
    ld c, b
    call #0xBC38
  __endasm;

  //Init
  memset(aStars, 0, sizeof(aStars));

  for(nStar = 0; nStar < STARS_NUM; nStar++)
  {
    int nX = rand() % 3200 - 1600;
    int nY = rand() % 4000 - 2000;
    int nZ = MAX_Z;
    int nVelocity = 1 + rand() % 5;

    pStar = &aStars[nStar];
    pStar->nColor = GetMode0PixelColorByte(1 + rand() % 3, 0);
    
    for(nAddress = 0; nAddress < MAX_Z; nAddress++)
    {
      nScreenX = ORIGIN_X + nX / (nZ > 0 ? nZ : 1);
      nScreenY = ORIGIN_Y + nY / (nZ > 0 ? nZ : 1);

      if(nScreenX >= 0 && nScreenX < 160 && nScreenY >= 0 && nScreenY < 200)
      {
        pStar->aAddress[nAddress] = GetLineAddress(nScreenY) + (nScreenX / 2);
      }
      else
      {
        break;
      }

      nZ -= nVelocity;

      if(nZ < 0)
        break;
    }

    pStar->nActualAddress = rand() % nAddress;
  }

  while(1)
  {
    for(nStar = 0; nStar < STARS_NUM; nStar++)
    {
      pStar = &aStars[nStar];

      //delete star
      *pStar->aAddress[pStar->nActualAddress] = 0;

      //move star
      pStar->nActualAddress++;

      if(pStar->aAddress[pStar->nActualAddress] == NULL)
        pStar->nActualAddress = 0;
      
      //paint star
      *pStar->aAddress[pStar->nActualAddress] = pStar->nColor;
    }
  }
}
////////////////////////////////////////////////////////////////////////

If we run in the emulator, you would see something like this (much more fluid and fast in the emulator):

giffff

You could download a zip with all files (source code, bat to compile, binary and dsk's) here: 3D_Starfield.zip

 

www.CPCMania.com 2012