10 /* APPROX. 170 FPS Minimum */
17 static RLEBitmap createRLEBitmap(unsigned int w, unsigned int h);
18 static destroyRLEBitmap(RLEBitmap b);
20 #define BG_FILENAME "data/grise.png"
21 #define GROBJ_01_FILENAME "data/grobj_01.png"
23 #define BB_SIZE 512 /* Let's use a power of 2. Maybe we'll zoom/rotate the effect */
25 /* Every backBuffer scanline is guaranteed to have that many dummy pixels before and after */
26 #define PIXEL_PADDING 32
28 #define MIN_SCROLL PIXEL_PADDING
29 #define MAX_SCROLL (backgroundW - fb_width - MIN_SCROLL)
31 #define FAR_SCROLL_SPEED 50.0f
32 #define NEAR_SCROLL_SPEED 400.0f
34 #define HORIZON_HEIGHT 100
35 #define REFLECTION_HEIGHT (240 - HORIZON_HEIGHT)
37 #define NORMALMAP_SCANLINE 372
39 static int init(void);
40 static void destroy(void);
41 static void start(long trans_time);
42 static void stop(long trans_time);
43 static void draw(void);
45 static void convert32To16(unsigned int *src32, unsigned short *dst16, unsigned int pixelCount);
46 static void processNormal();
47 static void initScrollTables();
48 static void updateScrollTables(float dt);
50 static RLEBitmap rleEncode(unsigned char *pixels, unsigned int w, unsigned int h);
52 static unsigned short *background = 0;
53 static unsigned int backgroundW = 0;
54 static unsigned int backgroundH = 0;
56 static unsigned int lastFrameTime = 0;
57 static float lastFrameDuration = 0.0f;
59 static short *displacementMap;
61 static unsigned short *backBuffer;
63 static float scrollScaleTable[REFLECTION_HEIGHT];
64 static float scrollTable[REFLECTION_HEIGHT];
65 static int scrollTableRounded[REFLECTION_HEIGHT];
66 static int scrollModTable[REFLECTION_HEIGHT];
67 static float nearScrollAmount = 0.0f;
69 static RLEBitmap grobj;
71 static struct screen scr = {
80 struct screen *grise_screen(void)
88 unsigned char *tmpBitmap;
89 int tmpBitmapW, tmpBitmapH;
91 /* Allocate back buffer */
92 backBuffer = (unsigned short*) malloc(BB_SIZE * BB_SIZE * sizeof(unsigned short));
94 /* grise.png contains the background (horizon), baked reflection and normalmap for displacement */
95 if (!(background = img_load_pixels(BG_FILENAME, &backgroundW, &backgroundH, IMG_FMT_RGBA32))) {
96 fprintf(stderr, "failed to load image " BG_FILENAME "\n");
100 /* Convert to 16bpp */
101 convert32To16((unsigned int*)background, background, backgroundW * NORMALMAP_SCANLINE); /* Normalmap will keep its 32 bit color */
103 /* Load reflected objects */
104 if (!(tmpBitmap = img_load_pixels(GROBJ_01_FILENAME, &tmpBitmapW, &tmpBitmapH, IMG_FMT_GREY8))) {
105 fprintf(stderr, "failed to load image " GROBJ_01_FILENAME "\n");
109 grobj = rleEncode(tmpBitmap, tmpBitmapW, tmpBitmapH);
111 img_free_pixels(tmpBitmap);
124 static void destroy(void)
129 img_free_pixels(background);
131 destroyRLEBitmap(grobj);
134 static void start(long trans_time)
136 lastFrameTime = time_msec;
139 static void stop(long trans_time)
143 static void draw(void)
145 int scroll = MIN_SCROLL + (MAX_SCROLL - MIN_SCROLL) * mouse_x / fb_width;
146 unsigned short *dst = backBuffer + PIXEL_PADDING;
147 unsigned short *src = background + scroll;
153 lastFrameDuration = (time_msec - lastFrameTime) / 1000.0f;
154 lastFrameTime = time_msec;
156 /* First, render the horizon */
157 for (scanline = 0; scanline < HORIZON_HEIGHT; scanline++) {
158 memcpy(dst, src, fb_width * 2);
163 /* Create scroll offsets for all scanlines of the normalmap */
164 updateScrollTables(lastFrameDuration);
166 /* Render the baked reflection one scanline below its place, so that
167 * the displacement that follows will be done in a cache-friendly way
169 src -= PIXEL_PADDING; /* We want to also fill the PADDING pixels here */
170 dst = backBuffer + (HORIZON_HEIGHT + 1) * BB_SIZE;
171 for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
172 memcpy(dst, src, (fb_width + PIXEL_PADDING) * 2);
177 /* Perform displacement */
178 dst = backBuffer + HORIZON_HEIGHT * BB_SIZE + PIXEL_PADDING;
179 src = dst + BB_SIZE; /* The pixels to be displaced are 1 scanline below */
180 dispScanline = displacementMap;
181 for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
182 for (i = 0; i < fb_width; i++) {
183 d = dispScanline[(i + scrollTableRounded[scanline]) % scrollModTable[scanline]];
187 dst += BB_SIZE - fb_width;
188 dispScanline += backgroundW;
191 /* Blit effect to framebuffer */
192 src = backBuffer + PIXEL_PADDING;
194 for (scanline = 0; scanline < fb_height; scanline++) {
195 memcpy(dst, src, fb_width * 2);
201 /* src and dst can be the same */
202 static void convert32To16(unsigned int *src32, unsigned short *dst16, unsigned int pixelCount) {
206 *dst16++ = ((p << 8) & 0xF800) /* R */
207 | ((p >> 5) & 0x07E0) /* G */
208 | ((p >> 19) & 0x001F); /* B */
213 /* Normal map preprocessing */
214 /* Scale normal with depth and unpack R component (horizontal component) */
215 static void processNormal() {
219 short maxDisplacement = 0;
220 short minDisplacement = 256;
223 unsigned int *normalmap = (unsigned int*)background;
224 normalmap += NORMALMAP_SCANLINE * backgroundW;
225 dst = (unsigned short*)normalmap;
226 displacementMap = (short*)dst;
227 dst2 = displacementMap;
229 for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
230 scrollModTable[scanline] = (int) (backgroundW / scrollScaleTable[scanline] + 0.5f);
231 for (i = 0; i < backgroundW; i++) {
232 x = (int)(i * scrollScaleTable[scanline] + 0.5f);
233 if (x < backgroundW) {
234 *dst = (unsigned short)(normalmap[x] >> 8) & 0xFF;
235 if ((short)*dst > maxDisplacement) maxDisplacement = (short)(*dst);
236 if ((short)*dst < minDisplacement) minDisplacement = (short)(*dst);
242 normalmap += backgroundW;
245 if (maxDisplacement == minDisplacement) {
246 printf("Warning: grise normalmap fucked up\n");
250 /* Second pass - subtract half maximum displacement to displace in both directions */
251 for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
252 for (i = 0; i < backgroundW; i++) {
253 /* Remember that MIN_SCROLL is the padding around the screen, so ti's the maximum displacement we can get (positive & negative) */
254 *dst2 = 2 * MIN_SCROLL * (*dst2 - minDisplacement) / (maxDisplacement - minDisplacement) - MIN_SCROLL;
255 *dst2 = (short)((float)*dst2 / scrollScaleTable[scanline] + 0.5f); /* Displacements must also scale with distance*/
261 static float distanceScale(int scanline) {
263 farScale = (float)NEAR_SCROLL_SPEED / (float)FAR_SCROLL_SPEED;
264 t = (float)scanline / ((float)REFLECTION_HEIGHT - 1);
265 return 1.0f / (1.0f / farScale + (1.0f - 1.0f / farScale) * t);
268 static void initScrollTables() {
270 for (i = 0; i < REFLECTION_HEIGHT; i++) {
271 scrollScaleTable[i] = distanceScale(i);
272 scrollTable[i] = 0.0f;
273 scrollTableRounded[i] = 0;
278 static void updateScrollTables(float dt) {
281 nearScrollAmount += dt * NEAR_SCROLL_SPEED;
282 nearScrollAmount = (float) fmod(nearScrollAmount, 512.0f);
284 for (i = 0; i < REFLECTION_HEIGHT; i++) {
285 scrollTable[i] = nearScrollAmount / scrollScaleTable[i];
286 scrollTableRounded[i] = (int)(scrollTable[i] + 0.5f) % scrollModTable[i];
290 /* -------------------------------------------------------------------------------------------------
292 * -------------------------------------------------------------------------------------------------
294 /* Limit streak count per scanline so we can directly jump to specific scanline */
295 #define RLE_STREAKS_PER_SCANLINE 4
296 /* Every streak is encoded by 2 bytes: offset and count of black pixels in the streak */
297 #define RLE_BYTES_PER_SCANLINE RLE_STREAKS_PER_SCANLINE * 2
299 static RLEBitmap createRLEBitmap(unsigned int w, unsigned int h) {
304 /* Add some padding at the end of the buffer, with the worst case for a scanline (w/2 streaks) */
305 ret.scans = (unsigned char*) calloc(h * RLE_BYTES_PER_SCANLINE + w, 1);
310 static destroyRLEBitmap(RLEBitmap b) {
314 static RLEBitmap rleEncode(unsigned char *pixels, unsigned int w, unsigned int h) {
321 unsigned char *output;
323 /* https://www.youtube.com/watch?v=RKMR02o1I88&feature=youtu.be&t=55 */
324 ret = createRLEBitmap(w, h);
326 for (scanline = 0; scanline < h; scanline++) {
327 output = ret.scans + scanline * RLE_BYTES_PER_SCANLINE;
329 for (i = 0; i < w; i++) {
332 if (counter >= PIXEL_PADDING) {
333 *output++ = (unsigned char) counter;
335 *output++ = (unsigned char)accum;
340 *output++ = (unsigned char)accum;
347 *output++ = (unsigned char)counter;
359 *output++ = (unsigned char)counter;