From ec84a99310d6178fdb3a7d92a5120948fd0308d9 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 22 Jun 2023 16:00:38 +0200 Subject: [PATCH 01/24] set up new spaces --- CairoMakie/src/primitives.jl | 9 +-- CairoMakie/src/utils.jl | 5 +- GLMakie/src/drawing_primitives.jl | 24 +++++--- WGLMakie/src/Camera.js | 1 + src/basic_recipes/error_and_rangebars.jl | 8 +-- src/camera/projection_math.jl | 77 ++++++++++++++++-------- src/units.jl | 8 ++- test/transformations.jl | 3 +- 8 files changed, 89 insertions(+), 46 deletions(-) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index 212b6ba1f62..5e169ed45c8 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -550,12 +550,12 @@ function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation, model33 = model[Vec(1, 2, 3), Vec(1, 2, 3)] id = Mat4f(I) + # TODO project method for this glyph_pos = let transform_func = transformation.transform_func[] p = Makie.apply_transform(transform_func, position, space) - Makie.clip_to_space(scene.camera, markerspace) * - Makie.space_to_clip(scene.camera, space) * + Makie.space_to_space_matrix(scene, space => markerspace) * model * to_ndim(Point4f, to_ndim(Point3f, p, 0), 1) end @@ -895,8 +895,9 @@ function draw_mesh3D( specular, shininess, faceculling ) ctx = screen.context - view = ifelse(is_data_space(space), scene.camera.view[], Mat4f(I)) - projection = Makie.space_to_clip(scene.camera, space, false) + @assert space in (:data, :transformed, :world) + view = scene.camera.view[] + projection = scene.camera.projection[] i = Vec(1, 2, 3) normalmatrix = transpose(inv(view[i, i] * model[i, i])) diff --git a/CairoMakie/src/utils.jl b/CairoMakie/src/utils.jl index 32dd81c49ac..1560b83a52d 100644 --- a/CairoMakie/src/utils.jl +++ b/CairoMakie/src/utils.jl @@ -8,10 +8,11 @@ function project_position(scene::Scene, transform_func::T, space, point, model:: _project_position(scene, space, point, model, yflip) end -function _project_position(scene::Scene, space, point, model, yflip::Bool) +# TODO Makie.project instead +function _project_position(scene, space, point, model, yflip) res = scene.camera.resolution[] p4d = to_ndim(Vec4f, to_ndim(Vec3f, point, 0f0), 1f0) - clip = Makie.space_to_clip(scene.camera, space) * model * p4d + clip = Makie.space_to_space_matrix(scene, space => :clip) * model * p4d @inbounds begin # between -1 and 1 p = (clip ./ clip[4])[Vec(1, 2)] diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 3dd5476484a..316ce199c64 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -41,11 +41,7 @@ function connect_camera!(plot, gl_attributes, cam, space = gl_attributes[:space] # Overwrite these, user defined attributes shouldn't use those! gl_attributes[key] = lift(identity, plot, getfield(cam, key)) end - get!(gl_attributes, :view) do - return lift(plot, cam.view, space) do view, space - return is_data_space(space) ? view : Mat4f(I) - end - end + get!(gl_attributes, :normalmatrix) do return lift(plot, gl_attributes[:view], gl_attributes[:model]) do v, m i = Vec(1, 2, 3) @@ -53,15 +49,25 @@ function connect_camera!(plot, gl_attributes, cam, space = gl_attributes[:space] end end + # TODO we can probably get rid of these? + get!(gl_attributes, :view) do + return lift(plot, cam.view, space) do view, space + return space in (:data, :transformed, :world) ? view : Mat4f(I) + end + end get!(gl_attributes, :projection) do return lift(cam.projection, cam.pixel_space, space) do _, _, space - return Makie.space_to_clip(cam, space, false) + if space in (:data, :transformed, :world) + return Makie.space_to_space_matrix(cam, :eye => :clip) + else + return MAkie.space_to_space_matrix(cam, space => :clip) + end end end get!(gl_attributes, :projectionview) do return lift(plot, cam.projectionview, cam.pixel_space, space) do _, _, space - Makie.space_to_clip(cam, space, true) + Makie.space_to_space_matrix(cam, space => :clip) end end @@ -223,7 +229,7 @@ function draw_atomic(screen::Screen, scene::Scene, @nospecialize(x::Union{Scatte mspace = x.markerspace cam = scene.camera gl_attributes[:preprojection] = map(space, mspace, cam.projectionview, cam.resolution) do space, mspace, _, _ - return Makie.clip_to_space(cam, mspace) * Makie.space_to_clip(cam, space) + return Makie.space_to_space_matrix(cam, space => mspace) end # fast pixel does its own setup if !(marker[] isa FastPixel) @@ -399,7 +405,7 @@ function draw_atomic(screen::Screen, scene::Scene, cam = scene.camera # gl_attributes[:preprojection] = Observable(Mat4f(I)) gl_attributes[:preprojection] = map(space, markerspace, cam.projectionview, cam.resolution) do s, ms, pv, res - Makie.clip_to_space(cam, ms) * Makie.space_to_clip(cam, s) + Makie.space_to_space_matrix(cam, s => ms) end return draw_scatter(screen, (DISTANCEFIELD, positions), gl_attributes) diff --git a/WGLMakie/src/Camera.js b/WGLMakie/src/Camera.js index 25223aca629..8e3d329b7bf 100644 --- a/WGLMakie/src/Camera.js +++ b/WGLMakie/src/Camera.js @@ -274,6 +274,7 @@ export class MakieCamera { return; } + // TODO clip_to_space(space) { if (space === "data") { return this.projectionview_inverse.value; diff --git a/src/basic_recipes/error_and_rangebars.jl b/src/basic_recipes/error_and_rangebars.jl index 706db1dd40d..056631f3634 100644 --- a/src/basic_recipes/error_and_rangebars.jl +++ b/src/basic_recipes/error_and_rangebars.jl @@ -246,7 +246,7 @@ end function plot_to_screen(plot, points::AbstractVector) cam = parent_scene(plot).camera space = to_value(get(plot, :space, :data)) - spvm = clip_to_space(cam, :pixel) * space_to_clip(cam, space) * transformationmatrix(plot)[] + spvm = space_to_space_matrix(cam, space => :pixel) * transformationmatrix(plot)[] return map(points) do p transformed = apply_transform(transform_func(plot), p, space) @@ -258,7 +258,7 @@ end function plot_to_screen(plot, p::VecTypes) cam = parent_scene(plot).camera space = to_value(get(plot, :space, :data)) - spvm = clip_to_space(cam, :pixel) * space_to_clip(cam, space) * transformationmatrix(plot)[] + spvm = space_to_space_matrix(cam, space => :pixel) * transformationmatrix(plot)[] transformed = apply_transform(transform_func(plot), p, space) p4d = spvm * to_ndim(Point4f, to_ndim(Point3f, transformed, 0), 1) return Point2f(p4d) / p4d[4] @@ -267,7 +267,7 @@ end function screen_to_plot(plot, points::AbstractVector) cam = parent_scene(plot).camera space = to_value(get(plot, :space, :data)) - mvps = inv(transformationmatrix(plot)[]) * clip_to_space(cam, space) * space_to_clip(cam, :pixel) + mvps = inv(transformationmatrix(plot)[]) * space_to_space_matrix(cam, :pixel => space) itf = inverse_transform(transform_func(plot)) return map(points) do p @@ -280,7 +280,7 @@ end function screen_to_plot(plot, p::VecTypes) cam = parent_scene(plot).camera space = to_value(get(plot, :space, :data)) - mvps = inv(transformationmatrix(plot)[]) * clip_to_space(cam, space) * space_to_clip(cam, :pixel) + mvps = inv(transformationmatrix(plot)[]) * space_to_space_matrix(cam, :pixel => space) pre_transform = mvps * to_ndim(Vec4f, to_ndim(Vec3f, p, 0.0), 1.0) p3 = Point3f(pre_transform) / pre_transform[4] return apply_transform(itf, p3, space) diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index 717224c6a6a..eda80c45867 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -314,41 +314,70 @@ function transform(model::Mat4, x::T) where T to_ndim(T, model * x4d, 0.0) end -# project between different coordinate systems/spaces -function space_to_clip(cam::Camera, space::Symbol, projectionview::Bool=true) - if is_data_space(space) - return projectionview ? cam.projectionview[] : cam.projection[] - elseif is_pixel_space(space) +################################################################################ +### new projection code +################################################################################ + + +""" + space_to_space_matrix(cam_or_scenelike, spaces::Pair) + space_to_space_matrix(cam_or_scenelike, input_space::Symbol, output_space::Symbol) + +Returns a matrix which transforms positional data from a given input space to a +given output space. Note that this does not include `plot.transformations`, i.e. +the model matrix and transform function. +""" +function space_to_space_matrix(obj, space2space::Pair{Symbol, Symbol}) + return space_to_space_matrix(camera(obj), space2space...) +end + +function space_to_space_matrix(obj, input_space::Symbol, output_space::Symbol) + return space_to_space_matrix(camera(obj), Pair(input_space, output_space)) +end + +function space_to_space_matrix(cam::Camera, s2s::Pair{Symbol, Symbol}) + # identities + if s2s[1] === s2s[2] + return Mat4f(I) + elseif s2s[1] in (:data, :transformed) && s2s[2] === :world + return Mat4f(I) + + # direct conversions (no calculations) + elseif s2s === Pair(:world, :eye) + return cam.view[] + elseif s2s === Pair(:eye, :clip) + return cam.projection[] + elseif s2s[1] in (:data, :transformed, :world) && s2s[2] === :clip + return cam.projectionview[] + elseif s2s === Pair(:pixel, :clip) return cam.pixel_space[] - elseif is_relative_space(space) + elseif s2s === Pair(:relative, :clip) return Mat4f(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, -1, -1, 0, 1) - elseif is_clip_space(space) - return Mat4f(I) - else - error("Space $space not recognized. Must be one of $(spaces())") - end -end -function clip_to_space(cam::Camera, space::Symbol) - if is_data_space(space) - return inv(cam.projectionview[]) - elseif is_pixel_space(space) - w, h = cam.resolution[] - return Mat4f(0.5w, 0, 0, 0, 0, 0.5h, 0, 0, 0, 0, -10_000, 0, 0.5w, 0.5h, 0, 1) # -10_000 - elseif is_relative_space(space) + # simple inversions + elseif s2s === Pair(:clip, :relative) return Mat4f(0.5, 0, 0, 0, 0, 0.5, 0, 0, 0, 0, 1, 0, 0.5, 0.5, 0, 1) - elseif is_clip_space(space) - return Mat4f(I) + elseif s2s === Pair(:clip, :pixel) + w, h = cam.resolution[] + return Mat4f(0.5w, 0, 0, 0, 0, 0.5h, 0, 0, 0, 0, -10_000, 0, 0.5w, 0.5h, 0, 1) + + # calculation neccessary + elseif s2s[1] === :clip + return inv(space_to_space_matrix(cam, Pair(s2s[2], s2s[1]))) + elseif s2s[1] in spaces() && s2s[2] in space() + return space_to_space_matrix(cam, Pair(:clip, s2s[2])) * + space_to_space_matrix(cam, Pair(s2s[1], :clip)) else error("Space $space not recognized. Must be one of $(spaces())") end end +@deprecate space_to_clip(cam, space, pv) space_to_space_matrix(cam, space, :clip) false +@deprecate clip_to_space(cam, space) space_to_space_matrix(cam, :clip, space) false + function project(cam::Camera, input_space::Symbol, output_space::Symbol, pos) input_space === output_space && return to_ndim(Point3f, pos, 0) - clip_from_input = space_to_clip(cam, input_space) - output_from_clip = clip_to_space(cam, output_space) p4d = to_ndim(Point4f, to_ndim(Point3f, pos, 0), 1) - transformed = output_from_clip * clip_from_input * p4d + transformed = space_to_space_matrix(cam, input_space, output_space) * p4d return Point3f(transformed[Vec(1, 2, 3)] ./ transformed[4]) end diff --git a/src/units.jl b/src/units.jl index d93d71f8331..398d935682c 100644 --- a/src/units.jl +++ b/src/units.jl @@ -127,8 +127,12 @@ export px ######################################## -spaces() = (:data, :pixel, :relative, :clip) +spaces() = (:data, :transformed, :world, :eye, :clip, :pixel, :relative) + is_data_space(space) = space === :data +is_transformed_space(space) = space === :transformed +is_world_space(space) = space === :world +is_eye_space(space) = space === :eye +is_clip_space(space) = space === :clip is_pixel_space(space) = space === :pixel is_relative_space(space) = space === :relative -is_clip_space(space) = space === :clip diff --git a/test/transformations.jl b/test/transformations.jl index ac66cf247af..fc4399fd89b 100644 --- a/test/transformations.jl +++ b/test/transformations.jl @@ -121,7 +121,8 @@ end scene = Scene(cam = cam3d!) scatter!(scene, [Point3f(-10), Point3f(10)]) for space in vcat(spaces...) - @test Makie.clip_to_space(scene.camera, space) * Makie.space_to_clip(scene.camera, space) ≈ Mat4f(I) + @test Makie.space_to_space_matrix(scene, :clip => space) * + Makie.space_to_space_matrix(scene.camera, space => :clip) ≈ Mat4f(I) end end From 33bf59548d57517954fbf7aec1a6749e6e8593de Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 22 Jun 2023 17:03:43 +0200 Subject: [PATCH 02/24] fix order related error --- GLMakie/src/drawing_primitives.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 316ce199c64..8b37059bd4f 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -42,13 +42,6 @@ function connect_camera!(plot, gl_attributes, cam, space = gl_attributes[:space] gl_attributes[key] = lift(identity, plot, getfield(cam, key)) end - get!(gl_attributes, :normalmatrix) do - return lift(plot, gl_attributes[:view], gl_attributes[:model]) do v, m - i = Vec(1, 2, 3) - return transpose(inv(v[i, i] * m[i, i])) - end - end - # TODO we can probably get rid of these? get!(gl_attributes, :view) do return lift(plot, cam.view, space) do view, space @@ -60,11 +53,18 @@ function connect_camera!(plot, gl_attributes, cam, space = gl_attributes[:space] if space in (:data, :transformed, :world) return Makie.space_to_space_matrix(cam, :eye => :clip) else - return MAkie.space_to_space_matrix(cam, space => :clip) + return Makie.space_to_space_matrix(cam, space => :clip) end end end + get!(gl_attributes, :normalmatrix) do + return lift(plot, gl_attributes[:view], gl_attributes[:model]) do v, m + i = Vec(1, 2, 3) + return transpose(inv(v[i, i] * m[i, i])) + end + end + get!(gl_attributes, :projectionview) do return lift(plot, cam.projectionview, cam.pixel_space, space) do _, _, space Makie.space_to_space_matrix(cam, space => :clip) From 31267118ab001cebfb121680a15d8237108fd55d Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 30 Jun 2023 18:56:15 +0200 Subject: [PATCH 03/24] fix typo --- src/camera/projection_math.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index eda80c45867..07398104e7c 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -364,7 +364,7 @@ function space_to_space_matrix(cam::Camera, s2s::Pair{Symbol, Symbol}) # calculation neccessary elseif s2s[1] === :clip return inv(space_to_space_matrix(cam, Pair(s2s[2], s2s[1]))) - elseif s2s[1] in spaces() && s2s[2] in space() + elseif s2s[1] in spaces() && s2s[2] in spaces() return space_to_space_matrix(cam, Pair(:clip, s2s[2])) * space_to_space_matrix(cam, Pair(s2s[1], :clip)) else From b132fe3b1ab1006fe3a5cc6fe8e7d2f6b929e987 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 30 Jun 2023 20:44:23 +0200 Subject: [PATCH 04/24] implement new project stack and update Makie --- src/basic_recipes/ablines.jl | 1 + src/basic_recipes/arrows.jl | 16 +- src/basic_recipes/bracket.jl | 12 +- src/basic_recipes/contours.jl | 20 +- src/basic_recipes/error_and_rangebars.jl | 52 +-- src/basic_recipes/hvlines.jl | 1 + src/basic_recipes/hvspan.jl | 1 + src/basic_recipes/streamplot.jl | 17 +- src/basic_recipes/text.jl | 7 +- src/basic_recipes/tooltip.jl | 5 +- src/camera/projection_math.jl | 420 ++++++++++++++++++----- src/interaction/inspector.jl | 56 +-- src/interaction/interactive_api.jl | 3 +- src/layouting/boundingbox.jl | 18 +- src/layouting/data_limits.jl | 1 + src/makielayout/blocks/axis3d.jl | 10 +- src/makielayout/interactions.jl | 3 +- src/units.jl | 1 + src/utilities/utilities.jl | 7 + 19 files changed, 395 insertions(+), 256 deletions(-) diff --git a/src/basic_recipes/ablines.jl b/src/basic_recipes/ablines.jl index 3c2268b241e..6d6ae3c9f53 100644 --- a/src/basic_recipes/ablines.jl +++ b/src/basic_recipes/ablines.jl @@ -21,6 +21,7 @@ function Makie.plot!(p::ABLines) is_identity_transform(transf) || throw(ArgumentError("ABLines is only defined for the identity transform, not $(typeof(transf)).")) + # TODO - should we generalize? limits = lift(projview_to_2d_limits, p, scene.camera.projectionview) points = Observable(Point2f[]) diff --git a/src/basic_recipes/arrows.jl b/src/basic_recipes/arrows.jl index 27fa6bfd7c3..236bbe6e345 100644 --- a/src/basic_recipes/arrows.jl +++ b/src/basic_recipes/arrows.jl @@ -146,20 +146,12 @@ function plot!(arrowplot::Arrows{<: Tuple{AbstractVector{<: Point{N}}, V}}) wher # for 2D arrows, compute the correct marker rotation given the projection / scene size # for the screen-space marker if is_pixel_space(arrowplot.markerspace[]) - rotations = lift(arrowplot, scene.camera.projectionview, scene.px_area, headstart) do pv, pxa, hs + rotations = lift(arrowplot, projection_obs(arrowplot), headstart) do _, hs angles = map(hs) do (start, stop) - pstart = project(scene, start) - pstop = project(scene, stop) - diff = pstop - pstart - n = norm(diff) - if n == 0 - zero(n) - else - angle = acos(diff[2] / norm(diff)) - angle = ifelse(diff[1] > 0, 2pi - angle, angle) - end + # angle() uses (x, 0) as 0°, we need (0, y) as 0° here + projected_angle(arrowplot, start, stop) - 0.5f0 * pi end - Billboard(angles) + return Billboard(angles) end end diff --git a/src/basic_recipes/bracket.jl b/src/basic_recipes/bracket.jl index bb2426f7c77..5e2047c333f 100644 --- a/src/basic_recipes/bracket.jl +++ b/src/basic_recipes/bracket.jl @@ -43,9 +43,6 @@ end function Makie.plot!(pl::Bracket) points = pl[1] - - scene = parent_scene(pl) - textoffset_vec = Observable(Vec2f[]) bp = Observable(BezierPath[]) textpoints = Observable(Point2f[]) @@ -54,17 +51,16 @@ function Makie.plot!(pl::Bracket) return to === automatic ? Float32.(0.75 .* fs) : Float32.(to) end - onany(pl, points, scene.camera.projectionview, pl.model, transform_func(pl), - scene.px_area, pl.offset, pl.width, pl.orientation, realtextoffset, - pl.style) do points, _, _, _, _, offset, width, orientation, textoff, style + onany(pl, points, projection_obs(pl), pl.offset, pl.width, pl.orientation, realtextoffset, + pl.style) do points, _, offset, width, orientation, textoff, style empty!(bp[]) empty!(textoffset_vec[]) empty!(textpoints[]) broadcast_foreach(points, offset, width, orientation, textoff, style) do (_p1, _p2), offset, width, orientation, textoff, style - p1 = plot_to_screen(pl, _p1) - p2 = plot_to_screen(pl, _p2) + p1 = project_to_pixel(pl, _p1) + p2 = project_to_pixel(pl, _p2) v = p2 - p1 d1 = normalize(v) diff --git a/src/basic_recipes/contours.jl b/src/basic_recipes/contours.jl index ed02afc1e69..8fe1ef32f83 100644 --- a/src/basic_recipes/contours.jl +++ b/src/basic_recipes/contours.jl @@ -57,9 +57,6 @@ $(ATTRIBUTES) default_theme(scene, Contour) end -angle(p1::Union{Vec2f,Point2f}, p2::Union{Vec2f,Point2f})::Float32 = - atan(p2[2] - p1[2], p2[1] - p1[1]) # result in [-π, π] - function label_info(lev, vertices, col) mid = ceil(Int, 0.5f0 * length(vertices)) pts = (vertices[max(firstindex(vertices), mid - 1)], vertices[mid], vertices[min(mid + 1, lastindex(vertices))]) @@ -235,8 +232,6 @@ function plot!(plot::T) where T <: Union{Contour, Contour3d} end P = T <: Contour ? Point2f : Point3f - scene = parent_scene(plot) - space = plot.space[] texts = text!( plot, @@ -249,17 +244,15 @@ function plot!(plot::T) where T <: Union{Contour, Contour3d} font = labelfont, ) - lift(scene.camera.projectionview, scene.px_area, labels, labelcolor, labelformatter, - lev_pos_col) do _, _, labels, labelcolor, labelformatter, lev_pos_col + lift(projection_obs(plot), labels, labelcolor, labelformatter, lev_pos_col) do _, + labels, labelcolor, labelformatter, lev_pos_col labels || return pos = texts.positions.val; empty!(pos) rot = texts.rotation.val; empty!(rot) col = texts.color.val; empty!(col) lbl = texts.text.val; empty!(lbl) for (lev, (p1, p2, p3), color) in lev_pos_col - px_pos1 = project(scene, apply_transform(transform_func(plot), p1, space)) - px_pos3 = project(scene, apply_transform(transform_func(plot), p3, space)) - rot_from_horz::Float32 = angle(px_pos1, px_pos3) + rot_from_horz::Float32 = projected_angle(plot, p1, p3) # transition from an angle from horizontal axis in [-π; π] # to a readable text with a rotation from vertical axis in [-π / 2; π / 2] rot_from_vert::Float32 = if abs(rot_from_horz) > 0.5f0 * π @@ -280,8 +273,7 @@ function plot!(plot::T) where T <: Union{Contour, Contour3d} labels || return return broadcast(texts.plots[1][1].val, texts.positions.val, texts.rotation.val) do gc, pt, rot # drop the depth component of the bounding box for 3D - px_pos = project(scene, apply_transform(transform_func(plot), pt, space)) - Rect2f(boundingbox(gc, to_ndim(Point3f, px_pos, 0f0), to_rotation(rot))) + Rect2f(boundingbox(gc, project(plot, pt), to_rotation(rot))) end end @@ -296,14 +288,14 @@ function plot!(plot::T) where T <: Union{Contour, Contour3d} if isnan(p) && n < nlab bb = bboxes[n += 1] # next segment is materialized by a NaN, thus consider next label # wireframe!(plot, bb, space = :pixel) # toggle to debug labels - elseif project(scene, apply_transform(transform_func(plot), p, space)) in bb + elseif project_to_pixel(plot, p) in bb masked[i] = nan for dir in (-1, +1) j = i while true j += dir checkbounds(Bool, segments, j) || break - project(scene, apply_transform(transform_func(plot), segments[j], space)) in bb || break + project_to_pixel(plot, segments[j]) in bb || break masked[j] = nan end end diff --git a/src/basic_recipes/error_and_rangebars.jl b/src/basic_recipes/error_and_rangebars.jl index 056631f3634..7629059c2ba 100644 --- a/src/basic_recipes/error_and_rangebars.jl +++ b/src/basic_recipes/error_and_rangebars.jl @@ -195,13 +195,8 @@ function _plot_bars!(plot, linesegpairs, is_in_y_direction) @extract plot (whiskerwidth, color, linewidth, visible, colormap, colorscale, colorrange, inspectable, transparency) - scene = parent_scene(plot) - - whiskers = lift(plot, linesegpairs, scene.camera.projectionview, plot.model, - scene.px_area, transform_func(plot), whiskerwidth) do endpoints, _, _, _, _, whiskerwidth - - screenendpoints = plot_to_screen(plot, endpoints) - + whiskers = lift(plot, linesegpairs, projection_obs(plot), whiskerwidth) do endpoints, _, whiskerwidth + screenendpoints = project_to_pixel(plot, endpoints) screenendpoints_shifted_pairs = map(screenendpoints) do sep (sep .+ f_if(is_in_y_direction[], reverse, Point(0, -whiskerwidth/2)), sep .+ f_if(is_in_y_direction[], reverse, Point(0, whiskerwidth/2))) @@ -243,49 +238,6 @@ function _plot_bars!(plot, linesegpairs, is_in_y_direction) plot end -function plot_to_screen(plot, points::AbstractVector) - cam = parent_scene(plot).camera - space = to_value(get(plot, :space, :data)) - spvm = space_to_space_matrix(cam, space => :pixel) * transformationmatrix(plot)[] - - return map(points) do p - transformed = apply_transform(transform_func(plot), p, space) - p4d = spvm * to_ndim(Point4f, to_ndim(Point3f, transformed, 0), 1) - return Point2f(p4d) / p4d[4] - end -end - -function plot_to_screen(plot, p::VecTypes) - cam = parent_scene(plot).camera - space = to_value(get(plot, :space, :data)) - spvm = space_to_space_matrix(cam, space => :pixel) * transformationmatrix(plot)[] - transformed = apply_transform(transform_func(plot), p, space) - p4d = spvm * to_ndim(Point4f, to_ndim(Point3f, transformed, 0), 1) - return Point2f(p4d) / p4d[4] -end - -function screen_to_plot(plot, points::AbstractVector) - cam = parent_scene(plot).camera - space = to_value(get(plot, :space, :data)) - mvps = inv(transformationmatrix(plot)[]) * space_to_space_matrix(cam, :pixel => space) - itf = inverse_transform(transform_func(plot)) - - return map(points) do p - pre_transform = mvps * to_ndim(Vec4f, to_ndim(Vec3f, p, 0.0), 1.0) - p3 = Point3f(pre_transform) / pre_transform[4] - return apply_transform(itf, p3, space) - end -end - -function screen_to_plot(plot, p::VecTypes) - cam = parent_scene(plot).camera - space = to_value(get(plot, :space, :data)) - mvps = inv(transformationmatrix(plot)[]) * space_to_space_matrix(cam, :pixel => space) - pre_transform = mvps * to_ndim(Vec4f, to_ndim(Vec3f, p, 0.0), 1.0) - p3 = Point3f(pre_transform) / pre_transform[4] - return apply_transform(itf, p3, space) -end - # ignore whiskers when determining data limits function point_iterator(bars::Union{Errorbars, Rangebars}) point_iterator(bars.plots[1]) diff --git a/src/basic_recipes/hvlines.jl b/src/basic_recipes/hvlines.jl index 23be173580a..1364c119f08 100644 --- a/src/basic_recipes/hvlines.jl +++ b/src/basic_recipes/hvlines.jl @@ -49,6 +49,7 @@ function Makie.plot!(p::Union{HLines, VLines}) scene = parent_scene(p) transf = transform_func_obs(scene) + # TODO switch to projecting? limits = lift(projview_to_2d_limits, p, scene.camera.projectionview) points = Observable(Point2f[]) diff --git a/src/basic_recipes/hvspan.jl b/src/basic_recipes/hvspan.jl index 3ecd9589551..adc7489f5e2 100644 --- a/src/basic_recipes/hvspan.jl +++ b/src/basic_recipes/hvspan.jl @@ -42,6 +42,7 @@ function Makie.plot!(p::Union{HSpan, VSpan}) scene = Makie.parent_scene(p) transf = transform_func_obs(scene) + # TODO: switch to projecting? limits = lift(projview_to_2d_limits, scene.camera.projectionview) rects = Observable(Rect2f[]) diff --git a/src/basic_recipes/streamplot.jl b/src/basic_recipes/streamplot.jl index 9a999d27300..33c3192488e 100644 --- a/src/basic_recipes/streamplot.jl +++ b/src/basic_recipes/streamplot.jl @@ -190,22 +190,13 @@ function plot!(p::StreamPlot) N = ndims(p.limits[]) - if N == 2 # && scatterplot.markerspace[] == Pixel (default) + if N == 2 # && scatterplot.markerspace[] == :pixel (default) # Calculate arrow head rotations as angles. To avoid distortions from # (extreme) aspect ratios we need to project to pixel space and renormalize. - scene = parent_scene(p) - rotations = lift(p, scene.camera.projectionview, scene.px_area, data) do pv, pxa, data + rotations = lift(p, projection_obs(p), data) do _, data angles = map(data[1], data[2]) do pos, dir - pstart = project(scene, pos) - pstop = project(scene, pos + dir) - pdir = pstop - pstart - n = norm(pdir) - if n == 0 - zero(n) - else - angle = acos(pdir[2] / n) - angle = ifelse(pdir[1] > 0, 2pi - angle, angle) - end + # angle() uses (x, 0) as 0°, we need (0, y) as 0° here + projected_angle(p, pos, pos+dir) - 0.5f0 * pi end Billboard(angles) end diff --git a/src/basic_recipes/text.jl b/src/basic_recipes/text.jl index aee85ac7d5e..31599452814 100644 --- a/src/basic_recipes/text.jl +++ b/src/basic_recipes/text.jl @@ -53,11 +53,8 @@ function plot!(plot::Text) linesegs_shifted = Observable(Point2f[]) - sc = parent_scene(plot) - - onany(linesegs, positions, sc.camera.projectionview, sc.px_area, - transform_func_obs(sc), get(plot, :space, :data)) do segs, pos, _, _, transf, space - pos_transf = plot_to_screen(plot, pos) + onany(linesegs, positions, projection_obs(plot)) do segs, pos, _ + pos_transf = project_to_pixel(plot, pos) linesegs_shifted[] = map(segs, lineindices[]) do seg, index seg + attr_broadcast_getindex(pos_transf, index) end diff --git a/src/basic_recipes/tooltip.jl b/src/basic_recipes/tooltip.jl index ae0a516f04c..4883cf30d03 100644 --- a/src/basic_recipes/tooltip.jl +++ b/src/basic_recipes/tooltip.jl @@ -82,9 +82,8 @@ end function plot!(p::Tooltip{<:Tuple{<:VecTypes}}) # TODO align - scene = parent_scene(p) - px_pos = map(scene.camera.projectionview, scene.camera.resolution, p[1]) do _, _, p - project(scene, p) + px_pos = map(projection_obs(p), p[1]) do _, pos + project(p, pos) end # Text diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index 07398104e7c..16de4a6346c 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -238,81 +238,93 @@ function rotation(u::Vec{3, T}, v::Vec{3, T}) where T return Quaternion(cross(u, half)..., dot(u, half)) end -function to_world(scene::Scene, point::T) where T <: StaticVector - cam = scene.camera - x = to_world( - point, - inv(transformationmatrix(scene)[]) * - inv(cam.view[]) * - inv(cam.projection[]), - T(widths(pixelarea(scene)[])) - ) - Point2f(x[1], x[2]) -end - -w_component(x::Point) = 1.0 -w_component(x::Vec) = 0.0 - -function to_world( - p::StaticVector{N, T}, - prj_view_inv::Mat4, - cam_res::StaticVector - ) where {N, T} - VT = typeof(p) - clip_space = ((VT(p) ./ VT(cam_res)) .* T(2)) .- T(1) - pix_space = Vec{4, T}( - clip_space[1], - clip_space[2], - T(0), w_component(p) - ) - ws = prj_view_inv * pix_space - ws ./ ws[4] -end - -function to_world( - p::Vec{N, T}, - prj_view_inv::Mat4, - cam_res::StaticVector - ) where {N, T} - to_world(Point(p), prj_view_inv, cam_res) .- - to_world(zeros(Point{N, T}), prj_view_inv, cam_res) -end - -function project(scene::Scene, point::T) where T<:StaticVector - cam = scene.camera - area = pixelarea(scene)[] - # TODO, I think we need .+ minimum(area) - # Which would be semi breaking at this point though, I suppose - return project( - cam.projectionview[] * - transformationmatrix(scene)[], - Vec2f(widths(area)), - Point(point) - ) -end - -function project(matrix::Mat4f, p::T, dim4 = 1.0) where T <: VecTypes - p = to_ndim(Vec4f, to_ndim(Vec3f, p, 0.0), dim4) - p = matrix * p - to_ndim(T, p, 0.0) -end -function project(proj_view::Mat4f, resolution::Vec2, point::Point) - p4d = to_ndim(Vec4f, to_ndim(Vec3f, point, 0f0), 1f0) - clip = proj_view * p4d - p = (clip ./ clip[4])[Vec(1, 2)] - p = Vec2f(p[1], p[2]) - return (((p .+ 1f0) ./ 2f0) .* (resolution .- 1f0)) .+ 1f0 -end - -function project_point2(mat4::Mat4, point2::Point2) - Point2f(mat4 * to_ndim(Point4f, to_ndim(Point3f, point2, 0), 1)) -end +################################################################################ +### Old Projection code +################################################################################ -function transform(model::Mat4, x::T) where T - x4d = to_ndim(Vec4f, x, 0.0) - to_ndim(T, model * x4d, 0.0) -end +# function to_world(scene::Scene, point::T) where T <: StaticVector +# cam = scene.camera +# x = to_world( +# point, +# inv(transformationmatrix(scene)[]) * +# inv(cam.view[]) * +# inv(cam.projection[]), +# T(widths(pixelarea(scene)[])) +# ) +# Point2f(x[1], x[2]) +# end + +# w_component(x::Point) = 1.0 +# w_component(x::Vec) = 0.0 + +# function to_world( +# p::StaticVector{N, T}, +# prj_view_inv::Mat4, +# cam_res::StaticVector +# ) where {N, T} +# VT = typeof(p) +# clip_space = ((VT(p) ./ VT(cam_res)) .* T(2)) .- T(1) +# pix_space = Vec{4, T}( +# clip_space[1], +# clip_space[2], +# T(0), w_component(p) +# ) +# ws = prj_view_inv * pix_space +# ws ./ ws[4] +# end + +# function to_world( +# p::Vec{N, T}, +# prj_view_inv::Mat4, +# cam_res::StaticVector +# ) where {N, T} +# to_world(Point(p), prj_view_inv, cam_res) .- +# to_world(zeros(Point{N, T}), prj_view_inv, cam_res) +# end + +# function project(scene::Scene, point::T) where T<:StaticVector +# cam = scene.camera +# area = pixelarea(scene)[] +# # TODO, I think we need .+ minimum(area) +# # Which would be semi breaking at this point though, I suppose +# return project( +# cam.projectionview[] * +# transformationmatrix(scene)[], +# Vec2f(widths(area)), +# Point(point) +# ) +# end + +# function project(matrix::Mat4f, p::T, dim4 = 1.0) where T <: VecTypes +# p = to_ndim(Vec4f, to_ndim(Vec3f, p, 0.0), dim4) +# p = matrix * p +# to_ndim(T, p, 0.0) +# end + +# function project(proj_view::Mat4f, resolution::Vec2, point::Point) +# p4d = to_ndim(Vec4f, to_ndim(Vec3f, point, 0f0), 1f0) +# clip = proj_view * p4d +# p = (clip ./ clip[4])[Vec(1, 2)] +# p = Vec2f(p[1], p[2]) +# return (((p .+ 1f0) ./ 2f0) .* (resolution .- 1f0)) .+ 1f0 +# end + +# function project_point2(mat4::Mat4, point2::Point2) +# Point2f(mat4 * to_ndim(Point4f, to_ndim(Point3f, point2, 0), 1)) +# end + +# function transform(model::Mat4, x::T) where T +# x4d = to_ndim(Vec4f, x, 0.0) +# to_ndim(T, model * x4d, 0.0) +# end + +# function project(cam::Camera, input_space::Symbol, output_space::Symbol, pos) +# input_space === output_space && return to_ndim(Point3f, pos, 0) +# p4d = to_ndim(Point4f, to_ndim(Point3f, pos, 0), 1) +# transformed = space_to_space_matrix(cam, input_space, output_space) * p4d +# return Point3f(transformed[Vec(1, 2, 3)] ./ transformed[4]) +# end ################################################################################ ### new projection code @@ -320,22 +332,44 @@ end """ - space_to_space_matrix(cam_or_scenelike, spaces::Pair) - space_to_space_matrix(cam_or_scenelike, input_space::Symbol, output_space::Symbol) + space_to_space_matrix(scenelike, spaces::Pair) + space_to_space_matrix(scenelike, input_space::Symbol, output_space::Symbol) Returns a matrix which transforms positional data from a given input space to a -given output space. Note that this does not include `plot.transformations`, i.e. -the model matrix and transform function. +given output space. This will not include the transform function, as it is not +representable as a matrix, but will include the model matrix if applicable. +(I.e. this includes `scale!()`, `translate!()` and `rotate!()`.) + +If you wish to exclude the model matrix, call +`_space_to_space_matrix(camera(scenelike), ...)`. """ +function space_to_space_matrix(obj, input_space::Symbol, output_space::Symbol) + return space_to_space_matrix(obj, Pair(input_space, output_space)) +end + +# this method does Axis -> Scene conversions function space_to_space_matrix(obj, space2space::Pair{Symbol, Symbol}) - return space_to_space_matrix(camera(obj), space2space...) + return space_to_space_matrix(get_scene(obj), space2space) end -function space_to_space_matrix(obj, input_space::Symbol, output_space::Symbol) - return space_to_space_matrix(camera(obj), Pair(input_space, output_space)) +function space_to_space_matrix(scene_or_plot::SceneLike, s2s::Pair{Symbol, Symbol}) + mat = _space_to_space_matrix(camera(scene_or_plot), s2s) + model = to_value(transformationmatrix(scene_or_plot)) + if s2s[1] in (:data, :transformed) + return mat * model + elseif s2s[2] in (:data, :transformed) + return inv(model) * mat + else + return mat + end end -function space_to_space_matrix(cam::Camera, s2s::Pair{Symbol, Symbol}) +# function space_to_space_matrix(cam::Camera, s2s::Pair{Symbol, Symbol}) +# @warn "No trans_func, no model" +# return _space_to_space_matrix(cam, s2s) +# end + +function _space_to_space_matrix(cam::Camera, s2s::Pair{Symbol, Symbol}) # identities if s2s[1] === s2s[2] return Mat4f(I) @@ -363,10 +397,10 @@ function space_to_space_matrix(cam::Camera, s2s::Pair{Symbol, Symbol}) # calculation neccessary elseif s2s[1] === :clip - return inv(space_to_space_matrix(cam, Pair(s2s[2], s2s[1]))) + return inv(_space_to_space_matrix(cam, Pair(s2s[2], s2s[1]))) elseif s2s[1] in spaces() && s2s[2] in spaces() - return space_to_space_matrix(cam, Pair(:clip, s2s[2])) * - space_to_space_matrix(cam, Pair(s2s[1], :clip)) + return _space_to_space_matrix(cam, Pair(:clip, s2s[2])) * + _space_to_space_matrix(cam, Pair(s2s[1], :clip)) else error("Space $space not recognized. Must be one of $(spaces())") end @@ -375,9 +409,217 @@ end @deprecate space_to_clip(cam, space, pv) space_to_space_matrix(cam, space, :clip) false @deprecate clip_to_space(cam, space) space_to_space_matrix(cam, :clip, space) false -function project(cam::Camera, input_space::Symbol, output_space::Symbol, pos) - input_space === output_space && return to_ndim(Point3f, pos, 0) - p4d = to_ndim(Point4f, to_ndim(Point3f, pos, 0), 1) - transformed = space_to_space_matrix(cam, input_space, output_space) * p4d - return Point3f(transformed[Vec(1, 2, 3)] ./ transformed[4]) +# For users/convenient project functions: + +""" + project(scenelike, pos[; input_space, output_space = :pixel, type = Point3f]) + +Projects the given positional data from the space of the given plot, scene or +axis (`scenelike`) to pixel space. Optionally the input and output space can be +changed via the respective keyword arguments. + +## Notes + +Depending on the `scenelike` object passed the context of what data, +transformed, world and eye space is may change. A `scene` with a pixel-space +camera will yield different results than a `scene` with a 3D camera, for example. + +Transformations and the input space can be different between a plot and its +parent scene and also between child plots and their parent plots. In some cases +you may also see varying results because of this. + +If this function projects to pixel space it returns the pixel positions relative +to the scene most closely related to `scenelike` (i.e. the given scene or parent +scene). If you want the pixel position relative to the screen/figure/window, you +may need to add `minimum(pixelarea(scene))`. `project_to_screen` does this for +you. +""" +function project( + plot::AbstractPlot, pos; + input_space::Symbol = to_value(get(plot, :space, :data)), + output_space::Symbol = :pixel, type = _point3_type(pos) + ) + + tf = transform_func(plot) + mat = space_to_space_matrix(plot, input_space => output_space) + return project(mat, tf, input_space, output_space, pos, type) +end + +function project( + obj, pos; + input_space::Symbol = :data, output_space::Symbol = :pixel, + type = _point3_type(pos) + ) + + tf = transform_func(get_scene(obj)) # this should error with a camera + mat = space_to_space_matrix(obj, input_space => output_space) + return project(mat, tf, input_space, output_space, pos, type) +end + +function project(cam::Camera, input_space::Symbol, output_space::Symbol, pos; type = _point3_type(pos)) + @assert input_space !== :data "Cannot transform from :data space with just the camera." + @assert output_space !== :data "Cannot transform to :data space with just the camera." + mat = space_to_space_matrix(cam, input_space => output_space) + return project(mat, indentity, input_space, output_space, pos, type) +end + +_point3_type(::VecTypes{N, T}) where {N, T} = Point3{T} +_point3_type(::AbstractArray{<: VecTypes{N, T}}) where {N, T} = Point3{T} + +# Internal / Implementations + +""" + project(projection_matrix::Mat4f, transform_func, input_space::Symbol, output_space::Symbol, pos) + +Projects the given position or positions according to the given projection matrix, +transform function and input space. + +For a simpler interface, use `project(scenelike, pos)`. +""" +function project( + mat::Mat4, tf, input_space::Symbol, output_space::Symbol, + pos::AbstractArray{<: VecTypes{N, T}}, type = Point3{T} + ) where {N, T} + if input_space === output_space + return to_ndim.(type, pos, 0) + elseif output_space !== :data + return map(p -> project(mat, tf, input_space, p, type), pos) + else + itf = inverse_transform(tf) + return map(p -> inv_project(mat, itf, p, type), pos) + end +end +function project( + mat::Mat4, tf, input_space::Symbol, output_space::Symbol, + pos::VecTypes{N, T}, type = Point3{T}) where {N, T} + if input_space === output_space + return to_ndim(type, pos, 0) + elseif output_space !== :data + return project(mat, tf, input_space, pos, type) + else + itf = inverse_transform(tf) + return inv_project(mat, itf, pos, type) + end +end + + +function project(mat::Mat4, tf, space::Symbol, pos::VecTypes{N, T}, type = Point3{T}) where {N, T} + return project(mat, apply_transform(tf, pos, space), type) +end + +function inv_project(mat::Mat4f, itf, pos::VecTypes{N, T}, type = Point3{T}) where {N, T} + p = project(mat, pos, type) + return apply_transform(itf, p) +end + +function project(mat::Mat4, pos::VecTypes{N, T}, type = Point3{T}) where {N, T} + # TODO is to_ndim slow? It alone seems to + p4d = to_ndim(Point4{T}, to_ndim(Point3{T}, pos, 0), 1) + p4d = mat * p4d + return to_ndim(type, p4d[Vec(1,2,3)] ./ p4d[4], 1f0) +end + +# TODO - should we use p4d[4] = 0 here? +# function project(mat::Mat4, pos::Vec{N, T}, type = Vec3{T}) where {N, T} +# p4d = to_ndim(Vec4{T}, to_ndim(Vec3{T}, pos, 0), 1) +# p4d = mat * p4d +# return to_ndim(type, p4d[Vec(1,2,3)] ./ p4d[4], 1f0) +# end + + +# in-place version +# function project!(target::AbstractArray, mat::Mat4f, tf, space::Symbol, pos::AbstractArray) +# resize!(target, length(pos)) +# return map!(p -> project(mat, tf, space, p), target, pos) +# end + + +# Named project functions + +""" + project_to_world(scenelike[, input_space], pos) + +Transforms the given position(s) to world space, using the space of `scenelike` +as the default input space. +""" +project_to_world(obj, pos) = project(obj, pos, output_space = :world) +project_to_world(obj, input_space::Symbol, pos) = project(obj, pos, input_space = input_space, output_space = :world) + +@deprecate to_world project_to_world false + +""" + project_to_screen(scenelike[, input_space], pos) + +Transforms the given position(s) to (2D) screen/pixel space, using the space of +`scenelike` as the default input space. The returned positions will be relative +to the screen/window/figure origin, rather than the (parent) scene. +""" +function project_to_screen(obj, pos) + scene = get_scene(obj) + offset = minimum(to_value(pixelarea(scene))) + return apply_offset!(project(obj, pos, type = Point2f), offset) end + +function project_to_screen(obj, input_space::Symbol, pos) + scene = get_scene(obj) + offset = minimum(to_value(pixelarea(scene))) + return apply_offset!(project(obj, pos, input_space = input_space, type = Point2f), offset) +end + +function apply_offset!(pos::VT, off::VecTypes) where {VT <: VecTypes} + return pos + to_ndim(VT, off, 0) +end + +function apply_offset!(pos::AbstractArray{VT}, off::VecTypes) where {VT} + off = to_ndim(VT, off, 0) + for i in eachindex(pos) + pos[i] = pos[i] + off + end + return pos +end + +@deprecate shift_project(scene, plot, pos) project_to_screen(plot, pos) false + +""" + project_to_pixel(scenelike[, input_space], pos[; type = Point2f]) + +Transforms the given position(s) to (2D) pixel space, using the space of `scenelike` +as the default input space. The returned positions will be relative to the +scene derived from scenelike, not the screen/window/figure origin. + +This is equivalent to `project(scenelike, pos[; input_space], type = Point2f)`. +""" +project_to_pixel(obj, pos; type = Point2f) = project(obj, pos; type = type) +function project_to_pixel(obj, input_space::Symbol, pos; type = Point2f) + return project(obj, pos, input_space = input_space, type = type) +end + +# Helper function +""" + projection_obs(plot) + +Returns an observable that triggers whenever the result of `project` could change +""" +function projection_obs(plot::AbstractPlot) + return lift( + (_, _, _, _, _, _) -> nothing, + plot, + camera(plot).view, + camera(plot).projection, + get_scene(plot).px_area, + transformationmatrix(plot), + transform_func(plot), + get(plot, :space, Observable(:data)), + ) +end +function projection_obs(scene::Scene) + return lift( + (_, _, _, _, _, _) -> nothing, + scene, + camera(scene).view, + camera(scene).projection, + scene.px_area, + transformationmatrix(scene), + transform_func(scene) + ) +end \ No newline at end of file diff --git a/src/interaction/inspector.jl b/src/interaction/inspector.jl index e7cfa61eb67..35956b02369 100644 --- a/src/interaction/inspector.jl +++ b/src/interaction/inspector.jl @@ -125,12 +125,12 @@ A --- B | | D --- C -this computes parameter `f` such that the line from `A + f * (B - A)` to -`D + f * (C - D)` crosses through the given point `P`. This assumes that `P` is +this computes parameter `f` such that the line from `A + f * (B - A)` to +`D + f * (C - D)` crosses through the given point `P`. This assumes that `P` is inside the quad and that none of the edges cross. """ function point_in_quad_parameter( - A::Point2, B::Point2, C::Point2, D::Point2, P::Point2; + A::Point2, B::Point2, C::Point2, D::Point2, P::Point2; iterations = 50, epsilon = 1e-6 ) @@ -158,21 +158,6 @@ function point_in_quad_parameter( end -## Shifted projection -######################################## - -@deprecate shift_project(scene, plot, pos) shift_project(scene, pos) false - -function shift_project(scene, pos) - project( - camera(scene).projectionview[], - Vec2f(widths(pixelarea(scene)[])), - pos - ) .+ Vec2f(origin(pixelarea(scene)[])) -end - - - ################################################################################ ### Interactive selection via DataInspector ################################################################################ @@ -236,7 +221,7 @@ returning a label. See Makie documentation for more detail. - `enable_indicators = true)`: Enables or disables indicators - `depth = 9e3`: Depth value of the tooltip. This should be high so that the tooltip is always in front. -- `apply_tooltip_offset = true`: Enables or disables offsetting tooltips based +- `apply_tooltip_offset = true`: Enables or disables offsetting tooltips based on, for example, markersize. - and all attributes from `Tooltip` """ @@ -420,10 +405,8 @@ end function show_data(inspector::DataInspector, plot::Scatter, idx) a = inspector.attributes tt = inspector.plot - scene = parent_scene(plot) - pos = position_on_plot(plot, idx) - proj_pos = shift_project(scene, pos) + proj_pos = project_to_screen(plot, pos) update_tooltip_alignment!(inspector, proj_pos) if haskey(plot, :inspector_label) @@ -490,7 +473,7 @@ function show_data(inspector::DataInspector, plot::MeshScatter, idx) end pos = position_on_plot(plot, idx) - proj_pos = shift_project(scene, pos) + proj_pos = project_to_screen(plot, pos) update_tooltip_alignment!(inspector, proj_pos) if haskey(plot, :inspector_label) @@ -511,13 +494,12 @@ function show_data(inspector::DataInspector, plot::Union{Lines, LineSegments}, i # cast ray from cursor into screen, find closest point to line pos = position_on_plot(plot, idx) - - proj_pos = shift_project(scene, pos) + proj_pos = project_to_screen(plot, pos) update_tooltip_alignment!(inspector, proj_pos) tt.offset[] = ifelse( - a.apply_tooltip_offset[], - sv_getindex(plot.linewidth[], idx) + 2, + a.apply_tooltip_offset[], + sv_getindex(plot.linewidth[], idx) + 2, a.offset[] ) @@ -561,7 +543,7 @@ function show_data(inspector::DataInspector, plot::Mesh, idx) end p = wireframe!( - scene, bbox, color = a.indicator_color, + scene, bbox, color = a.indicator_color, transformation = Transformation(), linewidth = a.indicator_linewidth, linestyle = a.indicator_linestyle, visible = a.indicator_visible, inspectable = false @@ -673,16 +655,16 @@ function show_imagelike(inspector, plot, name, edge_based) if a.enable_indicators[] if plot.interpolate[] - if inspector.selection != plot || (length(inspector.temp_plots) != 1) || + if inspector.selection != plot || (length(inspector.temp_plots) != 1) || !(inspector.temp_plots[1] isa Scatter) clear_temporary_plots!(inspector, plot) p = scatter!( scene, pos, color = a._color, visible = a.indicator_visible, inspectable = false, model = plot.model, - # TODO switch to Rect with 2r-1 or 2r-2 markersize to have + # TODO switch to Rect with 2r-1 or 2r-2 markersize to have # just enough space to always detect the underlying image - marker=:rect, markersize = map(r -> 2r, a.range), + marker=:rect, markersize = map(r -> 2r, a.range), strokecolor = a.indicator_color, strokewidth = a.indicator_linewidth, depth_shift = -1f-3 @@ -695,7 +677,7 @@ function show_imagelike(inspector, plot, name, edge_based) end else bbox = _pixelated_image_bbox(plot[1][], plot[2][], plot[3][], i, j, edge_based) - if inspector.selection != plot || (length(inspector.temp_plots) != 1) || + if inspector.selection != plot || (length(inspector.temp_plots) != 1) || !(inspector.temp_plots[1] isa Wireframe) clear_temporary_plots!(inspector, plot) p = wireframe!( @@ -811,8 +793,8 @@ function show_data(inspector::DataInspector, plot::BarPlot, idx) tt = inspector.plot scene = parent_scene(plot) - pos = apply_transform_and_model(plot, plot[1][][idx]) - proj_pos = shift_project(scene, to_ndim(Point3f, pos, 0)) + pos = plot[1][][idx] + proj_pos = project_to_screen(plot, to_ndim(Point3f, pos, 0)) update_tooltip_alignment!(inspector, proj_pos) if a.enable_indicators[] @@ -919,7 +901,7 @@ function show_poly(inspector, plot, poly, idx, source) clear_temporary_plots!(inspector, plot) p = lines!( - scene, line_collection, color = a.indicator_color, + scene, line_collection, color = a.indicator_color, transformation = Transformation(source), strokewidth = a.indicator_linewidth, linestyle = a.indicator_linestyle, visible = a.indicator_visible, inspectable = false, depth_shift = -1f-3 @@ -954,7 +936,7 @@ function show_data(inspector::DataInspector, plot::VolumeSlices, idx, child::Hea proj_pos = Point2f(mouseposition_px(inspector.root)) update_tooltip_alignment!(inspector, proj_pos) tt[1][] = proj_pos - + world_pos = apply_transform_and_model(child, pos) if haskey(plot, :inspector_label) @@ -1004,7 +986,7 @@ function show_data(inspector::DataInspector, plot::Band, idx::Integer, mesh::Mes clear_temporary_plots!(inspector, plot) p = lines!( scene, [P1, P2], transformation = Transformation(plot.transformation), - color = a.indicator_color, strokewidth = a.indicator_linewidth, + color = a.indicator_color, strokewidth = a.indicator_linewidth, linestyle = a.indicator_linestyle, visible = a.indicator_visible, inspectable = false, depth_shift = -1f-3 diff --git a/src/interaction/interactive_api.jl b/src/interaction/interactive_api.jl index 7ea6243d497..a222ae56a84 100644 --- a/src/interaction/interactive_api.jl +++ b/src/interaction/interactive_api.jl @@ -177,6 +177,7 @@ screen_relative(x, mpos) = screen_relative(get_scene(x), mpos) function screen_relative(scene::Scene, mpos) return Point2f(mpos) .- Point2f(minimum(pixelarea(scene)[])) end +# ^ TODO kidna dublicate with projection changes """ mouseposition(scene = hovered_scene()) @@ -189,7 +190,7 @@ By default uses the `scene` that the mouse is currently hovering over. mouseposition(x) = mouseposition(get_scene(x)) function mouseposition(scene::Scene = hovered_scene()) - return to_world(scene, mouseposition_px(scene)) + return project_to_world(scene, :pixel, mouseposition_px(scene)) end mouseposition_px(x) = mouseposition_px(get_scene(x)) diff --git a/src/layouting/boundingbox.jl b/src/layouting/boundingbox.jl index 18757dcb7a5..10888b4d23e 100644 --- a/src/layouting/boundingbox.jl +++ b/src/layouting/boundingbox.jl @@ -6,7 +6,7 @@ end function boundingbox(x, exclude = (p)-> false) return parent_transform(x) * data_limits(x, exclude) end - +# function project_widths(matrix, vec) pr = project(matrix, vec) zero = project(matrix, zeros(typeof(vec))) @@ -91,24 +91,12 @@ function boundingbox(layouts::AbstractArray{<:GlyphCollection}, positions, rotat end function boundingbox(x::Text{<:Tuple{<:GlyphCollection}}) - if x.space[] == x.markerspace[] - pos = to_ndim(Point3f, x.position[], 0) - else - cam = parent_scene(x).camera - transformed = apply_transform(x.transformation.transform_func[], x.position[]) - pos = Makie.project(cam, x.space[], x.markerspace[], transformed) - end + pos = project(x, x.position[], output_space = x.markerspace[]) return boundingbox(x[1][], pos, to_rotation(x.rotation[])) end function boundingbox(x::Text{<:Tuple{<:AbstractArray{<:GlyphCollection}}}) - if x.space[] == x.markerspace[] - pos = to_ndim.(Point3f, x.position[], 0) - else - cam = (parent_scene(x).camera,) - transformed = apply_transform(x.transformation.transform_func[], x.position[]) - pos = Makie.project.(cam, x.space[], x.markerspace[], transformed) - end + pos = project(x, x.position[], output_space = x.markerspace[]) return boundingbox(x[1][], pos, to_rotation(x.rotation[])) end diff --git a/src/layouting/data_limits.jl b/src/layouting/data_limits.jl index df81ae151dc..c74581b71f0 100644 --- a/src/layouting/data_limits.jl +++ b/src/layouting/data_limits.jl @@ -115,6 +115,7 @@ function foreach_plot(f, plot::Combined) end end +# TODO - may need some reorganization? function foreach_transformed(f, point_iterator, model, trans_func) for point in point_iterator point_t = apply_transform(trans_func, point) diff --git a/src/makielayout/blocks/axis3d.jl b/src/makielayout/blocks/axis3d.jl index b96f840efc8..49940a78503 100644 --- a/src/makielayout/blocks/axis3d.jl +++ b/src/makielayout/blocks/axis3d.jl @@ -476,8 +476,7 @@ end # this function projects a point from a 3d subscene into the parent space with a really # small z value function to_topscene_z_2d(p3d, scene) - o = scene.px_area[].origin - p2d = Point2f(o + Makie.project(scene, p3d)) + p2d = Makie.project_to_pixel(scene, p3d) # -10000 is an arbitrary weird constant that in preliminary testing didn't seem # to clip into plot objects anymore Point3f(p2d..., -10000) @@ -558,8 +557,6 @@ function add_ticks_and_ticklabels!(topscene, scene, ax, dim::Int, limits, tickno map!(topscene, labels_positions, scene.px_area, scene.camera.projectionview, tick_segments, ticklabels, attr(:ticklabelpad)) do pxa, pv, ticksegs, ticklabs, pad - o = pxa.origin - points = map(ticksegs) do (tstart, tend) offset = pad * Makie.GeometryBasics.normalize(Point2f(tend - tstart)) tend + offset @@ -595,7 +592,6 @@ function add_ticks_and_ticklabels!(topscene, scene, ax, dim::Int, limits, tickno attr(:labeloffset), attr(:labelrotation), attr(:labelalign), xreversed, yreversed, zreversed ) do pxa, pv, lims, miv, min1, min2, labeloffset, lrotation, lalign, xrev, yrev, zrev - o = pxa.origin rev1 = (xrev, yrev, zrev)[d1] rev2 = (xrev, yrev, zrev)[d2] @@ -612,8 +608,8 @@ function add_ticks_and_ticklabels!(topscene, scene, ax, dim::Int, limits, tickno p2 = dpoint(maximum(lims)[dim], f1, f2) # project them into screen space - pp1 = Point2f(o + Makie.project(scene, p1)) - pp2 = Point2f(o + Makie.project(scene, p2)) + pp1 = Makie.project_to_pixel(scene, p1) + pp2 = Makie.project_to_pixel(scene, p2) # find the midpoint midpoint = (pp1 + pp2) ./ 2 diff --git a/src/makielayout/interactions.jl b/src/makielayout/interactions.jl index f94641d69b9..8667c0d8438 100644 --- a/src/makielayout/interactions.jl +++ b/src/makielayout/interactions.jl @@ -123,7 +123,6 @@ end function _selection_vertices(ax_scene, outer, inner) _clamp(p, plow, phigh) = Point2f(clamp(p[1], plow[1], phigh[1]), clamp(p[2], plow[2], phigh[2])) - proj(point) = project(ax_scene, point) .+ minimum(ax_scene.px_area[]) transf = Makie.transform_func(ax_scene) outer = positivize(Makie.apply_transform(transf, outer)) inner = positivize(Makie.apply_transform(transf, inner)) @@ -139,7 +138,7 @@ function _selection_vertices(ax_scene, outer, inner) itr = _clamp(topright(inner), obl, otr) # We plot the selection vertices in blockscene, which is pixelspace, so we need to manually # project the points to the space of `ax.scene` - return [proj(obl), proj(obr), proj(otr), proj(otl), proj(ibl), proj(ibr), proj(itr), proj(itl)] + return project_to_screen(ax_scene, :transformed, [obl, obr, otr, otl, ibl, ibr, itr, itl]) end function process_interaction(r::RectangleZoom, event::MouseEvent, ax::Axis) diff --git a/src/units.jl b/src/units.jl index 398d935682c..e0ab85b7929 100644 --- a/src/units.jl +++ b/src/units.jl @@ -101,6 +101,7 @@ function Base.convert(::Type{<: Pixel}, scene::Scene, x::DIP) Pixel(number(dots)) end +# TODO: maybe we should hide this stuff as long as we're using `space` instead of units? function Base.convert(::Type{<: SceneSpace}, scene::Scene, x::Vec{2, <:Pixel}) zero = to_world(scene, to_screen(scene, Point2f(0))) s = to_world(scene, to_screen(scene, number.(Point(x)))) diff --git a/src/utilities/utilities.jl b/src/utilities/utilities.jl index 564b1a53418..18f0947d876 100644 --- a/src/utilities/utilities.jl +++ b/src/utilities/utilities.jl @@ -251,6 +251,13 @@ end lerp(a::T, b::T, val::AbstractFloat) where {T} = (a .+ (val * (b .- a))) +function angle(p1::VecTypes{2, T}, p2::VecTypes{2, T}) where T + return atan(p2[2] - p1[2], p2[1] - p1[1]) # result in [-π, π] +end +function projected_angle(plot, p1, p2) + return angle(project_to_pixel(plot, p1), project_to_pixel(plot, p2)) +end + function merged_get!(defaults::Function, key, scene, input::Vector{Any}) return merged_get!(defaults, key, scene, Attributes(input)) end From 7ec76d67d65042760e89992da15a420a899b76b3 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 30 Jun 2023 20:46:03 +0200 Subject: [PATCH 05/24] update GLMakie --- GLMakie/src/drawing_primitives.jl | 14 +++++++------- GLMakie/test/unit_tests.jl | 12 +++--------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 8b37059bd4f..c3daddb3ad8 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -51,9 +51,9 @@ function connect_camera!(plot, gl_attributes, cam, space = gl_attributes[:space] get!(gl_attributes, :projection) do return lift(cam.projection, cam.pixel_space, space) do _, _, space if space in (:data, :transformed, :world) - return Makie.space_to_space_matrix(cam, :eye => :clip) + return Makie._space_to_space_matrix(cam, :eye => :clip) else - return Makie.space_to_space_matrix(cam, space => :clip) + return Makie._space_to_space_matrix(cam, space => :clip) end end end @@ -67,7 +67,7 @@ function connect_camera!(plot, gl_attributes, cam, space = gl_attributes[:space] get!(gl_attributes, :projectionview) do return lift(plot, cam.projectionview, cam.pixel_space, space) do _, _, space - Makie.space_to_space_matrix(cam, space => :clip) + Makie._space_to_space_matrix(cam, space => :clip) end end @@ -206,8 +206,8 @@ pixel2world(scene, msize::Number) = pixel2world(scene, Point2f(msize))[1] function pixel2world(scene, msize::StaticVector{2}) # TODO figure out why Vec(x, y) doesn't work correctly - p0 = Makie.to_world(scene, Point2f(0.0)) - p1 = Makie.to_world(scene, Point2f(msize)) + p0 = Makie.to_world(scene, :pixel, Point2f(0.0)) + p1 = Makie.to_world(scene, :pixel, Point2f(msize)) diff = p1 - p0 return diff end @@ -229,7 +229,7 @@ function draw_atomic(screen::Screen, scene::Scene, @nospecialize(x::Union{Scatte mspace = x.markerspace cam = scene.camera gl_attributes[:preprojection] = map(space, mspace, cam.projectionview, cam.resolution) do space, mspace, _, _ - return Makie.space_to_space_matrix(cam, space => mspace) + return Makie._space_to_space_matrix(cam, space => mspace) end # fast pixel does its own setup if !(marker[] isa FastPixel) @@ -405,7 +405,7 @@ function draw_atomic(screen::Screen, scene::Scene, cam = scene.camera # gl_attributes[:preprojection] = Observable(Mat4f(I)) gl_attributes[:preprojection] = map(space, markerspace, cam.projectionview, cam.resolution) do s, ms, pv, res - Makie.space_to_space_matrix(cam, s => ms) + Makie._space_to_space_matrix(cam, s => ms) end return draw_scatter(screen, (DISTANCEFIELD, positions), gl_attributes) diff --git a/GLMakie/test/unit_tests.jl b/GLMakie/test/unit_tests.jl index e1252cc934f..686d1c37d0a 100644 --- a/GLMakie/test/unit_tests.jl +++ b/GLMakie/test/unit_tests.jl @@ -1,11 +1,5 @@ using GLMakie.Makie: getscreen -function project_sp(scene, point) - point_px = Makie.project(scene, point) - offset = Point2f(minimum(pixelarea(scene)[])) - return point_px .+ offset -end - @testset "unit tests" begin GLMakie.closeall() @testset "Window handling" begin @@ -73,14 +67,14 @@ end # force a full render to happen GLMakie.Makie.colorbuffer(screen) # test for pick a single data point (with idx > 65535) - point_px = project_sp(ax.scene, Point2f(N-1,N-1)) + point_px = Makie.project_to_screen(ax.scene, Point2f(N-1,N-1)) plot,idx = pick(ax.scene, point_px) @test idx == N-1 # test for pick a rectangle of data points (also with some indices > 65535) rect = Rect2f(99990.5,99990.5,8,8) - origin_px = project_sp(ax.scene, Point(origin(rect))) - tip_px = project_sp(ax.scene, Point(origin(rect) .+ widths(rect))) + origin_px = Makie.project_to_screen(ax.scene, Point(origin(rect))) + tip_px = Makie.project_to_screen(ax.scene, Point(origin(rect) .+ widths(rect))) rect_px = Rect2i(round.(origin_px), round.(tip_px .- origin_px)) picks = unique(pick(ax.scene, rect_px)) From ab564d1044a0c8eb0c5d6d90acb918b007ef0312 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sun, 2 Jul 2023 15:45:52 +0200 Subject: [PATCH 06/24] implement changes in CairoMakie --- CairoMakie/src/overrides.jl | 32 ++--- CairoMakie/src/primitives.jl | 241 +++++++++++++++++----------------- CairoMakie/src/utils.jl | 54 +++----- src/camera/projection_math.jl | 4 +- src/utilities/utilities.jl | 3 + 5 files changed, 155 insertions(+), 179 deletions(-) diff --git a/CairoMakie/src/overrides.jl b/CairoMakie/src/overrides.jl index 25da6a9c9c6..17b1c36e081 100644 --- a/CairoMakie/src/overrides.jl +++ b/CairoMakie/src/overrides.jl @@ -30,7 +30,7 @@ end # in the rare case of per-vertex colors redirect to mesh drawing -function draw_poly(scene::Scene, screen::Screen, poly, points::Vector{<:Point2}, color::AbstractArray, model, strokecolor, strokewidth) +function draw_poly(scene::Scene, screen::Screen, poly, points::Vector{<:Point2}, color::AbstractArray, strokecolor, strokewidth) draw_poly_as_mesh(scene, screen, poly) end @@ -38,14 +38,13 @@ function draw_poly(scene::Scene, screen::Screen, poly, points::Vector{<:Point2}) color = to_cairo_color(poly.color[], poly) strokecolor = to_cairo_color(poly.strokecolor[], poly) strokestyle = Makie.convert_attribute(poly.linestyle[], key"linestyle"()) - draw_poly(scene, screen, poly, points, color, poly.model[], strokecolor, strokestyle, poly.strokewidth[]) + draw_poly(scene, screen, poly, points, color, strokecolor, strokestyle, poly.strokewidth[]) end # when color is a Makie.AbstractPattern, we don't need to go to Mesh function draw_poly(scene::Scene, screen::Screen, poly, points::Vector{<:Point2}, color::Union{Colorant, Cairo.CairoPattern}, - model, strokecolor, strokestyle, strokewidth) - space = to_value(get(poly, :space, :data)) - points = project_position.(Ref(scene), space, points, Ref(model)) + strokecolor, strokestyle, strokewidth) + points = cairo_project(poly, points) Cairo.move_to(screen.context, points[1]...) for p in points[2:end] Cairo.line_to(screen.context, p...) @@ -75,10 +74,7 @@ end draw_poly(scene::Scene, screen::Screen, poly, rect::Rect2) = draw_poly(scene, screen, poly, [rect]) function draw_poly(scene::Scene, screen::Screen, poly, rects::Vector{<:Rect2}) - model = poly.model[] - space = to_value(get(poly, :space, :data)) - projected_rects = project_rect.(Ref(scene), space, rects, Ref(model)) - + projected_rects = cairo_project.((poly,), rects) color = to_cairo_color(poly.color[], poly) linestyle = Makie.convert_attribute(poly.linestyle[], key"linestyle"()) @@ -127,10 +123,7 @@ draw_poly(scene::Scene, screen::Screen, poly, polygon::Polygon) = draw_poly(scen draw_poly(scene::Scene, screen::Screen, poly, circle::Circle) = draw_poly(scene, screen, poly, decompose(Point2f, circle)) function draw_poly(scene::Scene, screen::Screen, poly, polygons::AbstractArray{<:Polygon}) - model = poly.model[] - space = to_value(get(poly, :space, :data)) - projected_polys = project_polygon.(Ref(poly), space, polygons, Ref(model)) - + projected_polys = cairo_project.((poly,), polygons) color = to_cairo_color(poly.color[], poly) strokecolor = to_cairo_color(poly.strokecolor[], poly) strokestyle = Makie.convert_attribute(poly.linestyle[], key"linestyle"()) @@ -147,10 +140,7 @@ function draw_poly(scene::Scene, screen::Screen, poly, polygons::AbstractArray{< end function draw_poly(scene::Scene, screen::Screen, poly, polygons::AbstractArray{<: MultiPolygon}) - model = poly.model[] - space = to_value(get(poly, :space, :data)) - projected_polys = project_multipolygon.(Ref(scene), space, polygons, Ref(model)) - + projected_polys = cairo_project.((poly,), polygons) color = to_cairo_color(poly.color[], poly) strokecolor = to_cairo_color(poly.strokecolor[], poly) strokestyle = Makie.convert_attribute(poly.linestyle[], key"linestyle"()) @@ -183,9 +173,7 @@ function draw_plot(scene::Scene, screen::Screen, upperpoints = band[1][] lowerpoints = band[2][] points = vcat(lowerpoints, reverse(upperpoints)) - model = band.model[] - space = to_value(get(band, :space, :data)) - points = project_position.(Ref(scene), space, points, Ref(model)) + points = cairo_project(band, points) Cairo.move_to(screen.context, points[1]...) for p in points[2:end] Cairo.line_to(screen.context, p...) @@ -221,9 +209,7 @@ function draw_plot(scene::Scene, screen::Screen, tric::Tricontourf) colornumbers = pol.color[] colors = to_cairo_color(colornumbers, pol) polygons = pol[1][] - model = pol.model[] - space = to_value(get(pol, :space, :data)) - projected_polys = project_polygon.(Ref(scene), space, polygons, Ref(model)) + projected_polys = cairo_project.((tric,), polygons) function draw_tripolys(polys, colornumbers, colors) for (i, (pol, colnum, col)) in enumerate(zip(polys, colornumbers, colors)) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index 5e169ed45c8..05a08de7b15 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -6,7 +6,6 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio fields = @get_attribute(primitive, (color, linewidth, linestyle)) linestyle = Makie.convert_attribute(linestyle, Makie.key"linestyle"()) ctx = screen.context - model = primitive[:model][] positions = primitive[1][] isempty(positions) && return @@ -27,8 +26,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio end end - space = to_value(get(primitive, :space, :data)) - projected_positions = project_position.(Ref(scene), (Makie.transform_func(primitive),), Ref(space), positions, Ref(model)) + projected_positions = cairo_project(primitive, positions) color = to_color(primitive.calculated_colors[]) @@ -51,7 +49,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio end if primitive isa Lines && primitive.input_args[1][] isa BezierPath - return draw_bezierpath_lines(ctx, primitive.input_args[1][], scene, color, space, model, linewidth) + return draw_bezierpath_lines(ctx, primitive.input_args[1][], primitive, color, linewidth) end if color isa AbstractArray || linewidth isa AbstractArray @@ -79,9 +77,9 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio nothing end -function draw_bezierpath_lines(ctx, bezierpath::BezierPath, scene, color, space, model, linewidth) +function draw_bezierpath_lines(ctx, bezierpath::BezierPath, plot, color, linewidth) for c in bezierpath.commands - proj_comm = project_command(c, scene, space, model) + proj_comm = project_command(c, plot) path_command(ctx, proj_comm) end Cairo.set_source_rgba(ctx, rgbatuple(color)...) @@ -90,23 +88,23 @@ function draw_bezierpath_lines(ctx, bezierpath::BezierPath, scene, color, space, return end -function project_command(m::MoveTo, scene, space, model) - MoveTo(project_position(scene, space, m.p, model)) +function project_command(m::MoveTo, plot) + MoveTo(cairo_project(plot, m.p)) end -function project_command(l::LineTo, scene, space, model) - LineTo(project_position(scene, space, l.p, model)) +function project_command(l::LineTo, plot) + LineTo(cairo_project(plot, l.p)) end -function project_command(c::CurveTo, scene, space, model) +function project_command(c::CurveTo, plot) CurveTo( - project_position(scene, space, c.c1, model), - project_position(scene, space, c.c2, model), - project_position(scene, space, c.p, model), + cairo_project(plot, c.c1), + cairo_project(plot, c.c2), + cairo_project(plot, c.p), ) end -project_command(c::ClosePath, scene, space, model) = c +project_command(c::ClosePath, plot) = c function draw_single(primitive::Lines, ctx, positions) n = length(positions) @@ -245,7 +243,7 @@ function draw_multi(primitive::Lines, ctx, positions, colors::AbstractArray, lin this_linewidth != prev_linewidth && error("Encountered two different linewidth values $prev_linewidth and $this_linewidth in `lines` at index $(i-1). Different linewidths in one line are only permitted in CairoMakie when separated by a NaN point.") Cairo.line_to(ctx, this_position...) prev_continued = true - + if i == lastindex(positions) # this is the last element so stroke this Cairo.set_line_width(ctx, this_linewidth) @@ -301,19 +299,14 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Scat positions = primitive[1][] isempty(positions) && return size_model = transform_marker ? model : Mat4f(I) - font = to_font(to_value(get(primitive, :font, Makie.defaultfont()))) colors = to_color(primitive.calculated_colors[]) markerspace = to_value(get(primitive, :markerspace, :pixel)) - space = to_value(get(primitive, :space, :data)) - - transfunc = Makie.transform_func(primitive) - marker_conv = _marker_convert(marker) - draw_atomic_scatter(scene, ctx, transfunc, colors, markersize, strokecolor, strokewidth, marker_conv, marker_offset, rotations, model, positions, size_model, font, markerspace, space) + draw_atomic_scatter(scene, ctx, primitive, colors, markersize, strokecolor, strokewidth, marker_conv, marker_offset, rotations, positions, size_model, font, markerspace) end # an array of markers is converted to string by itself, which is inconvenient for the iteration logic @@ -322,15 +315,15 @@ _marker_convert(marker) = convert_attribute(marker, key"marker"(), key"scatter"( # image arrays need to be converted as a whole _marker_convert(marker::AbstractMatrix{<:Colorant}) = [ convert_attribute(marker, key"marker"(), key"scatter"()) ] -function draw_atomic_scatter(scene, ctx, transfunc, colors, markersize, strokecolor, strokewidth, marker, marker_offset, rotations, model, positions, size_model, font, markerspace, space) +function draw_atomic_scatter(scene, ctx, plot, colors, markersize, strokecolor, strokewidth, marker, marker_offset, rotations, positions, size_model, font, markerspace) broadcast_foreach(positions, colors, markersize, strokecolor, strokewidth, marker, marker_offset, remove_billboard(rotations)) do point, col, markersize, strokecolor, strokewidth, m, mo, rotation - scale = project_scale(scene, markerspace, markersize, size_model) - offset = project_scale(scene, markerspace, mo, size_model) + scale = project_scale(scene, markerspace, markersize, size_model) # TODO + offset = project_scale(scene, markerspace, mo, size_model) # TODO - pos = project_position(scene, transfunc, space, point, model) + pos = cairo_project(plot, point) isnan(pos) && return Cairo.set_source_rgba(ctx, rgbatuple(col)...) @@ -505,14 +498,14 @@ end function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Text{<:Tuple{<:Union{AbstractArray{<:Makie.GlyphCollection}, Makie.GlyphCollection}}})) ctx = screen.context - @get_attribute(primitive, (rotation, model, space, markerspace, offset)) + @get_attribute(primitive, (rotation, model, markerspace, offset)) position = primitive.position[] # use cached glyph info glyph_collection = to_value(primitive[1]) draw_glyph_collection( - scene, ctx, position, glyph_collection, remove_billboard(rotation), - model, space, markerspace, offset, primitive.transformation + primitive, ctx, position, glyph_collection, remove_billboard(rotation), + markerspace, offset ) nothing @@ -520,22 +513,16 @@ end function draw_glyph_collection( - scene, ctx, positions, glyph_collections::AbstractArray, rotation, - model::Mat, space, markerspace, offset, transformation + @nospecialize(plot), ctx, positions, glyph_collections::AbstractArray, rotation, + markerspace, offset ) - # TODO: why is the Ref around model necessary? doesn't broadcast_foreach handle staticarrays matrices? - broadcast_foreach(positions, glyph_collections, rotation, Ref(model), space, - markerspace, offset) do pos, glayout, ro, mo, sp, msp, off - - draw_glyph_collection(scene, ctx, pos, glayout, ro, mo, sp, msp, off, transformation) + broadcast_foreach(positions, glyph_collections, rotation, markerspace, offset) do pos, glayout, ro, msp, off + draw_glyph_collection(plot, ctx, pos, glayout, ro, msp, off) end end -_deref(x) = x -_deref(x::Ref) = x[] - -function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation, _model, space, markerspace, offsets, transformation) +function draw_glyph_collection(@nospecialize(plot), ctx, position, glyph_collection, rotation, markerspace, offsets) glyphs = glyph_collection.glyphs glyphoffsets = glyph_collection.origins @@ -546,18 +533,16 @@ function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation, strokewidths = glyph_collection.strokewidths strokecolors = glyph_collection.strokecolors - model = _deref(_model) - model33 = model[Vec(1, 2, 3), Vec(1, 2, 3)] - id = Mat4f(I) + model33 = Makie.to_value(plot.model)[Vec(1, 2, 3), Vec(1, 2, 3)] - # TODO project method for this - glyph_pos = let - transform_func = transformation.transform_func[] - p = Makie.apply_transform(transform_func, position, space) + glyph_pos = Makie.project(plot, position, output_space = markerspace) + # let + # transform_func = transformation.transform_func[] + # p = Makie.apply_transform(transform_func, position, space) - Makie.space_to_space_matrix(scene, space => markerspace) * - model * to_ndim(Point4f, to_ndim(Point3f, p, 0), 1) - end + # Makie.space_to_space_matrix(scene, space => markerspace) * + # model * to_ndim(Point4f, to_ndim(Point3f, p, 0), 1) + # end Cairo.save(ctx) @@ -577,7 +562,7 @@ function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation, Cairo.set_source_rgba(ctx, rgbatuple(color)...) # offsets and scale apply in markerspace - gp3 = glyph_pos[Vec(1, 2, 3)] ./ glyph_pos[4] .+ model33 * (glyphoffset .+ p3_offset) + gp3 = glyph_pos .+ model33 * (glyphoffset .+ p3_offset) scale3 = scale isa Number ? Point3f(scale, scale, 0) : to_ndim(Point3f, scale, 0) @@ -589,9 +574,9 @@ function draw_glyph_collection(scene, ctx, position, glyph_collection, rotation, xvec = rotation * (scale3[1] * Point3f(1, 0, 0)) yvec = rotation * (scale3[2] * Point3f(0, -1, 0)) - glyphpos = _project_position(scene, markerspace, gp3, id, true) - xproj = _project_position(scene, markerspace, gp3 + model33 * xvec, id, true) - yproj = _project_position(scene, markerspace, gp3 + model33 * yvec, id, true) + glyphpos = cairo_project(plot, gp3, input_space = markerspace) + xproj = cairo_project(plot, gp3 + model33 * xvec, input_space = markerspace) + yproj = cairo_project(plot, gp3 + model33 * yvec, input_space = markerspace) xdiff = xproj - glyphpos ydiff = yproj - glyphpos @@ -691,7 +676,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio t = Makie.transform_func(primitive) identity_transform = (t === identity || t isa Tuple && all(x-> x === identity, t)) && (abs(model[1, 2]) < 1e-15) regular_grid = xs isa AbstractRange && ys isa AbstractRange - xy_aligned = let + xy_aligned = let # Only allow scaling and translation pv = scene.camera.projectionview[] M = Mat4f( @@ -715,12 +700,11 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio imsize = ((first(xs), last(xs)), (first(ys), last(ys))) # find projected image corners # this already takes care of flipping the image to correct cairo orientation - space = to_value(get(primitive, :space, :data)) - xy = project_position(scene, space, Point2f(first.(imsize)), model) - xymax = project_position(scene, space, Point2f(last.(imsize)), model) + xy = cairo_project(primitive, Point2f(first.(imsize))) + xymax = cairo_project(primitive, Point2f(last.(imsize))) w, h = xymax .- xy - can_use_fast_path = !(is_vector && !interpolate) && regular_grid && identity_transform && + can_use_fast_path = !(is_vector && !interpolate) && regular_grid && identity_transform && (interpolate || xy_aligned) use_fast_path = can_use_fast_path && !disable_fast_path @@ -746,8 +730,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio else # find projected image corners # this already takes care of flipping the image to correct cairo orientation - space = to_value(get(primitive, :space, :data)) - xys = project_position.(scene, (Makie.transform_func(primitive),), space, [Point2f(x, y) for x in xs, y in ys], (model,)) + xys = cairo_project(primitive, [Point2f(x, y) for x in xs, y in ys]) colors = to_color(primitive.calculated_colors[]) # Note: xs and ys should have size ni+1, nj+1 @@ -816,24 +799,19 @@ function draw_mesh2D(scene, screen, @nospecialize(plot), @nospecialize(mesh)) vs = decompose(Point2f, mesh)::Vector{Point2f} fs = decompose(GLTriangleFace, mesh)::Vector{GLTriangleFace} uv = decompose_uv(mesh)::Union{Nothing, Vector{Vec2f}} - model = plot.model[]::Mat4f color = hasproperty(mesh, :color) ? to_color(mesh.color) : plot.calculated_colors[] cols = per_face_colors(color, nothing, fs, nothing, uv) - space = to_value(get(plot, :space, :data))::Symbol - transform_func = Makie.transform_func(plot) - return draw_mesh2D(scene, screen, cols, space, transform_func, vs, fs, model) + return draw_mesh2D(scene, screen, cols, vs, fs) end -function draw_mesh2D(scene, screen, per_face_cols, space::Symbol, transform_func, - vs::Vector{Point2f}, fs::Vector{GLTriangleFace}, model::Mat4f) - +function draw_mesh2D(plot, screen, per_face_cols, vs::Vector{Point2f}, fs::Vector{GLTriangleFace}) ctx = screen.context # Priorize colors of the mesh if present # This is a hack, which needs cleaning up in the Mesh plot type! for (f, (c1, c2, c3)) in zip(fs, per_face_cols) pattern = Cairo.CairoPatternMesh() - t1, t2, t3 = project_position.(scene, (transform_func,), space, vs[f], (model,)) #triangle points + t1, t2, t3 = cairo_project(plot, vs[f]) #triangle points Cairo.mesh_pattern_begin_patch(pattern) Cairo.mesh_pattern_move_to(pattern, t1...) @@ -861,7 +839,10 @@ end nan2zero(x) = !isnan(x) * x -function draw_mesh3D(scene, screen, attributes, mesh; pos = Vec4f(0), scale = 1f0) +function draw_mesh3D( + scene, screen, attributes, mesh; + pos = Vec3f(0), scale = Vec3f(1f0), rotation = Makie.Quaternionf(0,0,0,1) + ) # Priorize colors of the mesh if present @get_attribute(attributes, (color,)) @@ -883,36 +864,58 @@ function draw_mesh3D(scene, screen, attributes, mesh; pos = Vec4f(0), scale = 1f space = to_value(get(attributes, :space, :data))::Symbol func = Makie.transform_func(attributes) draw_mesh3D( - scene, screen, space, func, meshpoints, meshfaces, meshnormals, per_face_col, pos, scale, - model, shading::Bool, diffuse::Vec3f, - specular::Vec3f, shininess::Float32, faceculling::Int + scene, screen, + meshpoints, meshfaces, meshnormals, per_face_col, + space, func, pos, scale, rotation, model, + shading::Bool, diffuse::Vec3f, specular::Vec3f, shininess::Float32, + faceculling::Int ) end function draw_mesh3D( - scene, screen, space, transform_func, meshpoints, meshfaces, meshnormals, per_face_col, pos, scale, - model, shading, diffuse, - specular, shininess, faceculling + scene, screen, + # mesh data + meshpoints, meshfaces, meshnormals, per_face_col, + # transformation data + space, transform_func, pos::VecTypes{3}, scale::VecTypes{3}, rotation::Quaternion, model, + # lighting data + shading, diffuse, specular, shininess, + faceculling ) ctx = screen.context - @assert space in (:data, :transformed, :world) view = scene.camera.view[] - projection = scene.camera.projection[] - i = Vec(1, 2, 3) - normalmatrix = transpose(inv(view[i, i] * model[i, i])) - - # Mesh data - # transform to view/camera space - - # pass transform_func as argument to function, so that we get a function barrier - # and have `transform_func` be fully typed inside closure - vs = broadcast(meshpoints, (transform_func,)) do v, f - # Should v get a nan2zero? - v = Makie.apply_transform(f, v, space) - p4d = to_ndim(Vec4f, scale .* to_ndim(Vec3f, v, 0f0), 1f0) - view * (model * p4d .+ to_ndim(Vec4f, pos, 0f0)) + i3 = Vec(1, 2, 3) + normalmatrix = transpose(inv(view[i3, i3] * model[i3, i3])) + + # Mesh projection + + # For meshscatter - build a second model matrix from meshscatter position, + # scale (markersize) and rotation + pos = Makie.apply_transform(transform_func, pos, space) + T = Makie.transformationmatrix(pos, scale, rotation) + + if shading + # With shading == true we need eye space positions for the light calculation. + # (The transform function only applies to meshpoints here, not pos.) + projection_matrix = Makie.space_to_space_matrix(scene, space => :eye) + vs = Makie.project(projection_matrix * T, transform_func, space, :eye, meshpoints, Point4f) + ts = cairo_project(scene, vs, input_space = :eye, type = Point3f) + else + # Without it we can go directly to pixel space. + projection_matrix = Makie.space_to_space_matrix(scene, space => :pixel) + w, h = widths(pixelarea(scene)[]) + ts = Makie.project(projection_matrix * T, transform_func, space, :pixel, meshpoints, Point3f) + ts = _yflip(ts, h) + vs = Point4f[] # TODO is this needed for typing? end + # vs = broadcast(meshpoints, (transform_func,)) do v, f + # # Should v get a nan2zero? + # v = Makie.apply_transform(f, v, space) + # p4d = to_ndim(Vec4f, scale .* to_ndim(Vec3f, v, 0f0), 1f0) + # view * (model * p4d .+ to_ndim(Vec4f, pos, 0f0)) + # end + ns = map(n -> normalize(normalmatrix * n), meshnormals) # Liight math happens in view/camera space pointlight = Makie.get_point_light(scene) @@ -930,19 +933,19 @@ function draw_mesh3D( Vec3f(0) end - lightpos = (view * to_ndim(Vec4f, lightposition, 1.0))[Vec(1, 2, 3)] + lightpos = (view * to_ndim(Vec4f, lightposition, 1.0))[i3] # Camera to screen space - ts = map(vs) do v - clip = projection * v - @inbounds begin - p = (clip ./ clip[4])[Vec(1, 2)] - p_yflip = Vec2f(p[1], -p[2]) - p_0_to_1 = (p_yflip .+ 1f0) ./ 2f0 - end - p = p_0_to_1 .* scene.camera.resolution[] - return Vec3f(p[1], p[2], clip[3]) - end + # ts = map(vs) do v + # clip = projection * v + # @inbounds begin + # p = (clip ./ clip[4])[Vec(1, 2)] + # p_yflip = Vec2f(p[1], -p[2]) + # p_0_to_1 = (p_yflip .+ 1f0) ./ 2f0 + # end + # p = p_0_to_1 .* scene.camera.resolution[] + # return Vec3f(p[1], p[2], clip[3]) + # end # Approximate zorder average_zs = map(f -> average_z(ts, f), meshfaces) @@ -1056,15 +1059,18 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Maki m = convert_attribute(marker, key"marker"(), key"meshscatter"()) pos = primitive[1][] - # For correct z-ordering we need to be in view/camera or screen space - model = copy(model) - view = scene.camera.view[] - zorder = sortperm(pos, by = p -> begin - p4d = to_ndim(Vec4f, to_ndim(Vec3f, p, 0f0), 1f0) - cam_pos = view * model * p4d - cam_pos[3] / cam_pos[4] - end, rev=false) + # For correct z-ordering we need to be in eye (camera), clip, pixel or + # relative space + if to_value(get(primitive, :space, :data)) in (:data, :transformed, :world) + zs = last.(Makie.project(primitive, pos, output_space = :eye)) + zorder = sortperm(zs, rev = false) + elseif length(pos[1]) > 2 + zs = getindex.(zs, 3) + zorder = sortperm(zs, rev = false) + else + zorder = eachindex(pos) + end color = to_color(primitive.calculated_colors[]) submesh = Attributes( @@ -1077,25 +1083,18 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Maki ) - if !(rotations isa Vector) - R = Makie.rotationmatrix4(to_rotation(rotations)) - submesh[:model] = model * R - end - scales = primitive[:markersize][] for i in zorder p = pos[i] if color isa AbstractVector submesh[:calculated_colors] = color[i] end - if rotations isa Vector - R = Makie.rotationmatrix4(to_rotation(rotations[i])) - submesh[:model] = model * R - end scale = markersize isa Vector ? markersize[i] : markersize draw_mesh3D( - scene, screen, submesh, m, pos = p, - scale = scale isa Real ? Vec3f(scale) : to_ndim(Vec3f, scale, 1f0) + scene, screen, submesh, m, + pos = p, + scale = scale isa Real ? Vec3f(scale) : to_ndim(Vec3f, scale, 1f0), + rotation = rotations isa Vector ? to_rotation(rotations[i]) : to_rotation(rotations) ) end diff --git a/CairoMakie/src/utils.jl b/CairoMakie/src/utils.jl index 1560b83a52d..4909a43ba95 100644 --- a/CairoMakie/src/utils.jl +++ b/CairoMakie/src/utils.jl @@ -2,33 +2,21 @@ # Projection utilities # ################################################################################ -function project_position(scene::Scene, transform_func::T, space, point, model::Mat4, yflip::Bool = true) where T - # use transform func - point = Makie.apply_transform(transform_func, point, space) - _project_position(scene, space, point, model, yflip) -end +""" + cairo_project(plot, pos[; yflip = true, kwargs...]) -# TODO Makie.project instead -function _project_position(scene, space, point, model, yflip) - res = scene.camera.resolution[] - p4d = to_ndim(Vec4f, to_ndim(Vec3f, point, 0f0), 1f0) - clip = Makie.space_to_space_matrix(scene, space => :clip) * model * p4d - @inbounds begin - # between -1 and 1 - p = (clip ./ clip[4])[Vec(1, 2)] - # flip y to match cairo - p_yflip = Vec2f(p[1], (1f0 - 2f0 * yflip) * p[2]) - # normalize to between 0 and 1 - p_0_to_1 = (p_yflip .+ 1f0) ./ 2f0 - end - # multiply with scene resolution for final position - return p_0_to_1 .* res +Calls `Makie.project(plot, pos; kwargs...)` but returns 2D Points and optionally +flips the y axis. +""" +function cairo_project(plot, pos; yflip = true, type = Point2f, kwargs...) + w, h = widths(Makie.pixelarea(Makie.get_scene(plot))[]) + ps = Makie.project(plot, pos; type = type, kwargs...) + return yflip ? _yflip(ps, h) : ps end -function project_position(scenelike, space, point, model, yflip::Bool = true) - scene = Makie.get_scene(scenelike) - project_position(scene, Makie.transform_func(scenelike), space, point, model, yflip) -end +_yflip(p::VT, h) where {T <: Real, VT <: VecTypes{2, T}} = VT(p[1], h - p[2]) +_yflip(p::VT, h) where {T <: Real, VT <: VecTypes{3, T}} = VT(p[1], h - p[2], p[3]) +_yflip(ps::AbstractArray, h) = _yflip.(ps, h) function project_scale(scene::Scene, space, s::Number, model = Mat4f(I)) project_scale(scene, space, Vec2f(s), model) @@ -48,23 +36,23 @@ function project_scale(scene::Scene, space, s, model = Mat4f(I)) end end -function project_rect(scenelike, space, rect::Rect, model) - mini = project_position(scenelike, space, minimum(rect), model) - maxi = project_position(scenelike, space, maximum(rect), model) - return Rect(mini, maxi .- mini) +function cairo_project(plot, rect::Rect; kwargs...) + mini = cairo_project(plot, minimum(rect); kwargs...) + maxi = cairo_project(plot, maximum(rect); kwargs...) + return Rect(Vec(mini), Vec(maxi .- mini)) end -function project_polygon(scenelike, space, poly::P, model) where P <: Polygon +function cairo_project(plot, poly::P; type = Point2f, kwargs...) where P <: Polygon ext = decompose(Point2f, poly.exterior) interiors = decompose.(Point2f, poly.interiors) Polygon( - Point2f.(project_position.(Ref(scenelike), space, ext, Ref(model))), - [Point2f.(project_position.(Ref(scenelike), space, interior, Ref(model))) for interior in interiors], + cairo_project(plot, ext; type = type, kwargs...), + Vector{type}[cairo_project(plot, interior; type = type, kwargs...) for interior in interiors] ) end -function project_multipolygon(scenelike, space, multipoly::MP, model) where MP <: MultiPolygon - return MultiPolygon(project_polygon.(Ref(scenelike), Ref(space), multipoly.polygons, Ref(model))) +function cairo_project(plot, multipoly::MP; kwargs...) where MP <: MultiPolygon + return MultiPolygon(cairo_project.((plot, ), multipoly.polygons; kwargs...)) end scale_matrix(x, y) = Cairo.CairoMatrix(x, 0.0, 0.0, y, 0.0, 0.0) diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index 16de4a6346c..45b271b526b 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -479,7 +479,7 @@ For a simpler interface, use `project(scenelike, pos)`. function project( mat::Mat4, tf, input_space::Symbol, output_space::Symbol, pos::AbstractArray{<: VecTypes{N, T}}, type = Point3{T} - ) where {N, T} + ) where {N, T <: Real} if input_space === output_space return to_ndim.(type, pos, 0) elseif output_space !== :data @@ -491,7 +491,7 @@ function project( end function project( mat::Mat4, tf, input_space::Symbol, output_space::Symbol, - pos::VecTypes{N, T}, type = Point3{T}) where {N, T} + pos::VecTypes{N, T}, type = Point3{T}) where {N, T <: Real} if input_space === output_space return to_ndim(type, pos, 0) elseif output_space !== :data diff --git a/src/utilities/utilities.jl b/src/utilities/utilities.jl index 18f0947d876..af8defa2e81 100644 --- a/src/utilities/utilities.jl +++ b/src/utilities/utilities.jl @@ -242,6 +242,9 @@ function same_length_array(arr, value::Vector) end same_length_array(arr, value, key) = same_length_array(arr, convert_attribute(value, key)) +function to_ndim(T::Type{<: VecTypes}, vecs::AbstractArray{<: VecTypes}, fillval) + return map(vec -> to_ndim(T, vec, fillval), vecs) +end function to_ndim(T::Type{<: VecTypes{N,ET}}, vec::VecTypes{N2}, fillval) where {N,ET,N2} T(ntuple(Val(N)) do i i > N2 && return ET(fillval) From 1312a9a965157077764c82f69ee7f05c8df106ae Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sun, 2 Jul 2023 16:27:57 +0200 Subject: [PATCH 07/24] more nospecialize and some reorganization --- CairoMakie/src/CairoMakie.jl | 3 +- CairoMakie/src/primitives.jl | 85 ++++++++++++++++++----------------- CairoMakie/src/utils.jl | 6 +-- src/camera/projection_math.jl | 2 +- src/utilities/utilities.jl | 4 +- 5 files changed, 54 insertions(+), 46 deletions(-) diff --git a/CairoMakie/src/CairoMakie.jl b/CairoMakie/src/CairoMakie.jl index b5995fe1f33..51e19c8d6db 100644 --- a/CairoMakie/src/CairoMakie.jl +++ b/CairoMakie/src/CairoMakie.jl @@ -9,9 +9,10 @@ import Cairo using Makie: Scene, Lines, Text, Image, Heatmap, Scatter, @key_str, broadcast_foreach using Makie: convert_attribute, @extractvalue, LineSegments, to_ndim, NativeFont using Makie: @info, @get_attribute, Combined, MakieScreen -using Makie: to_value, to_colormap, extrema_nan +using Makie: to_value, to_colormap, extrema_nan, get_value using Makie.Observables using Makie: spaces, is_data_space, is_pixel_space, is_relative_space, is_clip_space +using Makie: project, pixelarea using Makie: numbers_to_colors # re-export Makie, including deprecated names diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index 05a08de7b15..ae5fa10537b 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -77,7 +77,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio nothing end -function draw_bezierpath_lines(ctx, bezierpath::BezierPath, plot, color, linewidth) +function draw_bezierpath_lines(ctx, bezierpath::BezierPath, @nospecialize(plot), color, linewidth) for c in bezierpath.commands proj_comm = project_command(c, plot) path_command(ctx, proj_comm) @@ -88,15 +88,15 @@ function draw_bezierpath_lines(ctx, bezierpath::BezierPath, plot, color, linewid return end -function project_command(m::MoveTo, plot) +function project_command(m::MoveTo, @nospecialize(plot)) MoveTo(cairo_project(plot, m.p)) end -function project_command(l::LineTo, plot) +function project_command(l::LineTo, @nospecialize(plot)) LineTo(cairo_project(plot, l.p)) end -function project_command(c::CurveTo, plot) +function project_command(c::CurveTo, @nospecialize(plot)) CurveTo( cairo_project(plot, c.c1), cairo_project(plot, c.c2), @@ -104,9 +104,9 @@ function project_command(c::CurveTo, plot) ) end -project_command(c::ClosePath, plot) = c +project_command(c::ClosePath, @nospecialize(plot)) = c -function draw_single(primitive::Lines, ctx, positions) +function draw_single(@nospecialize(primitive::Lines), ctx, positions) n = length(positions) @inbounds for i in 1:n p = positions[i] @@ -128,7 +128,7 @@ function draw_single(primitive::Lines, ctx, positions) Cairo.new_path(ctx) end -function draw_single(primitive::LineSegments, ctx, positions) +function draw_single(@nospecialize(primitive::LineSegments), ctx, positions) @assert iseven(length(positions)) @@ -149,17 +149,22 @@ function draw_single(primitive::LineSegments, ctx, positions) end # if linewidth is not an array -function draw_multi(primitive, ctx, positions, colors::AbstractArray, linewidth, dash) +function draw_multi(@nospecialize(primitive), ctx, positions, colors::AbstractArray, linewidth, dash) draw_multi(primitive, ctx, positions, colors, [linewidth for c in colors], dash) end # if color is not an array -function draw_multi(primitive, ctx, positions, color, linewidths::AbstractArray, dash) +function draw_multi(@nospecialize(primitive), ctx, positions, color, linewidths::AbstractArray, dash) draw_multi(primitive, ctx, positions, [color for l in linewidths], linewidths, dash) end -function draw_multi(primitive::LineSegments, ctx, positions, colors::AbstractArray, linewidths::AbstractArray, dash) - @assert iseven(length(positions)) +function draw_multi( + @nospecialize(primitive::Union{Lines, LineSegments}), ctx, positions, + colors::AbstractArray, linewidths::AbstractArray, dash + ) + if primitive isa LineSegments + @assert iseven(length(positions)) + end @assert length(positions) == length(colors) @assert length(linewidths) == length(colors) @@ -299,11 +304,9 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Scat positions = primitive[1][] isempty(positions) && return size_model = transform_marker ? model : Mat4f(I) - font = to_font(to_value(get(primitive, :font, Makie.defaultfont()))) - + font = to_font(get_value(primitive, :font, Makie.defaultfont())) colors = to_color(primitive.calculated_colors[]) - - markerspace = to_value(get(primitive, :markerspace, :pixel)) + markerspace = get_value(primitive, :markerspace, :pixel) marker_conv = _marker_convert(marker) draw_atomic_scatter(scene, ctx, primitive, colors, markersize, strokecolor, strokewidth, marker_conv, marker_offset, rotations, positions, size_model, font, markerspace) @@ -315,7 +318,11 @@ _marker_convert(marker) = convert_attribute(marker, key"marker"(), key"scatter"( # image arrays need to be converted as a whole _marker_convert(marker::AbstractMatrix{<:Colorant}) = [ convert_attribute(marker, key"marker"(), key"scatter"()) ] -function draw_atomic_scatter(scene, ctx, plot, colors, markersize, strokecolor, strokewidth, marker, marker_offset, rotations, positions, size_model, font, markerspace) +function draw_atomic_scatter( + scene, ctx, @nospecialize(plot), colors, markersize, strokecolor, strokewidth, + marker, marker_offset, rotations, positions, size_model, font, markerspace + ) + broadcast_foreach(positions, colors, markersize, strokecolor, strokewidth, marker, marker_offset, remove_billboard(rotations)) do point, col, markersize, strokecolor, strokewidth, m, mo, rotation @@ -669,7 +676,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Unio # Debug attribute we can set to disable fastpath # probably shouldn't really be part of the interface - fast_path = to_value(get(primitive, :fast_path, true)) + fast_path = get_value(primitive, :fast_path, true) disable_fast_path = !fast_path # Vector backends don't support FILTER_NEAREST for interp == false, so in that case we also need to draw rects is_vector = is_vector_backend(ctx) @@ -790,7 +797,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Maki if !haskey(primitive, :faceculling) primitive[:faceculling] = Observable(-10) end - draw_mesh3D(scene, screen, primitive, mesh) + draw_mesh3D(scene, screen, primitive, primitive.attributes, mesh) end return nothing end @@ -804,7 +811,7 @@ function draw_mesh2D(scene, screen, @nospecialize(plot), @nospecialize(mesh)) return draw_mesh2D(scene, screen, cols, vs, fs) end -function draw_mesh2D(plot, screen, per_face_cols, vs::Vector{Point2f}, fs::Vector{GLTriangleFace}) +function draw_mesh2D(@nospecialize(plot), screen, per_face_cols, vs::Vector{Point2f}, fs::Vector{GLTriangleFace}) ctx = screen.context # Priorize colors of the mesh if present # This is a hack, which needs cleaning up in the Mesh plot type! @@ -840,7 +847,7 @@ nan2zero(x) = !isnan(x) * x function draw_mesh3D( - scene, screen, attributes, mesh; + scene, screen, @nospecialize(plot), attributes, mesh; pos = Vec3f(0), scale = Vec3f(1f0), rotation = Makie.Quaternionf(0,0,0,1) ) # Priorize colors of the mesh if present @@ -857,54 +864,51 @@ function draw_mesh3D( per_face_col = per_face_colors(color, matcap, meshfaces, meshnormals, meshuvs) - @get_attribute(attributes, (shading, diffuse, - specular, shininess, faceculling)) + @get_attribute(attributes, (shading, diffuse, specular, shininess, faceculling)) - model = attributes.model[]::Mat4f - space = to_value(get(attributes, :space, :data))::Symbol - func = Makie.transform_func(attributes) draw_mesh3D( - scene, screen, + scene, screen, plot, meshpoints, meshfaces, meshnormals, per_face_col, - space, func, pos, scale, rotation, model, + pos, scale, rotation, shading::Bool, diffuse::Vec3f, specular::Vec3f, shininess::Float32, faceculling::Int ) end function draw_mesh3D( - scene, screen, + scene, screen, @nospecialize(plot), # mesh data meshpoints, meshfaces, meshnormals, per_face_col, # transformation data - space, transform_func, pos::VecTypes{3}, scale::VecTypes{3}, rotation::Quaternion, model, + pos::VecTypes{3}, scale::VecTypes{3}, rotation::Quaternion, # lighting data shading, diffuse, specular, shininess, faceculling ) ctx = screen.context + space = get_value(plot, :space, :data) + transform_func = Makie.transform_func(plot) view = scene.camera.view[] i3 = Vec(1, 2, 3) - normalmatrix = transpose(inv(view[i3, i3] * model[i3, i3])) # Mesh projection # For meshscatter - build a second model matrix from meshscatter position, - # scale (markersize) and rotation + # scale (markersize) and rotation and combine with `model` pos = Makie.apply_transform(transform_func, pos, space) T = Makie.transformationmatrix(pos, scale, rotation) if shading # With shading == true we need eye space positions for the light calculation. # (The transform function only applies to meshpoints here, not pos.) - projection_matrix = Makie.space_to_space_matrix(scene, space => :eye) - vs = Makie.project(projection_matrix * T, transform_func, space, :eye, meshpoints, Point4f) + projection_matrix = Makie.space_to_space_matrix(plot, space => :eye) + vs = project(projection_matrix * T, transform_func, space, :eye, meshpoints, Point4f) ts = cairo_project(scene, vs, input_space = :eye, type = Point3f) else # Without it we can go directly to pixel space. - projection_matrix = Makie.space_to_space_matrix(scene, space => :pixel) + projection_matrix = Makie.space_to_space_matrix(plot, space => :pixel) w, h = widths(pixelarea(scene)[]) - ts = Makie.project(projection_matrix * T, transform_func, space, :pixel, meshpoints, Point3f) + ts = project(projection_matrix * T, transform_func, space, :pixel, meshpoints, Point3f) ts = _yflip(ts, h) vs = Point4f[] # TODO is this needed for typing? end @@ -916,6 +920,8 @@ function draw_mesh3D( # view * (model * p4d .+ to_ndim(Vec4f, pos, 0f0)) # end + model = plot.model[] * Makie.rotationmatrix4(rotation) + normalmatrix = transpose(inv(view[i3, i3] * model[i3, i3])) ns = map(n -> normalize(normalmatrix * n), meshnormals) # Liight math happens in view/camera space pointlight = Makie.get_point_light(scene) @@ -1035,7 +1041,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Maki if !haskey(primitive, :faceculling) primitive[:faceculling] = Observable(-10) end - draw_mesh3D(scene, screen, primitive, mesh) + draw_mesh3D(scene, screen, primitive, primitive.attributes, mesh) primitive[:color] = old return nothing end @@ -1062,7 +1068,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Maki # For correct z-ordering we need to be in eye (camera), clip, pixel or # relative space - if to_value(get(primitive, :space, :data)) in (:data, :transformed, :world) + if get_value(primitive, :space, :data) in (:data, :transformed, :world) zs = last.(Makie.project(primitive, pos, output_space = :eye)) zorder = sortperm(zs, rev = false) elseif length(pos[1]) > 2 @@ -1078,9 +1084,8 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Maki calculated_colors = color, shading=primitive.shading, diffuse=primitive.diffuse, specular=primitive.specular, shininess=primitive.shininess, - faceculling=get(primitive, :faceculling, -10), + faceculling=get_value(primitive, :faceculling, -10), transformation=Makie.transformation(primitive) - ) for i in zorder @@ -1091,7 +1096,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Maki scale = markersize isa Vector ? markersize[i] : markersize draw_mesh3D( - scene, screen, submesh, m, + scene, screen, primitive, submesh, m, pos = p, scale = scale isa Real ? Vec3f(scale) : to_ndim(Vec3f, scale, 1f0), rotation = rotations isa Vector ? to_rotation(rotations[i]) : to_rotation(rotations) diff --git a/CairoMakie/src/utils.jl b/CairoMakie/src/utils.jl index 4909a43ba95..60b980c7c64 100644 --- a/CairoMakie/src/utils.jl +++ b/CairoMakie/src/utils.jl @@ -5,12 +5,12 @@ """ cairo_project(plot, pos[; yflip = true, kwargs...]) -Calls `Makie.project(plot, pos; kwargs...)` but returns 2D Points and optionally +Calls `project(plot, pos; kwargs...)` but returns 2D Points and optionally flips the y axis. """ function cairo_project(plot, pos; yflip = true, type = Point2f, kwargs...) - w, h = widths(Makie.pixelarea(Makie.get_scene(plot))[]) - ps = Makie.project(plot, pos; type = type, kwargs...) + w, h = widths(pixelarea(Makie.get_scene(plot))[]) + ps = project(plot, pos; type = type, kwargs...) return yflip ? _yflip(ps, h) : ps end diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index 45b271b526b..20b816cc5c4 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -436,7 +436,7 @@ you. """ function project( plot::AbstractPlot, pos; - input_space::Symbol = to_value(get(plot, :space, :data)), + input_space::Symbol = get_value(plot, :space, :data), output_space::Symbol = :pixel, type = _point3_type(pos) ) diff --git a/src/utilities/utilities.jl b/src/utilities/utilities.jl index af8defa2e81..eb34e745bbc 100644 --- a/src/utilities/utilities.jl +++ b/src/utilities/utilities.jl @@ -150,6 +150,8 @@ end @inline getindex_value(x::Union{Dict,Attributes,AbstractPlot}, key::Symbol) = to_value(x[key]) @inline getindex_value(x, key::Symbol) = to_value(getfield(x, key)) +@inline get_value(obj, key, default) = to_value(get(obj, key, default)) + """ usage @extractvalue scene (a, b, c, d) will become: @@ -356,4 +358,4 @@ end # Scalar - Vector getindex sv_getindex(v::Vector, i::Integer) = v[i] -sv_getindex(x, i::Integer) = x +sv_getindex(x, i::Integer) = x \ No newline at end of file From 95429593fbf4f37bdf4975a13133703a5583fc9a Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sun, 2 Jul 2023 16:57:56 +0200 Subject: [PATCH 08/24] fix zorder --- CairoMakie/src/primitives.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index ae5fa10537b..521318dc795 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -955,7 +955,7 @@ function draw_mesh3D( # Approximate zorder average_zs = map(f -> average_z(ts, f), meshfaces) - zorder = sortperm(average_zs) + zorder = sortperm(average_zs, rev = true) # Face culling zorder = filter(i -> any(last.(ns[meshfaces[i]]) .> faceculling), zorder) From eba581c6c7979e60d7f69b667979c61de7591f20 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Mon, 3 Jul 2023 13:28:27 +0200 Subject: [PATCH 09/24] cleanup old code --- CairoMakie/src/primitives.jl | 29 +--------- CairoMakie/src/utils.jl | 15 +++--- GLMakie/src/drawing_primitives.jl | 3 +- src/camera/projection_math.jl | 89 +------------------------------ 4 files changed, 11 insertions(+), 125 deletions(-) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index 521318dc795..0a5c7555291 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -541,15 +541,7 @@ function draw_glyph_collection(@nospecialize(plot), ctx, position, glyph_collect strokecolors = glyph_collection.strokecolors model33 = Makie.to_value(plot.model)[Vec(1, 2, 3), Vec(1, 2, 3)] - glyph_pos = Makie.project(plot, position, output_space = markerspace) - # let - # transform_func = transformation.transform_func[] - # p = Makie.apply_transform(transform_func, position, space) - - # Makie.space_to_space_matrix(scene, space => markerspace) * - # model * to_ndim(Point4f, to_ndim(Point3f, p, 0), 1) - # end Cairo.save(ctx) @@ -909,17 +901,10 @@ function draw_mesh3D( projection_matrix = Makie.space_to_space_matrix(plot, space => :pixel) w, h = widths(pixelarea(scene)[]) ts = project(projection_matrix * T, transform_func, space, :pixel, meshpoints, Point3f) - ts = _yflip(ts, h) + ts = yflip(ts, h) vs = Point4f[] # TODO is this needed for typing? end - # vs = broadcast(meshpoints, (transform_func,)) do v, f - # # Should v get a nan2zero? - # v = Makie.apply_transform(f, v, space) - # p4d = to_ndim(Vec4f, scale .* to_ndim(Vec3f, v, 0f0), 1f0) - # view * (model * p4d .+ to_ndim(Vec4f, pos, 0f0)) - # end - model = plot.model[] * Makie.rotationmatrix4(rotation) normalmatrix = transpose(inv(view[i3, i3] * model[i3, i3])) ns = map(n -> normalize(normalmatrix * n), meshnormals) @@ -941,18 +926,6 @@ function draw_mesh3D( lightpos = (view * to_ndim(Vec4f, lightposition, 1.0))[i3] - # Camera to screen space - # ts = map(vs) do v - # clip = projection * v - # @inbounds begin - # p = (clip ./ clip[4])[Vec(1, 2)] - # p_yflip = Vec2f(p[1], -p[2]) - # p_0_to_1 = (p_yflip .+ 1f0) ./ 2f0 - # end - # p = p_0_to_1 .* scene.camera.resolution[] - # return Vec3f(p[1], p[2], clip[3]) - # end - # Approximate zorder average_zs = map(f -> average_z(ts, f), meshfaces) zorder = sortperm(average_zs, rev = true) diff --git a/CairoMakie/src/utils.jl b/CairoMakie/src/utils.jl index 60b980c7c64..b9102866b40 100644 --- a/CairoMakie/src/utils.jl +++ b/CairoMakie/src/utils.jl @@ -3,20 +3,19 @@ ################################################################################ """ - cairo_project(plot, pos[; yflip = true, kwargs...]) + cairo_project(plot, pos[; kwargs...]) -Calls `project(plot, pos; kwargs...)` but returns 2D Points and optionally -flips the y axis. +Calls `project(plot, pos; kwargs...)` and flips the y axis of the result. """ -function cairo_project(plot, pos; yflip = true, type = Point2f, kwargs...) +function cairo_project(plot, pos; type = Point2f, kwargs...) w, h = widths(pixelarea(Makie.get_scene(plot))[]) ps = project(plot, pos; type = type, kwargs...) - return yflip ? _yflip(ps, h) : ps + return yflip(ps, h) end -_yflip(p::VT, h) where {T <: Real, VT <: VecTypes{2, T}} = VT(p[1], h - p[2]) -_yflip(p::VT, h) where {T <: Real, VT <: VecTypes{3, T}} = VT(p[1], h - p[2], p[3]) -_yflip(ps::AbstractArray, h) = _yflip.(ps, h) +yflip(p::VT, h) where {T <: Real, VT <: VecTypes{2, T}} = VT(p[1], h - p[2]) +yflip(p::VT, h) where {T <: Real, VT <: VecTypes{3, T}} = VT(p[1], h - p[2], p[3]) +yflip(ps::AbstractArray, h) = yflip.(ps, h) function project_scale(scene::Scene, space, s::Number, model = Mat4f(I)) project_scale(scene, space, Vec2f(s), model) diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index c3daddb3ad8..07507744a17 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -42,7 +42,8 @@ function connect_camera!(plot, gl_attributes, cam, space = gl_attributes[:space] gl_attributes[key] = lift(identity, plot, getfield(cam, key)) end - # TODO we can probably get rid of these? + # TODO should these be input -> eye and eye -> clip? + # They are used for lighting (check) get!(gl_attributes, :view) do return lift(plot, cam.view, space) do view, space return space in (:data, :transformed, :world) ? view : Mat4f(I) diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index 20b816cc5c4..905e48c1ca7 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -240,94 +240,7 @@ end ################################################################################ -### Old Projection code -################################################################################ - -# function to_world(scene::Scene, point::T) where T <: StaticVector -# cam = scene.camera -# x = to_world( -# point, -# inv(transformationmatrix(scene)[]) * -# inv(cam.view[]) * -# inv(cam.projection[]), -# T(widths(pixelarea(scene)[])) -# ) -# Point2f(x[1], x[2]) -# end - -# w_component(x::Point) = 1.0 -# w_component(x::Vec) = 0.0 - -# function to_world( -# p::StaticVector{N, T}, -# prj_view_inv::Mat4, -# cam_res::StaticVector -# ) where {N, T} -# VT = typeof(p) -# clip_space = ((VT(p) ./ VT(cam_res)) .* T(2)) .- T(1) -# pix_space = Vec{4, T}( -# clip_space[1], -# clip_space[2], -# T(0), w_component(p) -# ) -# ws = prj_view_inv * pix_space -# ws ./ ws[4] -# end - -# function to_world( -# p::Vec{N, T}, -# prj_view_inv::Mat4, -# cam_res::StaticVector -# ) where {N, T} -# to_world(Point(p), prj_view_inv, cam_res) .- -# to_world(zeros(Point{N, T}), prj_view_inv, cam_res) -# end - -# function project(scene::Scene, point::T) where T<:StaticVector -# cam = scene.camera -# area = pixelarea(scene)[] -# # TODO, I think we need .+ minimum(area) -# # Which would be semi breaking at this point though, I suppose -# return project( -# cam.projectionview[] * -# transformationmatrix(scene)[], -# Vec2f(widths(area)), -# Point(point) -# ) -# end - -# function project(matrix::Mat4f, p::T, dim4 = 1.0) where T <: VecTypes -# p = to_ndim(Vec4f, to_ndim(Vec3f, p, 0.0), dim4) -# p = matrix * p -# to_ndim(T, p, 0.0) -# end - -# function project(proj_view::Mat4f, resolution::Vec2, point::Point) -# p4d = to_ndim(Vec4f, to_ndim(Vec3f, point, 0f0), 1f0) -# clip = proj_view * p4d -# p = (clip ./ clip[4])[Vec(1, 2)] -# p = Vec2f(p[1], p[2]) -# return (((p .+ 1f0) ./ 2f0) .* (resolution .- 1f0)) .+ 1f0 -# end - -# function project_point2(mat4::Mat4, point2::Point2) -# Point2f(mat4 * to_ndim(Point4f, to_ndim(Point3f, point2, 0), 1)) -# end - -# function transform(model::Mat4, x::T) where T -# x4d = to_ndim(Vec4f, x, 0.0) -# to_ndim(T, model * x4d, 0.0) -# end - -# function project(cam::Camera, input_space::Symbol, output_space::Symbol, pos) -# input_space === output_space && return to_ndim(Point3f, pos, 0) -# p4d = to_ndim(Point4f, to_ndim(Point3f, pos, 0), 1) -# transformed = space_to_space_matrix(cam, input_space, output_space) * p4d -# return Point3f(transformed[Vec(1, 2, 3)] ./ transformed[4]) -# end - -################################################################################ -### new projection code +### `project` functionality ################################################################################ From 6149c5c64bb7a6a9da8aa1dea8121000b38f0836 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Mon, 3 Jul 2023 13:34:06 +0200 Subject: [PATCH 10/24] fix test failure --- test/transformations.jl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/transformations.jl b/test/transformations.jl index fc4399fd89b..5ab6aad5d63 100644 --- a/test/transformations.jl +++ b/test/transformations.jl @@ -110,8 +110,12 @@ end end @testset "Coordinate Systems" begin - funcs = [Makie.is_data_space, Makie.is_pixel_space, Makie.is_relative_space, Makie.is_clip_space] - spaces = [:data, :pixel, :relative, :clip] + funcs = [ + Makie.is_data_space, Makie.is_transformed_space, Makie.is_world_space, + Makie.is_eye_space, Makie.is_clip_space, + Makie.is_pixel_space, Makie.is_relative_space + ] + spaces = [:data, :transformed, :world, :eye, :clip, :pixel, :relative] for (i, f) in enumerate(funcs) for j in 1:4 @test f(spaces[j]) == (i == j) @@ -122,7 +126,7 @@ end scatter!(scene, [Point3f(-10), Point3f(10)]) for space in vcat(spaces...) @test Makie.space_to_space_matrix(scene, :clip => space) * - Makie.space_to_space_matrix(scene.camera, space => :clip) ≈ Mat4f(I) + Makie.space_to_space_matrix(scene, space => :clip) ≈ Mat4f(I) end end From 474a24a0596967785bbfa63905d30aab61ade4a5 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Mon, 3 Jul 2023 14:30:32 +0200 Subject: [PATCH 11/24] improve performance --- CairoMakie/src/primitives.jl | 12 +++---- CairoMakie/src/utils.jl | 10 +++--- src/camera/projection_math.jl | 67 ++++++++++++++++++----------------- src/utilities/utilities.jl | 6 ++++ 4 files changed, 51 insertions(+), 44 deletions(-) diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index 0a5c7555291..ded9a852cb5 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -894,15 +894,15 @@ function draw_mesh3D( # With shading == true we need eye space positions for the light calculation. # (The transform function only applies to meshpoints here, not pos.) projection_matrix = Makie.space_to_space_matrix(plot, space => :eye) - vs = project(projection_matrix * T, transform_func, space, :eye, meshpoints, Point4f) - ts = cairo_project(scene, vs, input_space = :eye, type = Point3f) + vs = project(projection_matrix * T, transform_func, space, :eye, meshpoints) + ts = cairo_project(scene, vs, input_space = :eye, target = Point3f(0)) else # Without it we can go directly to pixel space. projection_matrix = Makie.space_to_space_matrix(plot, space => :pixel) w, h = widths(pixelarea(scene)[]) - ts = project(projection_matrix * T, transform_func, space, :pixel, meshpoints, Point3f) + ts = project(projection_matrix * T, transform_func, space, :pixel, meshpoints) ts = yflip(ts, h) - vs = Point4f[] # TODO is this needed for typing? + vs = Point3f[] # TODO is this needed for typing? end model = plot.model[] * Makie.rotationmatrix4(rotation) @@ -938,9 +938,9 @@ function draw_mesh3D( end function _calculate_shaded_vertexcolors(N, v, c, lightpos, ambient, diffuse, specular, shininess) - L = normalize(lightpos .- v[Vec(1,2,3)]) + L = normalize(lightpos .- v) diff_coeff = max(dot(L, N), 0f0) - H = normalize(L + normalize(-v[Vec(1, 2, 3)])) + H = normalize(L + normalize(-v)) spec_coeff = max(dot(H, N), 0f0)^shininess c = RGBAf(c) # if this is one expression it introduces allocations?? diff --git a/CairoMakie/src/utils.jl b/CairoMakie/src/utils.jl index b9102866b40..f6f607302dc 100644 --- a/CairoMakie/src/utils.jl +++ b/CairoMakie/src/utils.jl @@ -7,9 +7,9 @@ Calls `project(plot, pos; kwargs...)` and flips the y axis of the result. """ -function cairo_project(plot, pos; type = Point2f, kwargs...) +function cairo_project(plot, pos; target = Point2f(0), kwargs...) w, h = widths(pixelarea(Makie.get_scene(plot))[]) - ps = project(plot, pos; type = type, kwargs...) + ps = project(plot, pos; target = target, kwargs...) return yflip(ps, h) end @@ -41,12 +41,12 @@ function cairo_project(plot, rect::Rect; kwargs...) return Rect(Vec(mini), Vec(maxi .- mini)) end -function cairo_project(plot, poly::P; type = Point2f, kwargs...) where P <: Polygon +function cairo_project(plot, poly::P; kwargs...) where P <: Polygon ext = decompose(Point2f, poly.exterior) interiors = decompose.(Point2f, poly.interiors) Polygon( - cairo_project(plot, ext; type = type, kwargs...), - Vector{type}[cairo_project(plot, interior; type = type, kwargs...) for interior in interiors] + cairo_project(plot, ext; kwargs...), + Vector{Point2f}[cairo_project(plot, interior; kwargs...) for interior in interiors] ) end diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index 905e48c1ca7..eb1d5a802bc 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -325,7 +325,7 @@ end # For users/convenient project functions: """ - project(scenelike, pos[; input_space, output_space = :pixel, type = Point3f]) + project(scenelike, pos[; input_space, output_space = :pixel, target = Point3f(0)]) Projects the given positional data from the space of the given plot, scene or axis (`scenelike`) to pixel space. Optionally the input and output space can be @@ -350,34 +350,34 @@ you. function project( plot::AbstractPlot, pos; input_space::Symbol = get_value(plot, :space, :data), - output_space::Symbol = :pixel, type = _point3_type(pos) + output_space::Symbol = :pixel, target = _point3_target(pos) ) tf = transform_func(plot) mat = space_to_space_matrix(plot, input_space => output_space) - return project(mat, tf, input_space, output_space, pos, type) + return project(mat, tf, input_space, output_space, pos, target) end function project( obj, pos; input_space::Symbol = :data, output_space::Symbol = :pixel, - type = _point3_type(pos) + target = _point3_target(pos) ) tf = transform_func(get_scene(obj)) # this should error with a camera mat = space_to_space_matrix(obj, input_space => output_space) - return project(mat, tf, input_space, output_space, pos, type) + return project(mat, tf, input_space, output_space, pos, target) end -function project(cam::Camera, input_space::Symbol, output_space::Symbol, pos; type = _point3_type(pos)) +function project(cam::Camera, input_space::Symbol, output_space::Symbol, pos; target = _point3_target(pos)) @assert input_space !== :data "Cannot transform from :data space with just the camera." @assert output_space !== :data "Cannot transform to :data space with just the camera." mat = space_to_space_matrix(cam, input_space => output_space) - return project(mat, indentity, input_space, output_space, pos, type) + return project(mat, indentity, input_space, output_space, pos, target) end -_point3_type(::VecTypes{N, T}) where {N, T} = Point3{T} -_point3_type(::AbstractArray{<: VecTypes{N, T}}) where {N, T} = Point3{T} +_point3_target(::VecTypes{N, T}) where {N, T} = Point3{T}(0) +_point3_target(::AbstractArray{<: VecTypes{N, T}}) where {N, T} = Point3{T}(0) # Internal / Implementations @@ -391,52 +391,53 @@ For a simpler interface, use `project(scenelike, pos)`. """ function project( mat::Mat4, tf, input_space::Symbol, output_space::Symbol, - pos::AbstractArray{<: VecTypes{N, T}}, type = Point3{T} + pos::AbstractArray{<: VecTypes{N, T}}, target::VecTypes = Point3{T}(0) ) where {N, T <: Real} if input_space === output_space - return to_ndim.(type, pos, 0) + return to_ndim.((target,), pos) elseif output_space !== :data - return map(p -> project(mat, tf, input_space, p, type), pos) + return map(p -> project(mat, tf, input_space, p, target), pos) else itf = inverse_transform(tf) - return map(p -> inv_project(mat, itf, p, type), pos) + return map(p -> inv_project(mat, itf, p, target), pos) end end function project( mat::Mat4, tf, input_space::Symbol, output_space::Symbol, - pos::VecTypes{N, T}, type = Point3{T}) where {N, T <: Real} + pos::VecTypes{N, T}, target = Point3{T}(0)) where {N, T <: Real} if input_space === output_space - return to_ndim(type, pos, 0) + return to_ndim(target, pos) elseif output_space !== :data - return project(mat, tf, input_space, pos, type) + return project(mat, tf, input_space, pos, target) else itf = inverse_transform(tf) - return inv_project(mat, itf, pos, type) + return inv_project(mat, itf, pos, target) end end -function project(mat::Mat4, tf, space::Symbol, pos::VecTypes{N, T}, type = Point3{T}) where {N, T} - return project(mat, apply_transform(tf, pos, space), type) +function project(mat::Mat4, tf, space::Symbol, pos::VecTypes{N, T}, target = Point3{T}(0)) where {N, T} + return project(mat, apply_transform(tf, pos, space), target) end -function inv_project(mat::Mat4f, itf, pos::VecTypes{N, T}, type = Point3{T}) where {N, T} - p = project(mat, pos, type) +function inv_project(mat::Mat4f, itf, pos::VecTypes{N, T}, target = Point3{T}(0)) where {N, T} + p = project(mat, pos, target) return apply_transform(itf, p) end -function project(mat::Mat4, pos::VecTypes{N, T}, type = Point3{T}) where {N, T} +function project(mat::Mat4, pos::VecTypes{N, T}, target = Point3{T}(0)) where {N, T} # TODO is to_ndim slow? It alone seems to - p4d = to_ndim(Point4{T}, to_ndim(Point3{T}, pos, 0), 1) + # p4d = to_ndim(Point4{T}, to_ndim(Point3{T}, pos, 0), 1) + p4d = to_ndim(Point4{T}(0,0,0,1), pos) p4d = mat * p4d - return to_ndim(type, p4d[Vec(1,2,3)] ./ p4d[4], 1f0) + return to_ndim(target, p4d[Vec(1,2,3)] ./ p4d[4]) end # TODO - should we use p4d[4] = 0 here? -# function project(mat::Mat4, pos::Vec{N, T}, type = Vec3{T}) where {N, T} +# function project(mat::Mat4, pos::Vec{N, T}, target = Vec3{T}) where {N, T} # p4d = to_ndim(Vec4{T}, to_ndim(Vec3{T}, pos, 0), 1) # p4d = mat * p4d -# return to_ndim(type, p4d[Vec(1,2,3)] ./ p4d[4], 1f0) +# return to_ndim(target, p4d[Vec(1,2,3)] ./ p4d[4], 1f0) # end @@ -470,13 +471,13 @@ to the screen/window/figure origin, rather than the (parent) scene. function project_to_screen(obj, pos) scene = get_scene(obj) offset = minimum(to_value(pixelarea(scene))) - return apply_offset!(project(obj, pos, type = Point2f), offset) + return apply_offset!(project(obj, pos, target = Point2f(0)), offset) end function project_to_screen(obj, input_space::Symbol, pos) scene = get_scene(obj) offset = minimum(to_value(pixelarea(scene))) - return apply_offset!(project(obj, pos, input_space = input_space, type = Point2f), offset) + return apply_offset!(project(obj, pos, input_space = input_space, target = Point2f(0)), offset) end function apply_offset!(pos::VT, off::VecTypes) where {VT <: VecTypes} @@ -494,17 +495,17 @@ end @deprecate shift_project(scene, plot, pos) project_to_screen(plot, pos) false """ - project_to_pixel(scenelike[, input_space], pos[; type = Point2f]) + project_to_pixel(scenelike[, input_space], pos[; target = Point2f(0)]) Transforms the given position(s) to (2D) pixel space, using the space of `scenelike` as the default input space. The returned positions will be relative to the scene derived from scenelike, not the screen/window/figure origin. -This is equivalent to `project(scenelike, pos[; input_space], type = Point2f)`. +This is equivalent to `project(scenelike, pos[; input_space], target = Point2f(0))`. """ -project_to_pixel(obj, pos; type = Point2f) = project(obj, pos; type = type) -function project_to_pixel(obj, input_space::Symbol, pos; type = Point2f) - return project(obj, pos, input_space = input_space, type = type) +project_to_pixel(obj, pos; target = Point2f(0)) = project(obj, pos; target = target) +function project_to_pixel(obj, input_space::Symbol, pos; target = Point2f(0)) + return project(obj, pos, input_space = input_space, target = target) end # Helper function diff --git a/src/utilities/utilities.jl b/src/utilities/utilities.jl index eb34e745bbc..994859b9bd1 100644 --- a/src/utilities/utilities.jl +++ b/src/utilities/utilities.jl @@ -254,6 +254,12 @@ function to_ndim(T::Type{<: VecTypes{N,ET}}, vec::VecTypes{N2}, fillval) where { end) end +function to_ndim(trg::VT, src::VecTypes{N2}) where {N, N2, VT <: VecTypes{N}} + VT(ntuple(Val(N)) do i + @inbounds i > N2 ? trg[i] : src[i] + end) +end + lerp(a::T, b::T, val::AbstractFloat) where {T} = (a .+ (val * (b .- a))) function angle(p1::VecTypes{2, T}, p2::VecTypes{2, T}) where T From 648a2e6c07e012cdbba167e2866b12bba8b35e73 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Mon, 3 Jul 2023 15:33:29 +0200 Subject: [PATCH 12/24] try to improve compile time with nospecialize --- CairoMakie/src/overrides.jl | 33 ++++++++++++++++++++------------- CairoMakie/src/utils.jl | 8 ++++---- src/camera/projection_math.jl | 24 +++++++++++++++--------- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/CairoMakie/src/overrides.jl b/CairoMakie/src/overrides.jl index 17b1c36e081..9f4312cb138 100644 --- a/CairoMakie/src/overrides.jl +++ b/CairoMakie/src/overrides.jl @@ -6,7 +6,7 @@ Special method for polys so we don't fall back to atomic meshes, which are much more complex and slower to draw than standard paths with single color. """ -function draw_plot(scene::Scene, screen::Screen, poly::Poly) +function draw_plot(scene::Scene, screen::Screen, @nospecialize(poly::Poly)) # dispatch on input arguments to poly to use smarter drawing methods than # meshes if possible draw_poly(scene, screen, poly, to_value.(poly.input_args)...) @@ -19,22 +19,25 @@ is_cairomakie_atomic_plot(plot::Poly) = true """ Fallback method for args without special treatment. """ -function draw_poly(scene::Scene, screen::Screen, poly, args...) +function draw_poly(scene::Scene, screen::Screen, @nospecialize(poly), args...) draw_poly_as_mesh(scene, screen, poly) end -function draw_poly_as_mesh(scene, screen, poly) +function draw_poly_as_mesh(scene, screen, @nospecialize(poly)) draw_plot(scene, screen, poly.plots[1]) draw_plot(scene, screen, poly.plots[2]) end # in the rare case of per-vertex colors redirect to mesh drawing -function draw_poly(scene::Scene, screen::Screen, poly, points::Vector{<:Point2}, color::AbstractArray, strokecolor, strokewidth) +function draw_poly( + scene::Scene, screen::Screen, @nospecialize(poly), + points::Vector{<:Point2}, color::AbstractArray, strokecolor, strokewidth + ) draw_poly_as_mesh(scene, screen, poly) end -function draw_poly(scene::Scene, screen::Screen, poly, points::Vector{<:Point2}) +function draw_poly(scene::Scene, screen::Screen, @nospecialize(poly), points::Vector{<:Point2}) color = to_cairo_color(poly.color[], poly) strokecolor = to_cairo_color(poly.strokecolor[], poly) strokestyle = Makie.convert_attribute(poly.linestyle[], key"linestyle"()) @@ -42,7 +45,7 @@ function draw_poly(scene::Scene, screen::Screen, poly, points::Vector{<:Point2}) end # when color is a Makie.AbstractPattern, we don't need to go to Mesh -function draw_poly(scene::Scene, screen::Screen, poly, points::Vector{<:Point2}, color::Union{Colorant, Cairo.CairoPattern}, +function draw_poly(scene::Scene, screen::Screen, @nospecialize(poly), points::Vector{<:Point2}, color::Union{Colorant, Cairo.CairoPattern}, strokecolor, strokestyle, strokewidth) points = cairo_project(poly, points) Cairo.move_to(screen.context, points[1]...) @@ -60,7 +63,7 @@ function draw_poly(scene::Scene, screen::Screen, poly, points::Vector{<:Point2}, Cairo.stroke(screen.context) end -function draw_poly(scene::Scene, screen::Screen, poly, points_list::Vector{<:Vector{<:Point2}}) +function draw_poly(scene::Scene, screen::Screen, @nospecialize(poly), points_list::Vector{<:Vector{<:Point2}}) color = to_cairo_color(poly.color[], poly) strokecolor = to_cairo_color(poly.strokecolor[], poly) strokestyle = Makie.convert_attribute(poly.linestyle[], key"linestyle"()) @@ -71,9 +74,9 @@ function draw_poly(scene::Scene, screen::Screen, poly, points_list::Vector{<:Vec end end -draw_poly(scene::Scene, screen::Screen, poly, rect::Rect2) = draw_poly(scene, screen, poly, [rect]) +draw_poly(scene::Scene, screen::Screen, @nospecialize(poly), rect::Rect2) = draw_poly(scene, screen, poly, [rect]) -function draw_poly(scene::Scene, screen::Screen, poly, rects::Vector{<:Rect2}) +function draw_poly(scene::Scene, screen::Screen, @nospecialize(poly), rects::Vector{<:Rect2}) projected_rects = cairo_project.((poly,), rects) color = to_cairo_color(poly.color[], poly) @@ -119,10 +122,14 @@ function polypath(ctx, polygon) end end -draw_poly(scene::Scene, screen::Screen, poly, polygon::Polygon) = draw_poly(scene, screen, poly, [polygon]) -draw_poly(scene::Scene, screen::Screen, poly, circle::Circle) = draw_poly(scene, screen, poly, decompose(Point2f, circle)) +function draw_poly(scene::Scene, screen::Screen, @nospecialize(poly), polygon::Polygon) + return draw_poly(scene, screen, poly, [polygon]) +end +function draw_poly(scene::Scene, screen::Screen, @nospecialize(poly), circle::Circle) + return draw_poly(scene, screen, poly, decompose(Point2f, circle)) +end -function draw_poly(scene::Scene, screen::Screen, poly, polygons::AbstractArray{<:Polygon}) +function draw_poly(scene::Scene, screen::Screen, @nospecialize(poly), polygons::AbstractArray{<:Polygon}) projected_polys = cairo_project.((poly,), polygons) color = to_cairo_color(poly.color[], poly) strokecolor = to_cairo_color(poly.strokecolor[], poly) @@ -139,7 +146,7 @@ function draw_poly(scene::Scene, screen::Screen, poly, polygons::AbstractArray{< end -function draw_poly(scene::Scene, screen::Screen, poly, polygons::AbstractArray{<: MultiPolygon}) +function draw_poly(scene::Scene, screen::Screen, @nospecialize(poly), polygons::AbstractArray{<: MultiPolygon}) projected_polys = cairo_project.((poly,), polygons) color = to_cairo_color(poly.color[], poly) strokecolor = to_cairo_color(poly.strokecolor[], poly) diff --git a/CairoMakie/src/utils.jl b/CairoMakie/src/utils.jl index f6f607302dc..2b106dfbef9 100644 --- a/CairoMakie/src/utils.jl +++ b/CairoMakie/src/utils.jl @@ -7,7 +7,7 @@ Calls `project(plot, pos; kwargs...)` and flips the y axis of the result. """ -function cairo_project(plot, pos; target = Point2f(0), kwargs...) +function cairo_project(@nospecialize(plot), pos; target = Point2f(0), kwargs...) w, h = widths(pixelarea(Makie.get_scene(plot))[]) ps = project(plot, pos; target = target, kwargs...) return yflip(ps, h) @@ -35,13 +35,13 @@ function project_scale(scene::Scene, space, s, model = Mat4f(I)) end end -function cairo_project(plot, rect::Rect; kwargs...) +function cairo_project(@nospecialize(plot), rect::Rect; kwargs...) mini = cairo_project(plot, minimum(rect); kwargs...) maxi = cairo_project(plot, maximum(rect); kwargs...) return Rect(Vec(mini), Vec(maxi .- mini)) end -function cairo_project(plot, poly::P; kwargs...) where P <: Polygon +function cairo_project(@nospecialize(plot), poly::P; kwargs...) where P <: Polygon ext = decompose(Point2f, poly.exterior) interiors = decompose.(Point2f, poly.interiors) Polygon( @@ -50,7 +50,7 @@ function cairo_project(plot, poly::P; kwargs...) where P <: Polygon ) end -function cairo_project(plot, multipoly::MP; kwargs...) where MP <: MultiPolygon +function cairo_project(@nospecialize(plot), multipoly::MP; kwargs...) where MP <: MultiPolygon return MultiPolygon(cairo_project.((plot, ), multipoly.polygons; kwargs...)) end diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index eb1d5a802bc..eba78f82b19 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -348,7 +348,7 @@ may need to add `minimum(pixelarea(scene))`. `project_to_screen` does this for you. """ function project( - plot::AbstractPlot, pos; + @nospecialize(plot::AbstractPlot), pos; input_space::Symbol = get_value(plot, :space, :data), output_space::Symbol = :pixel, target = _point3_target(pos) ) @@ -372,7 +372,7 @@ end function project(cam::Camera, input_space::Symbol, output_space::Symbol, pos; target = _point3_target(pos)) @assert input_space !== :data "Cannot transform from :data space with just the camera." @assert output_space !== :data "Cannot transform to :data space with just the camera." - mat = space_to_space_matrix(cam, input_space => output_space) + mat = _space_to_space_matrix(cam, input_space => output_space) return project(mat, indentity, input_space, output_space, pos, target) end @@ -456,8 +456,12 @@ end Transforms the given position(s) to world space, using the space of `scenelike` as the default input space. """ -project_to_world(obj, pos) = project(obj, pos, output_space = :world) -project_to_world(obj, input_space::Symbol, pos) = project(obj, pos, input_space = input_space, output_space = :world) +@inline function project_to_world(@nospecialize(obj), pos) + return project(obj, pos, output_space = :world) +end +@inline function project_to_world(@nospecialize(obj), input_space::Symbol, pos) + return project(obj, pos, input_space = input_space, output_space = :world) +end @deprecate to_world project_to_world false @@ -468,13 +472,13 @@ Transforms the given position(s) to (2D) screen/pixel space, using the space of `scenelike` as the default input space. The returned positions will be relative to the screen/window/figure origin, rather than the (parent) scene. """ -function project_to_screen(obj, pos) +function project_to_screen(@nospecialize(obj), pos) scene = get_scene(obj) offset = minimum(to_value(pixelarea(scene))) return apply_offset!(project(obj, pos, target = Point2f(0)), offset) end -function project_to_screen(obj, input_space::Symbol, pos) +function project_to_screen(@nospecialize(obj), input_space::Symbol, pos) scene = get_scene(obj) offset = minimum(to_value(pixelarea(scene))) return apply_offset!(project(obj, pos, input_space = input_space, target = Point2f(0)), offset) @@ -503,8 +507,10 @@ scene derived from scenelike, not the screen/window/figure origin. This is equivalent to `project(scenelike, pos[; input_space], target = Point2f(0))`. """ -project_to_pixel(obj, pos; target = Point2f(0)) = project(obj, pos; target = target) -function project_to_pixel(obj, input_space::Symbol, pos; target = Point2f(0)) +@inline function project_to_pixel(@nospecialize(obj), pos; target = Point2f(0)) + return project(obj, pos; target = target) +end +@inline function project_to_pixel(@nospecialize(obj), input_space::Symbol, pos; target = Point2f(0)) return project(obj, pos, input_space = input_space, target = target) end @@ -514,7 +520,7 @@ end Returns an observable that triggers whenever the result of `project` could change """ -function projection_obs(plot::AbstractPlot) +function projection_obs(@nospecialize(plot::AbstractPlot)) return lift( (_, _, _, _, _, _) -> nothing, plot, From c2a1b20359142bf0fe0ef08aa755ccbf4f343a5d Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 7 Jul 2023 14:46:37 +0200 Subject: [PATCH 13/24] update WGLMakie & fix model passing --- GLMakie/src/drawing_primitives.jl | 18 +++++------------- WGLMakie/src/Camera.js | 12 ++++++++++-- WGLMakie/src/lines.jl | 2 +- WGLMakie/src/meshes.jl | 2 +- WGLMakie/src/particles.jl | 9 +++++---- src/camera/projection_math.jl | 7 +++++++ 6 files changed, 29 insertions(+), 21 deletions(-) diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 07507744a17..11fc3200ec3 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -113,8 +113,8 @@ function cached_robj!(robj_func, screen, scene, x::AbstractPlot) :transformation, :tickranges, :ticklabels, :raw, :SSAO, :lightposition, :material, :inspector_label, :inspector_hover, :inspector_clear, :inspectable, - :colorrange, :colormap, :colorscale, :highclip, :lowclip, :nan_color, - :calculated_colors + :colorrange, :colormap, :colorscale, :highclip, :lowclip, :nan_color, + :calculated_colors )) end @@ -125,6 +125,8 @@ function cached_robj!(robj_func, screen, scene, x::AbstractPlot) gl_key => gl_value end) + gl_attributes[:model] = Makie._get_model_obs(x) + pointlight = Makie.get_point_light(scene) if !isnothing(pointlight) gl_attributes[:lightposition] = pointlight.position @@ -301,17 +303,7 @@ function draw_atomic(screen::Screen, scene::Scene, @nospecialize(x::Lines)) data[:pattern] = map((ls, lw) -> ls .* _mean(lw), linestyle, linewidth) data[:fast] = false - pvm = map(*, data[:projectionview], data[:model]) - positions = map(transform_func, positions, space, pvm, data[:resolution]) do f, ps, space, pvm, res - transformed = apply_transform(f, ps, space) - output = Vector{Point3f}(undef, length(transformed)) - scale = Vec3f(res[1], res[2], 1f0) - for i in eachindex(transformed) - clip = pvm * to_ndim(Point4f, to_ndim(Point3f, transformed[i], 0f0), 1f0) - output[i] = scale .* Point3f(clip) ./ clip[4] - end - output - end + positions = map(ps -> Makie.project(x, ps), positions) end return draw_lines(screen, positions, data) end diff --git a/WGLMakie/src/Camera.js b/WGLMakie/src/Camera.js index 8e3d329b7bf..30c19446036 100644 --- a/WGLMakie/src/Camera.js +++ b/WGLMakie/src/Camera.js @@ -224,6 +224,8 @@ export class MakieCamera { // inverses this.pixel_space_inverse = new THREE.Uniform(Identity4x4()); + // this.view_inverse = new THREE.Uniform(Identity4x4()); + this.projection_inverse = new THREE.Uniform(Identity4x4()); this.projectionview_inverse = new THREE.Uniform(Identity4x4()); // Constant matrices @@ -255,6 +257,8 @@ export class MakieCamera { const proj_view = mul(this.view.value, this.projection.value); this.projectionview.value = proj_view; + // this.view_inverse = this.view.clone().invert(); + this.projection_inverse = this.projection.clone().invert(); this.projectionview_inverse.value = proj_view.clone().invert(); // update all existing preprojection matrices @@ -276,8 +280,10 @@ export class MakieCamera { // TODO clip_to_space(space) { - if (space === "data") { + if (space === "data" || space === "transformed" || space === "world") { return this.projectionview_inverse.value; + } else if (space === "eye") { + return this.projection_inverse.value; } else if (space === "pixel") { return this.pixel_space_inverse.value; } else if (space === "relative") { @@ -290,8 +296,10 @@ export class MakieCamera { } space_to_clip(space) { - if (space === "data") { + if (space === "data" || space === "transformed" || space === "world") { return this.projectionview.value; + } else if (space === "eye") { + return this.projection.value; } else if (space === "pixel") { return this.pixel_space.value; } else if (space === "relative") { diff --git a/WGLMakie/src/lines.jl b/WGLMakie/src/lines.jl index f8fce2b5f68..a39fb753aa8 100644 --- a/WGLMakie/src/lines.jl +++ b/WGLMakie/src/lines.jl @@ -65,7 +65,7 @@ function create_shader(scene::Scene, plot::Union{Lines,LineSegments}) uniforms[:resolution] = to_value(scene.camera.resolution) # updates in JS - uniforms[:model] = plot.model + uniforms[:model] = Makie._get_model_obs(plot) uniforms[:depth_shift] = get(plot, :depth_shift, Observable(0f0)) positions = meta(Point2f[(0, -1), (0, 1), (1, -1), (1, 1)], uv=Vec2f[(0, 0), (0, 0), (0, 0), (0, 0)]) diff --git a/WGLMakie/src/meshes.jl b/WGLMakie/src/meshes.jl index 83e314c1e33..f8f3e35b729 100644 --- a/WGLMakie/src/meshes.jl +++ b/WGLMakie/src/meshes.jl @@ -61,7 +61,7 @@ function draw_mesh(mscene::Scene, per_vertex, plot, uniforms; permute_tex=true) handle_color!(plot, uniforms, per_vertex; permute_tex=permute_tex) get!(uniforms, :pattern, false) - get!(uniforms, :model, plot.model) + get!(uniforms, :model, Makie._get_model_obs(plot)) get!(uniforms, :lightposition, Vec3f(1)) get!(uniforms, :ambient, Vec3f(1)) diff --git a/WGLMakie/src/particles.jl b/WGLMakie/src/particles.jl index 381d8076853..a508b73f88f 100644 --- a/WGLMakie/src/particles.jl +++ b/WGLMakie/src/particles.jl @@ -39,7 +39,7 @@ const IGNORE_KEYS = Set([ :shading, :overdraw, :rotation, :distancefield, :space, :markerspace, :fxaa, :visible, :transformation, :alpha, :linewidth, :transparency, :marker, :lightposition, :cycle, :label, :inspector_clear, :inspector_hover, - :inspector_label + :inspector_label, :model ]) function create_shader(scene::Scene, plot::MeshScatter) @@ -66,6 +66,7 @@ function create_shader(scene::Scene, plot::MeshScatter) k in color_keys && continue uniform_dict[k] = lift_convert(k, v, plot) end + uniform_dict[:model] = Makie._get_model_obs(plot) handle_color!(plot, uniform_dict, per_instance, :color) handle_color_getter!(uniform_dict, per_instance) @@ -163,7 +164,7 @@ function scatter_shader(scene::Scene, attributes, plot) k in color_keys && continue uniform_dict[k] = lift_convert(k, v, plot) end - + uniform_dict[:model] = Makie._get_model_obs(plot) if !isnothing(marker) get!(uniform_dict, :shape_type) do return Makie.marker_to_sdf_shape(marker) @@ -215,7 +216,7 @@ function create_shader(scene::Scene, plot::Scatter) attributes[:marker_offset] = Vec3f(0) attributes[:quad_offset] = quad_offset attributes[:billboard] = map(rot -> isa(rot, Billboard), plot.rotations) - attributes[:model] = plot.model + attributes[:model] = Makie._get_model_obs(plot) attributes[:depth_shift] = get(plot, :depth_shift, Observable(0f0)) delete!(attributes, :uv_offset_width) @@ -286,7 +287,7 @@ function create_shader(scene::Scene, plot::Makie.Text{<:Tuple{<:Union{<:Makie.Gl plot_attributes.attributes[:calculated_colors] = uniform_color uniforms = Dict( - :model => plot.model, + :model => Makie._get_model_obs(plot), :shape_type => Observable(Cint(3)), :rotations => uniform_rotation, :pos => positions, diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index eba78f82b19..74f75c9ccc7 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -542,4 +542,11 @@ function projection_obs(scene::Scene) transformationmatrix(scene), transform_func(scene) ) +end + +# TODO +# naming, do we keep this? +function _get_model_obs(plot::AbstractPlot) + space = get(plot, :space, Observable(:data)) + return map((s, m) -> s in (:data, :transformed) ? m : Mat4f(I), space, plot.model) end \ No newline at end of file From e2370b606e7c5b71070b13d42db8b0f9850e4ca7 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 7 Jul 2023 15:39:23 +0200 Subject: [PATCH 14/24] add observable lock for discarding duplicate updates --- src/camera/camera.jl | 12 ++++++++---- src/camera/projection_math.jl | 14 ++++++-------- src/interaction/observables.jl | 26 ++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/camera/camera.jl b/src/camera/camera.jl index 48d63063fa7..d1f34516529 100644 --- a/src/camera/camera.jl +++ b/src/camera/camera.jl @@ -77,6 +77,8 @@ function Camera(px_area) view = Observable(Mat4f(I)) proj = Observable(Mat4f(I)) proj_view = map(*, proj, view) + # so we can avoid duplicate updates + attach_lock!(proj_view) return Camera( pixel_space, view, @@ -89,11 +91,13 @@ function Camera(px_area) end function set_proj_view!(camera::Camera, projection, view) - # hack, to not double update projectionview - # TODO, this makes code doing on(view), not work correctly... - # But nobody should do that, right? - # GLMakie uses map on view + # Both view and projection trigger an update of projectionview which can + # have rather expensive listeners. To avoid running those twice we block + # them when updating view. + lock = Observables.listeners(camera.projectionview)[1][2]::ObservableLocker + lock!(lock) camera.view[] = view + unlock!(lock) camera.projection[] = projection end diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index 74f75c9ccc7..e4c1ba99b66 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -522,25 +522,23 @@ Returns an observable that triggers whenever the result of `project` could chang """ function projection_obs(@nospecialize(plot::AbstractPlot)) return lift( - (_, _, _, _, _, _) -> nothing, + (_, _, _, _, _) -> nothing, plot, - camera(plot).view, - camera(plot).projection, + camera(plot).projectionview, get_scene(plot).px_area, transformationmatrix(plot), - transform_func(plot), + transform_func_obs(plot), get(plot, :space, Observable(:data)), ) end function projection_obs(scene::Scene) return lift( - (_, _, _, _, _, _) -> nothing, + (_, _, _, _) -> nothing, scene, - camera(scene).view, - camera(scene).projection, + camera(scene).projectionview, scene.px_area, transformationmatrix(scene), - transform_func(scene) + transform_func_obs(scene) ) end diff --git a/src/interaction/observables.jl b/src/interaction/observables.jl index 42d7fde312e..ef9a7dabcdc 100644 --- a/src/interaction/observables.jl +++ b/src/interaction/observables.jl @@ -42,3 +42,29 @@ function map_once( end lift(f, input, inputrest...) end + +""" + ObservableLocker([locked::Bool = false]) + +Creates a callable struct which returns `Consume(true)` when locked and +`Consume(false)` when unlocked. This can be used to to avoid calling listeners +of an Observable by adding it at high/maximum priority. +""" +mutable struct ObservableLocker + locked::Bool +end +ObservableLocker() = ObservableLocker(false) + +""" + attach_lock!(obs::AbstractObservable[, lock = ObservableLocker()]) + +Creates an `ObservableLocker`, attaches it to the given observable and returns +it. +""" +function attach_lock!(obs::AbstractObservable, lock = ObservableLocker()) + on(lock, obs, priority = typemax(Int)) + return lock +end +(x::ObservableLocker)(@nospecialize(args...)) = Consume(x.locked) +lock!(x::ObservableLocker) = x.locked = true +unlock!(x::ObservableLocker) = x.locked = false \ No newline at end of file From 75d9347943e1624a072d48a22754e5a55cb07812 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 7 Jul 2023 16:38:10 +0200 Subject: [PATCH 15/24] fix lines project --- GLMakie/src/drawing_primitives.jl | 10 ++++++++-- src/camera/projection_math.jl | 10 +++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 11fc3200ec3..da83ff9c9d0 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -302,8 +302,14 @@ function draw_atomic(screen::Screen, scene::Scene, @nospecialize(x::Lines)) linewidth = gl_attributes[:thickness] data[:pattern] = map((ls, lw) -> ls .* _mean(lw), linestyle, linewidth) data[:fast] = false - - positions = map(ps -> Makie.project(x, ps), positions) + + positions = map(Makie.projection_obs(x), positions) do _, ps + # The shader uses scaled pixel space + w, h = data[:resolution][] + mat = Makie.scalematrix(Vec3f(w, h, 1f0)) * + Makie.space_to_space_matrix(x, space[], :clip) + Makie.project(mat, transform_func[], space[], :other, ps) + end end return draw_lines(screen, positions, data) end diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index e4c1ba99b66..8d015de3f6b 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -466,22 +466,22 @@ end @deprecate to_world project_to_world false """ - project_to_screen(scenelike[, input_space], pos) + project_to_screen(scenelike[, input_space], pos[; target = Point2f(0)]) Transforms the given position(s) to (2D) screen/pixel space, using the space of `scenelike` as the default input space. The returned positions will be relative to the screen/window/figure origin, rather than the (parent) scene. """ -function project_to_screen(@nospecialize(obj), pos) +function project_to_screen(@nospecialize(obj), pos; target = Point2f(0)) scene = get_scene(obj) offset = minimum(to_value(pixelarea(scene))) - return apply_offset!(project(obj, pos, target = Point2f(0)), offset) + return apply_offset!(project(obj, pos, target = target), offset) end -function project_to_screen(@nospecialize(obj), input_space::Symbol, pos) +function project_to_screen(@nospecialize(obj), input_space::Symbol, pos; target = Point2f(0)) scene = get_scene(obj) offset = minimum(to_value(pixelarea(scene))) - return apply_offset!(project(obj, pos, input_space = input_space, target = Point2f(0)), offset) + return apply_offset!(project(obj, pos, input_space = input_space, target = target), offset) end function apply_offset!(pos::VT, off::VecTypes) where {VT <: VecTypes} From 8c1fb81e0925580f68f47d5e039d4699903ad7a0 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 7 Jul 2023 18:47:38 +0200 Subject: [PATCH 16/24] allow passing transform_func --- src/interfaces.jl | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/interfaces.jl b/src/interfaces.jl index c040da6cdd3..358f98af435 100644 --- a/src/interfaces.jl +++ b/src/interfaces.jl @@ -188,20 +188,35 @@ function (PlotType::Type{<: AbstractPlot{Typ}})(scene::SceneLike, attributes::At ) # Transformation is a field of the plot type, but can be given as an attribute - trans = get(plot_attributes, :transformation, automatic) + trans = pop!(plot_attributes, :transformation, automatic) + transform_func = pop!(plot_attributes, :transform_func, automatic) transval = to_value(trans) + transformation = if transval === automatic - Transformation(scene) + Transformation(scene, transform_func = transform_func) elseif isa(transval, Transformation) - transval + if transform_func === automatic + transval + else + trans = Transformation( + transval.translation, transval.scale, transval.rotation, + transval.model, transform_func + ) + if isassigned(transval.parent) + trans.parent[] = transval.parent[] + end + trans + end else - t = Transformation(scene) + t = Transformation(scene, transform_func = transform_func) transform!(t, transval) t end + replace_automatic!(plot_attributes, :model) do transformation.model end + # create the plot, with the full attributes, the input signals, and the final signals. plot_obj = FinalType(scene, transformation, plot_attributes, input, seperate_tuple(args)) calculated_attributes!(plot_obj) From 780868c5a91a9aff7711946d92c57cfb00acd2d8 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Fri, 7 Jul 2023 22:42:50 +0200 Subject: [PATCH 17/24] generalize some vectypes --- src/camera/projection_math.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index 8d015de3f6b..8ebf0aa26f4 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -1,4 +1,4 @@ -function scalematrix(s::Vec{3, T}) where T +function scalematrix(s::VecTypes{3, T}) where T T0, T1 = zero(T), one(T) Mat{4}( s[1],T0, T0, T0, @@ -12,7 +12,7 @@ translationmatrix_x(x::T) where {T} = translationmatrix(Vec{3, T}(x, 0, 0)) translationmatrix_y(y::T) where {T} = translationmatrix(Vec{3, T}(0, y, 0)) translationmatrix_z(z::T) where {T} = translationmatrix(Vec{3, T}(0, 0, z)) -function translationmatrix(t::Vec{3, T}) where T +function translationmatrix(t::VecTypes{3, T}) where T T0, T1 = zero(T), one(T) Mat{4}( T1, T0, T0, T0, @@ -22,8 +22,10 @@ function translationmatrix(t::Vec{3, T}) where T ) end -rotate(angle, axis::Vec{3}) = rotationmatrix4(qrotation(convert(Array, axis), angle)) -rotate(::Type{T}, angle::Number, axis::Vec{3}) where {T} = rotate(T(angle), convert(Vec{3, T}, axis)) +rotate(angle, axis::VecTypes{3}) = rotationmatrix4(qrotation(convert(Array, axis), angle)) +function rotate(::Type{T}, angle::Number, axis::VecTypes{3}) where {T} + return rotate(T(angle), convert(Vec{3, T}, axis)) +end function rotationmatrix_x(angle::Number) T0, T1 = (0, 1) From 4c3daaaa18cd80ecc97584e3a8ec7653c41f6081 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 8 Jul 2023 14:03:15 +0200 Subject: [PATCH 18/24] fix missplaced Axis3 frame --- src/makielayout/blocks/axis3d.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/makielayout/blocks/axis3d.jl b/src/makielayout/blocks/axis3d.jl index 49940a78503..b196b07153e 100644 --- a/src/makielayout/blocks/axis3d.jl +++ b/src/makielayout/blocks/axis3d.jl @@ -476,10 +476,10 @@ end # this function projects a point from a 3d subscene into the parent space with a really # small z value function to_topscene_z_2d(p3d, scene) - p2d = Makie.project_to_pixel(scene, p3d) - # -10000 is an arbitrary weird constant that in preliminary testing didn't seem - # to clip into plot objects anymore - Point3f(p2d..., -10000) + p2d = Makie.project_to_screen(scene, p3d) + # -10000 is the default minimum depth set in + # campixel!(scene::Scene; nearclip=-10_000f0, farclip=10_000f0) + return Point3f(p2d..., -10000) end function add_ticks_and_ticklabels!(topscene, scene, ax, dim::Int, limits, ticknode, miv, min1, min2, azimuth, xreversed, yreversed, zreversed) @@ -607,9 +607,9 @@ function add_ticks_and_ticklabels!(topscene, scene, ax, dim::Int, limits, tickno p1 = dpoint(minimum(lims)[dim], f1, f2) p2 = dpoint(maximum(lims)[dim], f1, f2) - # project them into screen space - pp1 = Makie.project_to_pixel(scene, p1) - pp2 = Makie.project_to_pixel(scene, p2) + # project them into global screen space + pp1 = Makie.project_to_screen(scene, p1) + pp2 = Makie.project_to_screen(scene, p2) # find the midpoint midpoint = (pp1 + pp2) ./ 2 From 61a24b2c26e0be7947c9c2687f589438f381df72 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 8 Jul 2023 14:09:00 +0200 Subject: [PATCH 19/24] add transformation matrix decomposition function --- src/camera/projection_math.jl | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index 8ebf0aa26f4..3f21db8b1c0 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -55,6 +55,49 @@ function rotationmatrix_z(angle::Number) ) end +""" + decompose_transformation_matrix(matrix::Mat4) + +Attempts to decompose a matrix into a translation, scale and rotation. Note +that this will still return a result even if the matrix cannot be decomposed. +""" +function decompose_transformation_matrix(model::Mat4{T}) where T + trans = model[Vec(1,2,3), 4] + m33 = model[Vec(1,2,3), Vec(1,2,3)] + if m33[1, 2] ≈ m33[1, 3] ≈ m33[2, 3] ≈ 0 + scale = diag(m33) + rot = Quaternion{T}(0, 0, 0, 1) + return trans, scale, rot + else + scale = sqrt.(diag(m33 * m33')) + R = Diagonal(1 ./ scale) * m33 + + # inverse of Mat4(q::Quaternion) + xz = 0.5 * (R[1, 3] + R[3, 1]) + sy = 0.5 * (R[1, 3] - R[3, 1]) + yz = 0.5 * (R[2, 3] + R[3, 2]) + sx = 0.5 * (R[3, 2] - R[2, 3]) + xy = 0.5 * (R[1, 2] + R[2, 1]) + sz = 0.5 * (R[2, 1] - R[1, 2]) + + m = max(abs(xy), abs(xz), abs(yz)) + if abs(xy) == m + q4 = sqrt(0.5 * sx * sy / xy) + elseif abs(xz) == m + q4 = sqrt(0.5 * sx * sz / xz) + else + q4 = sqrt(0.5 * sy * sz / yz) + end + + q1 = 0.5 * sx / q4 + q2 = 0.5 * sy / q4 + q3 = 0.5 * sz / q4 + rot = Quaternion{T}(q1, q2, q3, q4) + + return trans, scale, rot + end +end + """ Create view frustum From 0e58ba1a73efbb7a5d99afbcaeea43c8c3c2e61a Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 8 Jul 2023 15:10:33 +0200 Subject: [PATCH 20/24] cleanup iterate_transformed --- src/layouting/data_limits.jl | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/layouting/data_limits.jl b/src/layouting/data_limits.jl index c74581b71f0..6659945d268 100644 --- a/src/layouting/data_limits.jl +++ b/src/layouting/data_limits.jl @@ -135,17 +135,9 @@ function foreach_transformed(f, plot) end function iterate_transformed(plot) - points = point_iterator(plot) - t = transformation(plot) - model = model_transform(t) - # TODO: without this, axes with log scales error. Why? - trans_func = identity # transform_func(t) - # trans_func = identity - iterate_transformed(points, model, to_value(get(plot, :space, :data)), trans_func) -end - -function iterate_transformed(points, model, space, trans_func) - (to_ndim(Point3f, project(model, apply_transform(trans_func, point, space)), 0f0) for point in points) + # TODO: + # We skip transform_func here because axes with log scales error. Fix this! + return project_to_world(plot, :transformed, point_iterator(plot)) end function update_boundingbox!(bb_ref, point) From 3a0fdf68f41187dc0567146440a532f3c5d0d5ea Mon Sep 17 00:00:00 2001 From: ffreyer Date: Sat, 8 Jul 2023 16:10:47 +0200 Subject: [PATCH 21/24] change space Pair to two variables --- src/camera/projection_math.jl | 52 ++++++++++++++++------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index 3f21db8b1c0..bcfc7b2140a 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -302,65 +302,61 @@ If you wish to exclude the model matrix, call `_space_to_space_matrix(camera(scenelike), ...)`. """ function space_to_space_matrix(obj, input_space::Symbol, output_space::Symbol) - return space_to_space_matrix(obj, Pair(input_space, output_space)) + return space_to_space_matrix(get_scene(obj), input_space, output_space) end # this method does Axis -> Scene conversions -function space_to_space_matrix(obj, space2space::Pair{Symbol, Symbol}) - return space_to_space_matrix(get_scene(obj), space2space) +function space_to_space_matrix(obj, s2s::Pair{Symbol, Symbol}) + return space_to_space_matrix(get_scene(obj), s2s[1], s2s[2]) end function space_to_space_matrix(scene_or_plot::SceneLike, s2s::Pair{Symbol, Symbol}) - mat = _space_to_space_matrix(camera(scene_or_plot), s2s) + space_to_space_matrix(scene_or_plot, s2s[1], s2s[2]) +end +function space_to_space_matrix(scene_or_plot::SceneLike, input::Symbol, output::Symbol) + mat = _space_to_space_matrix(camera(scene_or_plot), input, output) model = to_value(transformationmatrix(scene_or_plot)) - if s2s[1] in (:data, :transformed) + if input in (:data, :transformed) return mat * model - elseif s2s[2] in (:data, :transformed) + elseif output in (:data, :transformed) return inv(model) * mat else return mat end end -# function space_to_space_matrix(cam::Camera, s2s::Pair{Symbol, Symbol}) -# @warn "No trans_func, no model" -# return _space_to_space_matrix(cam, s2s) -# end - -function _space_to_space_matrix(cam::Camera, s2s::Pair{Symbol, Symbol}) +function _space_to_space_matrix(cam::Camera, input::Symbol, output::Symbol) # identities - if s2s[1] === s2s[2] - return Mat4f(I) - elseif s2s[1] in (:data, :transformed) && s2s[2] === :world + if input in (:data, :transformed, :world) && output in (:data, :transformed, :world) return Mat4f(I) # direct conversions (no calculations) - elseif s2s === Pair(:world, :eye) + elseif input === :world && output === :eye return cam.view[] - elseif s2s === Pair(:eye, :clip) + elseif input === :eye && output === :clip return cam.projection[] - elseif s2s[1] in (:data, :transformed, :world) && s2s[2] === :clip + elseif input in (:data, :transformed, :world) && output === :clip return cam.projectionview[] - elseif s2s === Pair(:pixel, :clip) + elseif input === :pixel && output === :clip return cam.pixel_space[] - elseif s2s === Pair(:relative, :clip) + elseif input === :relative && output === :clip return Mat4f(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, -1, -1, 0, 1) # simple inversions - elseif s2s === Pair(:clip, :relative) + elseif input === :clip && output === :relative return Mat4f(0.5, 0, 0, 0, 0, 0.5, 0, 0, 0, 0, 1, 0, 0.5, 0.5, 0, 1) - elseif s2s === Pair(:clip, :pixel) + elseif input === :clip && output === :pixel w, h = cam.resolution[] return Mat4f(0.5w, 0, 0, 0, 0, 0.5h, 0, 0, 0, 0, -10_000, 0, 0.5w, 0.5h, 0, 1) # calculation neccessary - elseif s2s[1] === :clip - return inv(_space_to_space_matrix(cam, Pair(s2s[2], s2s[1]))) - elseif s2s[1] in spaces() && s2s[2] in spaces() - return _space_to_space_matrix(cam, Pair(:clip, s2s[2])) * - _space_to_space_matrix(cam, Pair(s2s[1], :clip)) + elseif input === :clip + return inv(_space_to_space_matrix(cam, output, input)) + elseif input in spaces() && output in spaces() + return _space_to_space_matrix(cam, :clip, output) * + _space_to_space_matrix(cam, input, :clip) else - error("Space $space not recognized. Must be one of $(spaces())") + error("Space $input or $output not recognized. Must be one of $(spaces())") end end From 1535afd82e32432a60a32c9eb07b2e86eeb6597e Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 11 Jul 2023 16:07:46 +0200 Subject: [PATCH 22/24] fix space_to_space_matrix call syntax --- GLMakie/src/drawing_primitives.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index da83ff9c9d0..54d3d06bbc9 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -52,9 +52,9 @@ function connect_camera!(plot, gl_attributes, cam, space = gl_attributes[:space] get!(gl_attributes, :projection) do return lift(cam.projection, cam.pixel_space, space) do _, _, space if space in (:data, :transformed, :world) - return Makie._space_to_space_matrix(cam, :eye => :clip) + return Makie._space_to_space_matrix(cam, :eye, :clip) else - return Makie._space_to_space_matrix(cam, space => :clip) + return Makie._space_to_space_matrix(cam, space, :clip) end end end @@ -68,7 +68,7 @@ function connect_camera!(plot, gl_attributes, cam, space = gl_attributes[:space] get!(gl_attributes, :projectionview) do return lift(plot, cam.projectionview, cam.pixel_space, space) do _, _, space - Makie._space_to_space_matrix(cam, space => :clip) + Makie._space_to_space_matrix(cam, space, :clip) end end @@ -232,7 +232,7 @@ function draw_atomic(screen::Screen, scene::Scene, @nospecialize(x::Union{Scatte mspace = x.markerspace cam = scene.camera gl_attributes[:preprojection] = map(space, mspace, cam.projectionview, cam.resolution) do space, mspace, _, _ - return Makie._space_to_space_matrix(cam, space => mspace) + return Makie._space_to_space_matrix(cam, space, mspace) end # fast pixel does its own setup if !(marker[] isa FastPixel) @@ -404,7 +404,7 @@ function draw_atomic(screen::Screen, scene::Scene, cam = scene.camera # gl_attributes[:preprojection] = Observable(Mat4f(I)) gl_attributes[:preprojection] = map(space, markerspace, cam.projectionview, cam.resolution) do s, ms, pv, res - Makie._space_to_space_matrix(cam, s => ms) + Makie._space_to_space_matrix(cam, s, ms) end return draw_scatter(screen, (DISTANCEFIELD, positions), gl_attributes) From 2f8bd2eff1e3fd6cb2c7ff8d3d21e37e4615fa13 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 13 Jul 2023 14:18:25 +0200 Subject: [PATCH 23/24] fix infinite loop --- src/camera/projection_math.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index bcfc7b2140a..8f09b4d8a40 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -329,6 +329,8 @@ function _space_to_space_matrix(cam::Camera, input::Symbol, output::Symbol) # identities if input in (:data, :transformed, :world) && output in (:data, :transformed, :world) return Mat4f(I) + elseif input == output + return Mat4f(I) # direct conversions (no calculations) elseif input === :world && output === :eye From 26ade4645b4664fb6205709d72981d73d1023611 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 31 Aug 2023 21:03:22 +0200 Subject: [PATCH 24/24] fix some rebase errors --- src/camera/projection_math.jl | 48 +++++++++++++++++--------------- src/interfaces.jl | 7 +++-- src/layouting/transformation.jl | 4 +-- src/makielayout/blocks/axis3d.jl | 10 +++---- 4 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/camera/projection_math.jl b/src/camera/projection_math.jl index 8f09b4d8a40..5455289011f 100644 --- a/src/camera/projection_math.jl +++ b/src/camera/projection_math.jl @@ -58,8 +58,8 @@ end """ decompose_transformation_matrix(matrix::Mat4) -Attempts to decompose a matrix into a translation, scale and rotation. Note -that this will still return a result even if the matrix cannot be decomposed. +Attempts to decompose a matrix into a translation, scale and rotation. Note +that this will still return a result even if the matrix cannot be decomposed. """ function decompose_transformation_matrix(model::Mat4{T}) where T trans = model[Vec(1,2,3), 4] @@ -293,12 +293,12 @@ end space_to_space_matrix(scenelike, spaces::Pair) space_to_space_matrix(scenelike, input_space::Symbol, output_space::Symbol) -Returns a matrix which transforms positional data from a given input space to a +Returns a matrix which transforms positional data from a given input space to a given output space. This will not include the transform function, as it is not -representable as a matrix, but will include the model matrix if applicable. +representable as a matrix, but will include the model matrix if applicable. (I.e. this includes `scale!()`, `translate!()` and `rotate!()`.) -If you wish to exclude the model matrix, call +If you wish to exclude the model matrix, call `_space_to_space_matrix(camera(scenelike), ...)`. """ function space_to_space_matrix(obj, input_space::Symbol, output_space::Symbol) @@ -331,7 +331,7 @@ function _space_to_space_matrix(cam::Camera, input::Symbol, output::Symbol) return Mat4f(I) elseif input == output return Mat4f(I) - + # direct conversions (no calculations) elseif input === :world && output === :eye return cam.view[] @@ -355,7 +355,7 @@ function _space_to_space_matrix(cam::Camera, input::Symbol, output::Symbol) elseif input === :clip return inv(_space_to_space_matrix(cam, output, input)) elseif input in spaces() && output in spaces() - return _space_to_space_matrix(cam, :clip, output) * + return _space_to_space_matrix(cam, :clip, output) * _space_to_space_matrix(cam, input, :clip) else error("Space $input or $output not recognized. Must be one of $(spaces())") @@ -370,17 +370,17 @@ end """ project(scenelike, pos[; input_space, output_space = :pixel, target = Point3f(0)]) -Projects the given positional data from the space of the given plot, scene or +Projects the given positional data from the space of the given plot, scene or axis (`scenelike`) to pixel space. Optionally the input and output space can be changed via the respective keyword arguments. ## Notes Depending on the `scenelike` object passed the context of what data, -transformed, world and eye space is may change. A `scene` with a pixel-space +transformed, world and eye space is may change. A `scene` with a pixel-space camera will yield different results than a `scene` with a 3D camera, for example. -Transformations and the input space can be different between a plot and its +Transformations and the input space can be different between a plot and its parent scene and also between child plots and their parent plots. In some cases you may also see varying results because of this. @@ -391,7 +391,7 @@ may need to add `minimum(pixelarea(scene))`. `project_to_screen` does this for you. """ function project( - @nospecialize(plot::AbstractPlot), pos; + @nospecialize(plot::AbstractPlot), pos; input_space::Symbol = get_value(plot, :space, :data), output_space::Symbol = :pixel, target = _point3_target(pos) ) @@ -402,7 +402,7 @@ function project( end function project( - obj, pos; + obj, pos; input_space::Symbol = :data, output_space::Symbol = :pixel, target = _point3_target(pos) ) @@ -433,7 +433,7 @@ transform function and input space. For a simpler interface, use `project(scenelike, pos)`. """ function project( - mat::Mat4, tf, input_space::Symbol, output_space::Symbol, + mat::Mat4, tf, input_space::Symbol, output_space::Symbol, pos::AbstractArray{<: VecTypes{N, T}}, target::VecTypes = Point3{T}(0) ) where {N, T <: Real} if input_space === output_space @@ -446,7 +446,7 @@ function project( end end function project( - mat::Mat4, tf, input_space::Symbol, output_space::Symbol, + mat::Mat4, tf, input_space::Symbol, output_space::Symbol, pos::VecTypes{N, T}, target = Point3{T}(0)) where {N, T <: Real} if input_space === output_space return to_ndim(target, pos) @@ -469,7 +469,7 @@ function inv_project(mat::Mat4f, itf, pos::VecTypes{N, T}, target = Point3{T}(0) end function project(mat::Mat4, pos::VecTypes{N, T}, target = Point3{T}(0)) where {N, T} - # TODO is to_ndim slow? It alone seems to + # TODO is to_ndim slow? It alone seems to # p4d = to_ndim(Point4{T}, to_ndim(Point3{T}, pos, 0), 1) p4d = to_ndim(Point4{T}(0,0,0,1), pos) p4d = mat * p4d @@ -511,7 +511,7 @@ end """ project_to_screen(scenelike[, input_space], pos[; target = Point2f(0)]) -Transforms the given position(s) to (2D) screen/pixel space, using the space of +Transforms the given position(s) to (2D) screen/pixel space, using the space of `scenelike` as the default input space. The returned positions will be relative to the screen/window/figure origin, rather than the (parent) scene. """ @@ -545,7 +545,7 @@ end project_to_pixel(scenelike[, input_space], pos[; target = Point2f(0)]) Transforms the given position(s) to (2D) pixel space, using the space of `scenelike` -as the default input space. The returned positions will be relative to the +as the default input space. The returned positions will be relative to the scene derived from scenelike, not the screen/window/figure origin. This is equivalent to `project(scenelike, pos[; input_space], target = Point2f(0))`. @@ -564,30 +564,32 @@ end Returns an observable that triggers whenever the result of `project` could change """ function projection_obs(@nospecialize(plot::AbstractPlot)) - return lift( + return map( (_, _, _, _, _) -> nothing, plot, - camera(plot).projectionview, + camera(plot).projectionview, get_scene(plot).px_area, transformationmatrix(plot), transform_func_obs(plot), get(plot, :space, Observable(:data)), + priority = 100 ) end function projection_obs(scene::Scene) - return lift( + return map( (_, _, _, _) -> nothing, scene, - camera(scene).projectionview, + camera(scene).projectionview, scene.px_area, transformationmatrix(scene), - transform_func_obs(scene) + transform_func_obs(scene), + priority = 100 ) end # TODO # naming, do we keep this? -function _get_model_obs(plot::AbstractPlot) +function _get_model_obs(plot::Union{AbstractPlot, Attributes}) space = get(plot, :space, Observable(:data)) return map((s, m) -> s in (:data, :transformed) ? m : Mat4f(I), space, plot.model) end \ No newline at end of file diff --git a/src/interfaces.jl b/src/interfaces.jl index 358f98af435..fcd8eb73490 100644 --- a/src/interfaces.jl +++ b/src/interfaces.jl @@ -188,10 +188,11 @@ function (PlotType::Type{<: AbstractPlot{Typ}})(scene::SceneLike, attributes::At ) # Transformation is a field of the plot type, but can be given as an attribute - trans = pop!(plot_attributes, :transformation, automatic) + # TODO probably pop! instead? but generic_plot_attributes() incompatible atm + trans = get!(plot_attributes, :transformation, automatic) transform_func = pop!(plot_attributes, :transform_func, automatic) transval = to_value(trans) - + transformation = if transval === automatic Transformation(scene, transform_func = transform_func) elseif isa(transval, Transformation) @@ -199,7 +200,7 @@ function (PlotType::Type{<: AbstractPlot{Typ}})(scene::SceneLike, attributes::At transval else trans = Transformation( - transval.translation, transval.scale, transval.rotation, + transval.translation, transval.scale, transval.rotation, transval.model, transform_func ) if isassigned(transval.parent) diff --git a/src/layouting/transformation.jl b/src/layouting/transformation.jl index 96ed01fbace..39e0936c28b 100644 --- a/src/layouting/transformation.jl +++ b/src/layouting/transformation.jl @@ -22,7 +22,7 @@ function Transformation(transformable::Transformable; scale=Vec3f(1), translation=Vec3f(0), rotation=Quaternionf(0, 0, 0, 1), - transform_func = copy(transformation(transformable).transform_func)) + transform_func = automatic) scale_o = convert(Observable{Vec3f}, scale) translation_o = convert(Observable{Vec3f}, translation) @@ -39,7 +39,7 @@ function Transformation(transformable::Transformable; scale_o, rotation_o, model, - convert(Observable{Any}, transform_func) + transform_func === automatic ? copy(parent_transform.transform_func) : transform_func ) trans.parent[] = parent_transform diff --git a/src/makielayout/blocks/axis3d.jl b/src/makielayout/blocks/axis3d.jl index b196b07153e..2b5ddbbecbc 100644 --- a/src/makielayout/blocks/axis3d.jl +++ b/src/makielayout/blocks/axis3d.jl @@ -499,8 +499,8 @@ function add_ticks_and_ticklabels!(topscene, scene, ax, dim::Int, limits, tickno ticksize = attr(:ticksize) tick_segments = lift(topscene, limits, tickvalues, miv, min1, min2, - scene.camera.projectionview, scene.px_area, ticksize, xreversed, yreversed, zreversed) do lims, ticks, miv, min1, min2, - pview, pxa, tsize, xrev, yrev, zrev + projection_obs(topscene), ticksize, xreversed, yreversed, zreversed) do lims, ticks, miv, min1, min2, + _, tsize, xrev, yrev, zrev rev1 = (xrev, yrev, zrev)[d1] rev2 = (xrev, yrev, zrev)[d2] @@ -514,8 +514,6 @@ function add_ticks_and_ticklabels!(topscene, scene, ax, dim::Int, limits, tickno diff_f1 = f1 - f1_oppo diff_f2 = f2 - f2_oppo - o = pxa.origin - return map(ticks) do t p1 = dpoint(t, f1, f2) p2 = if dim == 3 @@ -529,8 +527,8 @@ function add_ticks_and_ticklabels!(topscene, scene, ax, dim::Int, limits, tickno dpoint(t, f1 + diff_f1, f2) end - pp1 = Point2f(o + Makie.project(scene, p1)) - pp2 = Point2f(o + Makie.project(scene, p2)) + pp1 = project_to_screen(scene, p1) + pp2 = project_to_screen(scene, p2) diff_pp = Makie.GeometryBasics.normalize(Point2f(pp2 - pp1)) return (pp1, pp1 .+ Float32(tsize) .* diff_pp)