Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions docs/docs/c-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,9 @@ web-mercator world
coordinates (the range 0 to 1, with y pointing down). Point symbols, soundings, and
text come as a world anchor plus a small outline in reference pixels, so you can
draw them at a fixed size on screen. Every call carries the feature's SCAMIN, so you
can hide it by zoom in a shader.
can hide it by zoom in a shader — together with the display category it came in on,
so you can honour the S-52 rule that SCAMIN never hides a display-base feature
(`f->disp_cat == TILE57_DISP_BASE` => draw it at every zoom).

You pass the view rotation (`rotation_rad`, 0 = north-up) and apply it to your own
transform. Each rotatable call carries a `tile57_rot_align` saying what its angle is
Expand All @@ -324,7 +326,11 @@ value labels laid out along their contour.
typedef struct { double x, y; } tile57_world_point; /* web-mercator 0..1, y down */
typedef struct { const tile57_world_point *pts; uint32_t n;
const uint32_t *ring_starts; uint32_t ring_count; } tile57_world_rings;
typedef struct { const char *cls; int64_t scamin; int32_t plane; } tile57_feature;
/* The S-52 display category the feature came in on. */
typedef enum { TILE57_DISP_BASE=0, TILE57_DISP_STANDARD=1, TILE57_DISP_OTHER=2 } tile57_disp_cat;

typedef struct { const char *cls; int64_t scamin; int32_t plane;
tile57_disp_cat disp_cat; } tile57_feature;

