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): 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): 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 |