Skip to content

Commit

Permalink
Meshimage improvements (#253)
Browse files Browse the repository at this point in the history
* Add an option for default z level and force 3 dimensionality for the mesh

* Use plot.converted, not that it is helping much

* Make the main projection function clearer

* Make geoaxis lon/lat tick plotting clearer also

* Add basic framework for the Black Hole Initiative slideshow

* Full BHI code

* try some fixes to the geodesic example file

* Construct a Point3d directly in meshimage
  • Loading branch information
asinghvi17 authored Jun 29, 2024
1 parent 6d5b8f9 commit 8c4c8cc
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 24 deletions.
92 changes: 92 additions & 0 deletions examples/blackholeinitiative.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#=
Makie x Black Hole Initiative
=#

# # The structure of a Makie plot
# Figures are basically used to manage layout - all the rendering goes on in the Scene tree.

# Scenes are fundamental building blocks of Makie figures.
# A Scene is like a container for Plots and other Scenes and a Camera.
# Scenes have Plots and Subscenes associated with them.
# Every Scene also has a transformation, made up of scale, translation, and rotation, as well as a
# "black-box" nonlinear transform function.

# Plots are basically descriptions of how to render some input. They have child plots, which are basically
# decompositions of this data, until they are decomposed to an "atomic" form - a set of some basic plot types
# which the backend knows how to render.

# All of these things are connected by Observables - basically, "boxes" that can hold a value.
# Observables have the property that they can signal "observer functions" whenever their value
# changes. This allows us to eagerly integrate changes at an incremental level.

using Makie
using FileIO
using CairoMakie # backend

fig, ax, plt = lines(rand(Point2f, 10))

# The way the Makie pipeline works now, is that dimensions are first
args = Makie.expand_dimensions(Makie.PointBased(), rand(10))
args = Makie.expand_dimensions(Makie.PointBased(), rand(Point2f, 10))
# check if we should extract dims.
Makie.convert_arguments(Makie.PointBased(), args...)

plt.converted[1]

plt.color[] = :red

plt.args#[1][] = randn(100)

ax.blockscene.plots[2].color[] = :blue#RGBAf(1, 0, 0, 1)


fig.scene

fig.layout

ax.blockscene

ax.scene

# To investigate this in more detail, we can extract the Scene graph as a graph,
# and plot it using GraphMakie.


# See the GraphMakie scene graph example (PR ) for more.

# # Plotting on the sphere

# We can use a transformation function to treat input values as spherical coordinates,
# and transform to Cartesian space which will be visualized.

cow_img = load(Makie.assetpath("cow.png"))

using CoordinateTransformations
SPHERICAL_TRANSFORM_FUNC = Makie.PointTrans{3}() do point
θ, ϕ, r = point # we interpret this way so that e.g. surface works correctly.
CoordinateTransformations.CartesianFromSpherical()(Spherical(r + 1, θ, ϕ))
end

f, a, p = lines(rand(Point3f, 100))
p.transformation.transform_func[] = SPHERICAL_TRANSFORM_FUNC
f

using GeoMakie
f, a, p = meshimage(-π/2..π/2, -π..π, cow_img; axis = (; type = LScene,))
p.transformation.transform_func[] = SPHERICAL_TRANSFORM_FUNC
f

p.plots[1].color[] = cow_img

cameracontrols(a.scene)
f

Makie.apply_transform(SPHERICAL_TRANSFORM_FUNC, Point3f(1, 1, 0))


# You can also plot directly on a spherical mesh:

mesh(
Sphere(Point3f(0), 1),
color =
)
13 changes: 7 additions & 6 deletions examples/geodesy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
# GeoMakie integrates Makie's transformation interface and Geodesy.jl.
# Let's get a Raster and set our data up:
using Makie, GeoMakie, CairoMakie
using Rasters, Dates
using Rasters, ArchGDAL, RasterDataSources
using Dates

# First, load the Raster.
tmax_stack = Rasters.RasterSeries(WorldClim{Climate}, :tmax; month = 1:12) .|> x -> x[:, :, 1]
Expand All @@ -18,15 +19,15 @@ x, y, z = Makie.convert_arguments(Makie.ContinuousSurface(), current_raster)
# We want any place with missing data to show up as black, so we replace all NaNs with 0.
# For the type of data we're using, and the type of visualization, this is a reasonable assumption.
transform_z = replace(z, NaN => 0.0)
# Now, we use a utility from CairoMakie to transform this grid of xs, ys, and zs into a matrix with representation
surface_mesh = Makie.surface2mesh(x, y, transform_z .* 100)

# Let's now construct the plot.
f_ax_pl, title = with_theme(theme_black()) do
f = Figure()
ax = GeoAxis(f[1, 1]; source=GeoMakie.Geodesy.ECEFfromLLA(GeoMakie.Geodesy.WGS84()))
pl = surface!(current_raster; nan_color=:black, axis=(; type=GeoAxis))
ax = LScene(f[1, 1])
pl = meshimage!(ax, current_raster; nan_color=:black)
pl.transformation.transform_func[] = GeoMakie.Geodesy.ECEFfromLLA(GeoMakie.Geodesy.WGS84())
title = Label(f[begin-1, :], "Title", fontsize = 20, font = :bold, tellwidth = false)
return f, ax, pl, title
return (Makie.FigureAxisPlot(f, ax, pl), title)
end
f_ax_pl
# Having done all this construction, we set the transformation function:
Expand Down
18 changes: 13 additions & 5 deletions src/geoaxis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -679,16 +679,24 @@ function Makie.initialize_block!(axis::GeoAxis)
# scatter!(axis.blockscene, latpoints, markersize=7, color=(:blue, 0.5))

lattex = text!(axis.blockscene, lat_points_px;
text=lat_text, space=:pixel, align=(:center, :center),
font=axis.xticklabelfont, color=axis.xticklabelcolor,
fontsize=axis.xticklabelsize, visible=axis.xticklabelsvisible,
text=lat_text,
space=:pixel,
align=(:center, :center),
font=axis.xticklabelfont,
color=axis.xticklabelcolor,
fontsize=axis.xticklabelsize,
visible=axis.xticklabelsvisible,
)

lontex = text!(axis.blockscene, lon_points_px;
text=lon_text, space=:pixel, align=(:center, :center),
text=lon_text,
space=:pixel,
align=(:center, :center),
font=axis.yticklabelfont,
color=axis.yticklabelcolor,
fontsize=axis.yticklabelsize, visible=axis.yticklabelsvisible,)
fontsize=axis.yticklabelsize,
visible=axis.yticklabelsvisible,
)

