Skip to content
Haiku OSDeep Dive Published Updated 6 min readViews unavailable

Haiku BPolygon: Vertex Geometry, Mapping, and Safe Drawing

Construct Haiku BPolygon geometry from validated vertices, map coordinate frames safely, and draw closed shapes without stale bounds or ownership bugs.

BPolygon stores a sequence of BPoint vertices and a bounding frame. It is useful for drawing irregular closed shapes in an Interface Kit view with StrokePolygon() or FillPolygon(). It is not a general path engine: the class has no public curve segments, transform matrix, hit-testing API, or boolean geometry operations. Use it for vertex-based outlines and fills, and use BShape when the design needs reusable path commands or curves.

Make the vertex sequence explicit

The array constructor copies a supplied point list; the default constructor starts empty, and AddPoints() appends a new set of points. CountPoints() lets a caller check the stored vertex count, and Frame() reports the rectangle enclosing the current polygon. The destructor frees the polygon’s private point allocation, so a caller should not attempt to free or retain a pointer into that internal storage.

Treat coordinates as view-space geometry only when they are actually expressed in the target view’s coordinate system. Haiku views have local coordinates and can be nested or transformed relative to their parents. A polygon built in model coordinates should be mapped or converted explicitly before drawing; passing world coordinates straight to BView::FillPolygon() does not make them device coordinates.

const BPoint points[] = {
    BPoint(12.0f, 8.0f),
    BPoint(96.0f, 8.0f),
    BPoint(72.0f, 58.0f),
    BPoint(24.0f, 58.0f)
};

BPolygon outline(points, 4);
if (outline.CountPoints() == 4)
    view->StrokePolygon(&outline, true);

The explicit true requests a closed stroke through the documented overload. A filled polygon is closed as an area. Keep the point array alive for the constructor call; the BPolygon copies the data, so the local array can then go out of scope. Do not pass a null pointer or zero/negative count and assume a useful polygon was created.

Validate geometry before adding it

The implementation rejects a null point array, non-positive count, and a total point count over its implementation limit; allocation failure can also prevent points from being added. AddPoints() returns void, so it does not give callers a direct status code. If input points are user-controlled, validate count and coordinates before calling it, and compare CountPoints() before and after if the result matters. Avoid building up an unbounded polygon from a stream of incremental events.

NaN, infinity, extremely large coordinate ranges, and near-degenerate edges are poor input even if the class stores them. Validate each numeric coordinate as finite and within the application’s model range. Remove accidental duplicate consecutive vertices where appropriate, but do not normalize a polygon blindly if repeated vertices or a self-intersecting shape is meaningful to the caller. The API defines points and drawing operations; it does not promise a particular fill rule for self-intersecting contours.

Frame() is a bounding rectangle, not a collision shape. Many different polygons share the same frame. Use it for coarse invalidation or layout only; a true hit test must evaluate the intended polygon geometry, including the application’s boundary and edge policy. Likewise, the frame alone does not describe winding, holes, or path topology.

Understand MapTo() before reusing an object

MapTo(source, destination) changes every stored point so the polygon fits proportionally from one rectangle into another, and updates the stored bounds. The source rectangle defines the coordinate range to map; it need not be the polygon’s actual frame. Passing the frame is the documented way to map the polygon so its bounds are inscribed in the destination rectangle. Passing a different source intentionally applies a different scale/offset.

This operation mutates the polygon. It is not a temporary draw transform and does not preserve an original copy. If a responsive view needs to redraw the same geometry at multiple sizes, retain immutable model vertices and construct or copy a display polygon for each destination size. Repeatedly mapping an already-mapped polygon causes cumulative coordinate changes and can introduce drift.

Check the source and destination rectangles before mapping. A zero-width or zero-height source cannot provide a meaningful proportional scale; negative or inverted rectangles also deserve explicit normalization or rejection. The API’s MapTo() returns no status, so the caller is responsible for not asking it to perform an invalid transform. After mapping, validate the new Frame() against the expected destination bounds.

Draw through the Interface Kit

Use BView::StrokePolygon() for an outline and FillPolygon() for a filled area. The API provides overloads that accept a BPolygon* or a raw point array; the object form is convenient when the same vertices need a frame or repeated drawing. Drawing belongs in the view’s normal drawing lifecycle, where clipping and invalidation are handled by the Interface Kit. Do not draw directly from a worker thread into a view; publish model updates to the window’s looper and invalidate the affected region.

Polygon fill and stroke are affected by view state such as high/low colors, patterns, clipping, drawing mode, and view coordinate transforms. A missing shape can therefore be a clipping or color-state issue rather than malformed vertices. A useful debugging overlay draws the polygon frame and each vertex in a contrasting color, and logs the point count and local coordinates.

For a stroke, the closed flag distinguishes a closed outline from a polyline. Be deliberate about this at the call site instead of depending on an overload’s default when the visual distinction matters. For a fill, the polygon represents an area; do not add the first point again solely to close the shape unless that repeated vertex is required by your data model. Duplicate endpoints can create zero-length segments and confusing hit-test behavior.

Keep ownership simple

BPolygon has copy construction and assignment that duplicate the point list. That makes a value copy useful for separating source geometry from a mutable, mapped display instance. Copies do cost memory proportional to vertex count, so use them deliberately for reasonably sized shapes. Do not keep a pointer to the original input array under the assumption the object references it; the implementation copies points into its own allocation.

If polygon updates cross threads, protect the model or pass an immutable copy. A drawing callback must not race with AddPoints() or MapTo() on the same instance. The class does not provide a synchronization contract. A clean UI architecture computes geometry away from drawing, swaps a complete value copy under a short lock or on the window looper, then invalidates the new frame.

Test geometry, not just the happy-path rectangle

Test empty, one-point, two-point, convex, concave, repeated-point, self-intersecting, very large, and invalid numeric inputs. Test MapTo() into wider, taller, translated, and zero-area destinations; ensure the source frame is captured before mutation. Draw at multiple window sizes, under clipping, and in nested views. Verify that copied polygons remain unchanged when a display instance is mapped.

For hit testing, explicitly test points inside, outside, on a vertex, on an edge, and near floating-point boundaries. For rendering, compare the shape frame and actual fill separately; neither should be treated as an authoritative result for the other. If geometry must be serialized, persist a versioned list of finite coordinates rather than relying on undocumented internal object layout.

BPolygon offers a compact vertex-based drawing primitive with copyable storage and a simple frame mapping operation. Validate inputs, preserve model geometry before mutation, respect view-local coordinates, and do not infer path or hit-test semantics that the API does not expose.

Related:

Sources:

Comments