Skip to content

Commit

Permalink
Add ability to use vector path markers (#979)
Browse files Browse the repository at this point in the history
* add bezierpaths

* add bbox stuff, cairomakie routines

* add elliptical arc to bezier conversion

* fix .y and closepath

* try enabling BezierPaths in texture atlas

* fix bug

* better bitmap size, adjust triangles

* options for bezier from string

* add some docs

* improve examples

* enable any size of bezierpath, normalized

* fix scaling and offset

* fix bbox

* fix offsets

* add Vector markersizes & fix aspect ratio

* use billboarded rotations for all Real inputs

* center within unit-square and bbox ellipticalarc

* reassign markers in marker_map

* cairomakie fixed

* fix GLMakie

* fix test syntax

* fix array of mixed markers

* replace all default markers

* add test for most markers

* microadjust markers

* adjust bezier marker size relative to chars

* make square little bit smaller

* add test for more complex marker shapes

* correct bbox fitting logic

* add docs

* fix sentence

* make testsets easier to read

* remove only for 1.3 compat

* remove old scale_x/y/z code

* add primitive shape for bezier for WGLMakie

* replace `@cell`

* fix elliptical arc scaling and rotation bugs

* test squishing without ellipticalarc

* first step at fixing WGLMakie + remove string for marker

* remove erroneous line from merge

* refactor marker scaling

* refactor scaling code

* fix segfault on windows

* fix tests

* fix CairoMakie

* fix image marker

* add tests for marker_offset + data space

* implement wglmakie single image marker

* fix CairoMakie newline rendering

* fix newline rendering and clarify what's going on

* implement polygon markers and remove more string markers

* clean up spritemarker conversion some more

* forgot import

* fetch all PRs in for stale removal

* use constructor

* bring back precompiles

* fixes + cleanup

* fix indentation

* fix spelling

* make default marker :circle, same as in palette

* fix image marker orientation

* fix spaces

* update bezier path docs

* add more info about markersize and marker types

* change to Circle marker for failing tests

* remove stars test

* increase default markersize from 9 to 12

* remove unused variables

Co-authored-by: ffreyer <frederic481994@hotmail.de>
Co-authored-by: Simon <sdanisch@protonmail.com>
Co-authored-by: SimonDanisch <sdanisch@gmail.com>
  • Loading branch information
4 people committed Sep 14, 2022
1 parent 00e0cad commit c335989
Show file tree
Hide file tree
Showing 24 changed files with 1,391 additions and 244 deletions.
16 changes: 14 additions & 2 deletions .github/workflows/stale_preview_removal.yml
Expand Up @@ -33,7 +33,19 @@ jobs:
parse(Int, match(r"PR(\d*)", dir)[1])
end
prs = JSON3.read(HTTP.get("https://api.github.com/repos/$repo/pulls").body)
function all_prs()
query_prs(page) = JSON3.read(HTTP.get("https://api.github.com/repos/$repo/pulls?per_page=100;page=$(page)").body)
prs = []
page = 1
while true
page_prs = query_prs(page)
isempty(page_prs) && break
append!(prs, page_prs)
page += 1
end
return prs
end
prs = all_prs()
open_within_threshold = map(x -> x.number, filter(prs) do pr
time = DateTime(pr.updated_at[1:19], ISODateTimeFormat)
return pr.state == "open" && Dates.days(now() - time) <= retention_days
Expand All @@ -58,4 +70,4 @@ jobs:
git config user.email "documenter@juliadocs.github.io"
git commit -m "delete preview"
git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree})
git push --force origin gh-pages-new:gh-pages
git push --force origin gh-pages-new:gh-pages
6 changes: 0 additions & 6 deletions CairoMakie/src/CairoMakie.jl
Expand Up @@ -15,12 +15,6 @@ using Makie.Observables
using Makie: spaces, is_data_space, is_pixel_space, is_relative_space, is_clip_space
using Makie: numbers_to_colors

const OneOrVec{T} = Union{
T,
Vec{N1, T} where N1,
NTuple{N2, T} where N2,
}

