Bulge
Description
A bulge value is a compact scalar attached to a polyline vertex that encodes an arc segment between that vertex and the next. When the bulge is zero the segment is a straight line; any non-zero value turns the segment into a circular arc.
Bulge is defined as the tangent of one quarter of the arc’s included angle \(\theta\):
or equivalently, the included angle is recovered from the bulge with:
This is implemented in the codebase as:
bulgeAngle() {
return Math.atan(this.bulge) * 4;
}
Sign convention
The sign of the bulge determines the sweep direction of the arc:
Bulge value |
Meaning |
|---|---|
0 |
Straight line segment |
Positive |
Arc sweeps counter-clockwise (CCW) |
Negative |
Arc sweeps clockwise (CW) |
Special values
Bulge |
Arc |
|---|---|
\(|b| < 1\) |
Arc spanning less than a semicircle |
\(|b| = 1\) |
Exact semicircle (\(\theta = \pi\)) |
\(|b| > 1\) |
Arc spanning more than a semicircle |
DXF Representation
In the DXF format, the bulge is stored as group code 42 on the start vertex
of each arc segment in an LWPOLYLINE entity. A value of zero (the default)
means a straight segment and is often omitted.
0
LWPOLYLINE
8 ← layer name
0
90 ← vertex count
3
10 ← vertex 1 X
0.0
20 ← vertex 1 Y
0.0
42 ← bulge at vertex 1 (arc to vertex 2)
1.0
10 ← vertex 2 X
50.0
20 ← vertex 2 Y
0.0
Calculating the Radius
Given the start point \(S\) (this) and end point \(E\)
(nextPoint), the chord length is:
The arc radius follows directly from the bulge and chord length:
This is derived by noting that the sagitta (the perpendicular distance from the chord midpoint to the arc) is \(s = |b| \cdot d / 2\), and applying the chord–sagitta–radius relationship \(r = d^2 / (8s) + s / 2\).
bulgeRadius(nextPoint) {
if (this.bulge == 0) return 0;
return this.distance(nextPoint) * (1 + Math.pow(this.bulge, 2))
/ (4 * Math.abs(this.bulge));
}
Calculating the Centre Point
Finding the arc centre requires three steps.
Step 1 — Find the apothem
The apothem is the perpendicular distance from the chord midpoint to the arc centre:
For arcs spanning more than a semicircle (\(|\theta| > \pi\)), the centre lies on the opposite side of the chord, so the sign is flipped:
apothem(nextPoint) {
if (this.bulge == 0) return 0;
return Math.sqrt(
Math.pow(this.bulgeRadius(nextPoint), 2)
- Math.pow(this.distance(nextPoint) / 2, 2)
);
}
Step 2 — Find the perpendicular direction
The centre always lies on the perpendicular bisector of the chord. The direction from the chord midpoint to the centre is:
A positive bulge (CCW arc) places the centre to the left of the chord; a negative bulge (CW arc) places it to the right.
Step 3 — Project from the midpoint
The centre is the apothem distance from the chord midpoint in direction \(\alpha\):
where \(M\) is the midpoint of \(SE\).
bulgeCentrePoint(nextPoint) {
const midp = this.midPoint(nextPoint);
if (this.bulge == 0) return midp;
let a = this.apothem(nextPoint);
if (Math.abs(this.bulgeAngle()) > Math.PI) a = -a;
const direction = this.angle(nextPoint)
+ (Math.PI / 2) * Math.sign(this.bulge);
return midp.project(direction, a);
}
Computing Bulge from Geometry
When the user draws an arc segment in polyline mode, the bulge value is computed from the tangent continuity condition: the new arc must start tangent to the previous segment.
The angle delta between the incoming tangent direction and the direction from the current point to the selected endpoint is half the included angle \(\theta\):
In the code this is resolved via:
getBulgeFromSegment(point) {
// ... compute lastSegAngle (tangent of prior segment at the join point)
const mouseAngle = this.points.at(-1).angle(point) % (2 * Math.PI);
const angleDelta = ((mouseAngle - lastSegAngle) + 3 * Math.PI)
% (2 * Math.PI) - Math.PI;
// angleDelta is half the included angle
const bulge = Math.tan((angleDelta * 2) / 4);
return bulge;
}
Usage in Design
Polyline drawing — when the user switches to
Arcmode, each new point computes a bulge value viagetBulgeFromSegmentso that the arc joins the previous segment tangentially.Rendering —
buildDrawPointsinBasePolylinereads the bulge of each vertex: a zero bulge draws a straight line; a non-zero bulge callsbulgeCentrePointandbulgeRadiusto reconstruct the arc for drawing.Snapping — the centre snap for a polyline arc segment is found with
bulgeCentrePoint; mid-arc snaps step along the arc using the arc angle derived from the bulge.DXF I/O — the bulge is written as group code 42 and read back on load, fully preserving arc geometry across file save/open cycles.
Trim / Split —
partialBulgerecomputes the bulge for the portion of an arc segment either side of a trim or split point, allowing arcs to be shortened without losing their curvature.