// 渲染顶点批数值内核(assembly/batch.ts 的 Moonbit 同构移植):
// x,y,r,g,b,a 平铺,f64 中间量 → f32 存储;静态容量、运行期零分配,容量溢出逐图元丢弃。
// 渲染几何非混沌路径,三角函数按 Moonbit 标准库实现(与 AS 的 libm 最低位可能不同,视觉等价)。
///|
let vertex_stride : Int = 6
// 最坏帧余量(示踪/拖尾 tessellate 已迁 GPU ribbon,主批只剩场景几何)
///|
/// Vertex stride (interleaved x, y, r, g, b, a = 6 floats).
#export_name("bVertexStride")
pub fn b_vertex_stride() -> Int {
vertex_stride
}
///|
let capacity : Int = 262152
///|
let pts_cap : Int = 2048
///|
let miter_limit : Int = 4
// 地形 SDF 场容量(格点数 nx×ny):= 流体网格上限(grid.mbt 单点)
///|
let tg_cap : Int = grid_max_cells
///|
let data : FixedArray[Float] = FixedArray::make(capacity * vertex_stride, 0.0)
///|
let pts_buf : FixedArray[Float] = FixedArray::make(pts_cap, 0.0)
///|
let tg_field : FixedArray[Float] = FixedArray::make(tg_cap, 0.0)
// 地形烘焙输出缓冲(独立于主批):静态几何一次烘焙,每帧免重切 + 免上传
// 容量 = 最坏全实体 grid_max_cells×6 顶点(超限逐格截断)
///|
let tg_out_cap : Int = grid_terrain_verts
///|
let tg_data : FixedArray[Float] = FixedArray::make(
tg_out_cap * vertex_stride,
0.0,
)
///|
let tg_cx : FixedArray[Double] = FixedArray::make(4, 0.0)
///|
let tg_cy : FixedArray[Double] = FixedArray::make(4, 0.0)
///|
let tg_cd : FixedArray[Double] = FixedArray::make(4, 0.0)
///|
let tg_cs : FixedArray[Byte] = FixedArray::make(4, b'\x00')
///|
let poly_x : FixedArray[Double] = FixedArray::make(6, 0.0)
///|
let poly_y : FixedArray[Double] = FixedArray::make(6, 0.0)
///|
let poly_d : FixedArray[Double] = FixedArray::make(6, 0.0)
///|
priv struct BState {
mut count : Int
mut tg_nx : Int
mut tg_ny : Int
mut tg_x0 : Double
mut tg_y0 : Double
mut tg_cell : Double
// 配色:地表色(d=0)随入地深度指数渐近混向深处色(渲染常量由宿主传入)
mut tg_sr : Double
mut tg_sg : Double
mut tg_sb : Double
mut tg_dr : Double
mut tg_dg : Double
mut tg_db : Double
mut tg_len : Double
mut tg_count : Int
}
///|
let bs : BState = BState::{
count: 0,
tg_nx: 0,
tg_ny: 0,
tg_x0: 0.0,
tg_y0: 0.0,
tg_cell: 1.0,
tg_sr: 0.0,
tg_sg: 0.0,
tg_sb: 0.0,
tg_dr: 0.0,
tg_dg: 0.0,
tg_db: 0.0,
tg_len: 1.0,
tg_count: 0,
}
///|
/// Maximum vertex-batch capacity (in vertices).
#export_name("bCapacity")
pub fn b_capacity() -> Int {
capacity
}
///|
/// Linear-memory address of the vertex data buffer (Float32Array, stride bVertexStride).
#export_name("bData")
pub fn b_data() -> Int {
addr_of_f32(data)
}
///|
/// Number of vertices currently in the batch.
#export_name("bCount")
pub fn b_count() -> Int {
bs.count
}
///|
/// Reset the vertex batch (clear all queued vertices).
#export_name("bReset")
pub fn b_reset() -> Unit {
bs.count = 0
}
///|
fn push(
x : Double,
y : Double,
r : Double,
g : Double,
bl : Double,
a : Double,
) -> Unit {
let o = bs.count * vertex_stride
data[o] = Float::from_double(x)
data[o + 1] = Float::from_double(y)
data[o + 2] = Float::from_double(r)
data[o + 3] = Float::from_double(g)
data[o + 4] = Float::from_double(bl)
data[o + 5] = Float::from_double(a)
bs.count = bs.count + 1
}
///|
/// Append a filled triangle (3 vertices) with a flat color.
#export_name("bTri")
pub fn b_tri(
x0 : Double,
y0 : Double,
x1 : Double,
y1 : Double,
x2 : Double,
y2 : Double,
r : Double,
g : Double,
bl : Double,
a : Double,
) -> Unit {
if bs.count + 3 > capacity {
return
}
push(x0, y0, r, g, bl, a)
push(x1, y1, r, g, bl, a)
push(x2, y2, r, g, bl, a)
}
///|
/// Append a filled rectangle (2 triangles, 6 vertices) with a flat color.
#export_name("bRect")
pub fn b_rect(
x0 : Double,
y0 : Double,
x1 : Double,
y1 : Double,
r : Double,
g : Double,
bl : Double,
a : Double,
) -> Unit {
if bs.count + 6 > capacity {
return
}
push(x0, y0, r, g, bl, a)
push(x1, y0, r, g, bl, a)
push(x0, y1, r, g, bl, a)
push(x1, y0, r, g, bl, a)
push(x1, y1, r, g, bl, a)
push(x0, y1, r, g, bl, a)
}
///|
/// Append a filled rectangle with a vertical color gradient (top to bottom).
#export_name("bRectVGrad")
pub fn b_rect_vgrad(
x0 : Double,
y0 : Double,
x1 : Double,
y1 : Double,
r0 : Double,
g0 : Double,
b0 : Double,
a0 : Double,
r1 : Double,
g1 : Double,
b1 : Double,
a1 : Double,
) -> Unit {
if bs.count + 6 > capacity {
return
}
push(x0, y0, r0, g0, b0, a0)
push(x1, y0, r0, g0, b0, a0)
push(x0, y1, r1, g1, b1, a1)
push(x1, y0, r0, g0, b0, a0)
push(x1, y1, r1, g1, b1, a1)
push(x0, y1, r1, g1, b1, a1)
}
///|
fn disc(
cx : Double,
cy : Double,
rx : Double,
ry : Double,
rot : Double,
seg : Int,
r : Double,
g : Double,
bl : Double,
a : Double,
) -> Unit {
if rx <= 0.0 || ry <= 0.0 || a <= 0.0 {
return
}
if bs.count + seg * 3 > capacity {
return
}
let cos = @math.cos(rot)
let sin = @math.sin(rot)
let mut px = 0.0
let mut py = 0.0
for i in 0..<(seg + 1) {
let th = i.to_double() / seg.to_double() * @math.PI * 2.0
let ex = rx * @math.cos(th)
let ey = ry * @math.sin(th)
let qx = cx + ex * cos - ey * sin
let qy = cy + ex * sin + ey * cos
if i > 0 {
push(cx, cy, r, g, bl, a)
push(px, py, r, g, bl, a)
push(qx, qy, r, g, bl, a)
}
px = qx
py = qy
}
}
///|
/// Append a filled ellipse/disk (rotated, segment count) with a flat color.
#export_name("bDisc")
pub fn b_disc(
cx : Double,
cy : Double,
rx : Double,
ry : Double,
rot : Double,
seg : Int,
r : Double,
g : Double,
bl : Double,
a : Double,
) -> Unit {
disc(cx, cy, rx, ry, rot, seg, r, g, bl, a)
}
// 半圆盘(π 扇):圆润端帽专用——整圆盘与线段带重叠会使端头发深;
// 半圆盘恰好补在线段带之外的延长区,零重叠、alpha 均匀
///|
fn half_disc(
cx : Double,
cy : Double,
r : Double,
phi : Double,
seg : Int,
cr : Double,
cg : Double,
cb : Double,
a : Double,
) -> Unit {
if r <= 0.0 || a <= 0.0 {
return
}
if bs.count + seg * 3 > capacity {
return
}
let mut px = 0.0
let mut py = 0.0
for i in 0..<(seg + 1) {
let th = phi - @math.PI / 2.0 + @math.PI * i.to_double() / seg.to_double()
let qx = cx + r * @math.cos(th)
let qy = cy + r * @math.sin(th)
if i > 0 {
push(cx, cy, cr, cg, cb, a)
push(px, py, cr, cg, cb, a)
push(qx, qy, cr, cg, cb, a)
}
px = qx
py = qy
}
}
///|
fn stroke(
x0 : Double,
y0 : Double,
x1 : Double,
y1 : Double,
w : Double,
r : Double,
g : Double,
bl : Double,
a : Double,
round : Bool,
) -> Unit {
let dx = x1 - x0
let dy = y1 - y0
let len = (dx * dx + dy * dy).sqrt()
if len < 1.0e-8 {
return
}
// 圆头 = 四边形带 + 两端朝外半圆盘(6 + 2×18 顶点,互不重叠)
if bs.count + (if round { 42 } else { 6 }) > capacity {
return
}
let hw = w / 2.0 / len
let nx = -dy * hw
let ny = dx * hw
push(x0 + nx, y0 + ny, r, g, bl, a)
push(x1 + nx, y1 + ny, r, g, bl, a)
push(x0 - nx, y0 - ny, r, g, bl, a)
push(x1 + nx, y1 + ny, r, g, bl, a)
push(x1 - nx, y1 - ny, r, g, bl, a)
push(x0 - nx, y0 - ny, r, g, bl, a)
if round {
let phi = @math.atan2(dy, dx)
half_disc(x1, y1, w / 2.0, phi, 6, r, g, bl, a)
half_disc(x0, y0, w / 2.0, phi + @math.PI, 6, r, g, bl, a)
}
}
///|
/// Append a stroked segment (miter join, optional round caps) with a flat color.
#export_name("bStroke")
pub fn b_stroke(
x0 : Double,
y0 : Double,
x1 : Double,
y1 : Double,
w : Double,
r : Double,
g : Double,
bl : Double,
a : Double,
round : Bool,
) -> Unit {
stroke(x0, y0, x1, y1, w, r, g, bl, a, round)
}
// pts 由内核自填(ring/arc);n 为 float 个数(点数 × 2)
///|
fn miter(
n : Int,
w : Double,
r : Double,
g : Double,
bl : Double,
a : Double,
) -> Unit {
if n < 4 {
return
}
if bs.count + 3 * n > capacity {
return
}
let hw = w / 2.0
let limit = miter_limit.to_double() * hw
let mut sx = 0.0
let mut sy = 0.0
let mut mx = 0.0
let mut my = 0.0
let mut pnx = 0.0
let mut pny = 0.0
let mut ready = false
let mut i = 0
while i + 3 < n {
let ax = pts_buf[i].to_double()
let ay = pts_buf[i + 1].to_double()
let bx = pts_buf[i + 2].to_double()
let by = pts_buf[i + 3].to_double()
let dx = bx - ax
let dy = by - ay
let len = (dx * dx + dy * dy).sqrt()
if len >= 1.0e-8 {
let nx = -dy / len
let ny = dx / len
if !ready {
sx = ax
sy = ay
mx = nx * hw
my = ny * hw
pnx = nx
pny = ny
ready = true
} else {
let mut tx = pnx + nx
let mut ty = pny + ny
let mut fl = (tx * tx + ty * ty).sqrt()
if fl < 1.0e-6 {
tx = nx
ty = ny
fl = 1.0
} else {
tx = tx / fl
ty = ty / fl
fl = hw / (pnx * tx + pny * ty)
if fl > limit {
fl = limit
}
}
let ex = tx * fl
let ey = ty * fl
push(sx + mx, sy + my, r, g, bl, a)
push(ax + ex, ay + ey, r, g, bl, a)
push(sx - mx, sy - my, r, g, bl, a)
push(ax + ex, ay + ey, r, g, bl, a)
push(ax - ex, ay - ey, r, g, bl, a)
push(sx - mx, sy - my, r, g, bl, a)
sx = ax
sy = ay
mx = ex
my = ey
pnx = nx
pny = ny
}
}
i = i + 2
}
if ready {
let bx = pts_buf[n - 2].to_double()
let by = pts_buf[n - 1].to_double()
let ex = pnx * hw
let ey = pny * hw
push(sx + mx, sy + my, r, g, bl, a)
push(bx + ex, by + ey, r, g, bl, a)
push(sx - mx, sy - my, r, g, bl, a)
push(bx + ex, by + ey, r, g, bl, a)
push(bx - ex, by - ey, r, g, bl, a)
push(sx - mx, sy - my, r, g, bl, a)
}
}
// 地形固体填充(marching squares):宿主每关上传格心 SDF 场,每帧对可视格做等值线切割——
// 轮廓 = 格内线性插值的 d=0 线,矢量级锐利;颜色按入地深度指数渐近。越界格索引钳至边缘列 = 地形自然延展;
// 鞍点(对角双固体)以格心均值消歧
///|
fn tg_push(
x : Double,
y : Double,
r : Double,
g : Double,
bl : Double,
a : Double,
) -> Unit {
let o = bs.tg_count * vertex_stride
tg_data[o] = Float::from_double(x)
tg_data[o + 1] = Float::from_double(y)
tg_data[o + 2] = Float::from_double(r)
tg_data[o + 3] = Float::from_double(g)
tg_data[o + 4] = Float::from_double(bl)
tg_data[o + 5] = Float::from_double(a)
bs.tg_count = bs.tg_count + 1
}
///|
fn push_tg(x : Double, y : Double, d : Double) -> Unit {
let depth : Double = if d < 0.0 { -d } else { 0.0 }
let k = 1.0 - @math.exp(-depth / bs.tg_len)
tg_push(
x,
y,
bs.tg_sr + (bs.tg_dr - bs.tg_sr) * k,
bs.tg_sg + (bs.tg_dg - bs.tg_sg) * k,
bs.tg_sb + (bs.tg_db - bs.tg_sb) * k,
1.0,
)
}
///|
/// Linear-memory address of the terrain SDF field buffer (Float32Array, nx*ny).
#export_name("bTerrainFieldBuf")
pub fn b_terrain_field_buf() -> Int {
addr_of_f32(tg_field)
}
///|
/// Terrain SDF field capacity (in cells, equals the fluid grid upper bound).
#export_name("bTerrainFieldCap")
pub fn b_terrain_field_cap() -> Int {
tg_cap
}
///|
/// Configure the terrain field (grid, world origin, cell size, shading colors, depth length).
/// Returns 0 on success, 1 on invalid parameters.
#export_name("bTerrainField")
pub fn b_terrain_field(
nx : Int,
ny : Int,
x0 : Double,
y0 : Double,
cell : Double,
sr : Double,
sg : Double,
sb : Double,
dr : Double,
dg : Double,
db : Double,
depth_len : Double,
) -> Int {
if nx < 2 || ny < 2 || nx * ny > tg_cap || depth_len <= 0.0 {
return 1
}
bs.tg_nx = nx
bs.tg_ny = ny
bs.tg_x0 = x0
bs.tg_y0 = y0
bs.tg_cell = cell
bs.tg_sr = sr
bs.tg_sg = sg
bs.tg_sb = sb
bs.tg_dr = dr
bs.tg_dg = dg
bs.tg_db = db
bs.tg_len = depth_len
0
}
///|
/// Tessellate the terrain d=0 contour within a cell range (i0..i1, j0..j1) into the batch.
/// Returns the number of vertices emitted.
#export_name("bTerrainDraw")
pub fn b_terrain_draw(i0 : Int, j0 : Int, i1 : Int, j1 : Int) -> Int {
if bs.tg_nx < 2 || bs.tg_ny < 2 {
return 0
}
if i1 <= i0 || j1 <= j0 {
return 0
}
bs.tg_count = 0
let mx = bs.tg_nx - 1
let my = bs.tg_ny - 1
let tg_nx = bs.tg_nx
let tg_cell = bs.tg_cell
let tg_x0 = bs.tg_x0
let tg_y0 = bs.tg_y0
for j in j0.. my {
bj = my
}
let mut bj1 = j + 1
if bj1 < 0 {
bj1 = 0
} else if bj1 > my {
bj1 = my
}
let y0 = tg_y0 + j.to_double() * tg_cell
let y1 = y0 + tg_cell
for i in i0.. tg_out_cap {
return bs.tg_count
}
let mut ai = i
if ai < 0 {
ai = 0
} else if ai > mx {
ai = mx
}
let mut ai1 = i + 1
if ai1 < 0 {
ai1 = 0
} else if ai1 > mx {
ai1 = mx
}
let d00 = tg_field[bj * tg_nx + ai].to_double()
let d10 = tg_field[bj * tg_nx + ai1].to_double()
let d11 = tg_field[bj1 * tg_nx + ai1].to_double()
let d01 = tg_field[bj1 * tg_nx + ai].to_double()
let s00 = d00 <= 0.0
let s10 = d10 <= 0.0
let s11 = d11 <= 0.0
let s01 = d01 <= 0.0
let n = (if s00 { 1 } else { 0 }) +
(if s10 { 1 } else { 0 }) +
(if s11 { 1 } else { 0 }) +
(if s01 { 1 } else { 0 })
if n == 0 {
continue
}
let x0 = tg_x0 + i.to_double() * tg_cell
let x1 = x0 + tg_cell
if n == 4 {
push_tg(x0, y0, d00)
push_tg(x1, y0, d10)
push_tg(x0, y1, d01)
push_tg(x1, y0, d10)
push_tg(x1, y1, d11)
push_tg(x0, y1, d01)
continue
}
// 鞍点且格心为空气:两块固体互不相连,拆成两个独立三角形(行走法会错误连通)
if n == 2 && s00 == s11 && d00 + d10 + d01 + d11 > 0.0 {
let top_x = x0 + tg_cell * (d00 / (d00 - d10))
let right_y = y0 + tg_cell * (d10 / (d10 - d11))
let bot_x = x1 - tg_cell * (d11 / (d11 - d01))
let left_y = y1 - tg_cell * (d01 / (d01 - d00))
if s00 {
push_tg(x0, y0, d00)
push_tg(top_x, y0, 0.0)
push_tg(x0, left_y, 0.0)
push_tg(x1, y1, d11)
push_tg(x1, right_y, 0.0)
push_tg(bot_x, y1, 0.0)
} else {
push_tg(x1, y0, d10)
push_tg(top_x, y0, 0.0)
push_tg(x1, right_y, 0.0)
push_tg(x0, y1, d01)
push_tg(x0, left_y, 0.0)
push_tg(bot_x, y1, 0.0)
}
continue
}
// 常规:绕格行走(TL→TR→BR→BL)收集固体角与交点成凸多边形,扇形化
tg_cx[0] = x0
tg_cy[0] = y0
tg_cd[0] = d00
tg_cs[0] = if s00 { b'\x01' } else { b'\x00' }
tg_cx[1] = x1
tg_cy[1] = y0
tg_cd[1] = d10
tg_cs[1] = if s10 { b'\x01' } else { b'\x00' }
tg_cx[2] = x1
tg_cy[2] = y1
tg_cd[2] = d11
tg_cs[2] = if s11 { b'\x01' } else { b'\x00' }
tg_cx[3] = x0
tg_cy[3] = y1
tg_cd[3] = d01
tg_cs[3] = if s01 { b'\x01' } else { b'\x00' }
let mut m = 0
for k in 0..<4 {
let k2 = (k + 1) & 3
if tg_cs[k] != b'\x00' {
poly_x[m] = tg_cx[k]
poly_y[m] = tg_cy[k]
poly_d[m] = tg_cd[k]
m = m + 1
}
if tg_cs[k] != tg_cs[k2] {
let da = tg_cd[k]
let tt = da / (da - tg_cd[k2])
poly_x[m] = tg_cx[k] + (tg_cx[k2] - tg_cx[k]) * tt
poly_y[m] = tg_cy[k] + (tg_cy[k2] - tg_cy[k]) * tt
poly_d[m] = 0.0
m = m + 1
}
}
let mut k = 1
while k + 1 < m {
push_tg(poly_x[0], poly_y[0], poly_d[0])
push_tg(poly_x[k], poly_y[k], poly_d[k])
push_tg(poly_x[k + 1], poly_y[k + 1], poly_d[k + 1])
k = k + 1
}
}
}
bs.tg_count
}
///|
/// Linear-memory address of the baked terrain vertex data (Float32Array).
#export_name("bTerrainData")
pub fn b_terrain_data() -> Int {
addr_of_f32(tg_data)
}
///|
/// Baked terrain vertex data capacity (in vertices).
#export_name("bTerrainCap")
pub fn b_terrain_cap() -> Int {
tg_out_cap
}
///|
/// Append a filled disk with a radial color gradient (center to edge).
#export_name("bDiscGrad")
pub fn b_disc_grad(
cx : Double,
cy : Double,
radius : Double,
seg : Int,
cr : Double,
cg : Double,
cb : Double,
ca : Double,
er : Double,
eg : Double,
eb : Double,
ea : Double,
) -> Unit {
if radius <= 0.0 {
return
}
if bs.count + seg * 3 > capacity {
return
}
let mut px = 0.0
let mut py = 0.0
for i in 0..<(seg + 1) {
let th = i.to_double() / seg.to_double() * @math.PI * 2.0
let qx = cx + radius * @math.cos(th)
let qy = cy + radius * @math.sin(th)
if i > 0 {
push(cx, cy, cr, cg, cb, ca)
push(px, py, er, eg, eb, ea)
push(qx, qy, er, eg, eb, ea)
}
px = qx
py = qy
}
}
///|
/// Append a stroked ellipse ring (width w, miter-joined) with a flat color.
#export_name("bRing")
pub fn b_ring(
cx : Double,
cy : Double,
rx : Double,
ry : Double,
rot : Double,
seg : Int,
w : Double,
r : Double,
g : Double,
bl : Double,
a : Double,
) -> Unit {
if rx <= 0.0 || ry <= 0.0 || a <= 0.0 {
return
}
let n = seg + 1
if n * 2 > pts_cap {
return
}
let cos = @math.cos(rot)
let sin = @math.sin(rot)
for i in 0..<(seg + 1) {
let th = i.to_double() / seg.to_double() * @math.PI * 2.0
let ex = rx * @math.cos(th)
let ey = ry * @math.sin(th)
pts_buf[i * 2] = Float::from_double(cx + ex * cos - ey * sin)
pts_buf[i * 2 + 1] = Float::from_double(cy + ex * sin + ey * cos)
}
// 闭环折线(末点回首点)miter 接头:旧逐段 stroke 在接头处双重混合发深且有角度缺口
miter(n * 2, w, r, g, bl, a)
}
// 与 ring 复用 pts_buf:调用方不得在 arc 期间改写入参缓冲(单线程天然成立)
///|
fn arc(
cx : Double,
cy : Double,
radius : Double,
a0 : Double,
a1 : Double,
seg : Int,
w : Double,
r : Double,
g : Double,
bl : Double,
a : Double,
) -> Unit {
if radius <= 0.0 || a <= 0.0 || a1 == a0 {
return
}
let n = seg + 1
if n * 2 > pts_cap {
return
}
for i in 0..<(seg + 1) {
let th = a0 + (a1 - a0) * i.to_double() / seg.to_double()
pts_buf[i * 2] = Float::from_double(cx + radius * @math.cos(th))
pts_buf[i * 2 + 1] = Float::from_double(cy + radius * @math.sin(th))
}
miter(n * 2, w, r, g, bl, a)
let hw = w / 2.0
// 端帽 = 沿切线朝外的半圆盘(与折线带零重叠):旧整圆盘与末段双混使虚线每节两端发深
let i1 = seg * 2
let phi0 = @math.atan2(
pts_buf[1].to_double() - pts_buf[3].to_double(),
pts_buf[0].to_double() - pts_buf[2].to_double(),
)
let phi1 = @math.atan2(
pts_buf[i1 + 1].to_double() - pts_buf[i1 - 1].to_double(),
pts_buf[i1].to_double() - pts_buf[i1 - 2].to_double(),
)
half_disc(
pts_buf[0].to_double(),
pts_buf[1].to_double(),
hw,
phi0,
6,
r,
g,
bl,
a,
)
half_disc(
pts_buf[i1].to_double(),
pts_buf[i1 + 1].to_double(),
hw,
phi1,
6,
r,
g,
bl,
a,
)
}
///|
/// Append a stroked arc segment (angle a0..a1) with a flat color.
#export_name("bArc")
pub fn b_arc(
cx : Double,
cy : Double,
radius : Double,
a0 : Double,
a1 : Double,
seg : Int,
w : Double,
r : Double,
g : Double,
bl : Double,
a : Double,
) -> Unit {
arc(cx, cy, radius, a0, a1, seg, w, r, g, bl, a)
}
///|
/// Append a dashed stroked ring (dash on/off lengths) with a flat color.
#export_name("bDashRing")
pub fn b_dash_ring(
cx : Double,
cy : Double,
radius : Double,
on : Double,
off : Double,
w : Double,
r : Double,
g : Double,
bl : Double,
a : Double,
) -> Unit {
let circ = @math.PI * 2.0 * radius
let mut on_len = on
let mut off_len = off
let mut period = on_len + off_len
if circ <= 0.0 || period <= 0.0 {
return
}
if circ < 6.0 * period {
let k = circ / (6.0 * period)
on_len = on_len * k
off_len = off_len * k
period = on_len + off_len
}
let mut s = 0.0
while s < circ {
let seg_len = @cmp.minimum(on_len, circ - s)
let ang0 = s / radius
let ang1 = (s + seg_len) / radius
let mut segs = (seg_len / 0.5).ceil().to_int()
if segs < 2 {
segs = 2
}
arc(cx, cy, radius, ang0, ang1, segs, w, r, g, bl, a)
s = s + period
}
}