Skip to content

Commit ec5a4ba

Browse files
authored
Add xmas edition of trail shader
1 parent 81a167c commit ec5a4ba

File tree

2 files changed

+439
-0
lines changed

2 files changed

+439
-0
lines changed

cursor_sha_der_trail_xmas.lua

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
S = obslua
2+
local ffi = require"ffi"
3+
local C = ffi.C
4+
local bit = require"bit"
5+
local _OR = bit.bor
6+
if ffi.os == "Windows" then
7+
ffi.cdef[[
8+
typedef struct {int x, y;} Point;
9+
bool GetCursorPos(Point *lpPoint);
10+
]]
11+
mouse_pos = ffi.new("Point")
12+
else
13+
error("Not implemented, but may be possible on other platforms")
14+
end
15+
16+
local function skip_tick_render(ctx)
17+
local target = S.obs_filter_get_target(ctx.source)
18+
local width, height;
19+
if target == nil then width = 0; height = 0; else
20+
width = S.obs_source_get_base_width(target)
21+
height = S.obs_source_get_base_height(target)
22+
end
23+
ctx.width, ctx.height = width , height
24+
end
25+
26+
local SourceDef = {}
27+
28+
function SourceDef:new(o)
29+
o = o or {}
30+
setmetatable(o, self)
31+
self.__index = self
32+
return o
33+
end
34+
35+
function SourceDef:create(source)
36+
local instance = {}
37+
instance.width = 1
38+
instance.height = 1
39+
instance.current_time = 0
40+
instance.loop_time = 0
41+
instance.source = source
42+
instance.emit_time = 0.33
43+
instance.afk = true
44+
instance.prev_location = {x=999,y=999}
45+
46+
instance.spawn_rate = 0.01
47+
instance.afk_time = 5.85 + 0.2
48+
instance.min_age = 1.3
49+
instance.max_age = 1.85
50+
instance.max_particles = 77
51+
52+
S.obs_enter_graphics()
53+
instance.effect = S.gs_effect_create(SHADER, nil, nil)
54+
if instance.effect ~= nil then
55+
instance.params = {}
56+
instance.params.width = S.gs_effect_get_param_by_name(instance.effect, 'width')
57+
instance.params.itime = S.gs_effect_get_param_by_name(instance.effect, 'itime')
58+
instance.params.height = S.gs_effect_get_param_by_name(instance.effect, 'height')
59+
instance.params.mouse_x = S.gs_effect_get_param_by_name(instance.effect, 'mouse_x')
60+
instance.params.mouse_y = S.gs_effect_get_param_by_name(instance.effect, 'mouse_y')
61+
instance.params.afk = S.gs_effect_get_param_by_name(instance.effect, 'afk')
62+
local all_particles = {}
63+
for i=1,instance.max_particles do
64+
instance.params['particle'.. i] = S.gs_effect_get_param_by_name(instance.effect, 'particle' .. i)
65+
instance['particle'.. i .. '_vec4'] = S.vec4()
66+
table.insert(all_particles,{x=0.0,y=0.0,dx=0.0,dy=0.0,age=0.0})
67+
end
68+
instance.all_particles = all_particles
69+
end
70+
S.obs_leave_graphics()
71+
if instance.effect == nil then
72+
SourceDef.destroy(instance)
73+
return nil
74+
end
75+
--SourceDef.update(instance,self) -- initialize, self = settings
76+
return instance
77+
end
78+
79+
function SourceDef:destroy()
80+
if self.effect ~= nil then
81+
S.obs_enter_graphics()
82+
S.gs_effect_destroy(self.effect)
83+
S.obs_leave_graphics()
84+
end
85+
end
86+
87+
function SourceDef:get_name() return "Cursor shader trail XMAS edition by upgradeQ" end
88+
89+
90+
function SourceDef:get_width() return self.width end
91+
function SourceDef:get_height() return self.height end
92+
93+
local function norm(val,amin,amax) return (val-amin ) / (amax- amin) end
94+
95+
local function get_cur_norm(w,h)
96+
C.GetCursorPos(mouse_pos)
97+
local norm_x = norm(mouse_pos.x,0,w)
98+
norm_x = norm_x * (w/h) -- aspect ratio
99+
local norm_y = norm(mouse_pos.y,0,h)
100+
return norm_x,norm_y
101+
end
102+
103+
function SourceDef:Emitter(norm_x,norm_y)
104+
-- on render call
105+
if not ((self.prev_location.x == norm_x) and ( self.prev_location.y == norm_y)) then
106+
self.prev_location.x, self.prev_location.y = norm_x, norm_y
107+
self.loop_time = 0 -- afk detector
108+
self.afk = false
109+
else
110+
self.loop_time = self.loop_time + self.tick_tock
111+
end
112+
self.emit_time = self.emit_time - self.tick_tock
113+
if self.loop_time > self.afk_time + 0.1 then
114+
self.afk = true
115+
return end
116+
for i=1,self.max_particles do
117+
self.all_particles[i].age = self.all_particles[i].age - self.tick_tock
118+
if (self.emit_time <= 0) and (self.all_particles[i].age <= 0.0) then
119+
local angle = 2 * math.pi * math.random()
120+
-- 0.013 radius, math.random() position on circle radius
121+
self.all_particles[i].x = 0.013*math.random()*math.cos(angle) + norm_x
122+
self.all_particles[i].y = 0.013*math.random()*math.sin(angle) + norm_y
123+
self.all_particles[i].dx, self.all_particles[i].dy =0.0002,0.0008
124+
--self.all_particles[i].age = 1.3 -- full trail
125+
self.all_particles[i].age = math.random(self.min_age,self.max_age)
126+
self['particle' .. i .. '_vec4'].z = math.random(0.7,1) -- size variation
127+
if (self.loop_time > 0.02) then
128+
self.emit_time = self.spawn_rate + math.min(0.2,self.loop_time)
129+
return
130+
end
131+
self.emit_time = self.spawn_rate -- required so all particles do not spawn as one
132+
end
133+
134+
if self.loop_time > self.afk_time then -- hide after 5 seconds
135+
self.all_particles[i].x,self.all_particles[i].y = -3,-3
136+
end
137+
if (self.all_particles[i].age < 0.1) then
138+
self.all_particles[i].x,self.all_particles[i].y = -3, -3
139+
end -- removes flickering
140+
--if i == 2 then
141+
--print(tostring(self.all_particles[i].x))
142+
--print(tostring(self.all_particles[i].age) .. 'log' .. 'i' .. tostring(i))
143+
--print(tostring(self.tick_tock)) -- 0.016
144+
--end
145+
self.all_particles[i].x = self.all_particles[i].x - self.all_particles[i].dx
146+
self.all_particles[i].y = self.all_particles[i].y + self.all_particles[i].dy
147+
148+
self['particle' .. i .. '_vec4'].x = self.all_particles[i].x
149+
self['particle' .. i .. '_vec4'].y = self.all_particles[i].y
150+
self['particle' .. i .. '_vec4'].w = self.all_particles[i].age
151+
end
152+
end
153+
154+
function SourceDef:video_tick(seconds)
155+
self.current_time = self.current_time + seconds
156+
self.tick_tock = seconds
157+
skip_tick_render(self) -- if source has crop or transform applied to it, this will let it render
158+
if ffi.os == "Windows" then
159+
C.GetCursorPos(mouse_pos)
160+
else
161+
mouse_pos.x,mouse_pos.y = get_x11_mpos()
162+
end
163+
local norm_x,norm_y = get_cur_norm(self.width,self.height)
164+
SourceDef.Emitter(self,norm_x,norm_y)
165+
end
166+
167+
function SourceDef:video_render()
168+
local parent = S.obs_filter_get_parent(self.source)
169+
self.width = S.obs_source_get_base_width(parent)
170+
self.height = S.obs_source_get_base_height(parent)
171+
if not S.obs_source_process_filter_begin(self.source, S.GS_RGBA, S.OBS_ALLOW_DIRECT_RENDERING) then return end
172+
S.gs_effect_set_float(self.params.itime, self.current_time+0.0)
173+
S.gs_effect_set_bool(self.params.afk, self.afk)
174+
S.gs_effect_set_int(self.params.width, self.width)
175+
S.gs_effect_set_int(self.params.height, self.height)
176+
for i=1,self.max_particles do
177+
S.gs_effect_set_vec4(self.params['particle' .. i],self['particle' .. i .. '_vec4'])
178+
end
179+
S.obs_source_process_filter_tech_end(self.source, self.effect, self.width, self.height,"Draw")
180+
end
181+
182+
function script_properties()
183+
local props = S.obs_properties_create()
184+
S.obs_properties_add_button(props, "button2", "Cursor shader trail XMAS edition by upgradeQ",
185+
function() end)
186+
return props
187+
end
188+
189+
function script_load(settings)
190+
local my_filter = SourceDef:new({id='filter_cursor_shader_xmas',type=S.OBS_SOURCE_TYPE_FILTER,
191+
output_flags=_OR(S.OBS_SOURCE_VIDEO,S.OBS_SOURCE_CUSTOM_DRAW)})
192+
S.obs_register_source(my_filter)
193+
end
194+
195+
function read_from(file)
196+
local f = assert(io.open(file, "rb"))
197+
local content = f:read("*all")
198+
f:close()
199+
return content
200+
end
201+
202+
SHADER = read_from(script_path() .. "my_shader_particles.hlsl")

0 commit comments

Comments
 (0)