///|
priv struct GradientSegment2D {
start_offset : Double
end_offset : Double
sample_start_offset : Double
sample_end_offset : Double
color_start : @render.Color
color_end : @render.Color
}
///|
pub fn tessellate_gradient_rect(
command : GradientRectDrawCommand2D,
) -> Array[ColoredTriangle2D] {
let triangles : Array[ColoredTriangle2D] = []
let dx = command.end[X] - command.start[X]
let dy = command.end[Y] - command.start[Y]
let length_squared = dx * dx + dy * dy
guard command.rect.size[X] > 0.0 &&
command.rect.size[Y] > 0.0 &&
length_squared > 0.0 &&
command.stops.length() >= 2 else {
return triangles
}
let rectangle = [
@math.Vec2(0.0, 0.0),
@math.Vec2(1.0, 0.0),
@math.Vec2(1.0, 1.0),
@math.Vec2(0.0, 1.0),
]
let shape = if maximum_radius(command.radii) <= 0.0 {
rectangle
} else {
rounded_perimeter(
{ position: @math.Vec2(0.0, 0.0), size: command.rect.size },
command.radii,
command.output_scale,
).map(fn(point) {
@math.Vec2(
point[X] / command.rect.size[X],
point[Y] / command.rect.size[Y],
)
})
}
let mut minimum_projection = 1.0e300
let mut maximum_projection = -1.0e300
for point in shape {
let projection = (
(point[X] - command.start[X]) * dx + (point[Y] - command.start[Y]) * dy
) /
length_squared
minimum_projection = @cmp.minimum(minimum_projection, projection)
maximum_projection = @cmp.maximum(maximum_projection, projection)
}
let segments : Array[GradientSegment2D] = []
let first = command.stops[0]
if minimum_projection < first.offset {
segments.push({
start_offset: minimum_projection,
end_offset: @cmp.minimum(first.offset, maximum_projection),
sample_start_offset: minimum_projection,
sample_end_offset: minimum_projection,
color_start: first.color,
color_end: first.color,
})
}
for index in 0..<(command.stops.length() - 1) {
let current = command.stops[index]
let next = command.stops[index + 1]
if next.offset > current.offset &&
next.offset > minimum_projection &&
current.offset < maximum_projection {
segments.push({
start_offset: @cmp.maximum(current.offset, minimum_projection),
end_offset: @cmp.minimum(next.offset, maximum_projection),
sample_start_offset: current.offset,
sample_end_offset: next.offset,
color_start: current.color,
color_end: next.color,
})
}
}
let last = command.stops[command.stops.length() - 1]
if maximum_projection > last.offset {
segments.push({
start_offset: @cmp.maximum(last.offset, minimum_projection),
end_offset: maximum_projection,
sample_start_offset: maximum_projection,
sample_end_offset: maximum_projection,
color_start: last.color,
color_end: last.color,
})
}
for segment in segments {
if segment.end_offset <= segment.start_offset {
continue
}
let mut polygon = shape.copy()
for boundary in [(segment.start_offset, true), (segment.end_offset, false)] {
if polygon.is_empty() {
break
}
let clipped : Array[@math.Vec2] = []
let mut previous = polygon[polygon.length() - 1]
let mut previous_projection = (
(previous[X] - command.start[X]) * dx +
(previous[Y] - command.start[Y]) * dy
) /
length_squared
let mut previous_inside = if boundary.1 {
previous_projection >= boundary.0
} else {
previous_projection <= boundary.0
}
for current in polygon {
let current_projection = (
(current[X] - command.start[X]) * dx +
(current[Y] - command.start[Y]) * dy
) /
length_squared
let current_inside = if boundary.1 {
current_projection >= boundary.0
} else {
current_projection <= boundary.0
}
if current_inside != previous_inside {
let amount = (boundary.0 - previous_projection) /
(current_projection - previous_projection)
clipped.push(
@math.Vec2(
previous[X] + (current[X] - previous[X]) * amount,
previous[Y] + (current[Y] - previous[Y]) * amount,
),
)
}
if current_inside {
clipped.push(current)
}
previous = current
previous_projection = current_projection
previous_inside = current_inside
}
polygon = clipped
}
if polygon.length() < 3 {
continue
}
let vertices : Array[ColoredVertex2D] = []
for point in polygon {
let projection = (
(point[X] - command.start[X]) * dx +
(point[Y] - command.start[Y]) * dy
) /
length_squared
let amount = if segment.sample_start_offset == segment.sample_end_offset {
0.0
} else {
(projection - segment.sample_start_offset) /
(segment.sample_end_offset - segment.sample_start_offset)
}
let (local_x, local_y) = command.transform.apply_to_point(
point[X] * command.rect.size[X],
point[Y] * command.rect.size[Y],
)
vertices.push({
position: @math.Vec2(
command.rect.position[X] + local_x,
command.rect.position[Y] + local_y,
),
color: {
r: (segment.color_start.r.to_double() +
(segment.color_end.r.to_double() - segment.color_start.r.to_double()) *
amount)
.round()
.to_uint(),
g: (segment.color_start.g.to_double() +
(segment.color_end.g.to_double() - segment.color_start.g.to_double()) *
amount)
.round()
.to_uint(),
b: (segment.color_start.b.to_double() +
(segment.color_end.b.to_double() - segment.color_start.b.to_double()) *
amount)
.round()
.to_uint(),
a: segment.color_start.a +
(segment.color_end.a - segment.color_start.a) * amount,
},
})
}
for index in 1..<(vertices.length() - 1) {
triangles.push({
a: vertices[0],
b: vertices[index],
c: vertices[index + 1],
})
}
}
triangles
}