DYNAMIC LIGHT OMG MEOW MEOW MEOW

This commit is contained in:
Issac332 2026-07-01 00:00:07 +03:00
parent 6d95f72376
commit e33ba85c03
33 changed files with 851 additions and 81 deletions

View file

@ -4,7 +4,7 @@ include(FindPackageHandleStandardArgs)
# Options
option(STEAM "Build for Steam" OFF)
option(DEBUG "Debug mode" OFF)
option(DEBUG "Debug mode" ON)
option(NATIVE "Use native instructions(for local use only)" OFF)
option(MODLOADER "Build with modloader" ON)
@ -77,6 +77,7 @@ set(MAIN_HEADERS
src/meow.h
src/modloader.h
src/sunshine.h
src/lightmap.h
)
set(MAIN_SOURCE
@ -122,6 +123,7 @@ set(MAIN_SOURCE
src/meow.cpp
src/modloader.cpp
src/sunshine.cpp
src/lightmap.cpp
)
if(WIN32)
@ -194,6 +196,7 @@ set(EMBEDDED_INPUT
shader/simpleColor.frag
shader/simpleAlpha.frag
shader/simpleAlphaUni.frag
shader/dynamicLight.frag
shader/flashMap.frag
shader/obscured.frag
shader/minimal.vert
@ -287,6 +290,7 @@ set(BINDING_SOURCE
binding-mri/shader-binging.cpp
binding-mri/modloader-binding.cpp
binding-mri/keybindings-binding.cpp
binding-mri/lightmap-binding.cpp
)
source_group("Binding Source" FILES ${BINDING_SOURCE} ${BINDING_HEADERS})

View file

@ -139,6 +139,7 @@ void steamBindingInit();
void shaderBindingInit();
void ModLoaderBindingInit();
void keybindingsBindingInit();
void lightmapBindingInit();
RB_METHOD(mriPrint);
RB_METHOD(mriP);
@ -176,6 +177,7 @@ static void mriBindingInit(){
shaderBindingInit();
ModLoaderBindingInit();
keybindingsBindingInit();
lightmapBindingInit();
_rb_define_module_function(rb_mKernel, "rgss_main", mriRgssMain);
_rb_define_module_function(rb_mKernel, "rgss_stop", mriRgssStop);

View file

@ -273,8 +273,8 @@ DEF_PROP_OBJ_VAL(Bitmap, Font, Font, "font")
RB_METHOD(bitmapGradientFillRect){
Bitmap *b = getPrivateData<Bitmap>(self);
VALUE color1Obj, color2Obj;
Color *color1, *color2;
VALUE color1Obj, color2Obj, color3Obj, color4Obj;
Color *color1, *color2, *color3, *color4;
bool vertical = false;
if (argc == 3 || argc == 4){
@ -289,7 +289,8 @@ RB_METHOD(bitmapGradientFillRect){
color2 = getPrivateDataCheck<Color>(color2Obj, ColorType);
GUARD_EXC( b->gradientFillRect(rect->toIntRect(), color1->norm, color2->norm, vertical); );
}else{
}
else if (argc == 6 || argc == 7){
int x, y, width, height;
rb_get_args(argc, argv, "iiiioo|b", &x, &y, &width, &height,
@ -300,21 +301,11 @@ RB_METHOD(bitmapGradientFillRect){
GUARD_EXC( b->gradientFillRect(x, y, width, height, color1->norm, color2->norm, vertical); );
}
return self;
}
RB_METHOD(bitmapGradientFillRect4){
Bitmap *b = getPrivateData<Bitmap>(self);
VALUE color1Obj, color2Obj, color3Obj, color4Obj;
Color *color1, *color2, *color3, *color4;
if (argc == 4 || argc == 5){
else if (argc == 5){
VALUE rectObj;
Rect *rect;
rb_get_args(argc, argv, "ooo|b", &rectObj, &color1Obj, &color2Obj, &color3Obj, &color4Obj RB_ARG_END);
rb_get_args(argc, argv, "ooooo", &rectObj, &color1Obj, &color2Obj, &color3Obj, &color4Obj RB_ARG_END);
rect = getPrivateDataCheck<Rect>(rectObj, RectType);
color1 = getPrivateDataCheck<Color>(color1Obj, ColorType);
@ -326,7 +317,7 @@ RB_METHOD(bitmapGradientFillRect4){
}else{
int x, y, width, height;
rb_get_args(argc, argv, "iiiioo|b", &x, &y, &width, &height, &color1Obj, &color2Obj, &color3Obj, &color4Obj RB_ARG_END);
rb_get_args(argc, argv, "iiiioooo", &x, &y, &width, &height, &color1Obj, &color2Obj, &color3Obj, &color4Obj RB_ARG_END);
color1 = getPrivateDataCheck<Color>(color1Obj, ColorType);
color2 = getPrivateDataCheck<Color>(color2Obj, ColorType);

View file

@ -0,0 +1,95 @@
#include "lightmap.h"
#include "disposable-binding.h"
#include "flashable-binding.h"
#include "sceneelement-binding.h"
#include "viewportelement-binding.h"
#include "binding-util.h"
DEF_TYPE(LightMap);
RB_METHOD(lightmapInitialize){
LightMap *s = viewportElementInitialize<LightMap>(argc, argv, self);
setPrivateData(self, s);
/* Wrap property objects */
s->initDynAttribs();
return self;
}
DEF_PROP_OBJ_REF(LightMap, Bitmap, WallMap, "wallmap")
DEF_PROP_I(LightMap, CameraX)
DEF_PROP_I(LightMap, CameraY)
DEF_PROP_I(LightMap, TilemapOffsetX)
DEF_PROP_I(LightMap, TilemapOffsetY)
static VALUE clearStaticSources(VALUE self) {
LightMap *k = getPrivateData<LightMap>(self);
k->clearStaticLightSources();
return Qnil;
}
static VALUE clearDynamicSources(VALUE self) {
LightMap *k = getPrivateData<LightMap>(self);
k->clearDynamicLightSources();
return Qnil;
}
static VALUE addStaticSource(VALUE self, VALUE rb_x, VALUE rb_y, VALUE rb_power, VALUE rb_radius, VALUE rb_color) {
LightMap *k = getPrivateData<LightMap>(self);
Color *color;
color = getPrivateDataCheck<Color>(rb_color, ColorType);
k->addStaticLightSource( LightSource (
NUM2DBL(rb_x),
NUM2DBL(rb_y),
NUM2DBL(rb_power),
NUM2DBL(rb_radius),
*color
));
return Qnil;
}
static VALUE addDynamicSource(VALUE self, VALUE rb_x, VALUE rb_y, VALUE rb_power, VALUE rb_radius, VALUE rb_color) {
LightMap *k = getPrivateData<LightMap>(self);
Color *color;
color = getPrivateDataCheck<Color>(rb_color, ColorType);
k->addDynamicLightSource( LightSource (
NUM2DBL(rb_x),
NUM2DBL(rb_y),
NUM2DBL(rb_power),
NUM2DBL(rb_radius),
*color
));
return Qnil;
}
static VALUE setAmbient(VALUE self, VALUE var){
LightMap *k = getPrivateData<LightMap>(self);
k->setAmbient(NUM2DBL(var));
return Qnil;
}
void lightmapBindingInit(){
VALUE klass = rb_define_class("LightMap", rb_cObject);
rb_define_alloc_func(klass, classAllocate<&LightMapType>);
disposableBindingInit <LightMap>(klass);
flashableBindingInit <LightMap>(klass);
viewportElementBindingInit<LightMap>(klass);
_rb_define_method(klass, "initialize", lightmapInitialize);
INIT_PROP_BIND( LightMap, WallMap, "wallmap" );
INIT_PROP_BIND( LightMap, CameraX, "camera_x" );
INIT_PROP_BIND( LightMap, CameraY, "camera_y" );
INIT_PROP_BIND( LightMap, TilemapOffsetX, "tilemap_offset_x" );
INIT_PROP_BIND( LightMap, TilemapOffsetY, "tilemap_offset_y" );
rb_define_method(klass, "clear_static_sources", clearStaticSources, 0);
rb_define_method(klass, "clear_dynamic_sources", clearDynamicSources, 0);
rb_define_method(klass, "add_static_source", addStaticSource, 5);
rb_define_method(klass, "add_dynamic_source", addDynamicSource, 5);
rb_define_method(klass, "ambient=", setAmbient, 1);
}

View file

@ -47,6 +47,22 @@ module RPG
end
end
class Table
def each_with_index
x = 0
y = 0
z = 0
while z < self.sizez
while y < self.sizey
while x < self.sizex
yield(self[x, y, z], x, y, z)
x += 1
end
y += 1
end
z += 1
end
end
def self._load(foo)
end
end

View file

@ -82,7 +82,8 @@ set(scripts
i18n_Language.rb
Window_TPtL.rb
Settings.rb
Input.rb)
Input.rb
DynamicLight.rb)
find_program(RUBY_EXE ruby
DOC "Location of the Ruby executable")

View file

@ -11,6 +11,8 @@ module Settings
:colorblind => false,
:frameskip => true,
:twm_shader => true,
:light => true,
:light_shadows => true,
# UI
:in_game_timer => false,
@ -153,6 +155,17 @@ class Window_Settings
:name => tr('World machine shader'),
:parameter => :twm_shader
},
{ :type => :sep, :name => tr("Lighting")},
{
:type => :bool,
:name => tr('Dynamic Light'),
:parameter => :light,
},
#{
# :type => :bool,
# :name => tr('Shadows'),
# :parameter => :light_shadows
#},
],
tr("UI") => [
{

View file

@ -34,7 +34,7 @@ class Window_Settings
@selection_sprite = Sprite.new(@viewport)
@selection_sprite.bitmap = Bitmap.new(PARAMETER_WIDTH + 16, PARAMETER_HEIGHT)
@selection_sprite.bitmap.fill_rect(Rect.new(0, 0, PARAMETER_WIDTH + 16, PARAMETER_HEIGHT), Color.new(255, 255, 255, 64))
@selection_sprite.bitmap.fill_rect(Rect.new(0, 0, PARAMETER_WIDTH + 16, PARAMETER_HEIGHT), Color.new(255, 255, 255, 128))
@selection_sprite.x = @x - 8
@selection_sprite.y = @offset
@selection_sprite.opacity = 0
@ -46,6 +46,7 @@ class Window_Settings
@screen_panel_selection_sprite.bitmap.gradient_fill_rect(0, 0, @screen_panel_selection_sprite.bitmap.width, @screen_panel_selection_sprite.bitmap.height, Color.new(255, 255, 255, 0), Color.new(255, 255, 255, 128), true)
@screen_panel_selection_sprite.y = SCREENS_PANELS_MARGIN + SCREENS_PANELS_TOP_MARGIN
@screen_panel_selection_sprite.x = Graphics.width / 2
@screen_panel_selection_sprite.blend_type = 1
@switch_panels_hint_left = Sprite.new(@viewport)
@switch_panels_hint_left.bitmap = Bitmap.new(PARAMETER_WIDTH / 2 - SCREENS_PANELS_MARGIN, @screen_panel_selection_sprite.height / 2)
@ -61,6 +62,7 @@ class Window_Settings
@switch_panels_hint_left.zoom_x = @switch_panels_hint_right.zoom_x =
@switch_panels_hint_left.zoom_y = @switch_panels_hint_right.zoom_y = 2
@switch_panels_hint_left.opacity = @switch_panels_hint_right.opacity = 127
@switch_panels_hint_left.blend_type = @switch_panels_hint_right.blend_type = 1
redraw_panels_hints

123
scripts/DynamicLight.rb Normal file
View file

@ -0,0 +1,123 @@
class DynamicLight
attr_accessor :done
attr_accessor :plr_light_power
attr_accessor :plr_light_color
def initialize(viewport)
@done = false # for events
@awaked = false
@light_npc = []
@ambient_light = 255
@ambient_light_lerped = 255
@plr_light_power = 0
@plr_light_radius = -1.0
@plr_light_radius_lerped = 5.0
@plr_light_color = Color.new(255, 255, 255)
@light_sprite = LightMap.new(viewport)
@light_sprite.wallmap = Bitmap.new($game_map.width, $game_map.height)
for x in 0...$game_map.width
for y in 0...$game_map.height
@light_sprite.wallmap.set_pixel(x, y, Color.new(
b(light_passable($game_map.data[x, y, 0])),
b(light_passable($game_map.data[x, y, 1])),
b(light_passable($game_map.data[x, y, 2]))
))
end
end
end
def dispose
@light_sprite.dispose
end
def update
@plr_light_radius_lerped = @plr_light_radius_lerped * 0.8 + @plr_light_radius * 0.2
@light_sprite.visible = Settings[:light] && @awaked
if (!Settings[:light] && @awaked)
return
end
@light_sprite.camera_x = $game_map.display_x / 4
@light_sprite.camera_y = $game_map.display_y / 4
@light_sprite.tilemap_offset_x = ($game_map.display_x / 128) * 32 - $game_map.display_x / 4
@light_sprite.tilemap_offset_y = ($game_map.display_y / 128) * 32 - $game_map.display_y / 4
@light_sprite.clear_dynamic_sources()
plr_light = [$game_player.real_x / 128.0, $game_player.real_y / 128.0, @plr_light_power, @plr_light_radius_lerped, @plr_light_color]
if has_effect(plr_light)
@light_sprite.add_dynamic_source(plr_light[0], plr_light[1] - 0.5, plr_light[2], plr_light[3], plr_light[4])
end
@light_npc.each { |npc_light_source|
npc_light = [npc_light_source[0].real_x / 128.0, npc_light_source[0].real_y / 128.0, npc_light_source[1], npc_light_source[2], npc_light_source[3]]
if has_effect(npc_light)
@light_sprite.add_dynamic_source(npc_light[0], npc_light[1] - 0.5, npc_light[2], npc_light[3], npc_light[4])
end
}
end
def light_passable(tile_id)
$game_map.passages[tile_id] & 15 == 0
end
def b(val)
val ? 255 : 0
end
def add(x, y, power, radius, color = Color.new(255, 255, 255))
@awaked = true
@light_sprite.add_static_source(x, y, power, radius, color)
end
def add_npc(npc_id, power, radius, color = Color.new(255, 255, 255))
@awaked = true
@light_npc << [$game_map.events[npc_id], power, radius, color]
end
def has_effect(source)
source[2] > 0 && source[3] > 0 && source[4].alpha >= 0 && (source[4].red > 0 || source[4].green > 0 || source[4].blue > 0)
end
#def remove(x, y)
# @light_sources.each do |sourse|
# if sourse[0] == x && sourse[1] == y
# @light_sources.delete(sourse)
# return true
# end
# end
# return false
#end
def awake
@awaked = true
end
def sleep # fully disables dynamic light system
@awaked = false
end
def ambient_light
@ambient_light
end
def ambient_light=(val)
@ambient_light = val
@light_sprite.ambient = val
@awaked = true
end
def plr_light_radius
@plr_light_radius
end
def plr_light_radius=(val)
if @plr_light_radius < 0
@plr_light_radius_lerped = val
end
@plr_light_radius = val
end
end

View file

@ -241,13 +241,13 @@ class Game_Event < Game_Character
def update
super
if(@made_text == false && @event.name.start_with?("@text"))
if @list.size > 1 && @list[0].code == 101
if(@made_text == false && @event.name.start_with?("@text"))
if @list.size > 1 && @list[0].code == 101
$scene.new_maptext(Language.tr(@list[0].parameters[0].strip), @x, @y)
@list.delete_at(0)
@list.delete_at(0)
end
@made_text = true
end
@made_text = true
end
# Automatic event starting determinant
check_event_trigger_auto
# If parallel process is valid

View file

@ -264,7 +264,7 @@ class Game_Map
# distance : scroll distance
#--------------------------------------------------------------------------
def scroll_down(distance)
if self.height < Graphics.height / 28
if self.height < Graphics.height / 28
@display_y = self.height / 2
else
@display_y = [@display_y + distance, (self.height - Graphics.height / 28) * 128].min
@ -289,10 +289,10 @@ class Game_Map
# distance : scroll distance
#--------------------------------------------------------------------------
def scroll_up(distance)
if self.height < Graphics.height / 28
@display_y = self.height / 2
if self.height < Graphics.height / 28
@display_y = self.height / 2
else
@display_y = [@display_y - distance, 0].max
@display_y = [@display_y - distance, 0].max
end
end
#--------------------------------------------------------------------------

View file

@ -8,4 +8,35 @@ module Input
return normalized < Settings[:gamepad_deadzone] / 10.0 ? 0.0 : normalized
end
end
GAMEPAD_BUTTONS_NAMES = {
"a" => "A", # ▲□Ο
"b" => "B",
"x" => "X",
"y" => "Y",
"lefttringger" => "LT",
"righttringger" => "RT",
"leftshoulder" => "LS",
"rightshoulder" => "RS",
"dpdown" => "D-Pad Down",
"dpleft" => "D-Pad Left",
"dpright" => "D-Pad Right",
"dpup" => "D-Pad Up",
"back" => "Back",
"guide" => "Guide",
"start" => "Start",
"leftstick" => "Left Stick",
"rightstick" => "Right Stick",
"leftpaddle1" => "Left Paddle 1",
"rightpaddle1" => "Right Paddle 1",
"leftpaddle2" => "Left Paddle 2",
"rightpaddle2" => "Right Paddle 2",
"touchpad" => "Touchpad",
"misc1" => "Misc 1",
"misc2" => "Misc 2",
"misc3" => "Misc 3",
"misc4" => "Misc 4",
"misc5" => "Misc 5",
"misc6" => "Misc 6",
}
end

View file

@ -438,22 +438,22 @@ class Scene_Map
def new_footprint(direction, x, y)
if @spriteset != nil
@spriteset.new_footprint(direction, x, y)
end
end
end
def new_footsplash(direction, x, y)
if @spriteset != nil
@spriteset.new_footsplash(direction, x, y)
end
end
end
def new_maptext(text, x, y)
if @spriteset != nil
@spriteset.new_maptext(text, x, y)
end
end
end
def fix_footsplashes(xDelt, yDelt)
if @spriteset != nil
@spriteset.fix_footsplashes(xDelt, yDelt)
end
end
end
def menu_open?
@menu.visible || @item_menu.visible

View file

@ -78,13 +78,16 @@ class Spriteset_Map
@timer_sprite = Sprite_Timer.new
# Make lightbulb sprite
@bulb = Sprite.new(@viewport_lights)
@bulb.x = 0
if Graphics.width == 1280
@bulb.bitmap = RPG::Cache.light('bulb_16')
else
@bulb.bitmap = RPG::Cache.light('bulb')
end
@bulb.x = 0
if Graphics.width == 1280
@bulb.bitmap = RPG::Cache.light('bulb_16')
else
@bulb.bitmap = RPG::Cache.light('bulb')
end
@bulb.opacity = has_lightbulb? ? 255 : 0
# Make dynamic light
@dynamic_light = DynamicLight.new(@viewport_lights)
$light = @dynamic_light
# Panorama animation timer
@pan_animate_timer = 0
RPG::Mod.exec_hooks("hooks/Spriteset_Map/init", binding)
@ -116,6 +119,8 @@ class Spriteset_Map
@footprint_sprites.each do |sprite|
sprite.dispose
end
# Dispose of dynamic light
@dynamic_light.dispose
# Dispose of weather
@weather.dispose
# Dispose of picture sprites
@ -264,9 +269,9 @@ class Spriteset_Map
@tilemap.oy = $game_map.display_y / 4
@tilemap.update
# Update panorama plane
if $game_map.always_moving
$game_map.pan_move_offset += 1
end
if $game_map.always_moving
$game_map.pan_move_offset += 1
end
if $game_map.clamped_x
x = ($game_player.real_x.to_f / (($game_map.width - 1) * 128)) * (@panorama.bitmap.width * $game_map.pan_zoom - Graphics.width)
@panorama.ox = x < 0.0 ? 0.0 : x
@ -318,25 +323,15 @@ class Spriteset_Map
@character_sprites.each do |sprite|
if !sprite.character.is_a?(Game_Event)
sprite.update
elsif
# this is just a check to make sure the sprite is onscreen for game events
# based on its current width and height
# no point in updating the sprite if offscreen
# this greatly increases performance on larger maps
if Graphics.width == 1280
((sprite.character.real_x + (sprite.ox*4) > Graphics.width - 128) &&
(sprite.character.real_x - (sprite.ox*4) < Graphics.width + (21 * 128)) &&
(sprite.character.real_y + (sprite.oy*4) > (Graphics.height) - 128) &&
(sprite.character.real_y - (sprite.oy*4) < (Graphics.height) + (17 * 128)))
sprite.update
else
((sprite.character.real_x + (sprite.ox*4) > Graphics.width - 128) &&
(sprite.character.real_x - (sprite.ox*4) < Graphics.width + (10 * 128)) &&
(sprite.character.real_y + (sprite.oy*4) > (Graphics.height) - 128) &&
(sprite.character.real_y - (sprite.oy*4) < (Graphics.height) + (6 * 128)))
sprite.update
end
elsif ((sprite.character.real_x + (sprite.ox*4) > $game_map.display_x - 128) &&
(sprite.character.real_x - (sprite.ox*4) < $game_map.display_x + ((Graphics.width / 32 + 1) * 128)) &&
(sprite.character.real_y + (sprite.oy*4) > ($game_map.display_y) - 128) &&
(sprite.character.real_y - (sprite.oy*4) < ($game_map.display_y) + (Graphics.height / 32 * 128)))
sprite.update
else
sprite.update_fast
end
@ -367,6 +362,8 @@ class Spriteset_Map
@bulb.opacity -= 2.125
end
end
@dynamic_light.update
# Update particles
@particles.update if @particles
# Update timer sprite

View file

@ -101,6 +101,7 @@ Doc_Message
Desktop_Message
Credits_Message
Particles
DynamicLight
EdText
Main

42
shader/dynamicLight.frag Normal file
View file

@ -0,0 +1,42 @@
uniform vec4 lightSources[64];
uniform vec4 lightSourcesColors[64];
uniform int lightSourcesCount;
uniform float ambientLight;
uniform sampler2D texture;
uniform sampler2D wallMapTexture;
uniform vec2 wallMapResolution;
uniform vec2 cameraPosition;
uniform vec2 tileMapOffset;
uniform vec2 texSizeInv;
varying vec2 v_texCoord;
const vec2 tileSize = vec2(32, 32);
const float powerBase = 255.0;
float distance(vec2 a, vec2 b){
vec2 c = a - b;
return sqrt(c.x * c.x + c.y * c.y);
}
void main(){
vec2 screenPoint = v_texCoord / texSizeInv;
vec2 mapPoint = cameraPosition + screenPoint;
vec2 wallmapUV = mapPoint / tileSize / wallMapResolution;
vec3 light = vec3(0, 0, 0);
for(int i = 0; i < lightSourcesCount; i++) {
vec4 sourceInfo = lightSources[i];
vec4 sourceColor = lightSourcesColors[i];
vec2 lightScreenPoint = (sourceInfo.xy - cameraPosition / tileSize) * tileSize + tileSize / 2.0;
light += sourceInfo.z / powerBase *
sourceColor.rgb *
pow(clamp(1.0 - distance(screenPoint, lightScreenPoint) / 32.0 / sourceInfo.w, 0.0, 1.0), sourceColor.a * 2.0);
}
gl_FragColor = vec4(light + ambientLight / powerBase, 1.0);
}

View file

@ -48,8 +48,10 @@ void main(){
float noise2 = pnoise(uv2 + vec2(WATER_DISTORTION, -WATER_DISTORTION) * noise);
float p2 = pow(noise2 + 0.2, 10.0);
vec4 frag = mix(vec4(waterColor1.rgb, 1.0), vec4(waterColor2.rgb, 1.0), noise1) + vec4(vec3(mix(p, 1.0, textureColor.r)) * waterColor3.rgb, 1.0);
frag += mix(vec4(waterColor1.rgb, 1.0), vec4(waterColor2.rgb, 1.0), noise2) + vec4(vec3(mix(p2, 1.0, textureColor.r)) * waterColor3.rgb, 1.0);
vec4 frag = mix(vec4(waterColor1.rgb, 1.0), vec4(waterColor2.rgb, 1.0), noise1) + vec4(vec3(mix(p, 1.0, textureColor.r)) * waterColor3.rgb, 0.0);
frag += mix(vec4(waterColor1.rgb, 1.0), vec4(waterColor2.rgb, 1.0), noise2) + vec4(vec3(mix(p2, 1.0, textureColor.r)) * waterColor3.rgb, 0.0);
frag.a = clamp(frag.a / 2.0, 0.0, 1.0);
frag.rgb = clamp(frag.rgb, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0));
float isWater = float(
waterColorCheck == vec4(1, 0, 0, 1)

View file

@ -40,7 +40,9 @@ void main(){
float p2 = pow(noise2 + 0.02, 20.0);
vec4 frag = mix(vec4(color.r, color.g, color.b, 1.0) * color.a, vec4(color.r, color.g, color.b, 1.0) * tone.a, noise1) + vec4(p, p, p, 1.0) * tone;
frag += mix(vec4(color.r, color.g, color.b, 1.0) * color.a, vec4(color.r, color.g, color.b, 1.0) * tone.a, noise2) + vec4(p2, p2, p2, 1.0) * tone;
frag += mix(vec4(color.r, color.g, color.b, 1.0) * color.a, vec4(color.r, color.g, color.b, 1.0) * tone.a, noise2) + vec4(p2, p2, p2, 1.0) * tone;
frag.a = clamp(frag.a / 2.0, 0.0, 1.0);
frag.rgb = clamp(frag.rgb, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0));
/* Apply opacity */
frag.a *= opacity;

View file

@ -770,6 +770,11 @@ void Bitmap::setPixel(int x, int y, const Color &color){
GUARD_MEGA;
if (x < 0 || y < 0 || x >= width() || y >= height()) {
Debug() << "Invalid pixel position " << x << " " << y << " in bitmap " << width() << "x" << height();
return;
}
uint8_t pixel[] = {
(uint8_t) clamp<double>(color.red, 0, 255),
(uint8_t) clamp<double>(color.green, 0, 255),

View file

@ -25,6 +25,7 @@
#include "disposable.h"
#include "etc-internal.h"
#include "etc.h"
#include "debugwriter.h"
// #include <sigc++/signal.h>
#include <sigc++/signal.h>

View file

@ -62,7 +62,7 @@ public:
#elif __EMSCRIPTEN__
emscripten_console_log(buf.str().c_str());
#else
std::cerr << buf.str() << "\n";
std::cout << buf.str() << "\n";
#endif
}

View file

@ -62,6 +62,22 @@ struct Vec4{
}
};
struct Vec4i{
int x, y, z, w;
Vec4i()
: x(0), y(0), z(0), w(0)
{}
Vec4i(int x, int y, int z, int w)
: x(x), y(y), z(z), w(w)
{}
bool operator==(const Vec4i &other) const{
return (x == other.x && y == other.y && z == other.z && w == other.w);
}
};
struct Vec2i{
int x, y;

View file

@ -68,17 +68,17 @@ void Color::set(double red, double green, double blue, double alpha){
void Color::setRed(double value){
red = value;
norm.x = clamp<double>(value, 0, 255) / 255;
norm.x = value / 255;
}
void Color::setGreen(double value){
green = value;
norm.y = clamp<double>(value, 0, 255) / 255;
norm.y = value / 255;
}
void Color::setBlue(double value){
blue = value;
norm.z = clamp<double>(value, 0, 255) / 255;
norm.z = value / 255;
}
void Color::setAlpha(double value){

View file

@ -34,7 +34,8 @@ enum BlendType{
BlendNormal = 0,
BlendAddition = 1,
BlendSubstraction = 2
BlendSubstraction = 2,
BlendMultiply = 3
};
struct Color : public Serializable{
@ -192,6 +193,27 @@ struct Rect : public Serializable{
sigc::signal<void> valueChanged;
};
struct LightSource {
float x;
float y;
float power;
float radius;
Color color;
LightSource()
: x(0), y(0), power(0), radius(0), color(Color())
{};
LightSource(float x, float y, float power, float radius, Color color)
: x(x), y(y), power(power), radius(radius), color(color)
{};
bool hasEffect(){
return radius > 0.0 && power > 0.0 && color.alpha >= 0.0 && (color.red > 0.0 || color.green > 0.0 || color.blue > 0.0);
}
};
/* For internal use.
* All drawable classes have properties of one or more of the above
* types, which in an interpreted environment act as independent

View file

@ -90,6 +90,7 @@ typedef void (APIENTRYP _PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0);
typedef void (APIENTRYP _PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1);
typedef void (APIENTRYP _PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
typedef void (APIENTRYP _PFNGLUNIFORM1IPROC) (GLint location, GLint v0);
typedef void (APIENTRYP _PFNGLUNIFORM4IPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
typedef void (APIENTRYP _PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
/* Vertex attribute */
@ -174,6 +175,7 @@ typedef void (APIENTRYP _PFNGLRELEASESHADERCOMPILERPROC) (void);
GL_FUN(Uniform2f, _PFNGLUNIFORM2FPROC) \
GL_FUN(Uniform4f, _PFNGLUNIFORM4FPROC) \
GL_FUN(Uniform1i, _PFNGLUNIFORM1IPROC) \
GL_FUN(Uniform4i, _PFNGLUNIFORM4IPROC) \
GL_FUN(UniformMatrix4fv, _PFNGLUNIFORMMATRIX4FVPROC) \
/* Vertex attribute */ \
GL_FUN(BindAttribLocation, _PFNGLBINDATTRIBLOCATIONPROC) \

View file

@ -72,7 +72,7 @@ namespace TEX{
}
static inline void uploadImage(GLsizei width, GLsizei height, const void *data, GLenum format){
gl.TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, format, GL_UNSIGNED_BYTE, data);
gl.TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, format, GL_UNSIGNED_BYTE, data);
}
static inline void uploadSubImage(GLint x, GLint y, GLsizei width, GLsizei height, const void *data, GLenum format){
@ -80,7 +80,7 @@ namespace TEX{
}
static inline void allocEmpty(GLsizei width, GLsizei height){
gl.TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
gl.TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_HALF_FLOAT, 0);
}
static inline void setRepeat(bool mode){
@ -217,4 +217,10 @@ struct TEXFBO{
}
};
#define GL_CHECK() { \
GLenum e = gl.GetError(); \
if (e != GL_NO_ERROR) \
printf("%s:%d -> %x\n", __FILE__, __LINE__, e); \
}
#endif // GLUTIL_H

View file

@ -82,6 +82,12 @@ void GLBlendMode::apply(const BlendType &value){
gl.BlendFuncSeparate(GL_SRC_ALPHA, GL_ONE,
GL_ONE, GL_ONE);
break;
case BlendMultiply :
gl.BlendEquation(GL_FUNC_ADD);
gl.BlendFuncSeparate(GL_DST_COLOR, GL_ZERO,
GL_ZERO, GL_ONE);
break;
}
}

256
src/lightmap.cpp Normal file
View file

@ -0,0 +1,256 @@
#include "lightmap.h"
#include "sharedstate.h"
#include "bitmap.h"
#include "etc.h"
#include "etc-internal.h"
#include "util.h"
#include "gl-util.h"
#include "quad.h"
#include "transform.h"
#include "shader.h"
#include "glstate.h"
#include "quadarray.h"
#include "config.h"
#include "debugwriter.h"
#include "sunshine.h"
#include <math.h>
#include <SDL3/SDL_rect.h>
#include <sigc++/connection.h>
#include <boost/chrono.hpp>
struct LightMapPrivate{
Bitmap *bitmap;
Bitmap *wallMap;
int cameraX, cameraY, tilemapOffsetX, tilemapOffsetY;
float ambient;
std::vector<LightSource> staticLightSources;
std::vector<LightSource> dynamicLightSources;
std::vector<LightSource> gpuBuffer;
Quad quad;
Transform trans;
Rect *srcRect;
sigc::connection srcRectCon;
IntRect sceneRect;
Vec2i sceneOrig;
/* Would this sprite be visible on
* the screen if drawn? */
bool isVisible;
EtcTemps tmp;
sigc::connection prepareCon;
LightMapPrivate()
: bitmap(new Bitmap(shState->graphics().width(), shState->graphics().height())),
wallMap(0),
cameraX(0),
cameraY(0),
tilemapOffsetX(0),
tilemapOffsetY(0),
ambient(255.0),
srcRect(&tmp.rect),
isVisible(false)
{
sceneRect.x = sceneRect.y = 0;
updateSrcRectCon();
prepareCon = shState->prepareDraw.connect(sigc::mem_fun(this, &LightMapPrivate::prepare));
bitmap->ensureNonMega();
//bitmap->fillRect(0, 0, shState->graphics().width(), shState->graphics().height(), Vec4(0, 0, 0, 1));
staticLightSources.reserve(48);
dynamicLightSources.reserve(16);
gpuBuffer.reserve(64);
}
~LightMapPrivate(){
srcRectCon.disconnect();
prepareCon.disconnect();
}
void onSrcRectChange(){
FloatRect rect = srcRect->toFloatRect();
Vec2i bmSize;
if (!nullOrDisposed(bitmap))
bmSize = Vec2i(bitmap->width(), bitmap->height());
/* Clamp the rectangle so it doesn't reach outside
* the bitmap bounds */
rect.w = clamp<int>(rect.w, 0, bmSize.x-rect.x);
rect.h = clamp<int>(rect.h, 0, bmSize.y-rect.y);
quad.setTexRect(rect);
quad.setPosRect(FloatRect(0, 0, rect.w, rect.h));
}
void updateSrcRectCon(){
/* Cut old connection */
srcRectCon.disconnect();
/* Create new one */
srcRectCon = srcRect->valueChanged.connect(sigc::mem_fun(this, &LightMapPrivate::onSrcRectChange));
}
void updateVisibility(){
isVisible = false;
if (nullOrDisposed(bitmap))
return;
/* Compare sprite bounding box against the scene */
/* If sprite is zoomed/rotated, just opt out for now
* for simplicity's sake */
const Vec2 &scale = trans.getScale();
if (scale.x != 1 || scale.y != 1 || trans.getRotation() != 0){
isVisible = true;
return;
}
IntRect self;
self.setPos(trans.getPositionI() - (trans.getOriginI() + sceneOrig));
self.w = bitmap->width();
self.h = bitmap->height();
isVisible = SDL_HasRectIntersection(&self, &sceneRect);
}
void prepare(){
updateVisibility();
}
};
LightMap::LightMap(Viewport *viewport)
: ViewportElement(viewport){
p = new LightMapPrivate;
onGeometryChange(scene->getGeometry());
if (nullOrDisposed(p->bitmap))
return;
p->bitmap->ensureNonMega();
*p->srcRect = p->bitmap->rect();
p->onSrcRectChange();
p->quad.setPosRect(p->srcRect->toFloatRect());
}
LightMap::~LightMap(){
dispose();
}
DEF_ATTR_RD_SIMPLE(LightMap, WallMap, Bitmap*, p->wallMap)
DEF_ATTR_SIMPLE(LightMap, CameraX, int, p->cameraX)
DEF_ATTR_SIMPLE(LightMap, CameraY, int, p->cameraY)
DEF_ATTR_SIMPLE(LightMap, TilemapOffsetX, int, p->tilemapOffsetX)
DEF_ATTR_SIMPLE(LightMap, TilemapOffsetY, int, p->tilemapOffsetY)
DEF_ATTR_SIMPLE(LightMap, Ambient, float, p->ambient);
void LightMap::setWallMap(Bitmap *bitmap){
guardDisposed();
if (p->wallMap == bitmap)
return;
p->wallMap = bitmap;
if (nullOrDisposed(bitmap))
return;
bitmap->ensureNonMega();
}
void LightMap::initDynAttribs(){
p->srcRect = new Rect;
p->updateSrcRectCon();
}
/* Flashable */
void LightMap::update(){
guardDisposed();
Flashable::update();
}
void LightMap::clearStaticLightSources(){
p->staticLightSources.clear();
}
void LightMap::clearDynamicLightSources(){
p->dynamicLightSources.clear();
}
void LightMap::addStaticLightSource(LightSource source){
p->staticLightSources.push_back(source);
}
void LightMap::addDynamicLightSource(LightSource source){
p->dynamicLightSources.push_back(source);
}
/* SceneElement */
void LightMap::draw(){
if (!p->isVisible)
return;
if (emptyFlashFlag)
return;
DynamicLightShader &shader = shState->shaders().dynamicLight;
shader.bind();
shader.applyViewportProj();
if (p->wallMap)
shader.setWallMapTexture(p->wallMap->getGLTypes().tex);
shader.setWallMapResolution(p->wallMap->width(), p->wallMap->height());
shader.setCameraPosition(p->cameraX, p->cameraY);
shader.setTileMapOffset(p->tilemapOffsetX, p->tilemapOffsetY);
p->gpuBuffer.clear();
p->gpuBuffer.insert(p->gpuBuffer.end(), p->staticLightSources.begin(), p->staticLightSources.end());
p->gpuBuffer.insert(p->gpuBuffer.end(), p->dynamicLightSources.begin(), p->dynamicLightSources.end());
shader.setLightSources(p->gpuBuffer);
shader.setAmbient(p->ambient);
boost::chrono::high_resolution_clock::time_point currentTime = boost::chrono::high_resolution_clock::now();
boost::chrono::duration<float> elapsed = currentTime - startTime;
shader.setTime(elapsed.count());
glState.blendMode.pushSet(BlendMultiply);
p->bitmap->bindTex(shader);
p->quad.draw();
glState.blendMode.pop();
}
void LightMap::onGeometryChange(const Scene::Geometry &geo){
/* Offset at which the sprite will be drawn
* relative to screen origin */
p->trans.setGlobalOffset(geo.offset());
p->sceneRect.setSize(geo.rect.size());
p->sceneOrig = geo.orig;
}
void LightMap::releaseResources(){
unlink();
delete p;
}

50
src/lightmap.h Normal file
View file

@ -0,0 +1,50 @@
#ifndef SPRITE_H
#define SPRITE_H
#include "scene.h"
#include "flashable.h"
#include "disposable.h"
#include "viewport.h"
#include "util.h"
#include "shader.h"
#include <boost/chrono.hpp>
class Bitmap;
struct LightMapPrivate;
class LightMap : public ViewportElement, public Flashable, public Disposable{
public:
LightMap(Viewport *viewport = 0);
~LightMap();
void update();
DECL_ATTR( Bitmap, Bitmap* )
DECL_ATTR( WallMap, Bitmap* )
DECL_ATTR( CameraX, int )
DECL_ATTR( CameraY, int )
DECL_ATTR( TilemapOffsetX, int )
DECL_ATTR( TilemapOffsetY, int )
DECL_ATTR( Ambient, float )
void clearStaticLightSources();
void clearDynamicLightSources();
void addStaticLightSource(LightSource source);
void addDynamicLightSource(LightSource source);
void initDynAttribs();
private:
LightMapPrivate *p;
void draw();
void onGeometryChange(const Scene::Geometry &);
void releaseResources();
const char *klassName() const { return "lightmap"; }
ABOUT_TO_ACCESS_DISP
};
#endif // SPRITE_H

View file

@ -56,6 +56,7 @@
#include "blurH.vert.xxd"
#include "blurV.vert.xxd"
#include "obscured.frag.xxd"
#include "dynamicLight.frag.xxd"
#include "meow.h"
#include "sunshine.h"
@ -200,7 +201,7 @@ void Shader::setVec4Uniform(GLint location, const Vec4 &vec) {
void Shader::setTexUniform(GLint location, unsigned unitIndex, TEX::ID texture) {
GLenum texUnit = GL_TEXTURE0 + unitIndex;
gl.ActiveTexture(texUnit);
gl.BindTexture(GL_TEXTURE_2D, texture.gl);
gl.Uniform1i(location, unitIndex);
@ -275,6 +276,65 @@ void SimpleShader::setTexOffsetX(int value){
}
DynamicLightShader::DynamicLightShader(){
INIT_SHADER(simple, dynamicLight, DynamicLightShader);
ShaderBase::init();
GET_U(wallMapTexture);
GET_U(wallMapResolution);
GET_U(cameraPosition);
GET_U(tileMapOffset);
GET_U(lightSourcesCount);
GET_U(ambientLight);
u_lightSources = gl.GetUniformLocation(program, "lightSources[0]");
u_lightSourcesColors = gl.GetUniformLocation(program, "lightSourcesColors[0]");
}
void DynamicLightShader::setWallMapTexture(TEX::ID texture){
setTexUniform(u_wallMapTexture, 1, texture);
}
void DynamicLightShader::setWallMapResolution(int x, int y){
gl.Uniform2f(u_wallMapResolution, x, y);
}
void DynamicLightShader::setCameraPosition(int x, int y){
gl.Uniform2f(u_cameraPosition, x, y);
}
void DynamicLightShader::setTileMapOffset(int x, int y){
gl.Uniform2f(u_tileMapOffset, x, y);
}
void DynamicLightShader::setLightSources(std::vector<LightSource> sources){
gl.Uniform1i(u_lightSourcesCount, sources.size());
for(int i = 0; i < sources.size(); i++)
{
if (!sources[i].hasEffect())
continue;
gl.Uniform4f(u_lightSources + i,
sources[i].x,
sources[i].y,
sources[i].power,
sources[i].radius
);
gl.Uniform4f(u_lightSourcesColors + i,
sources[i].color.red / 255.0,
sources[i].color.green / 255.0,
sources[i].color.blue / 255.0,
sources[i].color.alpha / 255.0
);
}
}
void DynamicLightShader::setAmbient(float power){
gl.Uniform1f(u_ambientLight, power);
}
SimpleColorShader::SimpleColorShader(){
INIT_SHADER(simpleColor, simpleColor, SimpleColorShader);

View file

@ -103,6 +103,22 @@ private:
GLint u_texOffsetX;
};
class DynamicLightShader : public ShaderBase{
public:
DynamicLightShader();
void setWallMapTexture(TEX::ID texture);
void setWallMapResolution(int x, int y);
void setCameraPosition(int x, int y);
void setTileMapOffset(int x, int y);
void setLightSources(std::vector<LightSource> sources);
void setAmbient(float power);
private:
GLint u_wallMapTexture, u_wallMapResolution, u_cameraPosition, u_tileMapOffset, u_lightSources, u_lightSourcesCount, u_lightSourcesColors, u_ambientLight;
};
class SimpleColorShader : public ShaderBase{
public:
SimpleColorShader();
@ -353,7 +369,8 @@ private:
X(blt, BltShader, Blt) \
X(simpleMatrix, SimpleMatrixShader, SimpleMatrix) \
X(blur, BlurShader, Blur) \
X(obscured, ObscuredShader, Obscured)
X(obscured, ObscuredShader, Obscured) \
X(dynamicLight, DynamicLightShader, DynamicLight)
struct ShaderSet{
#define DECLARE_SHADER(name, type, rb) type name;

View file

@ -442,6 +442,9 @@ void Sprite::setBlendType(int type){
case BlendSubstraction :
p->blendType = BlendSubstraction;
return;
case BlendMultiply :
p->blendType = BlendMultiply;
return;
}
}

View file

@ -63,12 +63,6 @@ static const int atAreaW = autotileW * 4;
static const int atAreaH = autotileH * autotileCount;
static const int tsLaneW = tilesetW / 2;
/* Map viewport size */
//TODO: разные значения под разные разрешения экрана для оптимизации
static const int viewpW = 42;
static const int viewpH = 26;
static const size_t zlayersMax = viewpH + 5;
/* Vocabulary:
*
@ -255,6 +249,9 @@ struct TilemapPrivate {
std::vector<uint8_t> animatedATs;
} atlas;
int viewpW, viewpH;
size_t zlayersMax;
/* Map viewport position */
Vec2i viewpPos;
@ -262,11 +259,11 @@ struct TilemapPrivate {
SVVector groundVert;
/* ZLayer vertices */
SVVector zlayerVert[zlayersMax];
std::vector<SVVector> zlayerVert;
/* Base quad indices of each zlayer
* in the shared buffer */
size_t zlayerBases[zlayersMax+1];
std::vector<size_t> zlayerBases;
/* Shared buffers for all tiles */
struct{
@ -285,7 +282,7 @@ struct TilemapPrivate {
/* Scene elements */
struct{
GroundLayer *ground;
ZLayer* zlayers[zlayersMax];
std::vector<ZLayer*> zlayers;
/* Used layers out of 'zlayers' (rest is hidden) */
size_t activeLayers;
Scene::Geometry sceneGeo;
@ -330,8 +327,14 @@ struct TilemapPrivate {
buffersDirty(false),
mapViewportDirty(false),
zOrderDirty(false),
tilemapReady(false)
tilemapReady(false),
viewpW(shState->graphics().width() / 32 + 1),
viewpH(shState->graphics().height() / 32 + 2),
zlayersMax(viewpH + 5)
{
zlayerVert.resize(zlayersMax);
zlayerBases.resize(zlayersMax + 1);
SDL_memset(autotiles, 0, sizeof(autotiles));
atlas.animatedATs.reserve(autotileCount);
@ -351,6 +354,7 @@ struct TilemapPrivate {
GLMeta::vaoInit(tiles.vao);
elem.ground = new GroundLayer(this, viewport);
elem.zlayers.resize(zlayersMax);
for (size_t i = 0; i < zlayersMax; ++i)
elem.zlayers[i] = new ZLayer(this, viewport);
@ -363,8 +367,8 @@ struct TilemapPrivate {
~TilemapPrivate(){
/* Destroy elements */
delete elem.ground;
for (size_t i = 0; i < zlayersMax; ++i)
delete elem.zlayers[i];
for (ZLayer* ptr : elem.zlayers)
delete ptr;
shState->releaseAtlasTex(atlas.gl);
@ -820,7 +824,7 @@ struct TilemapPrivate {
* are muted via the 'batchedFlag'. For simplicity,
* single sized batches are possible. */
void prepareZLayerBatches(){
ZLayer *const *zlayers = elem.zlayers;
ZLayer *const *zlayers = elem.zlayers.data();
for (size_t i = 0; i < elem.activeLayers; ++i){
ZLayer *batchHead = zlayers[i];