Skip to content

Commit bbb6779

Browse files
authored
Merge pull request #1507 from GenericMappingTools/FV-in-plot
Deal with the FaceVertices data (hide invisible faces) in plot3.
2 parents 83db297 + 3c59745 commit bbb6779

File tree

6 files changed

+138
-41
lines changed

6 files changed

+138
-41
lines changed

src/common_options.jl

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,18 @@ function parse_JZ(d::Dict, cmd::String, del::Bool=true; O::Bool=false, is3D::Boo
386386
(o == "") && (o = " ") # When scaning a string with only -J (i.e., no args), do not error
387387
(o[1] != 'X' || o[end] == 'd') && @warn("aspect3 works only in linear projections (and no geog), ignoring it.")
388388
if (o[1] == 'X' && o[end] != 'd' && length(o) > 1)
389-
opt_J = " -JZ" * split(o[2:end],'/')[1]; seek_JZ = false
389+
s_val::String = string(val)
390+
if (contains(s_val, ':')) # An explicit aspect ratio, e.g. 3:2
391+
dims = parse.(Float64, split(s_val, ':'))
392+
t = split(o[2:end],'/')[1]
393+
isletter(t[end]) && (t = t[1:end-1]) # Remove the letter 'x' which hopefully a 'c'
394+
w = parse(Float64, t)
395+
h = w * dims[2] / dims[1] # Apply the aspect ratio
396+
opt_J = string(" -JZ", h)
397+
else
398+
opt_J = " -JZ" * split(o[2:end],'/')[1];
399+
end
400+
seek_JZ = false
390401
cmd *= opt_J
391402
end
392403
end
@@ -602,9 +613,12 @@ function append_figsize(d::Dict, opt_J::String, width::String="", scale::Bool=fa
602613
return opt_J
603614
end
604615

605-
set_aspect_ratio(aspect::Nothing, width::String, def_fig::Bool=false, is_aspect3::Bool=false)::String = set_aspect_ratio("", width, def_fig, is_aspect3)
606-
set_aspect_ratio(aspect::Symbol, width::String, def_fig::Bool=false, is_aspect3::Bool=false)::String = set_aspect_ratio(string(aspect), width, def_fig, is_aspect3)
607-
set_aspect_ratio(aspect::Real, width::String, def_fig::Bool=false, is_aspect3::Bool=false)::String = set_aspect_ratio(string(aspect, ":1"), width, def_fig, is_aspect3)
616+
set_aspect_ratio(aspect::Nothing, width::String, def_fig::Bool=false, is_aspect3::Bool=false)::String =
617+
set_aspect_ratio("", width, def_fig, is_aspect3)
618+
set_aspect_ratio(aspect::Symbol, width::String, def_fig::Bool=false, is_aspect3::Bool=false)::String =
619+
set_aspect_ratio(string(aspect), width, def_fig, is_aspect3)
620+
set_aspect_ratio(aspect::Real, width::String, def_fig::Bool=false, is_aspect3::Bool=false)::String =
621+
set_aspect_ratio(string(aspect, ":1"), width, def_fig, is_aspect3)
608622
function set_aspect_ratio(aspect::String, width::String, def_fig::Bool=false, is_aspect3::Bool=false)::String
609623
# Set the aspect ratio. ASPECT can be "equal", "eq"; "square", "sq" or a ratio in the form "4:3", "16:12", etc.
610624
def_fig && (width = split(DEF_FIG_SIZE, '/')[1])
@@ -3711,6 +3725,7 @@ function _read_data(d::Dict, cmd::String, arg, opt_R::String="", is3D::Bool=fals
37113725
if (!occursin("?", opt_R) && !is_onecol) # is_onecol is true for DateTime data
37123726
dx::Float64 = (wesn_f64[2] - wesn_f64[1]) * 0.005; dy::Float64 = (wesn_f64[4] - wesn_f64[3]) * 0.005;
37133727
wesn_f64[1] -= dx; wesn_f64[2] += dx; wesn_f64[3] -= dy; wesn_f64[4] += dy;
3728+
length(wesn_f64) == 6 && (dz::Float64 = (wesn_f64[6] - wesn_f64[5]) * 0.005; wesn_f64[5] -= dz; wesn_f64[6] += dz;)
37143729
wesn_f64 = round_wesn(wesn_f64, is_geo) # Add a pad if not-tight
37153730
if (isGMTdataset(arg)) # Needed for the guess_proj case
37163731
if ((wesn_f64[3] < -90 || wesn_f64[4] > 90) || ((wesn_f64[2] - wesn_f64[1]) > 360))
@@ -3779,8 +3794,10 @@ function round_wesn(_wesn::Vector{Float64}, geo::Bool=false, pad=zeros(2))::Vect
37793794
dy = (wesn[4] - wesn[3]) * pad[2]
37803795
wesn[1:4] = [wesn[1]-dx, wesn[2]+dx, wesn[3]-dy, wesn[4]+dy]
37813796
end
3782-
set::Vector{Bool} = zeros(Bool, 2)
3783-
range::Vector{Float64} = [0.0, 0.0]
3797+
set = zeros(Bool, 3)
3798+
range = [0.0, 0.0, 0.0]
3799+
n_axe = (length(wesn) == 6) ? 3 : 2
3800+
37843801
if (wesn[1] == wesn[2])
37853802
wesn[1] -= abs(wesn[1]) * 0.05; wesn[2] += abs(wesn[2]) * 0.05
37863803
if (wesn[1] == wesn[2]) wesn[1] = -0.1; wesn[2] = 0.1; end # x was = 0
@@ -3791,6 +3808,7 @@ function round_wesn(_wesn::Vector{Float64}, geo::Bool=false, pad=zeros(2))::Vect
37913808
end
37923809
range[1] = wesn[2] - wesn[1]
37933810
range[2] = wesn[4] - wesn[3]
3811+
(n_axe == 3) && (range[3] = wesn[6] - wesn[5])
37943812
if (geo) # Special checks due to periodicity
37953813
if (range[1] > 306.0) # If within 15% of a full 360 we promote to 360
37963814
if ((wesn[1] + wesn[2]) / 2 < 100) wesn[1] = -180.; wesn[2] = 180.
@@ -3806,14 +3824,14 @@ function round_wesn(_wesn::Vector{Float64}, geo::Bool=false, pad=zeros(2))::Vect
38063824

38073825
_log10(x) = log(x) / 2.30258509299 # Compute log10 with ln because JET & SnoopCompile acuse it of "failed to optimize"
38083826
item = 1
3809-
for side = 1:2
3827+
for side = 1:n_axe
38103828
set[side] && continue # Done above */
38113829
mag::Float64 = round(_log10(range[side])) - 1.0
38123830
inc = exp10(mag)
38133831
if ((range[side] / inc) > 10.0) inc *= 2.0 end # Factor of 2 in the rounding
38143832
if ((range[side] / inc) > 10.0) inc *= 2.5 end # Factor of 5 in the rounding
38153833
s = 1.0
3816-
if (geo) # Use arc integer minutes or seconds if possible
3834+
if (geo && side < 3) # Use arc integer minutes or seconds if possible
38173835
if (inc < 1.0 && inc > 0.05) # Nearest arc minute
38183836
s, inc = 60.0, 1.0
38193837
if ((s * range[side] / inc) > 10.0) inc *= 2.0 end # 2 arcmin
@@ -3897,12 +3915,13 @@ end
38973915

38983916
# ---------------------------------------------------------------------------------------------------
38993917
"""
3900-
isFV(D::Vector{<:GMTdataset})::Bool
3918+
isFV(D)::Bool
39013919
3902-
Check if D, a vector of GMTdataset, is a Face-Vertices ensemble.
3920+
Check if D is a Face-Vertices ensemble (a 2 elements vector of GMTdataset).
39033921
"""
3904-
function isFV(D::Vector{<:GMTdataset})::Bool
3905-
length(D) == 2 && (D[1].geom == wkbPoint || D[1].geom == wkbPointZ) && eltype(D[2].data) <: Integer
3922+
function isFV(D)::Bool
3923+
(isa(D, Vector{<:GMTdataset}) && length(D) > 1) &&
3924+
(D[1].geom == wkbPoint || D[1].geom == wkbPointZ) && eltype(D[2].data) <: Integer
39063925
end
39073926

39083927
# ---------------------------------------------------------------------------------------------------

src/gmt_main.jl

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,25 +1094,18 @@ function dataset_init_FV(API::Ptr{Nothing}, FV)::Ptr{GMT_MATRIX}
10941094
n_segs = size(F.data, 1) # Number of segments or faces (polygons)
10951095
n_rows = size(F.data, 2) # Number of rows (vertexes of the polygon)
10961096
n_cols = size(V.data, 2) # Number of columns (2 for x,y; 3 for x,y,z)
1097-
view_vec = [sind(200) * cosd(30), cosd(200) * cosd(30), sind(30)]
1098-
1099-
view_proj = triage_faces(V, F, view_vec)
1100-
n_visible_faces = sum(view_proj .> 0)
1101-
dim = [1, n_visible_faces, n_rows, n_cols] # [1, GMT_SEG+1, GMT_ROW+1, GMT_COL+1]
1097+
dim = [1, n_segs, n_rows, n_cols] # [1, GMT_SEG+1, GMT_ROW+1, GMT_COL+1]
11021098

11031099
pdim = pointer(dim)
11041100
D = convert(Ptr{GMT_DATASET}, GMT_Create_Data(API, GMT_IS_DATASET, GMT_IS_PLP, GMT_NO_STRINGS, pdim, NULL, NULL, 0, 0, NULL))
11051101
DS::GMT_DATASET = unsafe_load(D)
11061102
DT = unsafe_load(unsafe_load(DS.table)) # GMT_DATATABLE
11071103

1108-
n_records, count_vis = 0, 0
1104+
n_records = 0
11091105
tmp = zeros(n_rows, n_cols)
11101106

11111107
for seg = 1:n_segs # Each row in F (a face) is a new data segment (a polygon)
1112-
view_proj[seg] <= 0 && continue
1113-
count_vis += 1
1114-
1115-
DSv = convert(Ptr{Nothing}, unsafe_load(DT.segment, count_vis)) # DT.segment = Ptr{Ptr{GMT_DATASEGMENT}}
1108+
DSv = convert(Ptr{Nothing}, unsafe_load(DT.segment, seg)) # DT.segment = Ptr{Ptr{GMT_DATASEGMENT}}
11161109
S = GMT_Alloc_Segment(API, GMT_NO_STRINGS, n_rows, n_cols, "", DSv) # Ptr{GMT_DATASEGMENT}
11171110
Sb = unsafe_load(S) # GMT_DATASEGMENT; Sb.data -> Ptr{Ptr{Float64}}
11181111

@@ -1126,7 +1119,7 @@ function dataset_init_FV(API::Ptr{Nothing}, FV)::Ptr{GMT_MATRIX}
11261119
n_records += n_rows # Must manually keep track of totals
11271120
DS.type_ = GMT_READ_DATA
11281121
unsafe_store!(S, Sb)
1129-
unsafe_store!(DT.segment, S, count_vis)
1122+
unsafe_store!(DT.segment, S, seg)
11301123
end
11311124
DT.n_records, DS.n_records = n_records, n_records # They are equal because our GMT_DATASET have only one table
11321125
Dt = unsafe_load(DS.table)
@@ -1137,21 +1130,6 @@ function dataset_init_FV(API::Ptr{Nothing}, FV)::Ptr{GMT_MATRIX}
11371130
return D
11381131
end
11391132

1140-
function triage_faces(V, F, view_vec)
1141-
# Compute the dot product between the view vector and the normal of each face
1142-
n_faces = size(F.data, 1) # Number of segments or faces (polygons)
1143-
n_rows = size(F.data, 2) # Number of rows (vertexes of the polygon)
1144-
tmp = zeros(n_rows, 3)
1145-
proj = zeros(n_faces)
1146-
for face = 1:n_faces # Each row in F (a face) is a new data segment (a polygon)
1147-
for c = 1:3, r = 1:n_rows
1148-
tmp[r,c] = V.data[F.data[face,r], c]
1149-
end
1150-
proj[face] = dot(facenorm(tmp), view_vec)
1151-
end
1152-
return proj
1153-
end
1154-
11551133
# ---------------------------------------------------------------------------------------------------
11561134
function dataset_init(API::Ptr{Nothing}, ptr, actual_family::Vector{<:Integer})::Ptr{GMT_MATRIX}
11571135
# Create empty Matrix container, associate it with julia data matrix, and use as GMT input.

src/imshow.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ function imshow(arg1, x::AbstractVector{Float64}=Float64[], y::AbstractVector{Fl
7070
isa(arg1, Matrix{<:Real}) && (arg1 = mat2ds(arg1))
7171
ginfo = isa(arg1, GMTdataset) ? arg1.bbox : arg1[1].ds_bbox
7272
CTRL.limits[1:4] = ginfo[1:4]; CTRL.limits[7:10] = ginfo[1:4]
73-
call_plot3 = ((isa(arg1, GMTdataset) && arg1.geom == Gdal.wkbLineStringZ) || (isa(arg1, Vector{<:GMTdataset}) && arg1[1].geom == Gdal.wkbLineStringZ)) ? true : false # Should evolve into a fun that detects the several plot3d cases.
73+
call_plot3 = ((isa(arg1, GMTdataset) && arg1.geom == Gdal.wkbLineStringZ) ||
74+
(isa(arg1, Vector{<:GMTdataset}) && arg1[1].geom == Gdal.wkbLineStringZ) ||
75+
isFV(arg1)) ? true : false # Should evolve into a fun that detects the several plot3d cases.
7476
!call_plot3 && (call_plot3 = isplot3(kw))
7577
return (call_plot3) ? plot3d(arg1; show=see, kw...) : plot(arg1; show=see, kw...)
7678
elseif (isa(arg1, GMTcpt))

src/psxy.jl

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,13 @@ function common_plot_xyz(cmd0::String, arg1, caller::String, first::Bool, is3D::
4444
end
4545

4646
if (occursin('3', caller) && !haskey(d, :p) && !haskey(d, :view) && !haskey(d, :perspective))
47-
d[:p] = "200/30" # Need this before parse_BJR() so MAP_FRAME_AXES can be guessed.
47+
d[:p] = "200/30" # Need this before parse_BJR() so MAP_FRAME_AXES can be guessed.
48+
end
49+
cmd, opt_p = parse_p(d, cmd) # Parse this one (view angle) aside so we can use it to remove invisible faces (3D)
50+
51+
if (is3D && isFV(arg1)) # case of 3D faces
52+
arg1 = deal_faceverts(arg1, d, opt_p)
53+
!haskey(d, :aspect3) && (d[:aspect3] = "equal") # Needs thinking
4854
end
4955

5056
isa(arg1, GMTdataset) && (arg1 = with_xyvar(d, arg1)) # See if we have a column request based on column names
@@ -82,7 +88,7 @@ function common_plot_xyz(cmd0::String, arg1, caller::String, first::Bool, is3D::
8288

8389
cmd, opt_JZ = parse_JZ(d, cmd; O=O, is3D=is3D)
8490
#(is3D && O && opt_JZ == "" && CTRL.pocket_J[3] != "") && (cmd *= CTRL.pocket_J[3])
85-
cmd, = parse_common_opts(d, cmd, [:a :e :f :g :p :t :w :params], first)
91+
cmd, = parse_common_opts(d, cmd, [:a :e :f :g :t :w :params], first)
8692
cmd, opt_l = parse_l(d, cmd) # Parse this one (legend) aside so we can use it in classic mode
8793
cmd, opt_f = parse_f(d, cmd) # Parse this one (-f) aside so we can check against D.attrib
8894
cmd = parse_these_opts(cmd, d, [[:D :shift :offset], [:I :intens], [:N :no_clip :noclip]])
@@ -333,6 +339,20 @@ function common_plot_xyz(cmd0::String, arg1, caller::String, first::Bool, is3D::
333339
return r
334340
end
335341

342+
# ---------------------------------------------------------------------------------------------------
343+
function deal_faceverts(arg1, d, opt_p)
344+
# Deal with the situation where we are plotting 3D FV's
345+
spl = split(opt_p[4:end], '/')
346+
az = parse(Float64, spl[1])
347+
elev = length(spl) > 1 ? parse(Float64, spl[2]) : 90.0
348+
arg1, dotprod = visible_faces(arg1, [sind(az) * cosd(elev), cosd(az) * cosd(elev), sind(elev)])
349+
if (is_in_dict(d, [:G :fill]) === nothing) # If fill not set we use the dotprod and a gray CPT to set the fill
350+
d[:Z] = dotprod
351+
(is_in_dict(d, CPTaliases) === nothing) && (d[:C] = gmt("makecpt -T0/1 -C150,210")) # Users may still set a CPT
352+
end
353+
return arg1
354+
end
355+
336356
# ---------------------------------------------------------------------------------------------------
337357
function frame_opaque(cmd::Vector{String}, oB::String, oR::String, oJ::String, oJZ::String=""; bot::Bool=true)
338358
# Transparency affects the frame too, which is bad. So, if we have a transparency request we
@@ -1340,3 +1360,30 @@ function zoom_reactangle(_cmd, isplot::Bool)
13401360
append!(_cmd, [ins]) # Add the inset call again
13411361
return _cmd
13421362
end
1363+
1364+
# ---------------------------------------------------------------------------------------------------
1365+
function faces_normals_view(V::GMTdataset{Float64,2}, F::GMTdataset{Int,2}, view_vec)
1366+
# Compute the dot product between the view vector and the normal of each face
1367+
# Faces hose dot product is <= 0 are not visible
1368+
n_faces = size(F.data, 1) # Number of segments or faces (polygons)
1369+
n_rows = size(F.data, 2) # Number of rows (vertices of the polygon)
1370+
tmp = zeros(n_rows, 3)
1371+
proj = zeros(n_faces)
1372+
for face = 1:n_faces # Each row in F (a face) is a new data segment (a polygon)
1373+
for c = 1:3, r = 1:n_rows
1374+
tmp[r,c] = V.data[F.data[face,r], c]
1375+
end
1376+
proj[face] = dot(facenorm(tmp), view_vec)
1377+
end
1378+
return proj
1379+
end
1380+
1381+
# ---------------------------------------------------------------------------------------------------
1382+
function visible_faces(FV::Vector{<:GMTdataset}, view_vec)
1383+
# Remove the faces that are not visible from the normal view_vec
1384+
V::GMTdataset{Float64,2}, F::GMTdataset{Int,2} = FV[1], FV[2]
1385+
proj = faces_normals_view(V, F, view_vec)
1386+
is_vis = (proj .> 0) # Visible faces
1387+
F2 = mat2ds(F.data[is_vis, :])
1388+
return [V,F2], proj[is_vis]
1389+
end

src/solids.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,56 @@ function octahedron(r=1.0)
7777
return [mat2ds(V, geom=wkbPointZ), mat2ds(F)]
7878
end
7979

80+
# ----------------------------------------------------------------------------
81+
"""
82+
FV = dodecahedron(r=1.0)
83+
84+
Creates an dodecahedron mesh with radius `r`.
85+
"""
86+
function dodecahedron(r=1.0)
87+
88+
ϕ = Base.MathConstants.golden # (1.0+sqrt(5.0))/2.0, Golden ratio
89+
s = r/sqrt(3.0)
90+
t = ϕ*s
91+
w =-1.0)*s
92+
93+
V = [ s s s # The Vertices
94+
w 0.0 t
95+
-t -w 0.0
96+
t w 0.0
97+
-s s -s
98+
0.0 -t -w
99+
-t w 0.0
100+
s -s s
101+
-s s s
102+
-s -s s
103+
s -s -s
104+
w 0.0 -t
105+
-s -s -s
106+
0.0 -t w
107+
0.0 t -w
108+
-w 0.0 t
109+
t -w 0.0
110+
-w 0.0 -t
111+
s s -s
112+
0.0 t w]
113+
114+
F = [20 9 16 2 1 # The Faces
115+
2 16 10 14 8
116+
16 9 7 3 10
117+
7 9 20 15 5
118+
18 13 3 7 5
119+
3 13 6 14 10
120+
6 13 18 12 11
121+
6 11 17 8 14
122+
11 12 19 4 17
123+
1 2 8 17 4
124+
1 4 19 15 20
125+
12 18 5 15 19]
126+
127+
return [mat2ds(V, geom=wkbPointZ), mat2ds(F)]
128+
end
129+
80130
# ----------------------------------------------------------------------------
81131
"""
82132
FV = tetrahedron(r=1.0)

test/test_solidos.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
GMT.icosahedron()
44
GMT.octahedron()
5+
GMT.dodecahedron()
56
GMT.tetrahedron()
67
GMT.cube()
78
GMT.geosphere(2)

0 commit comments

Comments
 (0)