diff --git a/CHANGELOG.md b/CHANGELOG.md index 4da91b477e5..71b6a893cdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Show DataInspector tooltip on NaN values if `nan_color` has been set to other than `:transparent` [#4310](https://github.com/MakieOrg/Makie.jl/pull/4310) - Fix `linestyle` not being used in `triplot` [#4332](https://github.com/MakieOrg/Makie.jl/pull/4332) - Invalid keyword arguments for `Block`s (e.g. `Axis` and `Colorbar`) now throw errors and show suggestions rather than simply throwing [#4392](https://github.com/MakieOrg/Makie.jl/pull/4392) +- Fix voxel clipping not being based on voxel centers [#4397](https://github.com/MakieOrg/Makie.jl/pull/4397) ## [0.21.11] - 2024-09-13 @@ -23,6 +24,7 @@ - `plotfunc()` and `func2type()` support functions ending with `!` [#4275](https://github.com/MakieOrg/Makie.jl/pull/4275). - Fixed Boundserror in clipped multicolor lines in CairoMakie [#4313](https://github.com/MakieOrg/Makie.jl/pull/4313) - Fix float precision based assertions error in GLMakie.volume [#4311](https://github.com/MakieOrg/Makie.jl/pull/4311) + - Support images with reversed axes [#4338](https://github.com/MakieOrg/Makie.jl/pull/4338) ## [0.21.9] - 2024-08-27 diff --git a/CairoMakie/src/primitives.jl b/CairoMakie/src/primitives.jl index f25a58f0213..a87386c5ce9 100644 --- a/CairoMakie/src/primitives.jl +++ b/CairoMakie/src/primitives.jl @@ -730,14 +730,14 @@ function draw_atomic(scene::Scene, screen::Screen{RT}, @nospecialize(primitive:: image = primitive[3][] xs, ys = primitive[1][], primitive[2][] if xs isa Makie.EndPoints - l, r = extrema(xs) + l, r = xs N = size(image, 1) xs = range(l, r, length = N+1) else xs = regularly_spaced_array_to_range(xs) end if ys isa Makie.EndPoints - l, r = extrema(ys) + l, r = ys N = size(image, 2) ys = range(l, r, length = N+1) else @@ -1253,7 +1253,14 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Maki pos = Makie.voxel_positions(primitive) scale = Makie.voxel_size(primitive) colors = Makie.voxel_colors(primitive) - marker = normal_mesh(Rect3f(Point3f(-0.5), Vec3f(1))) + marker = GeometryBasics.normal_mesh(Rect3f(Point3f(-0.5), Vec3f(1))) + + # Face culling + if !isempty(primitive.clip_planes[]) && Makie.is_data_space(primitive.space[]) + valid = [is_visible(primitive.clip_planes[], p) for p in pos] + pos = pos[valid] + colors = colors[valid] + end # For correct z-ordering we need to be in view/camera or screen space model = copy(primitive.model[]) @@ -1271,7 +1278,7 @@ function draw_atomic(scene::Scene, screen::Screen, @nospecialize(primitive::Maki specular = primitive.specular, shininess = primitive.shininess, faceculling = get(primitive, :faceculling, -10), transformation = Makie.transformation(primitive), - clip_planes = primitive.clip_planes + clip_planes = Plane3f[] ) for i in zorder diff --git a/GLMakie/assets/shader/voxel.frag b/GLMakie/assets/shader/voxel.frag index 71f11e2c356..c9c6873210e 100644 --- a/GLMakie/assets/shader/voxel.frag +++ b/GLMakie/assets/shader/voxel.frag @@ -85,18 +85,16 @@ vec4 get_color(sampler2D color, Nothing color_map, int id) { bool is_clipped() { - float d1, d2; + float d; + // Center of voxel ivec3 size = ivec3(textureSize(voxel_id, 0).xyz); - vec3 xyz = vec3(ivec3(o_uvw * size)); + vec3 xyz = vec3(ivec3(o_uvw * size)) + vec3(0.5); for (int i = 0; i < _num_clip_planes; i++) { - // distance from clip planes with negative clipped - d1 = dot(xyz, clip_planes[i].xyz) - clip_planes[i].w; - d2 = dot(xyz, clip_planes[i].xyz) - clip_planes[i].w; + // distance between clip plane and center + d = dot(xyz, clip_planes[i].xyz) - clip_planes[i].w; - // both outside - clip everything - if (d1 < 0.0 || d2 < 0.0) { + if (d < 0.0) return true; - } } return false; diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 53bb504c7a6..9147fce4b6d 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -691,8 +691,8 @@ end function draw_image(screen::Screen, scene::Scene, plot::Union{Heatmap, Image}) return cached_robj!(screen, scene, plot) do gl_attributes position = lift(plot, plot[1], plot[2]) do x, y - xmin, xmax = extrema(x) - ymin, ymax = extrema(y) + xmin, xmax = x + ymin, ymax = y rect = Rect2(xmin, ymin, xmax - xmin, ymax - ymin) return decompose(Point2d, rect) end diff --git a/Project.toml b/Project.toml index d34ed86ea0b..184f59e60d3 100644 --- a/Project.toml +++ b/Project.toml @@ -85,9 +85,9 @@ FreeTypeAbstraction = "0.10.3" GeometryBasics = "0.4.11" GridLayoutBase = "0.11" ImageBase = "0.1.7" -ImageIO = "0.2, 0.3, 0.4, 0.5, 0.6" +ImageIO = "0.5, 0.6" InteractiveUtils = "1.0, 1.6" -Interpolations = "0.15.1" +Interpolations = "0.14, 0.15.1" IntervalSets = "0.3, 0.4, 0.5, 0.6, 0.7" Isoband = "0.1" KernelDensity = "0.5, 0.6" diff --git a/ReferenceTests/src/tests/examples3d.jl b/ReferenceTests/src/tests/examples3d.jl index 494f2fe95f5..949ce6932bf 100644 --- a/ReferenceTests/src/tests/examples3d.jl +++ b/ReferenceTests/src/tests/examples3d.jl @@ -682,7 +682,7 @@ end @reference_test "Clip planes - voxel" begin f = Figure() a = LScene(f[1, 1]) - a.scene.theme[:clip_planes][] = [Plane3f(Vec3f(-2, -1, -0.5), 0.0), Plane3f(Vec3f(-0.5, -1, -2), 0.0)] + a.scene.theme[:clip_planes][] = [Plane3f(Vec3f(-2, -1, -0.5), 0.1), Plane3f(Vec3f(-0.5, -1, -2), 0.1)] r = -10:10 p = voxels!(a, [cos(sin(x+y)+z) for x in r, y in r, z in r]) f diff --git a/ReferenceTests/src/tests/primitives.jl b/ReferenceTests/src/tests/primitives.jl index f514d9de174..87cfce9a10c 100644 --- a/ReferenceTests/src/tests/primitives.jl +++ b/ReferenceTests/src/tests/primitives.jl @@ -818,3 +818,31 @@ end end f end + +@reference_test "Reverse image, heatmap and surface axes" begin + img = [2 0 0 3; 0 0 0 0; 1 1 0 0; 1 1 0 4] + + f = Figure(size = (600, 400)) + + for (i, interp) in enumerate((true, false)) + for (j, plot_func) in enumerate(( + (fp, x, y, cs, interp) -> image(fp, x, y, cs, colormap = :viridis, interpolate = interp), + (fp, x, y, cs, interp) -> heatmap(fp, x, y, cs, colormap = :viridis, interpolate = interp), + (fp, x, y, cs, interp) -> surface(fp, x, y, zeros(size(cs)), color = cs, colormap = :viridis, interpolate = interp, shading = NoShading) + )) + + gl = GridLayout(f[i, j]) + + a, p = plot_func(gl[1, 1], 1:4, 1:4, img, interp) + hidedecorations!(a) + a, p = plot_func(gl[2, 1], 1:4, 4..1, img, interp) + hidedecorations!(a) + a, p = plot_func(gl[1, 2], 4:-1:1, 1:4, img, interp) + hidedecorations!(a) + a, p = plot_func(gl[2, 2], 4:-1:1, [4, 3, 2, 1], img, interp) + hidedecorations!(a) + end + end + + f +end \ No newline at end of file diff --git a/WGLMakie/assets/voxel.frag b/WGLMakie/assets/voxel.frag index 3fe7a6dfeee..4e095306341 100644 --- a/WGLMakie/assets/voxel.frag +++ b/WGLMakie/assets/voxel.frag @@ -85,18 +85,15 @@ vec3 blinnphong(vec3 N, vec3 V, vec3 L, vec3 color){ bool is_clipped() { - float d1, d2; + float d; + // get center pos of this voxel vec3 size = vec3(textureSize(voxel_id, 0).xyz); - vec3 xyz = vec3(ivec3(o_uvw * size)); + vec3 xyz = vec3(ivec3(o_uvw * size)) + vec3(0.5); for (int i = 0; i < num_clip_planes; i++) { - // distance from clip planes with negative clipped - d1 = dot(xyz, clip_planes[i].xyz) - clip_planes[i].w; - d2 = dot(xyz, clip_planes[i].xyz) - clip_planes[i].w; - - // both outside - clip everything - if (d1 < 0.0 || d2 < 0.0) { + // distance between clip plane and voxel center + d = dot(xyz, clip_planes[i].xyz) - clip_planes[i].w; + if (d < 0.0) return true; - } } return false; diff --git a/WGLMakie/src/imagelike.jl b/WGLMakie/src/imagelike.jl index 62d365ac929..b286e055e79 100644 --- a/WGLMakie/src/imagelike.jl +++ b/WGLMakie/src/imagelike.jl @@ -129,7 +129,7 @@ end -xy_convert(x::Makie.EndPoints, n) = LinRange(extrema(x)..., n + 1) +xy_convert(x::Makie.EndPoints, n) = LinRange(x..., n + 1) xy_convert(x::AbstractArray, n) = x # TODO, speed up GeometryBasics @@ -166,8 +166,8 @@ function limits_to_uvmesh(plot, f32c) py = lift(identity, plot, py; ignore_equal_values=true) if px[] isa Makie.EndPoints && py[] isa Makie.EndPoints && Makie.is_identity_transform(t) rect = lift(plot, px, py) do x, y - xmin, xmax = extrema(x) - ymin, ymax = extrema(y) + xmin, xmax = x + ymin, ymax = y return Rect2f(xmin, ymin, xmax - xmin, ymax - ymin) end ps = lift(rect -> decompose(Point2f, rect), plot, rect) diff --git a/src/conversions.jl b/src/conversions.jl index 752cb19c38c..6516860f0a5 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -365,12 +365,10 @@ function to_endpoints(x::Tuple{<:Real,<:Real}) T = float_type(x...) return EndPoints(T.(x)) end -to_endpoints(x::ClosedInterval) = to_endpoints(endpoints(x)) -function to_endpoints(x::Union{Interval,AbstractVector,ClosedInterval}) - return to_endpoints((minimum(x), maximum(x))) -end +to_endpoints(x::Interval) = to_endpoints(endpoints(x)) +to_endpoints(x::EndPoints) = x +to_endpoints(x::AbstractVector) = to_endpoints((first(x), last(x))) function to_endpoints(x, dim) - # having minimum and maximum here actually invites bugs x isa AbstractVector && !(x isa EndPoints) && print_range_warning(dim, x) return to_endpoints(x) end @@ -698,7 +696,8 @@ end # Helper Functions # ################################################################################ -to_linspace(interval, N) = range(minimum(interval), stop = maximum(interval), length = N) +to_linspace(interval::Interval, N) = range(leftendpoint(interval), stop = rightendpoint(interval), length = N) +to_linspace(x, N) = range(first(x), stop = last(x), length = N) """ Converts the element array type to `T1` without making a copy if the element type matches diff --git a/test/boundingboxes.jl b/test/boundingboxes.jl index 66897c8444f..88cda42f179 100644 --- a/test/boundingboxes.jl +++ b/test/boundingboxes.jl @@ -92,6 +92,11 @@ end @test bb.origin ≈ Point3f(0) @test bb.widths ≈ Vec3f(10.0, 10.0, 0) + fig, ax, p = image(1..0, 1:10, rand(10, 10)) + bb = boundingbox(p) + @test bb.origin ≈ Point3f(0, 1, 0) + @test bb.widths ≈ Vec3f(1.0, 9.0, 0) + # text transforms to pixel space atm (TODO) fig = Figure(size = (400, 400)) ax = Axis(fig[1, 1]) diff --git a/test/conversions.jl b/test/conversions.jl index 2d60f0cc5d9..118dde2ad66 100644 --- a/test/conversions.jl +++ b/test/conversions.jl @@ -342,9 +342,11 @@ end v1 = collect(1:10) v2 = collect(1:6) + v3 = reverse(v1) i1 = 1 .. 10 i2 = 1 .. 6 + i3 = 10 .. 1 o3 = Float32.(m3) @@ -354,6 +356,8 @@ end @test convert_arguments(Image, m3) == ((0.0f0, 10.0f0), (0.0f0, 6.0f0), o3) @test convert_arguments(Image, v1, r2, m3) == ((1.0f0, 10.0f0), (1.0f0, 6.0f0), o3) @test convert_arguments(Image, i1, v2, m3) == ((1.0f0, 10.0f0), (1.0f0, 6.0f0), o3) + @test convert_arguments(Image, v3, i1, m3) == ((10, 1), (1, 10), o3) + @test convert_arguments(Image, v1, i3, m3) == ((1, 10), (10, 1), o3) @test convert_arguments(Image, m1, m2, m3) === (m1, m2, m3) @test convert_arguments(Heatmap, m1, m2) === (m1, m2) end