-
-
Notifications
You must be signed in to change notification settings - Fork 290
/
particles.jl
253 lines (227 loc) · 9 KB
/
particles.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
using Makie: RectanglePacker
function to_meshcolor(color::TOrSignal{Vector{T}}) where T <: Colorant
TextureBuffer(color)
end
function to_meshcolor(color::TOrSignal{Matrix{T}}) where T <: Colorant
Texture(color)
end
function to_meshcolor(color)
color
end
vec2quaternion(rotation::StaticVector{4}) = rotation
function vec2quaternion(r::StaticVector{2})
vec2quaternion(Vec3f(r[1], r[2], 0))
end
function vec2quaternion(rotation::StaticVector{3})
Makie.rotation_between(Vec3f(0, 0, 1), Vec3f(rotation))
end
vec2quaternion(rotation::Vec4f) = rotation
vec2quaternion(rotation::VectorTypes) = const_lift(x-> vec2quaternion.(x), rotation)
vec2quaternion(rotation::Observable) = lift(vec2quaternion, rotation)
vec2quaternion(rotation::Makie.Quaternion)= Vec4f(rotation.data)
vec2quaternion(rotation)= vec2quaternion(to_rotation(rotation))
GLAbstraction.gl_convert(rotation::Makie.Quaternion)= Vec4f(rotation.data)
to_pointsize(x::Number) = Float32(x)
to_pointsize(x) = Float32(x[1])
struct PointSizeRender
size::Observable
end
(x::PointSizeRender)() = glPointSize(to_pointsize(x.size[]))
# For switching between ellipse method and faster circle method in shader
is_all_equal_scale(o::Observable) = is_all_equal_scale(o[])
is_all_equal_scale(::Real) = true
is_all_equal_scale(::Vector{Real}) = true
is_all_equal_scale(v::Vec2f) = v[1] == v[2] # could use ≈ too
is_all_equal_scale(vs::Vector{Vec2f}) = all(is_all_equal_scale, vs)
@nospecialize
"""
This is the main function to assemble particles with a GLNormalMesh as a primitive
"""
function draw_mesh_particle(screen, p, data)
rot = get!(data, :rotation, Vec4f(0, 0, 0, 1))
rot = vec2quaternion(rot)
delete!(data, :rotation)
to_opengl_mesh!(data, p[1])
@gen_defaults! data begin
position = p[2] => TextureBuffer
scale = Vec3f(1) => TextureBuffer
rotation = rot => TextureBuffer
texturecoordinates = nothing
end
shading = pop!(data, :shading)::Makie.MakieCore.ShadingAlgorithm
@gen_defaults! data begin
color_map = nothing => Texture
color_norm = nothing
intensity = nothing
image = nothing => Texture
color = nothing => to_meshcolor
vertex_color = Vec4f(1)
matcap = nothing => Texture
fetch_pixel = false
interpolate_in_fragment_shader = false
uv_scale = Vec2f(1)
backlight = 0f0
instances = const_lift(length, position)
transparency = false
shader = GLVisualizeShader(
screen,
"util.vert", "particles.vert",
"fragment_output.frag", "lighting.frag", "mesh.frag",
view = Dict(
"position_calc" => position_calc(position, nothing, nothing, nothing, TextureBuffer),
"shading" => light_calc(shading),
"MAX_LIGHTS" => "#define MAX_LIGHTS $(screen.config.max_lights)",
"MAX_LIGHT_PARAMETERS" => "#define MAX_LIGHT_PARAMETERS $(screen.config.max_light_parameters)",
"buffers" => output_buffers(screen, to_value(transparency)),
"buffer_writes" => output_buffer_writes(screen, to_value(transparency))
)
)
end
if !isnothing(Makie.to_value(intensity))
data[:intensity] = intensity_convert_tex(intensity, position)
data[:len] = const_lift(length, position)
end
return assemble_shader(data)
end
"""
This is the most primitive particle system, which uses simple points as primitives.
This is supposed to be the fastest way of displaying particles!
"""
function draw_pixel_scatter(screen, position::VectorTypes, data::Dict)
@gen_defaults! data begin
vertex = position => GLBuffer
color_map = nothing => Texture
color = nothing => GLBuffer
color_norm = nothing
scale = 2f0
transparency = false
shader = GLVisualizeShader(
screen,
"fragment_output.frag", "dots.vert", "dots.frag",
view = Dict(
"buffers" => output_buffers(screen, to_value(transparency)),
"buffer_writes" => output_buffer_writes(screen, to_value(transparency))
)
)
gl_primitive = GL_POINTS
end
data[:prerender] = PointSizeRender(data[:scale])
return assemble_shader(data)
end
function draw_scatter(
screen, p::Tuple{TOrSignal{Matrix{C}}, VectorTypes{P}}, data::Dict
) where {C <: Colorant, P <: Point}
data[:image] = p[1] # we don't want this to be overwritten by user
@gen_defaults! data begin
scale = lift(x-> Vec2f(size(x)), p[1])
offset = Vec2f(0)
end
draw_scatter(screen, (RECTANGLE, p[2]), data)
end
function draw_scatter(
screen, p::Tuple{VectorTypes{Matrix{C}}, VectorTypes{P}}, data::Dict
) where {C <: Colorant, P <: Point}
images = map(el32convert, to_value(p[1]))
isempty(images) && error("Can not display empty vector of images as primitive")
sizes = map(size, images)
if !all(x-> x == sizes[1], sizes) # if differently sized
# create texture atlas
maxdims = sum(map(Vec{2, Int}, sizes))
rectangles = map(x->Rect2(0, 0, x...), sizes)
rpack = RectanglePacker(Rect2(0, 0, maxdims...))
uv_coordinates = [push!(rpack, rect).area for rect in rectangles]
max_xy = mapreduce(maximum, (a,b)-> max.(a, b), uv_coordinates)
texture_atlas = Texture(eltype(images[1]), (max_xy...,))
for (area, img) in zip(uv_coordinates, images)
texture_atlas[area] = img #transfer to texture atlas
end
data[:uv_offset_width] = map(uv_coordinates) do uv
m = max_xy .- 1
mini = reverse((minimum(uv)) ./ m)
maxi = reverse((maximum(uv) .- 1) ./ m)
return Vec4f(mini..., maxi...)
end
images = texture_atlas
end
data[:image] = images # we don't want this to be overwritten by user
@gen_defaults! data begin
shape = RECTANGLE
quad_offset = Vec2f(0)
end
return draw_scatter(screen, (RECTANGLE, p[2]), data)
end
"""
Main assemble functions for scatter particles.
Sprites are anything like distance fields, images and simple geometries
"""
function draw_scatter(screen, (marker, position), data)
rot = get!(data, :rotation, Vec4f(0, 0, 0, 1))
rot = vec2quaternion(rot)
delete!(data, :rotation)
if to_value(pop!(data, :depthsorting, false))
data[:indices] = map(
data[:projectionview], data[:preprojection], data[:model],
position
) do pv, pp, m, pos
T = pv * pp * m
depth_vals = map(pos) do p
p4d = T * to_ndim(Point4f, to_ndim(Point3f, p, 0f0), 1f0)
p4d[3] / p4d[4]
end
UInt32.(sortperm(depth_vals, rev = true) .- 1)
end |> indexbuffer
end
@gen_defaults! data begin
shape = Cint(0)
position = position => GLBuffer
marker_offset = Vec3f(0) => GLBuffer;
scale = Vec2f(0) => GLBuffer
rotation = rot => GLBuffer
image = nothing => Texture
end
data[:shape] = map(
convert(Observable{Int}, pop!(data, :shape)), data[:scale]
) do shape, scale
if shape == 0 && !is_all_equal_scale(scale)
return Cint(5) # scaled CIRCLE -> ELLIPSE
else
return shape
end
end
@gen_defaults! data begin
quad_offset = Vec2f(0) => GLBuffer
intensity = nothing => GLBuffer
color_map = nothing => Texture
color_norm = nothing
color = nothing => GLBuffer
glow_color = RGBA{Float32}(0,0,0,0) => GLBuffer
stroke_color = RGBA{Float32}(0,0,0,0) => GLBuffer
stroke_width = 0f0
glow_width = 0f0
uv_offset_width = Vec4f(0) => GLBuffer
distancefield = nothing => Texture
indices = const_lift(length, position) => to_index_buffer
# rotation and billboard don't go along
billboard = rotation == Vec4f(0,0,0,1) => "if `billboard` == true, particles will always face camera"
fxaa = false
transparency = false
shader = GLVisualizeShader(
screen,
"fragment_output.frag", "util.vert", "sprites.geom",
"sprites.vert", "distance_shape.frag",
view = Dict(
"position_calc" => position_calc(position, nothing, nothing, nothing, GLBuffer),
"buffers" => output_buffers(screen, to_value(transparency)),
"buffer_writes" => output_buffer_writes(screen, to_value(transparency))
)
)
scale_primitive = true
gl_primitive = GL_POINTS
end
# Exception for intensity, to make it possible to handle intensity with a
# different length compared to position. Intensities will be interpolated in that case
data[:intensity] = intensity_convert(intensity, position)
data[:len] = const_lift(length, position)
return assemble_shader(data)
end
@specialize