# re-export Makie, including deprecated names
for name in names(Makie, all=true)
if Base.isexported(Makie, name)
Expand Down
1 change: 0 additions & 1 deletion CairoMakie/src/overrides.jl
Expand Up @@ -67,7 +67,6 @@ function draw_poly(scene::Scene, screen::CairoScreen, poly, points_list::Vector{
end
end


draw_poly(scene::Scene, screen::CairoScreen, poly, rect::Rect2) = draw_poly(scene, screen, poly, [rect])

function draw_poly(scene::Scene, screen::CairoScreen, poly, rects::Vector{<:Rect2})
Expand Down
57 changes: 49 additions & 8 deletions CairoMakie/src/primitives.jl
Expand Up @@ -219,11 +219,18 @@ function draw_atomic_scatter(scene, ctx, transfunc, colors, markersize, strokeco
isnan(pos) && return

Cairo.set_source_rgba(ctx, rgbatuple(col)...)

Cairo.save(ctx)
if m isa Char
draw_marker(ctx, m, best_font(m, font), pos, scale, strokecolor, strokewidth, offset, rotation)
else
draw_marker(ctx, m, pos, scale, strokecolor, strokewidth, offset, rotation)
marker_converted = Makie.to_spritemarker(m)
# Setting a markersize of 0.0 somehow seems to break Cairos global state?
# At least it stops drawing any marker afterwards
# TODO, maybe there's something wrong somewhere else?
if !(norm(scale) 0.0)
if marker_converted isa Char
draw_marker(ctx, marker_converted, best_font(m, font), pos, scale, strokecolor, strokewidth, offset, rotation)
else
draw_marker(ctx, marker_converted, pos, scale, strokecolor, strokewidth, offset, rotation)
end
end
Cairo.restore(ctx)
end
Expand Down Expand Up @@ -280,7 +287,7 @@ function draw_marker(ctx, marker::Char, font, pos, scale, strokecolor, strokewid
set_font_matrix(ctx, old_matrix)
end

function draw_marker(ctx, marker::Circle, pos, scale, strokecolor, strokewidth, marker_offset, rotation)
function draw_marker(ctx, ::Type{<: Circle}, pos, scale, strokecolor, strokewidth, marker_offset, rotation)
marker_offset = marker_offset + scale ./ 2
pos += Point2f(marker_offset[1], -marker_offset[2])

Expand All @@ -304,8 +311,8 @@ function draw_marker(ctx, marker::Circle, pos, scale, strokecolor, strokewidth,
nothing
end

function draw_marker(ctx, marker::Rect, pos, scale, strokecolor, strokewidth, marker_offset, rotation)
s2 = Point2((widths(marker) .* scale .* (1, -1))...)
function draw_marker(ctx, ::Type{<: Rect}, pos, scale, strokecolor, strokewidth, marker_offset, rotation)
s2 = Point2((scale .* (1, -1))...)
pos = pos .+ Point2f(marker_offset[1], -marker_offset[2])
Cairo.rotate(ctx, to_2d_rotation(rotation))
Cairo.rectangle(ctx, pos[1], pos[2], s2...)
Expand All @@ -316,6 +323,38 @@ function draw_marker(ctx, marker::Rect, pos, scale, strokecolor, strokewidth, ma
Cairo.stroke(ctx)
end

function draw_marker(ctx, beziermarker::BezierPath, pos, scale, strokecolor, strokewidth, marker_offset, rotation)
Cairo.save(ctx)
Cairo.translate(ctx, pos[1], pos[2])
Cairo.rotate(ctx, to_2d_rotation(rotation))
Cairo.scale(ctx, scale[1], -scale[2]) # flip y for cairo
draw_path(ctx, beziermarker)
Cairo.fill_preserve(ctx)
sc = to_color(strokecolor)
Cairo.set_source_rgba(ctx, rgbatuple(sc)...)
Cairo.set_line_width(ctx, Float64(strokewidth))
Cairo.stroke(ctx)
Cairo.restore(ctx)
end

draw_path(ctx, bp::BezierPath) = foreach(x -> path_command(ctx, x), bp.commands)
path_command(ctx, c::MoveTo) = Cairo.move_to(ctx, c.p...)
path_command(ctx, c::LineTo) = Cairo.line_to(ctx, c.p...)
path_command(ctx, c::CurveTo) = Cairo.curve_to(ctx, c.c1..., c.c2..., c.p...)
path_command(ctx, ::ClosePath) = Cairo.close_path(ctx)
function path_command(ctx, c::EllipticalArc)
Cairo.save(ctx)
Cairo.translate(ctx, c.c...)
Cairo.rotate(ctx, c.angle)
Cairo.scale(ctx, 1, c.r2 / c.r1)
if c.a2 > c.a1
Cairo.arc(ctx, 0, 0, c.r1, c.a1, c.a2)
else
Cairo.arc_negative(ctx, 0, 0, c.r1, c.a1, c.a2)
end
Cairo.restore(ctx)
end


function draw_marker(ctx, marker::Matrix{T}, pos, scale,
strokecolor #= unused =#, strokewidth #= unused =#,
Expand Down Expand Up @@ -413,7 +452,9 @@ function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation,

p3_offset = to_ndim(Point3f, offset, 0)

glyph in ('\r', '\n') && return
# Not renderable by font (e.g. '\n')
# TODO, filter out \n in GlyphCollection, and render unrenderables as box
glyph == 0 && return

Cairo.save(ctx)
Cairo.set_source_rgba(ctx, rgbatuple(color)...)
Expand Down
1 change: 0 additions & 1 deletion CairoMakie/test/runtests.jl
Expand Up @@ -66,7 +66,6 @@ excludes = Set([
"Image on Surface Sphere",
"FEM mesh 2D",
"Hbox",
"Stars",
"Subscenes",
"Arrows 3D",
"Layouting",
Expand Down
7 changes: 6 additions & 1 deletion GLMakie/src/GLAbstraction/GLTypes.jl
Expand Up @@ -333,7 +333,12 @@ function RenderObject(
if isa_gl_struct(v)
merge!(data, gl_convert_struct(v, k))
elseif applicable(gl_convert, v) # if can't be converted to an OpenGL datatype,
data[k] = gl_convert(v)
try
data[k] = gl_convert(v)
catch e
@warn("Can't convert $(typeof(v)) to opengl, for attribute $(k)")
rethrow(e)
end
else # put it in passthrough
delete!(data, k)
passthrough[k] = v
Expand Down
2 changes: 1 addition & 1 deletion GLMakie/src/GLMakie.jl
Expand Up @@ -16,7 +16,7 @@ using Makie: @get_attribute, to_value, to_colormap, extrema_nan
using Makie: ClosedInterval, (..)
using Makie: inline!, to_native
using Makie: spaces, is_data_space, is_pixel_space, is_relative_space, is_clip_space
import Makie: to_font, glyph_uv_width!, el32convert
import Makie: to_font, glyph_uv_width!, el32convert, Shape, CIRCLE, RECTANGLE, ROUNDED_RECTANGLE, DISTANCEFIELD, TRIANGLE

using ShaderAbstractions
using FreeTypeAbstraction
Expand Down
5 changes: 5 additions & 0 deletions GLMakie/src/drawing_primitives.jl
Expand Up @@ -212,6 +212,11 @@ function draw_atomic(screen::GLScreen, scene::Scene, @nospecialize(x::Union{Scat
gl_attributes[:billboard] = map(rot-> isa(rot, Billboard), x.rotations)
isnothing(gl_attributes[:distancefield][]) && delete!(gl_attributes, :distancefield)
gl_attributes[:uv_offset_width][] == Vec4f(0) && delete!(gl_attributes, :uv_offset_width)

font = get(gl_attributes, :font, Observable(Makie.defaultfont()))
scale, quad_offset = Makie.marker_attributes(marker, gl_attributes[:scale], font, gl_attributes[:quad_offset])
gl_attributes[:scale] = scale
gl_attributes[:quad_offset] = quad_offset
else
connect_camera!(gl_attributes, scene.camera)
end
Expand Down
86 changes: 11 additions & 75 deletions GLMakie/src/glshaders/particles.jl
Expand Up @@ -35,64 +35,7 @@ struct PointSizeRender
end
(x::PointSizeRender)() = glPointSize(to_pointsize(x.size[]))

"""
returns the Shape for the distancefield algorithm
"""
primitive_shape(::Union{AbstractString, Char}) = DISTANCEFIELD
primitive_shape(x::X) where {X} = primitive_shape(X)
primitive_shape(::Type{T}) where {T <: Circle} = CIRCLE
primitive_shape(::Type{T}) where {T <: Rect2} = RECTANGLE
primitive_shape(x::Shape) = x

"""
Extracts the scale from a primitive.
"""
primitive_scale(prim::GeometryPrimitive) = Vec2f(widths(prim))
primitive_scale(::Union{Shape, Char}) = Vec2f(40)
primitive_scale(c) = Vec2f(0.1)

"""
Extracts the offset from a primitive.
"""
primitive_offset(x, scale::Nothing) = Vec2f(0) # default offset
primitive_offset(x, scale) = const_lift(/, scale, -2f0) # default offset


"""
Extracts the uv offset and width from a primitive.
"""
primitive_uv_offset_width(c::Char) = glyph_uv_width!(c)
primitive_uv_offset_width(str::AbstractString) = map(glyph_uv_width!, collect(str))
primitive_uv_offset_width(x) = Vec4f(0,0,1,1)

"""
Gets the texture atlas if primitive is a char.
"""
primitive_distancefield(x) = nothing
primitive_distancefield(::Union{AbstractString, Char}) = get_texture!(get_texture_atlas())
primitive_distancefield(x::Observable) = primitive_distancefield(x[])

function char_scale_factor(char, font)
# uv * size(ta.data) / Makie.PIXELSIZE_IN_ATLAS[] is the padded glyph size
# normalized to the size the glyph was generated as.
ta = Makie.get_texture_atlas()
lbrt = glyph_uv_width!(ta, char, font)
width = Vec(lbrt[3] - lbrt[1], lbrt[4] - lbrt[2])
return width .* Vec2f(size(ta.data)) ./ Makie.PIXELSIZE_IN_ATLAS[]
end

# This works the same for x being widths and offsets
rescale_glyph(char::Char, font, x) = x * char_scale_factor(char, font)
function rescale_glyph(char::Char, font, xs::Vector)
f = char_scale_factor(char, font)
map(x -> f * x, xs)
end
function rescale_glyph(str::String, font, x)
[x * char_scale_factor(char, font) for char in collect(str)]
end
function rescale_glyph(str::String, font, xs::Vector)
map((char, x) -> x * char_scale_factor(char, font), collect(str), xs)
end

@nospecialize
"""
Expand Down Expand Up @@ -215,6 +158,12 @@ function draw_scatter(
return draw_scatter(shader_cache, (RECTANGLE, p[2]), data)
end

function texture_distancefield(shape)
df = Makie.primitive_distancefield(to_value(shape))
isnothing(df) && return nothing
return get_texture!(df)
end

"""
Main assemble functions for scatter particles.
Sprites are anything like distance fields, images and simple geometries
Expand All @@ -223,31 +172,18 @@ function draw_scatter(shader_cache, (marker, position), data)
rot = get!(data, :rotation, Vec4f(0, 0, 0, 1))
rot = vec2quaternion(rot)
delete!(data, :rotation)
# Rescale to include glyph padding and shape
if isa(to_value(marker), Union{AbstractString, Char})
scale = data[:scale]
font = get(data, :font, Observable(Makie.defaultfont()))
quad_offset = get(data, :quad_offset, Observable(Vec2f(0)))

# The same scaling that needs to be applied to scale also needs to apply
# to offset.
data[:quad_offset] = map(rescale_glyph, marker, font, quad_offset)
data[:scale] = map(rescale_glyph, marker, font, scale)
end

@gen_defaults! data begin
shape = const_lift(x-> Int32(primitive_shape(x)), marker)
shape = Makie.marker_to_sdf_shape(marker)
position = position => GLBuffer
marker_offset = Vec3f(0) => GLBuffer;

scale = const_lift(primitive_scale, marker) => GLBuffer

scale = Vec2f(0) => GLBuffer
rotation = rot => GLBuffer
image = nothing => Texture
end

@gen_defaults! data begin
quad_offset = primitive_offset(marker, scale) => GLBuffer
quad_offset = Vec2f(0) => GLBuffer
intensity = nothing => GLBuffer
color_map = nothing => Texture
color_norm = nothing
Expand All @@ -257,9 +193,9 @@ function draw_scatter(shader_cache, (marker, position), data)
stroke_color = RGBA{Float32}(0,0,0,0) => GLBuffer
stroke_width = 0f0
glow_width = 0f0
uv_offset_width = const_lift(primitive_uv_offset_width, marker) => GLBuffer
uv_offset_width = Makie.primitive_uv_offset_width(marker) => GLBuffer

distancefield = primitive_distancefield(marker) => Texture
distancefield = texture_distancefield(shape) => 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"
Expand Down
3 changes: 1 addition & 2 deletions GLMakie/src/glshaders/visualize_interface.jl
@@ -1,4 +1,3 @@
@enum Shape CIRCLE RECTANGLE ROUNDED_RECTANGLE DISTANCEFIELD TRIANGLE
@enum CubeSides TOP BOTTOM FRONT BACK RIGHT LEFT

struct Grid{N,T <: AbstractRange}
Expand Down Expand Up @@ -70,7 +69,7 @@ struct GLVisualizeShader <: AbstractLazyShader
paths::Tuple
kw_args::Dict{Symbol,Any}
function GLVisualizeShader(
shader_cache::GLAbstraction.ShaderCache, paths::String...;
shader_cache::GLAbstraction.ShaderCache, paths::String...;
view = Dict{String,String}(), kw_args...
)
# TODO properly check what extensions are available
Expand Down

0 comments on commit c335989

Please sign in to comment.