#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * * OpenRCT2 is the work of many authors, a full list can be found in contributors.md * For more information, visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * A full copy of the GNU General Public License can be found in licence.txt *****************************************************************************/ #pragma endregion #ifdef __ENABLE_LIGHTFX__ #include #include #include "../common.h" #include "../config/Config.h" #include "../Game.h" #include "../interface/Viewport.h" #include "../interface/Window.h" #include "../paint/tile_element/TileElement.h" #include "../util/Util.h" #include "../world/Climate.h" #include "Drawing.h" #include "LightFX.h" extern "C" { static uint8 _bakedLightTexture_lantern_0[32*32]; static uint8 _bakedLightTexture_lantern_1[64*64]; static uint8 _bakedLightTexture_lantern_2[128*128]; static uint8 _bakedLightTexture_lantern_3[256*256]; static uint8 _bakedLightTexture_spot_0[32 * 32]; static uint8 _bakedLightTexture_spot_1[64 * 64]; static uint8 _bakedLightTexture_spot_2[128 * 128]; static uint8 _bakedLightTexture_spot_3[256 * 256]; static rct_drawpixelinfo _pixelInfo; static bool _lightfxAvailable = false; static void* _light_rendered_buffer_back = nullptr; static void* _light_rendered_buffer_front = nullptr; static uint32 _lightPolution_back = 0; static uint32 _lightPolution_front = 0; typedef struct lightlist_entry { sint16 x, y, z; uint8 lightType; uint8 lightIntensity; uint32 lightID; uint16 lightIDqualifier; uint8 lightLinger; uint8 pad[1]; } lightlist_entry; static lightlist_entry _LightListA[16000]; static lightlist_entry _LightListB[16000]; static lightlist_entry *_LightListBack; static lightlist_entry *_LightListFront; static uint32 LightListCurrentCountBack; static uint32 LightListCurrentCountFront; static sint16 _current_view_x_front = 0; static sint16 _current_view_y_front = 0; static uint8 _current_view_rotation_front = 0; static uint8 _current_view_zoom_front = 0; static sint16 _current_view_x_back = 0; static sint16 _current_view_y_back = 0; static uint8 _current_view_rotation_back = 0; static uint8 _current_view_zoom_back = 0; static uint8 _current_view_zoom_back_delay = 0; static rct_palette gPalette_light; static uint8 calc_light_intensity_lantern(sint32 x, sint32 y) { double distance = (double)(x * x + y * y); double light = 0.03 + std::pow(10.0 / (1.0 + distance / 100.0), 0.55); light *= std::min(1.0, std::max(0.0, 2.0 - std::sqrt(distance) / 64)); light *= 0.1f; return (uint8)(std::min(255.0, light * 255.0)); } static uint8 calc_light_intensity_spot(sint32 x, sint32 y) { double distance = (double)(x * x + y * y); double light = 0.3 + std::pow(10.0 / (1.0 + distance / 100.0), 0.75); light *= std::min(1.0, std::max(0.0, 2.0 - std::sqrt(distance) / 64)); light *= 0.5f; return (uint8)(std::min(255.0, light * 255.0)) >> 4; } static void calc_rescale_light_half( uint8 *target, uint8 *source,uint32 targetWidth, uint32 targetHeight) { uint8 *parcerRead = source; uint8 *parcerWrite = target; for (uint32 y = 0; y < targetHeight; y++) { for (uint32 x = 0; x < targetWidth; x++) { *parcerWrite = (*parcerRead); parcerWrite++; parcerRead += 2; } parcerRead += targetWidth * 2; } } void lightfx_set_available(bool available) { _lightfxAvailable = available; } bool lightfx_is_available() { return _lightfxAvailable && gConfigGeneral.enable_light_fx != 0; } void lightfx_init() { _LightListBack = _LightListA; _LightListFront = _LightListB; memset(_bakedLightTexture_lantern_0, 0xFF, 32 * 32); memset(_bakedLightTexture_lantern_1, 0xFF, 64 * 64); memset(_bakedLightTexture_lantern_2, 0xFF, 128 * 128); memset(_bakedLightTexture_lantern_3, 0xFF, 256 * 256); uint8 *parcer = _bakedLightTexture_lantern_3; for (sint32 y = 0; y < 256; y++) { for (sint32 x = 0; x < 256; x++) { *parcer = calc_light_intensity_lantern(x - 128, y - 128); parcer++; } } parcer = _bakedLightTexture_spot_3; for (sint32 y = 0; y < 256; y++) { for (sint32 x = 0; x < 256; x++) { *parcer = calc_light_intensity_spot(x - 128, y - 128); parcer++; } } calc_rescale_light_half(_bakedLightTexture_lantern_2, _bakedLightTexture_lantern_3, 128, 128); calc_rescale_light_half(_bakedLightTexture_lantern_1, _bakedLightTexture_lantern_2, 64, 64); calc_rescale_light_half(_bakedLightTexture_lantern_0, _bakedLightTexture_lantern_1, 32, 32); calc_rescale_light_half(_bakedLightTexture_spot_2, _bakedLightTexture_spot_3, 128, 128); calc_rescale_light_half(_bakedLightTexture_spot_1, _bakedLightTexture_spot_2, 64, 64); calc_rescale_light_half(_bakedLightTexture_spot_0, _bakedLightTexture_spot_1, 32, 32); } void lightfx_update_buffers(rct_drawpixelinfo *info) { _light_rendered_buffer_front = realloc(_light_rendered_buffer_front, info->width * info->height); _light_rendered_buffer_back = realloc(_light_rendered_buffer_back, info->width * info->height); memcpy(&_pixelInfo, info, sizeof(rct_drawpixelinfo)); } extern void viewport_paint_setup(); void lightfx_prepare_light_list() { for (uint32 light = 0; light < LightListCurrentCountFront; light++) { lightlist_entry *entry = &_LightListFront[light]; if (entry->z == 0x7FFF) { entry->lightIntensity = 0xFF; continue; } LocationXYZ16 coord_3d = { /* .x = */ entry->x, /* .y = */ entry->y, /* .z = */ entry->z }; LocationXY16 coord_2d = coordinate_3d_to_2d(&coord_3d, _current_view_rotation_front); entry->x = coord_2d.x;// - (_current_view_x_front); entry->y = coord_2d.y;// - (_current_view_y_front); sint32 posOnScreenX = entry->x - _current_view_x_front; sint32 posOnScreenY = entry->y - _current_view_y_front; posOnScreenX >>= _current_view_zoom_front; posOnScreenY >>= _current_view_zoom_front; if ((posOnScreenX < -128) || (posOnScreenY < -128) || (posOnScreenX > _pixelInfo.width + 128) || (posOnScreenY > _pixelInfo.height + 128)) { entry->lightType = LIGHTFX_LIGHT_TYPE_NONE; continue; } // entry->x >>= _current_view_zoom_front; // entry->y >>= _current_view_zoom_front; uint32 lightIntensityOccluded = 0x0; sint32 dirVecX = 707; sint32 dirVecY = 707; switch (_current_view_rotation_front) { case 0: dirVecX = 707; dirVecY = 707; break; case 1: dirVecX = -707; dirVecY = 707; break; case 2: dirVecX = -707; dirVecY = -707; break; case 3: dirVecX = 707; dirVecY = -707; break; default: dirVecX = 0; dirVecY = 0; break; } #ifdef LIGHTFX_UNKNOWN_PART_1 sint32 tileOffsetX = 0; sint32 tileOffsetY = 0; switch (_current_view_rotation_front) { case 0: tileOffsetX = 0; tileOffsetY = 0; break; case 1: tileOffsetX = 16; tileOffsetY = 0; break; case 2: tileOffsetX = 32; tileOffsetY = 32; break; case 3: tileOffsetX = 0; tileOffsetY = 16; break; } sint32 mapFrontDiv = 1 << _current_view_zoom_front; static sint16 offsetPattern[26] = { 0, 0, -4, 0, 0, -3, 4, 0, 0, 3, -2, -1, -1, -1, 2, 1, 1, 1, -3, -2, -3, 2, 3, -2, 3, 2 }; #endif //LIGHTFX_UNKNOWN_PART_1 if (true) { sint32 totalSamplePoints = 5; sint32 startSamplePoint = 1; // sint32 lastSampleCount = 0; if ((entry->lightIDqualifier & 0xF) == LIGHTFX_LIGHT_QUALIFIER_MAP) { startSamplePoint = 0; totalSamplePoints = 1; } for (sint32 pat = startSamplePoint; pat < totalSamplePoints; pat++) { LocationXY16 mapCoord = { 0 }; rct_tile_element *tileElement = nullptr; sint32 interactionType = 0; rct_window *w = window_get_main(); if (w != nullptr) { // get_map_coordinates_from_pos(entry->x + offsetPattern[pat*2] / mapFrontDiv, entry->y + offsetPattern[pat*2+1] / mapFrontDiv, VIEWPORT_INTERACTION_MASK_NONE, &mapCoord.x, &mapCoord.y, &interactionType, &tileElement, NULL); #ifdef LIGHTFX_UNKNOWN_PART_1 _unk9AC154 = ~VIEWPORT_INTERACTION_MASK_SPRITE & 0xFFFF; _viewportDpi1.zoom = _current_view_zoom_front; _viewportDpi1.x = entry->x + offsetPattern[0 + pat * 2] / mapFrontDiv; _viewportDpi1.y = entry->y + offsetPattern[1 + pat * 2] / mapFrontDiv; rct_drawpixelinfo* dpi = &_viewportDpi2; dpi->x = _viewportDpi1.x; dpi->y = _viewportDpi1.y; dpi->zoom_level = _viewportDpi1.zoom; dpi->height = 1; dpi->width = 1; gPaintSession.EndOfPaintStructArray = 0xF1A4CC; gPaintSession.Unk140E9A8 = dpi; painter_setup(); viewport_paint_setup(); paint_session_arrange(gPaintSession); sub_68862C(); // log_warning("[%i, %i]", dpi->x, dpi->y); mapCoord.x = _interactionMapX + tileOffsetX; mapCoord.y = _interactionMapY + tileOffsetY; interactionType = _interactionSpriteType; tileElement = RCT2_GLOBAL(0x9AC150, rct_tile_element*); #endif //LIGHTFX_UNKNOWN_PART_1 //RCT2_GLOBAL(0x9AC154, uint16_t) = VIEWPORT_INTERACTION_MASK_NONE; //RCT2_GLOBAL(0x9AC148, uint8_t) = 0; //RCT2_GLOBAL(0x9AC138 + 4, int16_t) = screenX; //RCT2_GLOBAL(0x9AC138 + 6, int16_t) = screenY; //if (screenX >= 0 && screenX < (sint32)myviewport->width && screenY >= 0 && screenY < (sint32)myviewport->height) //{ // screenX <<= myviewport->zoom; // screenY <<= myviewport->zoom; // screenX += (sint32)myviewport->view_x; // screenY += (sint32)myviewport->view_y; // RCT2_GLOBAL(RCT2_ADDRESS_VIEWPORT_ZOOM, uint16_t) = myviewport->zoom; // screenX &= (0xFFFF << myviewport->zoom) & 0xFFFF; // screenY &= (0xFFFF << myviewport->zoom) & 0xFFFF; // RCT2_GLOBAL(RCT2_ADDRESS_VIEWPORT_PAINT_X, int16_t) = screenX; // RCT2_GLOBAL(RCT2_ADDRESS_VIEWPORT_PAINT_Y, int16_t) = screenY; // rct_drawpixelinfo* dpi = RCT2_ADDRESS(RCT2_ADDRESS_VIEWPORT_DPI, rct_drawpixelinfo); // dpi->y = RCT2_GLOBAL(RCT2_ADDRESS_VIEWPORT_PAINT_Y, int16_t); // dpi->height = 1; // dpi->zoom_level = RCT2_GLOBAL(RCT2_ADDRESS_VIEWPORT_ZOOM, uint16_t); // dpi->x = RCT2_GLOBAL(RCT2_ADDRESS_VIEWPORT_PAINT_X, int16_t); // dpi->width = 1; // RCT2_GLOBAL(0xEE7880, uint32_t) = 0xF1A4CC; // RCT2_GLOBAL(0x140E9A8, rct_drawpixelinfo*) = dpi; // painter_setup(); // viewport_paint_setup(); // paint_session_arrange(gPaintSession); // sub_68862C(); //} } sint32 minDist = 0; sint32 baseHeight = -999; if (interactionType != VIEWPORT_INTERACTION_ITEM_SPRITE && tileElement) { baseHeight = tileElement->base_height; } minDist = ((baseHeight * 8) - coord_3d.z) / 2; sint32 deltaX = mapCoord.x - coord_3d.x; sint32 deltaY = mapCoord.y - coord_3d.y; sint32 projDot = (dirVecX * deltaX + dirVecY * deltaY) / 1000; projDot = std::max(minDist, projDot); if (projDot < 5) { lightIntensityOccluded += 100; } else { lightIntensityOccluded += std::max(0, 200 - (projDot * 20)); } // log_warning("light %i [%i, %i, %i], [%i, %i] minDist to %i: %i; projdot: %i", light, coord_3d.x, coord_3d.y, coord_3d.z, mapCoord.x, mapCoord.y, baseHeight, minDist, projDot); if (pat == 0) { if (lightIntensityOccluded == 100) break; if (_current_view_zoom_front > 2) break; totalSamplePoints += 4; } else if (pat == 4) { if (_current_view_zoom_front > 1) break; if (lightIntensityOccluded == 0 || lightIntensityOccluded == 500) break; // lastSampleCount = lightIntensityOccluded / 500; // break; totalSamplePoints += 4; } else if (pat == 8) { break; // if (_current_view_zoom_front > 0) // break; // sint32 newSampleCount = lightIntensityOccluded / 900; // if (abs(newSampleCount - lastSampleCount) < 10) // break; // totalSamplePoints += 4; } } totalSamplePoints -= startSamplePoint; // lightIntensityOccluded = totalSamplePoints * 100; // log_warning("sample-count: %i, occlusion: %i", totalSamplePoints, lightIntensityOccluded); if (lightIntensityOccluded == 0) { entry->lightType = LIGHTFX_LIGHT_TYPE_NONE; continue; } // log_warning("sample-count: %i, occlusion: %i", totalSamplePoints, lightIntensityOccluded / totalSamplePoints); entry->lightIntensity = std::min(0xFF, (entry->lightIntensity * lightIntensityOccluded) / (totalSamplePoints * 100)); entry->lightIntensity = std::max(0x00, entry->lightIntensity - _current_view_zoom_front * 5); } if (_current_view_zoom_front > 0) { if ((entry->lightType & 0x3) < _current_view_zoom_front) { entry->lightType = LIGHTFX_LIGHT_TYPE_NONE; continue; } entry->lightType -= _current_view_zoom_front; } } } void lightfx_swap_buffers() { void *tmp = _light_rendered_buffer_back; _light_rendered_buffer_back = _light_rendered_buffer_front; _light_rendered_buffer_front = tmp; tmp = _light_rendered_buffer_back; _light_rendered_buffer_back = _light_rendered_buffer_front; _light_rendered_buffer_front = tmp; tmp = _LightListBack; _LightListBack = _LightListFront; _LightListFront = (lightlist_entry *)tmp; LightListCurrentCountFront = LightListCurrentCountBack; LightListCurrentCountBack = 0x0; uint32 uTmp = _lightPolution_back; _lightPolution_back = _lightPolution_front; _lightPolution_front = uTmp; _current_view_x_front = _current_view_x_back; _current_view_y_front = _current_view_y_back; _current_view_rotation_front = _current_view_rotation_back; _current_view_zoom_front = _current_view_zoom_back_delay; _current_view_zoom_back_delay = _current_view_zoom_back; } void lightfx_update_viewport_settings() { rct_window *mainWindow = window_get_main(); if (mainWindow) { rct_viewport * viewport = window_get_viewport(mainWindow); _current_view_x_back = viewport->view_x; _current_view_y_back = viewport->view_y; _current_view_rotation_back = get_current_rotation(); _current_view_zoom_back = viewport->zoom; } } void lightfx_render_lights_to_frontbuffer() { if (_light_rendered_buffer_front == nullptr) { return; } memset(_light_rendered_buffer_front, 0, _pixelInfo.width * _pixelInfo.height); _lightPolution_back = 0; // log_warning("%i lights", LightListCurrentCountFront); for (uint32 light = 0; light < LightListCurrentCountFront; light++) { const uint8 *bufReadBase = nullptr; uint8 *bufWriteBase = (uint8 *)_light_rendered_buffer_front; uint32 bufReadWidth, bufReadHeight; sint32 bufWriteX, bufWriteY; sint32 bufWriteWidth, bufWriteHeight; uint32 bufReadSkip, bufWriteSkip; lightlist_entry * entry = &_LightListFront[light]; sint32 inRectCentreX = entry->x; sint32 inRectCentreY = entry->y; if (entry->z != 0x7FFF) { inRectCentreX -= _current_view_x_front; inRectCentreY -= _current_view_y_front; inRectCentreX >>= _current_view_zoom_front; inRectCentreY >>= _current_view_zoom_front; } switch (entry->lightType) { case LIGHTFX_LIGHT_TYPE_LANTERN_0: bufReadWidth = 32; bufReadHeight = 32; bufReadBase = _bakedLightTexture_lantern_0; break; case LIGHTFX_LIGHT_TYPE_LANTERN_1: bufReadWidth = 64; bufReadHeight = 64; bufReadBase = _bakedLightTexture_lantern_1; break; case LIGHTFX_LIGHT_TYPE_LANTERN_2: bufReadWidth = 128; bufReadHeight = 128; bufReadBase = _bakedLightTexture_lantern_2; break; case LIGHTFX_LIGHT_TYPE_LANTERN_3: bufReadWidth = 256; bufReadHeight = 256; bufReadBase = _bakedLightTexture_lantern_3; break; case LIGHTFX_LIGHT_TYPE_SPOT_0: bufReadWidth = 32; bufReadHeight = 32; bufReadBase = _bakedLightTexture_spot_0; break; case LIGHTFX_LIGHT_TYPE_SPOT_1: bufReadWidth = 64; bufReadHeight = 64; bufReadBase = _bakedLightTexture_spot_1; break; case LIGHTFX_LIGHT_TYPE_SPOT_2: bufReadWidth = 128; bufReadHeight = 128; bufReadBase = _bakedLightTexture_spot_2; break; case LIGHTFX_LIGHT_TYPE_SPOT_3: bufReadWidth = 256; bufReadHeight = 256; bufReadBase = _bakedLightTexture_spot_3; break; default: continue; } bufWriteX = inRectCentreX - bufReadWidth / 2; bufWriteY = inRectCentreY - bufReadHeight / 2; bufWriteWidth = bufReadWidth; bufWriteHeight = bufReadHeight; if (bufWriteX < 0) { bufReadBase += -bufWriteX; bufWriteWidth += bufWriteX; } else { bufWriteBase += bufWriteX; } if (bufWriteWidth <= 0) continue; if (bufWriteY < 0) { bufReadBase += -bufWriteY * bufReadWidth; bufWriteHeight += bufWriteY; } else { bufWriteBase += bufWriteY * _pixelInfo.width; } if (bufWriteHeight <= 0) continue; sint32 rightEdge = bufWriteX + bufWriteWidth; sint32 bottomEdge = bufWriteY + bufWriteHeight; if (rightEdge > _pixelInfo.width) { bufWriteWidth -= rightEdge - _pixelInfo.width; } if (bottomEdge > _pixelInfo.height) { bufWriteHeight -= bottomEdge - _pixelInfo.height; } if (bufWriteWidth <= 0) continue; if (bufWriteHeight <= 0) continue; _lightPolution_back += (bufWriteWidth * bufWriteHeight) / 256; bufReadSkip = bufReadWidth - bufWriteWidth; bufWriteSkip = _pixelInfo.width - bufWriteWidth; if (entry->lightIntensity == 0xFF) { for (sint32 y = 0; y < bufWriteHeight; y++) { for (sint32 x = 0; x < bufWriteWidth; x++) { *bufWriteBase = std::min(0xFF, *bufWriteBase + *bufReadBase); bufWriteBase++; bufReadBase++; } bufWriteBase += bufWriteSkip; bufReadBase += bufReadSkip; } } else { for (sint32 y = 0; y < bufWriteHeight; y++) { for (sint32 x = 0; x < bufWriteWidth; x++) { *bufWriteBase = std::min(0xFF, *bufWriteBase + (((*bufReadBase) * (1 + entry->lightIntensity)) >> 8)); bufWriteBase++; bufReadBase++; } bufWriteBase += bufWriteSkip; bufReadBase += bufReadSkip; } } } } void* lightfx_get_front_buffer() { return _light_rendered_buffer_front; } const rct_palette * lightfx_get_palette() { return &gPalette_light; } void lightfx_add_3d_light(uint32 lightID, uint16 lightIDqualifier, sint16 x, sint16 y, uint16 z, uint8 lightType) { if (LightListCurrentCountBack == 15999) { return; } // log_warning("%i lights in back", LightListCurrentCountBack); for (uint32 i = 0; i < LightListCurrentCountBack; i++) { lightlist_entry *entry = &_LightListBack[i]; if (entry->lightID != lightID) continue; if (entry->lightIDqualifier != lightIDqualifier) continue; entry->x = x; entry->y = y; entry->z = z; entry->lightType = lightType; entry->lightIntensity = 0xFF; entry->lightID = lightID; entry->lightIDqualifier = lightIDqualifier; entry->lightLinger = 1; return; } lightlist_entry *entry = &_LightListBack[LightListCurrentCountBack++]; entry->x = x; entry->y = y; entry->z = z; entry->lightType = lightType; entry->lightIntensity = 0xFF; entry->lightID = lightID; entry->lightIDqualifier = lightIDqualifier; entry->lightLinger = 1; // log_warning("new 3d light"); } void lightfx_add_3d_light_magic_from_drawing_tile(LocationXY16 mapPosition, sint16 offsetX, sint16 offsetY, sint16 offsetZ, uint8 lightType) { sint16 x = mapPosition.x + offsetX; sint16 y = mapPosition.y + offsetY; switch (get_current_rotation()) { case 0: x += 16; y += 16; break; case 1: x += 16; y += 16; break; case 2: x += 16; y -= 16; break; case 3: x += 16; y -= 16; break; default: return; } lightfx_add_3d_light((x << 16) | y, (offsetZ << 8) | LIGHTFX_LIGHT_QUALIFIER_MAP, x, y, offsetZ, lightType); } uint32 lightfx_get_light_polution() { return _lightPolution_front; } void lightfx_add_lights_magic_vehicles() { uint16 spriteIndex = gSpriteListHead[SPRITE_LIST_TRAIN]; while (spriteIndex != SPRITE_INDEX_NULL) { rct_vehicle * vehicle = &(get_sprite(spriteIndex)->vehicle); uint16 vehicleID = spriteIndex; spriteIndex = vehicle->next; rct_vehicle *mother_vehicle = vehicle; if (mother_vehicle->ride_subtype == RIDE_ENTRY_INDEX_NULL) { continue; } for (uint16 q = vehicleID; q != SPRITE_INDEX_NULL; ) { vehicle = GET_VEHICLE(q); vehicleID = q; if (vehicle->next_vehicle_on_train == q) break; q = vehicle->next_vehicle_on_train; sint16 place_x, place_y, place_z; place_x = vehicle->x; place_y = vehicle->y; place_z = vehicle->z; static constexpr const sint16 offsetLookup[32] = { 10, 10, 9, 8, 7, 6, 4, 2, 0, -2, -4, -6, -7, -8, -9, -10, -10, -10, -9, -8, -7, -6, -4, -2, 0, 2, 4, 6, 7, 8, 9, 10 }; Ride *ride = get_ride(vehicle->ride); switch (ride->type) { case RIDE_TYPE_OBSERVATION_TOWER: lightfx_add_3d_light(vehicleID, 0x0000 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, vehicle->x, vehicle->y + 16, vehicle->z, LIGHTFX_LIGHT_TYPE_SPOT_3); lightfx_add_3d_light(vehicleID, 0x0100 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, vehicle->x + 16, vehicle->y, vehicle->z, LIGHTFX_LIGHT_TYPE_SPOT_3); lightfx_add_3d_light(vehicleID, 0x0200 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, vehicle->x - 16, vehicle->y, vehicle->z, LIGHTFX_LIGHT_TYPE_SPOT_3); lightfx_add_3d_light(vehicleID, 0x0300 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, vehicle->x, vehicle->y - 16, vehicle->z, LIGHTFX_LIGHT_TYPE_SPOT_3); break; case RIDE_TYPE_MINE_TRAIN_COASTER: case RIDE_TYPE_GHOST_TRAIN: if (vehicle == vehicle_get_head(vehicle)) { place_x -= offsetLookup[(vehicle->sprite_direction + 0) % 32] * 2; place_y -= offsetLookup[(vehicle->sprite_direction + 8) % 32] * 2; lightfx_add_3d_light(vehicleID, 0x0000 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, place_x, place_y, place_z, LIGHTFX_LIGHT_TYPE_SPOT_3); } break; case RIDE_TYPE_CHAIRLIFT: lightfx_add_3d_light(vehicleID, 0x0000 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, place_x, place_y, place_z - 16, LIGHTFX_LIGHT_TYPE_LANTERN_2); break; case RIDE_TYPE_BOAT_HIRE: case RIDE_TYPE_CAR_RIDE: case RIDE_TYPE_GO_KARTS: case RIDE_TYPE_DODGEMS: case RIDE_TYPE_MINI_HELICOPTERS: case RIDE_TYPE_MONORAIL_CYCLES: case RIDE_TYPE_SUBMARINE_RIDE: case RIDE_TYPE_SPLASH_BOATS: case RIDE_TYPE_WATER_COASTER: { rct_vehicle *vehicle_draw = vehicle_get_head(vehicle); if (vehicle_draw->next_vehicle_on_train != SPRITE_INDEX_NULL) { vehicle_draw = GET_VEHICLE(vehicle_draw->next_vehicle_on_train); } place_x = vehicle_draw->x; place_y = vehicle_draw->y; place_z = vehicle_draw->z; place_x -= offsetLookup[(vehicle_draw->sprite_direction + 0) % 32]; place_y -= offsetLookup[(vehicle_draw->sprite_direction + 8) % 32]; lightfx_add_3d_light(vehicleID, 0x0000 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, place_x, place_y, place_z, LIGHTFX_LIGHT_TYPE_SPOT_2); place_x -= offsetLookup[(vehicle_draw->sprite_direction + 0) % 32]; place_y -= offsetLookup[(vehicle_draw->sprite_direction + 8) % 32]; lightfx_add_3d_light(vehicleID, 0x0100 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, place_x, place_y, place_z, LIGHTFX_LIGHT_TYPE_SPOT_2); break; } case RIDE_TYPE_MONORAIL: lightfx_add_3d_light(vehicleID, 0x0000 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, vehicle->x, vehicle->y, vehicle->z + 12, LIGHTFX_LIGHT_TYPE_SPOT_2); if (vehicle == vehicle_get_head(vehicle)) { place_x -= offsetLookup[(vehicle->sprite_direction + 0) % 32] * 2; place_y -= offsetLookup[(vehicle->sprite_direction + 8) % 32] * 2; lightfx_add_3d_light(vehicleID, 0x0100 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, place_x, place_y, place_z + 10, LIGHTFX_LIGHT_TYPE_LANTERN_3); place_x -= offsetLookup[(vehicle->sprite_direction + 0) % 32] * 3; place_y -= offsetLookup[(vehicle->sprite_direction + 8) % 32] * 3; lightfx_add_3d_light(vehicleID, 0x0200 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, place_x, place_y, place_z + 2, LIGHTFX_LIGHT_TYPE_LANTERN_3); } if (vehicle == vehicle_get_tail(vehicle)) { place_x += offsetLookup[(vehicle->sprite_direction + 0) % 32] * 2; place_y += offsetLookup[(vehicle->sprite_direction + 8) % 32] * 2; lightfx_add_3d_light(vehicleID, 0x0300 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, place_x, place_y, place_z + 10, LIGHTFX_LIGHT_TYPE_LANTERN_3); place_x += offsetLookup[(vehicle->sprite_direction + 0) % 32] * 2; place_y += offsetLookup[(vehicle->sprite_direction + 8) % 32] * 2; lightfx_add_3d_light(vehicleID, 0x0400 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, place_x, place_y, place_z + 2, LIGHTFX_LIGHT_TYPE_LANTERN_3); } break; case RIDE_TYPE_MINIATURE_RAILWAY: if (vehicle == vehicle_get_head(vehicle)) { place_x -= offsetLookup[(vehicle->sprite_direction + 0) % 32] * 2; place_y -= offsetLookup[(vehicle->sprite_direction + 8) % 32] * 2; lightfx_add_3d_light(vehicleID, 0x0100 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, place_x, place_y, place_z + 10, LIGHTFX_LIGHT_TYPE_LANTERN_3); place_x -= offsetLookup[(vehicle->sprite_direction + 0) % 32] * 2; place_y -= offsetLookup[(vehicle->sprite_direction + 8) % 32] * 2; lightfx_add_3d_light(vehicleID, 0x0200 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, place_x, place_y, place_z + 2, LIGHTFX_LIGHT_TYPE_LANTERN_3); } else { lightfx_add_3d_light(vehicleID, 0x0000 | LIGHTFX_LIGHT_QUALIFIER_SPRITE, place_x, place_y, place_z + 10, LIGHTFX_LIGHT_TYPE_LANTERN_3); } break; default: break; }; } } } void lightfx_apply_palette_filter(uint8 i, uint8 *r, uint8 *g, uint8 *b) { float night = (float)(pow(gDayNightCycle, 1.5)); float natLightR = 1.0f; float natLightG = 1.0f; float natLightB = 1.0f; float elecMultR = 1.0f; float elecMultG = 0.95f; float elecMultB = 0.45f; static float wetness = 0.0f; static float fogginess = 0.0f; static float lightPolution = 0.0f; float sunLight = std::max(0.0f, std::min(1.0f, 2.0f - night * 3.0f)); // Night version natLightR = flerp(natLightR * 4.0f, 0.635f, (float)(std::pow(night, 0.035f + sunLight * 10.50f))); natLightG = flerp(natLightG * 4.0f, 0.650f, (float)(std::pow(night, 0.100f + sunLight * 5.50f))); natLightB = flerp(natLightB * 4.0f, 0.850f, (float)(std::pow(night, 0.200f + sunLight * 1.5f))); float overExpose = 0.0f; float lightAvg = (natLightR + natLightG + natLightB) / 3.0f; #ifdef LIGHTFX_UNKNOWN_PART_2 float lightMax = (natLightR + natLightG + natLightB) / 3.0f; #endif // LIGHTFX_UNKNOWN_PART_2 // overExpose += ((lightMax - lightAvg) / lightMax) * 0.01f; if (gClimateCurrent.Temperature > 20) { float offset = ((float)(gClimateCurrent.Temperature - 20)) * 0.04f; offset *= 1.0f - night; lightAvg /= 1.0f + offset; // overExpose += offset * 0.1f; } #ifdef LIGHTFX_UNKNOWN_PART_2 lightAvg += (lightMax - lightAvg) * 0.6f; #endif // LIGHTFX_UNKNOWN_PART_2 if (lightAvg > 1.0f) { natLightR /= lightAvg; natLightG /= lightAvg; natLightB /= lightAvg; } natLightR *= 1.0f + overExpose; natLightG *= 1.0f + overExpose; natLightB *= 1.0f + overExpose; overExpose *= 255.0f; float targetFogginess = (float)(gClimateCurrent.RainLevel) / 8.0f; targetFogginess += (night * night) * 0.15f; if (gClimateCurrent.Temperature < 10) { targetFogginess += ((float)(10 - gClimateCurrent.Temperature)) * 0.01f; } fogginess -= (fogginess - targetFogginess) * 0.00001f; wetness *= 0.999995f; wetness += fogginess * 0.001f; wetness = std::min(wetness, 1.0f); float boost = 1.0f; float envFog = fogginess; float lightFog = envFog; float addLightNatR = 0.0f; float addLightNatG = 0.0f; float addLightNatB = 0.0f; float reduceColourNat = 1.0f; float reduceColourLit = 1.0f; reduceColourLit *= night / (float)std::pow(std::max(1.01f, 0.4f + lightAvg), 2.0); float targetLightPollution = reduceColourLit * std::max(0.0f, 0.0f + 0.000001f * (float)lightfx_get_light_polution()); lightPolution -= (lightPolution - targetLightPollution) * 0.001f; // lightPollution /= 1.0f + fogginess * 1.0f; natLightR /= 1.0f + lightPolution * 20.0f; natLightG /= 1.0f + lightPolution * 20.0f; natLightB /= 1.0f + lightPolution * 20.0f; natLightR += elecMultR * 0.6f * lightPolution; natLightG += elecMultG * 0.6f * lightPolution; natLightB += elecMultB * 0.6f * lightPolution; natLightR /= 1.0f + lightPolution; natLightG /= 1.0f + lightPolution; natLightB /= 1.0f + lightPolution; reduceColourLit += (float)(gClimateCurrent.RainLevel) / 2.0f; reduceColourNat /= 1.0f + fogginess; reduceColourLit /= 1.0f + fogginess; lightFog *= reduceColourLit; reduceColourNat *= 1.0f - envFog; reduceColourLit *= 1.0f - lightFog; float fogR = 35.5f * natLightR * 1.3f; float fogG = 45.0f * natLightG * 1.3f; float fogB = 50.0f * natLightB * 1.3f; lightFog *= 10.0f; float wetnessBoost = 1.0f;//1.0f + wetness * wetness * 0.1f; if (night >= 0 && gClimateLightningFlash != 1) { *r = lerp(*r, soft_light(*r, 8), night); *g = lerp(*g, soft_light(*g, 8), night); *b = lerp(*b, soft_light(*b, 128), night); // if (i == 32) // boost = 300000.0f; if ((i % 32) == 0) boost = 1.01f * wetnessBoost; else if ((i % 16) < 7) boost = 1.001f * wetnessBoost; if (i > 230 && i < 232) boost = ((float)(*b)) / 64.0f; if (false) { // This experiment shifts the colour of pixels as-if they are wet, but it is not a pretty solution at all if ((i % 16)) { float iVal = ((float)((i + 12) % 16)) / 16.0f; float eff = (wetness * ((float)std::pow(iVal, 1.5) * 0.85f)); reduceColourNat *= 1.0f - eff; addLightNatR += fogR * eff * 3.95f; addLightNatG += fogR * eff * 3.95f; addLightNatB += fogR * eff * 3.95f; } } addLightNatR *= 1.0f - envFog; addLightNatG *= 1.0f - envFog; addLightNatB *= 1.0f - envFog; *r = (uint8)(std::min(255.0f, std::max(0.0f, (-overExpose + (float)(*r) * reduceColourNat * natLightR + envFog * fogR + addLightNatR)))); *g = (uint8)(std::min(255.0f, std::max(0.0f, (-overExpose + (float)(*g) * reduceColourNat * natLightG + envFog * fogG + addLightNatG)))); *b = (uint8)(std::min(255.0f, std::max(0.0f, (-overExpose + (float)(*b) * reduceColourNat * natLightB + envFog * fogB + addLightNatB)))); rct_palette_entry * dstEntry = &gPalette_light.entries[i]; dstEntry->red = (uint8)(std::min(0xFF, ((float)(*r) * reduceColourLit * boost + lightFog) * elecMultR)); dstEntry->green = (uint8)(std::min(0xFF, ((float)(*g) * reduceColourLit * boost + lightFog) * elecMultG)); dstEntry->blue = (uint8)(std::min(0xFF, ((float)(*b) * reduceColourLit * boost + lightFog) * elecMultB)); } } static uint8 mix_light(uint32 a, uint32 b, uint32 intensity) { intensity = intensity * 6; uint32 bMul = (b * intensity) >> 8; uint32 ab = a + bMul; uint8 result = std::min(255, ab); return result; } void lightfx_render_to_texture( void * dstPixels, uint32 dstPitch, uint8 * bits, uint32 width, uint32 height, const uint32 * palette, const uint32 * lightPalette) { lightfx_update_viewport_settings(); lightfx_swap_buffers(); lightfx_prepare_light_list(); lightfx_render_lights_to_frontbuffer(); uint8 * lightBits = (uint8 *)lightfx_get_front_buffer(); if (lightBits == nullptr) { return; } for (uint32 y = 0; y < height; y++) { uintptr_t dstOffset = (uintptr_t)(y * dstPitch); uint32 * dst = (uint32 *)((uintptr_t)dstPixels + dstOffset); for (uint32 x = 0; x < width; x++) { uint8 * src = &bits[y * width + x]; uint32 darkColour = palette[*src]; uint32 lightColour = lightPalette[*src]; uint8 lightIntensity = lightBits[y * width + x]; uint32 colour = 0; if (lightIntensity == 0) { colour = darkColour; } else { colour |= mix_light((darkColour >> 0) & 0xFF, (lightColour >> 0) & 0xFF, lightIntensity); colour |= mix_light((darkColour >> 8) & 0xFF, (lightColour >> 8) & 0xFF, lightIntensity) << 8; colour |= mix_light((darkColour >> 16) & 0xFF, (lightColour >> 16) & 0xFF, lightIntensity) << 16; colour |= mix_light((darkColour >> 24) & 0xFF, (lightColour >> 24) & 0xFF, lightIntensity) << 24; } *dst++ = colour; } } } } #endif // __ENABLE_LIGHTFX__