12 /* APPROX. 170 FPS Minimum */
14 static void updatePropeller(float t);
16 #define BG_FILENAME "data/grise.png"
17 #define GROBJ_01_FILENAME "data/grobj_01.png"
19 #define BB_SIZE 512 /* Let's use a power of 2. Maybe we'll zoom/rotate the effect */
21 /* Every backBuffer scanline is guaranteed to have that many dummy pixels before and after */
22 #define PIXEL_PADDING 32
24 /* Make sure this is less than PIXEL_PADDING*/
25 #define MAX_DISPLACEMENT 16
27 #define MIN_SCROLL PIXEL_PADDING
28 #define MAX_SCROLL (backgroundW - FB_WIDTH - MIN_SCROLL)
30 #define FAR_SCROLL_SPEED 15.0f
31 #define NEAR_SCROLL_SPEED 120.0f
33 #define HORIZON_HEIGHT 100
34 #define REFLECTION_HEIGHT (240 - HORIZON_HEIGHT)
36 #define NORMALMAP_SCANLINE 372
38 static int init(void);
39 static void destroy(void);
40 static void start(long trans_time);
41 static void stop(long trans_time);
42 static void draw(void);
44 static void convert32To16(unsigned int *src32, unsigned short *dst16, unsigned int pixelCount);
45 static void processNormal();
46 static void initScrollTables();
47 static void updateScrollTables(float dt);
49 static unsigned short *background = 0;
50 static int backgroundW = 0;
51 static int backgroundH = 0;
53 static unsigned int lastFrameTime = 0;
54 static float lastFrameDuration = 0.0f;
56 static short *displacementMap;
58 static unsigned short *backBuffer;
60 static float scrollScaleTable[REFLECTION_HEIGHT];
61 static float scrollTable[REFLECTION_HEIGHT];
62 static int scrollTableRounded[REFLECTION_HEIGHT];
63 static int scrollModTable[REFLECTION_HEIGHT];
64 static float nearScrollAmount = 0.0f;
66 static unsigned char miniFXBuffer[1024];
68 static RleBitmap *grobj = 0;
69 static RleBitmap *rlePropeller = 0;
71 static struct screen scr = {"galaxyrise", init, destroy, start, 0, draw};
73 struct screen *grise_screen(void) {
77 static int init(void) {
78 unsigned char *tmpBitmap;
79 int tmpBitmapW, tmpBitmapH;
81 /* Allocate back buffer */
82 backBuffer = (unsigned short *)calloc(BB_SIZE * BB_SIZE, sizeof(unsigned short));
84 /* grise.png contains the background (horizon), baked reflection and normalmap for
87 img_load_pixels(BG_FILENAME, &backgroundW, &backgroundH, IMG_FMT_RGBA32))) {
88 fprintf(stderr, "failed to load image " BG_FILENAME "\n");
92 /* Convert to 16bpp */
93 convert32To16((unsigned int *)background, background,
94 backgroundW * NORMALMAP_SCANLINE); /* Normalmap will keep its 32 bit color */
96 /* Load reflected objects */
98 img_load_pixels(GROBJ_01_FILENAME, &tmpBitmapW, &tmpBitmapH, IMG_FMT_GREY8))) {
99 fprintf(stderr, "failed to load image " GROBJ_01_FILENAME "\n");
103 grobj = rleEncode(0, tmpBitmap, tmpBitmapW, tmpBitmapH);
105 img_free_pixels(tmpBitmap);
114 static void destroy(void) {
118 img_free_pixels(background);
123 static void start(long trans_time) { lastFrameTime = time_msec; }
125 static void draw(void) {
126 int scroll = MIN_SCROLL + (MAX_SCROLL - MIN_SCROLL) * mouse_x / FB_WIDTH;
127 unsigned short *dst = backBuffer + PIXEL_PADDING;
128 unsigned short *src = background + scroll;
137 lastFrameDuration = (time_msec - lastFrameTime) / 1000.0f;
138 lastFrameTime = time_msec;
140 /* Update mini-effects here */
141 updatePropeller(4.0f * time_msec / 1000.0f);
143 /* First, render the horizon */
144 for (scanline = 0; scanline < HORIZON_HEIGHT; scanline++) {
145 memcpy(dst, src, FB_WIDTH * 2);
150 /* Create scroll offsets for all scanlines of the normalmap */
151 updateScrollTables(lastFrameDuration);
153 /* Render the baked reflection one scanline below its place, so that
154 * the displacement that follows will be done in a cache-friendly way
156 src -= PIXEL_PADDING; /* We want to also fill the PADDING pixels here */
157 dst = backBuffer + (HORIZON_HEIGHT + 1) * BB_SIZE;
158 for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
159 memcpy(dst, src, (FB_WIDTH + PIXEL_PADDING) * 2);
164 /* Blit reflections first, to be displaced */
165 for (i = 0; i < 5; i++)
166 rleBlitScaleInv(rlePropeller, backBuffer + PIXEL_PADDING, FB_WIDTH, FB_HEIGHT,
167 BB_SIZE, 134 + (i - 3) * 60, 200, 1.0f, 1.8f);
169 /* Perform displacement */
170 dst = backBuffer + HORIZON_HEIGHT * BB_SIZE + PIXEL_PADDING;
171 src = dst + BB_SIZE; /* The pixels to be displaced are 1 scanline below */
172 dispScanline = displacementMap;
173 for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
175 md = scrollModTable[scanline];
176 sc = scrollTableRounded[scanline];
179 for (i = 0; i < FB_WIDTH; i++) {
180 /* Try to immitate modulo without the division */
183 scrolledIndex = i - accum + sc;
184 if (scrolledIndex >= md)
188 d = dispScanline[scrolledIndex];
192 dst += BB_SIZE - FB_WIDTH;
193 dispScanline += backgroundW;
196 /* Then after displacement, blit the objects */
197 for (i = 0; i < 5; i++)
198 rleBlit(rlePropeller, backBuffer + PIXEL_PADDING, FB_WIDTH, FB_HEIGHT, BB_SIZE,
199 134 + (i - 3) * 60, 100);
201 /* Blit effect to framebuffer */
202 src = backBuffer + PIXEL_PADDING;
204 for (scanline = 0; scanline < FB_HEIGHT; scanline++) {
205 memcpy(dst, src, FB_WIDTH * 2);
213 /* src and dst can be the same */
214 static void convert32To16(unsigned int *src32, unsigned short *dst16, unsigned int pixelCount) {
218 *dst16++ = ((p << 8) & 0xF800) /* R */
219 | ((p >> 5) & 0x07E0) /* G */
220 | ((p >> 19) & 0x001F); /* B */
225 /* Normal map preprocessing */
226 /* Scale normal with depth and unpack R component (horizontal component) */
227 static void processNormal() {
231 short maxDisplacement = 0;
232 short minDisplacement = 256;
235 unsigned int *normalmap = (unsigned int *)background;
236 normalmap += NORMALMAP_SCANLINE * backgroundW;
237 dst = (unsigned short *)normalmap;
238 displacementMap = (short *)dst;
239 dst2 = displacementMap;
241 for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
242 scrollModTable[scanline] = (int)(backgroundW / scrollScaleTable[scanline] + 0.5f);
243 for (i = 0; i < backgroundW; i++) {
244 x = (int)(i * scrollScaleTable[scanline] + 0.5f);
245 if (x < backgroundW) {
246 *dst = (unsigned short)(normalmap[x] >> 8) & 0xFF;
247 if ((short)*dst > maxDisplacement)
248 maxDisplacement = (short)(*dst);
249 if ((short)*dst < minDisplacement)
250 minDisplacement = (short)(*dst);
256 normalmap += backgroundW;
259 if (maxDisplacement == minDisplacement) {
260 printf("Warning: grise normalmap fucked up\n");
264 /* Second pass - subtract half maximum displacement to displace in both directions */
265 for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
266 for (i = 0; i < backgroundW; i++) {
267 /* Remember that MIN_SCROLL is the padding around the screen, so ti's the
268 * maximum displacement we can get (positive & negative) */
269 *dst2 = 2 * MAX_DISPLACEMENT * (*dst2 - minDisplacement) /
270 (maxDisplacement - minDisplacement) -
272 *dst2 = (short)((float)*dst2 / scrollScaleTable[scanline] +
273 0.5f); /* Displacements must also scale with distance*/
279 static float distanceScale(int scanline) {
281 farScale = (float)NEAR_SCROLL_SPEED / (float)FAR_SCROLL_SPEED;
282 t = (float)scanline / ((float)REFLECTION_HEIGHT - 1);
283 return 1.0f / (1.0f / farScale + (1.0f - 1.0f / farScale) * t);
286 static void initScrollTables() {
288 for (i = 0; i < REFLECTION_HEIGHT; i++) {
289 scrollScaleTable[i] = distanceScale(i);
290 scrollTable[i] = 0.0f;
291 scrollTableRounded[i] = 0;
295 static void updateScrollTables(float dt) {
298 nearScrollAmount += dt * NEAR_SCROLL_SPEED;
299 nearScrollAmount = (float)fmod(nearScrollAmount, 512.0f);
301 for (i = 0; i < REFLECTION_HEIGHT; i++) {
302 scrollTable[i] = nearScrollAmount / scrollScaleTable[i];
303 scrollTableRounded[i] = (int)(scrollTable[i] + 0.5f) % scrollModTable[i];
307 /* -------------------------------------------------------------------------------------------------
309 * -------------------------------------------------------------------------------------------------
312 #define PROPELLER_CIRCLE_RADIUS 18
313 #define PROPELLER_CIRCLE_RADIUS_SQ (PROPELLER_CIRCLE_RADIUS * PROPELLER_CIRCLE_RADIUS)
320 static void updatePropeller(float t) {
322 int cx, cy, count = 0;
328 static float sin120 = 0.86602540378f;
329 static float cos120 = -0.5f;
334 nx = x * cost - y * sint;
335 ny = y * cost + x * sint;
338 propellerState.circleX[0] = (int)(x + 0.5f) + 16;
339 propellerState.circleY[0] = (int)(y + 0.5f) + 16;
341 /* Rotate by 120 degrees, for the second circle */
342 nx = x * cos120 - y * sin120;
343 ny = y * cos120 + x * sin120;
346 propellerState.circleX[1] = (int)(x + 0.5f) + 16;
347 propellerState.circleY[1] = (int)(y + 0.5f) + 16;
350 nx = x * cos120 - y * sin120;
351 ny = y * cos120 + x * sin120;
354 propellerState.circleX[2] = (int)(x + 0.5f) + 16;
355 propellerState.circleY[2] = (int)(y + 0.5f) + 16;
357 /* Write effect to the mini fx buffer*/
359 for (j = 0; j < 32; j++) {
360 for (i = 0; i < 32; i++) {
364 cx = propellerState.circleX[0] - i;
365 cy = propellerState.circleY[0] - j;
366 if (cx * cx + cy * cy < PROPELLER_CIRCLE_RADIUS_SQ)
370 cx = propellerState.circleX[1] - i;
371 cy = propellerState.circleY[1] - j;
372 if (cx * cx + cy * cy < PROPELLER_CIRCLE_RADIUS_SQ)
376 cx = propellerState.circleX[2] - i;
377 cy = propellerState.circleY[2] - j;
378 if (cx * cx + cy * cy < PROPELLER_CIRCLE_RADIUS_SQ)
385 /* Then, encode to rle */
386 rlePropeller = rleEncode(rlePropeller, miniFXBuffer, 32, 32);
388 /* Distribute the produced streaks so that they don't produce garbage when interpolated */
389 rleDistributeStreaks(rlePropeller);