///|
/// Small, allocation-conscious geometry operations used by importers.
pub fn translate_shape(
shape : Shape,
dx : Double,
dy : Double,
) -> Shape raise ShapeError {
shape.validate()
if !finite(dx) || !finite(dy) {
raise InvalidData(0, "nonfinite translation")
}
let points = shape.points.map(p => {
x: p.x + dx,
y: p.y + dy,
z: p.z,
m: p.m,
})
{ kind: shape.kind, points, parts: shape.parts.copy() }
}
///|
pub fn scale_shape(
shape : Shape,
sx : Double,
sy : Double,
) -> Shape raise ShapeError {
shape.validate()
if !finite(sx) || !finite(sy) || sx == 0.0 || sy == 0.0 {
raise InvalidData(0, "invalid scale")
}
let points = shape.points.map(p => {
x: p.x * sx,
y: p.y * sy,
z: p.z,
m: p.m,
})
let result : Shape = { kind: shape.kind, points, parts: shape.parts.copy() }
result.validate()
result
}
///|
pub fn reverse_parts(shape : Shape) -> Shape raise ShapeError {
shape.validate()
let points : Array[Coordinate] = []
let parts : Array[Int] = []
for part = 0; part < shape.parts.length(); part = part + 1 {
parts.push(points.length())
let start = shape.parts[part]
let end = shape.part_end(part)
let mut i = end - 1
while i >= start {
points.push(shape.points[i])
i = i - 1
}
}
{ kind: shape.kind, points, parts }
}
///|
pub fn centroid(shape : Shape) -> Coordinate? raise ShapeError {
shape.validate()
if shape.points.is_empty() {
return None
}
let mut x = 0.0
let mut y = 0.0
for p in shape.points {
x += p.x
y += p.y
}
Some(
coordinate(
x / shape.points.length().to_double(),
y / shape.points.length().to_double(),
),
)
}
///|
pub fn nearest_vertex(
shape : Shape,
target : Coordinate,
) -> (Int, Double)? raise ShapeError {
shape.validate()
let mut best : (Int, Double)? = None
for i = 0; i < shape.points.length(); i = i + 1 {
let p = shape.points[i]
let dx = p.x - target.x
let dy = p.y - target.y
let d = dx * dx + dy * dy
match best {
None => best = Some((i, d))
Some((_, old)) => if d < old { best = Some((i, d)) }
}
}
best
}
///|
pub fn clamp_bounds(shape : Shape, bounds : Bounds) -> Shape raise ShapeError {
shape.validate()
bounds.validate()
let points = shape.points.map(p => {
x: if p.x < bounds.xmin {
bounds.xmin
} else if p.x > bounds.xmax {
bounds.xmax
} else {
p.x
},
y: if p.y < bounds.ymin {
bounds.ymin
} else if p.y > bounds.ymax {
bounds.ymax
} else {
p.y
},
z: p.z,
m: p.m,
})
let result : Shape = { kind: shape.kind, points, parts: shape.parts.copy() }
result.validate()
result
}
///|
test "transformations preserve metadata and centroid" {
let shape : Shape = { kind: Point, points: [coordinate(1.0, 2.0)], parts: [] }
assert_eq(translate_shape(shape, 2.0, 3.0).points[0], coordinate(3.0, 5.0))
assert_eq(scale_shape(shape, 2.0, 2.0).points[0], coordinate(2.0, 4.0))
assert_eq(centroid(shape), Some(coordinate(1.0, 2.0)))
assert_eq(nearest_vertex(shape, coordinate(1.2, 2.1)).unwrap().0, 0)
}