Skip to content

Commit 432b8ab

Browse files
Add projection space option as a generic attribute (#1596)
* use markerspace::Symbol for text and scatter * adjust camera matrices based on space * only include data space plots in limits * add is_data_space etc * update text * cleanup markerspace * markerspace for scatter * fix interactivity * add default marker/-space * add efault space * get WGLMakie working * fix heatmap * fix tests * fix typo * get CairoMakie working * fix circle marker transform * add 2D space+markerspace tests * fix mesh3D space * add 3D space test * fix poly and band * change space to markerspace * fix rotations * fix 1.3 test error? * fix 1.3 * fix 1.3 error * add space attribute * cleanup comments * add news entry * add documentation for space * add tests for space * fix typo * move preprojection into shaders * minor cleanup * remove deleted import * fix test * clarify comment * Small clean ups (#1717) * remove overly eager optimizations (#1713) * small clean ups * clean up implementation a bit, use only one name for space * address review * fix usage of space_to_clip * fix cairomakie * fix fastpixel and add tests Co-authored-by: Simon <sdanisch@protonmail.com>
1 parent 7c148cd commit 432b8ab

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+630
-425
lines changed

CairoMakie/src/CairoMakie.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ using Makie: convert_attribute, @extractvalue, LineSegments, to_ndim, NativeFont
1111
using Makie: @info, @get_attribute, Combined
1212
using Makie: to_value, to_colormap, extrema_nan
1313
using Makie: inline!
14+
using Makie: spaces, is_data_space, is_pixel_space, is_relative_space, is_clip_space
1415

1516
const OneOrVec{T} = Union{
1617
T,

CairoMakie/src/overrides.jl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ function draw_poly(scene::Scene, screen::CairoScreen, poly, points::Vector{<:Poi
3636
end
3737

3838
function draw_poly(scene::Scene, screen::CairoScreen, poly, points::Vector{<:Point2}, color, model, strokecolor, strokewidth)
39-
points = project_position.(Ref(scene), points, Ref(model))
39+
space = to_value(get(poly, :space, :data))
40+
points = project_position.(Ref(scene), space, points, Ref(model))
4041
Cairo.move_to(screen.context, points[1]...)
4142
for p in points[2:end]
4243
Cairo.line_to(screen.context, p...)
@@ -62,7 +63,8 @@ draw_poly(scene::Scene, screen::CairoScreen, poly, rect::Rect2) = draw_poly(scen
6263

6364
function draw_poly(scene::Scene, screen::CairoScreen, poly, rects::Vector{<:Rect2})
6465
model = poly.model[]
65-
projected_rects = project_rect.(Ref(scene), rects, Ref(model))
66+
space = to_value(get(poly, :space, :data))
67+
projected_rects = project_rect.(Ref(scene), space, rects, Ref(model))
6668

6769
color = poly.color[]
6870
if color isa AbstractArray{<:Number}
@@ -110,7 +112,8 @@ end
110112
function draw_poly(scene::Scene, screen::CairoScreen, poly, polygons::AbstractArray{<:Polygon})
111113

112114
model = poly.model[]
113-
projected_polys = project_polygon.(Ref(scene), polygons, Ref(model))
115+
space = to_value(get(poly, :space, :data))
116+
projected_polys = project_polygon.(Ref(scene), space, polygons, Ref(model))
114117

115118
color = poly.color[]
116119
if color isa AbstractArray{<:Number}
@@ -153,7 +156,8 @@ function draw_plot(scene::Scene, screen::CairoScreen,
153156
lowerpoints = band[2][]
154157
points = vcat(lowerpoints, reverse(upperpoints))
155158
model = band.model[]
156-
points = project_position.(Ref(scene), points, Ref(model))
159+
space = to_value(get(band, :space, :data))
160+
points = project_position.(Ref(scene), space, points, Ref(model))
157161
Cairo.move_to(screen.context, points[1]...)
158162
for p in points[2:end]
159163
Cairo.line_to(screen.context, p...)

CairoMakie/src/primitives.jl

Lines changed: 74 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ function draw_atomic(scene::Scene, screen::CairoScreen, primitive::Union{Lines,
2727
end
2828
end
2929

30-
projected_positions = project_position.(Ref(scene), positions, Ref(model))
30+
space = to_value(get(primitive, :space, :data))
31+
projected_positions = project_position.(Ref(scene), Ref(space), positions, Ref(model))
3132

3233
if color isa AbstractArray{<: Number}
3334
color = numbers_to_colors(color, primitive)
@@ -194,22 +195,12 @@ function draw_atomic(scene::Scene, screen::CairoScreen, primitive::Scatter)
194195
strokewidth, marker, marker_offset, remove_billboard(rotations)) do point, col,
195196
markersize, strokecolor, strokewidth, marker, mo, rotation
196197

197-
# if we give size in pixels, the size is always equal to that value
198-
is_pixelspace = haskey(primitive, :markerspace) && primitive.markerspace[] == Makie.Pixel
199-
scale = if is_pixelspace
200-
Makie.to_2d_scale(markersize)
201-
else
202-
# otherwise calculate a scaled size
203-
project_scale(scene, markersize, size_model)
204-
end
205-
offset = if is_pixelspace
206-
Makie.to_2d_scale(mo)
207-
else
208-
project_scale(scene, mo, size_model)
209-
end
210-
211-
pos = project_position(scene, point, model)
198+
markerspace = to_value(get(primitive, :markerspace, :pixel))
199+
scale = project_scale(scene, markerspace, markersize, size_model)
200+
offset = project_scale(scene, markerspace, mo, size_model)
212201

202+
space = to_value(get(primitive, :space, :data))
203+
pos = project_position(scene, space, point, model)
213204
isnan(pos) && return
214205

215206
Cairo.set_source_rgba(ctx, rgbatuple(col)...)
@@ -276,17 +267,27 @@ function draw_marker(ctx, marker::Char, font, pos, scale, strokecolor, strokewid
276267
end
277268

278269
function draw_marker(ctx, marker::Circle, pos, scale, strokecolor, strokewidth, marker_offset, rotation)
279-
280270
marker_offset = marker_offset + scale ./ 2
281271
pos += Point2f(marker_offset[1], -marker_offset[2])
282-
Cairo.arc(ctx, pos[1], pos[2], scale[1]/2, 0, 2*pi)
272+
273+
if scale[1] != scale[2]
274+
old_matrix = Cairo.get_matrix(ctx)
275+
Cairo.scale(ctx, scale[1], scale[2])
276+
Cairo.translate(ctx, pos[1]/scale[1], pos[2]/scale[2])
277+
Cairo.arc(ctx, 0, 0, 0.5, 0, 2*pi)
278+
else
279+
Cairo.arc(ctx, pos[1], pos[2], scale[1]/2, 0, 2*pi)
280+
end
281+
283282
Cairo.fill_preserve(ctx)
284283

285284
Cairo.set_line_width(ctx, Float64(strokewidth))
286285

287286
sc = to_color(strokecolor)
288287
Cairo.set_source_rgba(ctx, rgbatuple(sc)...)
289288
Cairo.stroke(ctx)
289+
scale[1] != scale[2] && Cairo.set_matrix(ctx, old_matrix)
290+
nothing
290291
end
291292

292293
function draw_marker(ctx, marker::Rect, pos, scale, strokecolor, strokewidth, marker_offset, rotation)
@@ -316,31 +317,37 @@ end
316317

317318
function draw_atomic(scene::Scene, screen::CairoScreen, primitive::Text{<:Tuple{<:G}}) where G <: Union{AbstractArray{<:Makie.GlyphCollection}, Makie.GlyphCollection}
318319
ctx = screen.context
319-
@get_attribute(primitive, (rotation, model, space, offset))
320+
@get_attribute(primitive, (rotation, model, space, markerspace, offset))
320321
position = primitive.position[]
321322
# use cached glyph info
322323
glyph_collection = to_value(primitive[1])
323324

324-
draw_glyph_collection(scene, ctx, position, glyph_collection, remove_billboard(rotation), model, space, offset)
325+
draw_glyph_collection(
326+
scene, ctx, position, glyph_collection, remove_billboard(rotation),
327+
model, space, markerspace, offset
328+
)
325329

326330
nothing
327331
end
328332

329333

330-
function draw_glyph_collection(scene, ctx, positions, glyph_collections::AbstractArray, rotation, model::SMatrix, space, offset)
334+
function draw_glyph_collection(
335+
scene, ctx, positions, glyph_collections::AbstractArray, rotation,
336+
model::SMatrix, space, markerspace, offset
337+
)
331338

332339
# TODO: why is the Ref around model necessary? doesn't broadcast_foreach handle staticarrays matrices?
333-
broadcast_foreach(positions, glyph_collections, rotation,
334-
Ref(model), space, offset) do pos, glayout, ro, mo, sp, off
340+
broadcast_foreach(positions, glyph_collections, rotation, Ref(model), space,
341+
markerspace, offset) do pos, glayout, ro, mo, sp, msp, off
335342

336-
draw_glyph_collection(scene, ctx, pos, glayout, ro, mo, sp, off)
343+
draw_glyph_collection(scene, ctx, pos, glayout, ro, mo, sp, msp, off)
337344
end
338345
end
339346

340347
_deref(x) = x
341348
_deref(x::Ref) = x[]
342349

343-
function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation, model, space, offsets)
350+
function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation, model, space, markerspace, offsets)
344351

345352
glyphs = glyph_collection.glyphs
346353
glyphoffsets = glyph_collection.origins
@@ -351,6 +358,8 @@ function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation,
351358
strokewidths = glyph_collection.strokewidths
352359
strokecolors = glyph_collection.strokecolors
353360

361+
s2ms = Makie.clip_to_space(scene.camera, markerspace) * Makie.space_to_clip(scene.camera, space)
362+
354363
Cairo.save(ctx)
355364

356365
broadcast_foreach(glyphs, glyphoffsets, fonts, rotations, scales, colors, strokewidths, strokecolors, offsets) do glyph,
@@ -366,83 +375,43 @@ function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation,
366375
Cairo.save(ctx)
367376
Cairo.set_source_rgba(ctx, rgbatuple(color)...)
368377

369-
if space == :data
370-
# in data space, the glyph offsets are just added to the string positions
371-
# and then projected
372-
373-
# glyph position in data coordinates (offset has rotation applied already)
374-
gpos_data = to_ndim(Point3f, position, 0) .+ glyphoffset .+ p3_offset
375-
376-
scale3 = scale isa Number ? Point3f(scale, scale, 0) : to_ndim(Point3f, scale, 0)
377-
378-
# this could be done better but it works at least
379-
380-
# the CairoMatrix is found by transforming the right and up vector
381-
# of the character into screen space and then subtracting the projected
382-
# origin. The resulting vectors give the directions in which the character
383-
# needs to be stretched in order to match the 3D projection
384-
385-
xvec = rotation * (scale3[1] * Point3f(1, 0, 0))
386-
yvec = rotation * (scale3[2] * Point3f(0, -1, 0))
387-
388-
glyphpos = project_position(scene, gpos_data, _deref(model))
389-
xproj = project_position(scene, gpos_data + xvec, _deref(model))
390-
yproj = project_position(scene, gpos_data + yvec, _deref(model))
391-
392-
xdiff = xproj - glyphpos
393-
ydiff = yproj - glyphpos
394-
395-
mat = Cairo.CairoMatrix(
396-
xdiff[1], xdiff[2],
397-
ydiff[1], ydiff[2],
398-
0, 0,
399-
)
400-
401-
elseif space == :screen
402-
# in screen space, the glyph offsets are added after projecting
403-
# the string position into screen space
404-
glyphpos = let
405-
# project without yflip - we need to apply model before that
406-
p = project_position(scene, position, Mat4f(I), false)
407-
408-
# flip for Cairo
409-
p += (p3_to_p2(glyphoffset .+ p3_offset))
410-
p = (_deref(model) * Vec4f(p[1], p[2], 0, 1))[Vec(1, 2)]
411-
p = (0, 1) .* scene.camera.resolution[] .+ p .* (1, -1)
412-
p
413-
end
414-
# and the scale is just taken as is
415-
scale = length(scale) == 2 ? scale : SVector(scale, scale)
416-
417-
mat = let
418-
scale_mat = if length(scale) == 2
419-
Mat2f(scale[1], 0, 0, scale[2])
420-
else
421-
Mat2f(scale, 0, 0, scale)
422-
end
423-
T = _deref(model)[Vec(1, 2), Vec(1, 2)] * scale_mat
424-
Cairo.CairoMatrix(T[1, 1], T[1, 2], T[2, 1], T[2, 2], 0, 0)
425-
end
426-
else
427-
error()
428-
end
378+
# offsets and scale apply in markerspace
379+
glyph_pos = s2ms * to_ndim(Point4f, to_ndim(Point3f, position, 0), 1)
380+
gp3 = glyph_pos[SOneTo(3)] ./ glyph_pos[4] .+ glyphoffset .+ p3_offset
381+
382+
scale3 = scale isa Number ? Point3f(scale, scale, 0) : to_ndim(Point3f, scale, 0)
383+
384+
# the CairoMatrix is found by transforming the right and up vector
385+
# of the character into screen space and then subtracting the projected
386+
# origin. The resulting vectors give the directions in which the character
387+
# needs to be stretched in order to match the 3D projection
388+
389+
xvec = rotation * (scale3[1] * Point3f(1, 0, 0))
390+
yvec = rotation * (scale3[2] * Point3f(0, -1, 0))
391+
392+
glyphpos = project_position(scene, markerspace, gp3, _deref(model))
393+
xproj = project_position(scene, markerspace, gp3 + xvec, _deref(model))
394+
yproj = project_position(scene, markerspace, gp3 + yvec, _deref(model))
395+
396+
xdiff = xproj - glyphpos
397+
ydiff = yproj - glyphpos
398+
399+
mat = Cairo.CairoMatrix(
400+
xdiff[1], xdiff[2],
401+
ydiff[1], ydiff[2],
402+
0, 0,
403+
)
429404

430405
Cairo.save(ctx)
431406
Cairo.move_to(ctx, glyphpos...)
432407
set_font_matrix(ctx, mat)
433-
if space == :screen
434-
Cairo.rotate(ctx, to_2d_rotation(rotation))
435-
end
436408
Cairo.show_text(ctx, string(glyph))
437409
Cairo.restore(ctx)
438410

439411
if strokewidth > 0 && strokecolor != RGBAf(0, 0, 0, 0)
440412
Cairo.save(ctx)
441413
Cairo.move_to(ctx, glyphpos...)
442414
set_font_matrix(ctx, mat)
443-
if space == :screen
444-
Cairo.rotate(ctx, to_2d_rotation(rotation))
445-
end
446415
Cairo.text_path(ctx, string(glyph))
447416
Cairo.set_source_rgba(ctx, rgbatuple(strokecolor)...)
448417
Cairo.set_line_width(ctx, strokewidth)
@@ -548,8 +517,9 @@ function draw_atomic(scene::Scene, screen::CairoScreen, primitive::Union{Heatmap
548517

549518
# find projected image corners
550519
# this already takes care of flipping the image to correct cairo orientation
551-
xy = project_position(scene, Point2f(first.(imsize)), model)
552-
xymax = project_position(scene, Point2f(last.(imsize)), model)
520+
space = to_value(get(primitive, :space, :data))
521+
xy = project_position(scene, space, Point2f(first.(imsize)), model)
522+
xymax = project_position(scene, space, Point2f(last.(imsize)), model)
553523
w, h = xymax .- xy
554524

555525
s = to_cairo_image(image, primitive)
@@ -580,7 +550,8 @@ function draw_atomic(scene::Scene, screen::CairoScreen, primitive::Union{Heatmap
580550
end
581551
# find projected image corners
582552
# this already takes care of flipping the image to correct cairo orientation
583-
xys = [project_position(scene, Point2f(x, y), model) for x in xs, y in ys]
553+
space = to_value(get(primitive, :space, :data))
554+
xys = [project_position(scene, space, Point2f(x, y), model) for x in xs, y in ys]
584555
colors = to_rgba_image(image, primitive)
585556

586557
# Note: xs and ys should have size ni+1, nj+1
@@ -597,11 +568,11 @@ function draw_atomic(scene::Scene, screen::CairoScreen, primitive::Union{Heatmap
597568

598569
# Rectangles and polygons that are directly adjacent usually show
599570
# white lines between them due to anti aliasing. To avoid this we
600-
# increase their size slightly.
571+
# increase their size slightly.
601572

602573
if alpha(colors[i, j]) == 1
603-
# sign.(p - center) gives the direction in which we need to
604-
# extend the polygon. (Which may change due to rotations in the
574+
# sign.(p - center) gives the direction in which we need to
575+
# extend the polygon. (Which may change due to rotations in the
605576
# model matrix.) (i!=1) etc is used to avoid increasing the
606577
# outer extent of the heatmap.
607578
center = 0.25 * (p1 + p2 + p3 + p4)
@@ -657,8 +628,9 @@ function draw_mesh2D(scene, screen, primitive)
657628
pattern = Cairo.CairoPatternMesh()
658629

659630
cols = per_face_colors(color, colormap, colorrange, nothing, vs, fs, nothing, uv)
631+
space = to_value(get(primitive, :space, :data))
660632
for (f, (c1, c2, c3)) in zip(fs, cols)
661-
t1, t2, t3 = project_position.(scene, vs[f], (model,)) #triangle points
633+
t1, t2, t3 = project_position.(scene, space, vs[f], (model,)) #triangle points
662634
Cairo.mesh_pattern_begin_patch(pattern)
663635

664636
Cairo.mesh_pattern_move_to(pattern, t1...)
@@ -700,9 +672,10 @@ function draw_mesh3D(
700672
ctx = screen.context
701673

702674
model = primitive.model[]
703-
view = scene.camera.view[]
704-
projection = scene.camera.projection[]
705-
i = SOneTo(3)
675+
space = to_value(get(primitive, :space, :data))
676+
view = ifelse(is_data_space(space), scene.camera.view[], Mat4f(I))
677+
projection = Makie.space_to_clip(scene.camera, space, false)
678+
i = Vec(1, 2, 3)
706679
normalmatrix = transpose(inv(view[i, i] * model[i, i]))
707680

708681
# Mesh data

0 commit comments

Comments
 (0)