fonts = theme(axis.blockscene, :fonts)
# Finally calculate protrusions and report all bounding boxes
Expand Down
40 changes: 27 additions & 13 deletions src/mesh_image.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ the provided image. Its conversion trait is `ImageLike`.
Can be an Integer or a 2-tuple of integers representing number of points per side.
"""
npoints = 100
"The z-coordinate given to each mesh point. Useful in 3D transformations."
z_level = 0.0
"Sets the lighting algorithm used. Options are `NoShading` (no lighting), `FastShading` (AmbientLight + PointLight) or `MultiLightShading` (Multiple lights, GLMakie only). Note that this does not affect RPRMakie."
shading = NoShading
MakieCore.mixin_generic_plot_attributes()...
MakieCore.mixin_colormap_attributes()...
end
Expand All @@ -45,7 +49,7 @@ Makie.conversion_trait(::Type{<: MeshImage}) = Makie.ImageLike()
function Makie.plot!(plot::MeshImage)
# Initialize some Observables which will hold data.
# For right now, they point to some undefined place in memory.
points_observable = Observable{Vector{Point2f}}()
points_observable = Observable{Vector{Point3d}}()
faces_observable = Observable{Vector{Makie.GLTriangleFace}}()#=GeometryBasics.QuadFace{Int}=#
uv_observable = Observable{Vector{Vec2f}}()

Expand All @@ -55,12 +59,12 @@ function Makie.plot!(plot::MeshImage)
old_npoints = Ref(0)

# Handle the transformation
onany(plot, plot[1], plot[2], plot.transformation.transform_func, plot.npoints, plot.space; update=true) do x_in, y_in, tfunc, npoints, space
onany(plot, plot.converted[1], plot.converted[2], plot.transformation.transform_func, plot.npoints, plot.space, plot.z_level; update=true) do x_in, y_in, tfunc, npoints, space, z_level
# If `npoints` changed, then re-construct the mesh.
if npoints != old_npoints[]
# We need a new StructArray to hold all the points.
# TODO: resize the old structarray instead!
points_observable.val = Vector{Point2f}(undef, first(npoints) * last(npoints))
points_observable.val = Vector{Point3d}(undef, first(npoints) * last(npoints))
# This constructs an efficient triangulation of a rectangle (all images are rectangles).
rect = GeometryBasics.Tesselation(Rect2f(0, 0, 1, 1), (first(npoints), last(npoints)))
# This decomposes that Tesselation to actual triangles, with integer index values.
Expand All @@ -76,8 +80,16 @@ function Makie.plot!(plot::MeshImage)
poval = points_observable[]
# The array is in a grid, so we have to update them on a grid as well.
for (linear_ind, cartesian_ind) in enumerate(CartesianIndices((npoints, npoints)))
p = Point2f(xs[cartesian_ind[1]], ys[cartesian_ind[2]])
poval[linear_ind] = Makie.apply_transform(tfunc, p, space)
p = Point3d(xs[cartesian_ind[1]], ys[cartesian_ind[2]], z_level)
poval[linear_ind] = Makie.to_ndim(
Point3d,
Makie.apply_transform(
tfunc,
p,
space
),
0.0
)
end
# Finally, we notify the points observable that it has an update.
notify(points_observable)
Expand All @@ -88,10 +100,9 @@ function Makie.plot!(plot::MeshImage)
end
end

# TODO: figure out how to mutate a mesh's points in place

# You may have noticed that nowhere above did we actually create a mesh. Let's remedy that now!
final_mesh = lift(points_observable, faces_observable, uv_observable; ignore_equal_values = true#=, priority = -100=#) do points, faces, uv
# You may have noticed that nowhere above did we actually create a mesh.
# Let's remedy that now!
final_mesh = lift(plot, points_observable, faces_observable, uv_observable; ignore_equal_values = true#=, priority = -100=#) do points, faces, uv
return GeometryBasics.Mesh(
GeometryBasics.meta(points; uv=uv), # each point gets a UV, they're interpolated on faces
faces
Expand All @@ -102,10 +113,13 @@ function Makie.plot!(plot::MeshImage)
mesh!(
plot,
final_mesh;
color = plot[3],
MakieCore.colormap_attributes(plot)...,
shading = NoShading,
transformation = Transformation() # since the points are pre transformed, we don't need to transform them again
color = plot.converted[3], # pass on the color directly
MakieCore.colormap_attributes(plot)..., # pass on all colormap attributes
shading = NoShading, #
transformation = Transformation(
plot.transformation; # connect up the model matrix to the parent's model matrix
transform_func = nothing # do NOT connect the transform func, since we've already done that
)
)
# TODO: get a `:transformed` space out so we don't need this `transformation` hack
end
Expand Down

0 comments on commit 8c4c8cc

Please sign in to comment.