/* What a rotatable call's angle is referenced to: VIEWPORT = screen (stay upright),
* MAP = chart (add the view rotation, turn with the chart). */
Expand Down
14 changes: 13 additions & 1 deletion include/tile57.h
Original file line number Diff line number Diff line change
Expand Up @@ -518,13 +518,25 @@ typedef struct {
* `rot_north`, and it is what a depth-contour value must follow. */
typedef enum { TILE57_ALIGN_VIEWPORT = 0, TILE57_ALIGN_MAP = 1 } tile57_rot_align;

/* The S-52 display category a feature belongs to — the axis the mariner's
* display_base / display_standard / display_other settings select on. A feature
* only reaches the surface if its category is enabled, so this says WHICH of the
* enabled categories it came in on. Display base is the never-hide set (the
* safety-of-navigation minimum): SCAMIN is not applied to it. */
typedef enum {
TILE57_DISP_BASE = 0, TILE57_DISP_STANDARD = 1, TILE57_DISP_OTHER = 2
} tile57_disp_cat;

/* The feature the following draw calls belong to. `cls` is the S-57 object-class
* acronym (NUL-terminated; "" if none); `scamin` is the SCAMIN 1:N denominator
* (<= 0 => always visible); `plane` is the S-52 draw priority (paint hint). */
* (<= 0 => always visible); `plane` is the S-52 draw priority (paint hint);
* `disp_cat` is the feature's display category. A host that applies SCAMIN
* itself must skip it when disp_cat == TILE57_DISP_BASE. */
typedef struct {
const char *cls;
int64_t scamin;
int32_t plane;
tile57_disp_cat disp_cat;
} tile57_feature;

/* Draw table. Pointers are valid only for the duration of the call; ctx is
Expand Down
101 changes: 99 additions & 2 deletions src/render/vector.zig
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,21 @@ pub const CLocalRings = extern struct {
ring_count: u32,
};

/// The S-52 display category — the axis the mariner's display_base /
/// display_standard / display_other settings select on. Display base is the
/// never-hide set: SCAMIN is not applied to it.
pub const CDispCat = enum(c_int) { base = 0, standard = 1, other = 2 };

/// The feature the following draw calls belong to. `cls` is the S-57 object-class
/// acronym (NUL-terminated, "" if none); `scamin` is the SCAMIN 1:N denominator
/// (<=0 => always visible); `plane` is the S-52 draw priority (paint-order hint).
/// (<=0 => always visible); `plane` is the S-52 draw priority (paint-order hint);
/// `disp_cat` is the display category the feature came in on — a host applying
/// SCAMIN itself must skip it for `.base`.
pub const CFeature = extern struct {
cls: [*:0]const u8,
scamin: i64,
plane: i32,
disp_cat: CDispCat,
};

/// The GPU paint table. Every call gets `ctx` back verbatim; pointers are valid
Expand Down Expand Up @@ -289,7 +297,19 @@ pub const VectorSurface = struct {
// cur.class is a Zig slice; it is NUL-terminated in the meta (acronyms are
// static strings), but guard by duping a sentinel-terminated copy.
const cls = self.a.dupeZ(u8, self.cur.class) catch @as([:0]u8, @constCast(""));
return .{ .cls = cls.ptr, .scamin = self.cur.scamin orelse 0, .plane = @intCast(self.cur.draw_prio) };
return .{
.cls = cls.ptr,
.scamin = self.cur.scamin orelse 0,
.plane = @intCast(self.cur.draw_prio),
// Standard is the default for an unbanded feature (the baker's `cat < 0`
// fallback, and the style's coalesce) — any other value is already
// filtered out upstream by resolve.categoryVisible.
.disp_cat = switch (self.cur.cat) {
0 => .base,
2 => .other,
else => .standard,
},
};
}

// Flatten tile rings -> world CWorldRings (arena; valid for the call).
Expand Down Expand Up @@ -876,3 +896,80 @@ test "VectorSurface: important text outranks a name it overlaps, whatever the wa
try testing.expectEqual(@as(usize, 1), rec.texts.items.len);
try testing.expectEqualStrings("clr 11", rec.texts.items[0].text);
}

/// Records the CFeature each draw call is tagged with.
const FeatCb = struct {
a: Allocator,
feats: std.ArrayListUnmanaged(struct { cls: []const u8, scamin: i64, disp_cat: CDispCat }) = .empty,

fn onFill(ctx: ?*anyopaque, f: *const CFeature, _: *const CWorldRings, _: CColor, _: c_int) callconv(.c) void {
const self: *FeatCb = @ptrCast(@alignCast(ctx.?));
const cls = std.mem.span(f.cls);
self.feats.append(self.a, .{
.cls = self.a.dupe(u8, cls) catch return,
.scamin = f.scamin,
.disp_cat = f.disp_cat,
}) catch {};
}
fn noStroke(_: ?*anyopaque, _: *const CFeature, _: *const CWorldRings, _: f32, _: f32, _: f32, _: CColor) callconv(.c) void {}
fn noSymbol(_: ?*anyopaque, _: *const CFeature, _: CWorldPt, _: *const CLocalRings, _: CColor, _: c_int, _: f32, _: CRotAlign) callconv(.c) void {}
fn noText(_: ?*anyopaque, _: *const CFeature, _: CWorldPt, _: *const CLocalRings, _: CColor, _: CColor, _: f32, _: CRotAlign) callconv(.c) void {}

fn surface(self: *FeatCb) CSurface {
return .{ .ctx = self, .fill_area = onFill, .stroke_line = noStroke, .draw_symbol = noSymbol, .draw_text = noText };
}
};

test "VectorSurface: every draw call carries its feature's display category" {
var arena = std.heap.ArenaAllocator.init(testing.allocator);
defer arena.deinit();
const a = arena.allocator();

var colors = try resolve.Colors.init(a, "");
// "Other" is off by default (S-52's Standard display) — turn it on, or the
// cat-2 feature never reaches the surface to be tagged.
const settings = resolve.Settings{ .display_other = true };
var rec = FeatCb{ .a = a };
const cb = rec.surface();

var vs = VectorSurface.init(a, &colors, .day, &settings, &cb);
vs.view_zoom = 12;
vs.setTile(12, 0, 0);
const surf = vs.asSurface();

const ring = [_]rs.TilePoint{ .{ .x = 100, .y = 100 }, .{ .x = 900, .y = 100 }, .{ .x = 900, .y = 900 }, .{ .x = 100, .y = 900 } };
const rings = [_][]const rs.TilePoint{&ring};

// Display base carries a SCAMIN: the engine reports both and leaves the S-52
// "SCAMIN shall not be applied to display base" call to the host.
const depare = rs.FeatureMeta{ .class = "DEPARE", .cat = 0, .scamin = 45000 };
try surf.beginFeature(&depare);
try surf.fillArea("DEPVS", &rings, null);
try surf.endFeature();

const dmpgrd = rs.FeatureMeta{ .class = "DMPGRD", .cat = 2 };
try surf.beginFeature(&dmpgrd);
try surf.fillArea("CHBRN", &rings, null);
try surf.endFeature();

// Unbanded (no ViewingGroup in the catalogue) defaults to Standard.
const seaare = rs.FeatureMeta{ .class = "SEAARE" };
try surf.beginFeature(&seaare);
try surf.fillArea("CHBLK", &rings, null);
try surf.endFeature();
_ = try surf.endScene(a);

try testing.expectEqual(@as(usize, 3), rec.feats.items.len);
try testing.expectEqual(CDispCat.base, rec.feats.items[0].disp_cat);
try testing.expectEqual(@as(i64, 45000), rec.feats.items[0].scamin);
try testing.expectEqual(CDispCat.other, rec.feats.items[1].disp_cat);
try testing.expectEqual(CDispCat.standard, rec.feats.items[2].disp_cat);
}

test "CFeature matches the C tile57_feature layout" {
// The header spells this struct out by hand; a silent size/offset drift here
// is a wrong read on the host side of the ABI, not a compile error.
try testing.expectEqual(@sizeOf(CFeature), @sizeOf(extern struct { cls: [*:0]const u8, scamin: i64, plane: c_int, disp_cat: c_int }));
try testing.expectEqual(@as(usize, 0), @offsetOf(CFeature, "cls"));
try testing.expectEqual(@offsetOf(CFeature, "plane") + @sizeOf(c_int), @offsetOf(CFeature, "disp_cat"));
}
Loading