use crate::core::engine::opengl::{
GL_POINTS, GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP, GL_TRIANGLES, GLfloat, Vec2,
};
use crate::core::{
Attribute, Color, FontAtlas, Geometry, Mesh, Renderable, Renderer, Shader,
generate_texture_from_image, load_image,
};
use crate::graphics2d::shapes::{
Arc as ArcShape, Circle, Ellipse, Image, Line, MultiPoint, Polygon, Polyline, Rectangle,
RoundedRectangle, ShapeKind, Text, Triangle,
};
use crate::graphics2d::svg::ToSvg;
use glam::Mat4;
use std::cell::{OnceCell, RefCell};
use std::collections::HashMap;
use std::f32::consts::PI;
use std::rc::Rc;
const MIN_STROKE_WIDTH: f32 = 1.0;
#[derive(Clone, Debug)]
pub struct ShapeStyle {
pub fill: Option<Color>,
pub stroke_color: Option<Color>,
pub stroke_width: Option<f32>,
}
impl Default for ShapeStyle {
fn default() -> Self {
Self {
fill: Some(Color::from_rgb(1.0, 1.0, 1.0)),
stroke_color: None,
stroke_width: None,
}
}
}
impl ShapeStyle {
pub fn fill(fill: Color) -> Self {
Self {
fill: Some(fill),
stroke_color: None,
stroke_width: None,
}
}
pub fn stroke(color: Color, width: f32) -> Self {
Self {
fill: None,
stroke_color: Some(color),
stroke_width: Some(width),
}
}
pub fn fill_and_stroke(fill: Color, stroke: Color, width: f32) -> Self {
Self {
fill: Some(fill),
stroke_color: Some(stroke),
stroke_width: Some(width),
}
}
}
thread_local! {
static DEFAULT_SHADER: OnceCell<Rc<Shader>> = OnceCell::new();
}
fn default_shader() -> Rc<Shader> {
DEFAULT_SHADER.with(|cell| {
cell.get_or_init(|| {
let vert_src = include_str!("../shaders/shape.vert");
let frag_src = include_str!("../shaders/shape.frag");
Rc::new(
Shader::compile(vert_src, frag_src, None)
.expect("Failed to compile default shader"),
)
})
.clone()
})
}
thread_local! {
static POINT_SHADER: OnceCell<Rc<Shader>> = OnceCell::new();
}
fn point_shader() -> Rc<Shader> {
POINT_SHADER.with(|cell| {
cell.get_or_init(|| {
let vert_src = include_str!("../shaders/shape.vert");
let frag_src = include_str!("../shaders/point.frag");
Rc::new(
Shader::compile(vert_src, frag_src, None).expect("Failed to compile point shader"),
)
})
.clone()
})
}
thread_local! {
static IMAGE_SHADER: OnceCell<Rc<Shader>> = OnceCell::new();
}
fn image_shader() -> Rc<Shader> {
IMAGE_SHADER.with(|cell| {
cell.get_or_init(|| {
let vert_src = include_str!("../shaders/image.vert");
let frag_src = include_str!("../shaders/image.frag");
Rc::new(
Shader::compile(vert_src, frag_src, None).expect("Failed to compile image shader"),
)
})
.clone()
})
}
thread_local! {
static TEXT_SHADER: OnceCell<Rc<Shader>> = OnceCell::new();
}
fn text_shader() -> Rc<Shader> {
TEXT_SHADER.with(|cell| {
cell.get_or_init(|| {
let vert_src = include_str!("../shaders/text.vert");
let frag_src = include_str!("../shaders/text.frag");
Rc::new(
Shader::compile(vert_src, frag_src, None).expect("Failed to compile text shader"),
)
})
.clone()
})
}
type FontCacheKey = (String, u32);
thread_local! {
static FONT_CACHE: RefCell<HashMap<FontCacheKey, Rc<RefCell<FontAtlas>>>> = RefCell::new(HashMap::new());
}
fn get_or_create_font_atlas(font_path: &str, font_size: u32) -> Rc<RefCell<FontAtlas>> {
FONT_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
let key = (font_path.to_string(), font_size);
if let Some(atlas) = cache.get(&key) {
return atlas.clone();
}
let atlas = FontAtlas::new(font_path, font_size, 512)
.expect("Failed to create font atlas");
let atlas_rc = Rc::new(RefCell::new(atlas));
cache.insert(key, atlas_rc.clone());
atlas_rc
})
}
pub fn clear_font_cache() {
FONT_CACHE.with(|cache| {
cache.borrow_mut().clear();
});
}
fn ortho_2d(width: f32, height: f32) -> Mat4 {
Mat4::orthographic_rh_gl(0.0, width, height, 0.0, -1.0, 1.0)
}
pub struct ShapeRenderable {
x: f32,
y: f32,
scale: f32,
rotation: f32,
mesh: Mesh,
stroke_mesh: Option<Mesh>,
shape: ShapeKind,
}
impl Renderable for ShapeRenderable {
fn render(&mut self, renderer: &Renderer) {
let (window_width, window_height) = renderer.window_handle.size();
let transform = ortho_2d(window_width as f32, window_height as f32);
self.mesh.set_transform(transform);
self.mesh.set_scale(self.scale);
self.mesh.set_rotation(self.rotation);
if self.mesh.geometry.instance_count() > 0 {
renderer.draw_mesh_instanced(&self.mesh);
} else {
self.mesh.set_screen_offset(self.x, self.y);
renderer.draw_mesh(&self.mesh);
}
if let Some(stroke) = &mut self.stroke_mesh {
stroke.set_transform(transform);
stroke.set_scale(self.scale);
stroke.set_rotation(self.rotation);
if stroke.geometry.instance_count() > 0 {
renderer.draw_mesh_instanced(stroke);
} else {
stroke.set_screen_offset(self.x, self.y);
renderer.draw_mesh(stroke);
}
}
}
}
impl ShapeRenderable {
fn new(x: f32, y: f32, mesh: Mesh, shape: ShapeKind) -> Self {
Self { x, y, scale: 1.0, rotation: 0.0, mesh, stroke_mesh: None, shape }
}
fn new_with_stroke(x: f32, y: f32, mesh: Mesh, stroke_mesh: Mesh, shape: ShapeKind) -> Self {
Self { x, y, scale: 1.0, rotation: 0.0, mesh, stroke_mesh: Some(stroke_mesh), shape }
}
pub fn set_position(&mut self, x: f32, y: f32) {
self.x = x;
self.y = y;
}
pub fn set_scale(&mut self, scale: f32) {
self.scale = scale;
}
pub fn scale(&self) -> f32 {
self.scale
}
pub fn set_rotation(&mut self, angle: f32) {
self.rotation = angle;
}
pub fn rotation(&self) -> f32 {
self.rotation
}
pub fn from_shape(x: f32, y: f32, shape: ShapeKind, style: ShapeStyle) -> Self {
match shape {
ShapeKind::Point => {
ShapeRenderable::point(x, y, style.fill.unwrap_or(Color::white()))
}
ShapeKind::MultiPoint(mp) => {
ShapeRenderable::multi_points(x, y, mp, style.fill.unwrap_or(Color::white()))
}
ShapeKind::Line(line) => ShapeRenderable::line(
x,
y,
line,
style.stroke_color.unwrap_or_else(Color::white),
style.stroke_width.unwrap_or(1.0),
),
ShapeKind::Polyline(poly_line) => {
ShapeRenderable::polyline(
x,
y,
poly_line,
style.stroke_color.unwrap_or(Color::white()),
style.stroke_width.unwrap_or(1.0),
)
}
ShapeKind::Triangle(triangle) => {
ShapeRenderable::triangle(x, y, triangle, style.fill.unwrap_or(Color::white()))
}
ShapeKind::Rectangle(rect) => {
match (style.fill, style.stroke_color) {
(Some(fill), Some(stroke)) => {
ShapeRenderable::rectangle_fill_and_stroke(
x,
y,
rect,
fill,
stroke,
style.stroke_width.unwrap_or(1.0),
)
}
(None, Some(stroke)) => {
ShapeRenderable::rectangle_outline(
x,
y,
rect,
stroke,
style.stroke_width.unwrap_or(1.0),
)
}
(fill, None) => {
ShapeRenderable::rectangle(x, y, rect, fill.unwrap_or(Color::white()))
}
}
}
ShapeKind::RoundedRectangle(rr) => {
ShapeRenderable::rounded_rectangle(x, y, rr, style.fill.unwrap_or(Color::white()))
}
ShapeKind::Polygon(polygon) => {
ShapeRenderable::polygon(x, y, polygon, style.fill.unwrap_or(Color::white()))
}
ShapeKind::Circle(circle) => {
ShapeRenderable::circle(x, y, circle, style.fill.unwrap_or(Color::white()))
}
ShapeKind::Ellipse(ellipse) => {
ShapeRenderable::ellipse(x, y, ellipse, style.fill.unwrap_or(Color::white()))
}
ShapeKind::Arc(arc) => ShapeRenderable::arc(
x,
y,
arc,
style.stroke_color.unwrap_or(Color::white()),
style.stroke_width.unwrap_or(1.0),
),
ShapeKind::Image(_) => {
unimplemented!("ShapeRenderable::from_shape cannot create Image without path")
}
ShapeKind::Text(text) => {
ShapeRenderable::text(x, y, text, style.fill.unwrap_or(Color::white()))
}
}
}
pub fn create_multiple_instances(&mut self, capacity: usize) {
self.mesh.geometry.enable_instancing_xy(capacity);
if let Some(stroke) = &mut self.stroke_mesh {
stroke.geometry.enable_instancing_xy(capacity);
}
}
pub fn set_instance_positions(&mut self, positions: &[Vec2]) {
self.mesh.geometry.update_instance_xy(positions);
if let Some(stroke) = &mut self.stroke_mesh {
stroke.geometry.update_instance_xy(positions);
}
}
pub fn set_instance_colors(&mut self, colors: &[Color]) {
self.mesh.geometry.update_instance_colors(colors);
}
pub fn set_instance_stroke_colors(&mut self, colors: &[Color]) {
if let Some(stroke) = &mut self.stroke_mesh {
stroke.geometry.update_instance_colors(colors);
}
}
pub fn clear_instances(&mut self) {
self.mesh.geometry.clear_instancing();
if let Some(stroke) = &mut self.stroke_mesh {
stroke.geometry.clear_instancing();
}
}
fn point(x: GLfloat, y: GLfloat, color: Color) -> Self {
let geometry = ShapeRenderable::point_geometry();
let mesh = Mesh::with_color(point_shader(), geometry, Some(color));
ShapeRenderable::new(x, y, mesh, ShapeKind::Point)
}
fn multi_points(x: GLfloat, y: GLfloat, multi_point: MultiPoint, color: Color) -> Self {
let abs_points: Vec<(f32, f32)> = multi_point
.points
.iter()
.map(|(px, py)| (x + px, y + py))
.collect();
let (x0, y0) = abs_points[0];
let rel_points: Vec<(GLfloat, GLfloat)> =
abs_points.iter().map(|(x, y)| (x - x0, y - y0)).collect();
let geometry = ShapeRenderable::point_list_geometry(&rel_points);
let mesh = Mesh::with_color(point_shader(), geometry, Some(color));
ShapeRenderable::new(x0, y0, mesh, ShapeKind::MultiPoint(multi_point))
}
fn line(
x: GLfloat,
y: GLfloat,
shape: Line,
stroke: Color,
stroke_width: f32,
) -> Self {
let (x1, y1) = shape.start;
let (x2, y2) = shape.end;
let geometry = ShapeRenderable::line_geometry(x1, y1, x2, y2, stroke_width);
let mesh = Mesh::with_color(default_shader(), geometry, Some(stroke));
ShapeRenderable::new(x, y, mesh, ShapeKind::Line(shape))
}
fn polyline(
x: f32,
y: f32,
polyline: Polyline,
stroke: Color,
stroke_width: f32,
) -> Self {
assert!(polyline.points.len() >= 2, "Polyline requires at least two points");
let abs_points: Vec<(f32, f32)> =
polyline.points.iter().map(|(px, py)| (x + px, y + py)).collect();
let (x0, y0) = abs_points[0];
let rel_points: Vec<(f32, f32)> = abs_points.iter().map(|(x, y)| (x - x0, y - y0)).collect();
let geometry = ShapeRenderable::polyline_geometry(&rel_points, stroke_width);
let mesh = Mesh::with_color(default_shader(), geometry, Some(stroke));
ShapeRenderable::new(x0, y0, mesh, ShapeKind::Polyline(polyline))
}
fn polyline_from_points(
points: &[(f32, f32)],
shape: ShapeKind,
stroke: Color,
stroke_width: f32,
) -> Self {
assert!(points.len() >= 2, "Polyline requires at least two points");
let (x0, y0) = points[0];
let rel_points: Vec<(f32, f32)> = points.iter().map(|(x, y)| (x - x0, y - y0)).collect();
let geometry = ShapeRenderable::polyline_geometry(&rel_points, stroke_width);
let mesh = Mesh::with_color(default_shader(), geometry, Some(stroke));
ShapeRenderable::new(x0, y0, mesh, shape)
}
fn arc(x: f32, y: f32, arc: ArcShape, stroke: Color, stroke_width: f32) -> Self {
use std::f32::consts::TAU;
let segments = 64;
let mut sweep = arc.end_angle - arc.start_angle;
if sweep < 0.0 {
sweep += TAU;
}
let mut points = Vec::with_capacity(segments + 1);
for i in 0..=segments {
let t = i as f32 / segments as f32;
let theta = arc.start_angle + t * sweep;
let px = x + arc.radius * theta.cos();
let py = y - arc.radius * theta.sin();
points.push((px, py));
}
Self::polyline_from_points(&points, ShapeKind::Arc(arc), stroke, stroke_width)
}
fn triangle(x: f32, y: f32, triangle: Triangle, color: Color) -> Self {
let geometry = ShapeRenderable::triangle_geometry(&triangle.vertices);
let mesh = Mesh::with_color(default_shader(), geometry, Some(color));
ShapeRenderable::new(x, y, mesh, ShapeKind::Triangle(triangle))
}
fn rectangle(x: f32, y: f32, rect: Rectangle, color: Color) -> Self {
let geometry = ShapeRenderable::rectangle_geometry(rect.width, rect.height);
let mesh = Mesh::with_color(default_shader(), geometry, Some(color));
ShapeRenderable::new(x, y, mesh, ShapeKind::Rectangle(rect))
}
fn rectangle_outline(
x: f32,
y: f32,
rect: Rectangle,
stroke: Color,
stroke_width: f32,
) -> Self {
let points = vec![
(0.0, 0.0),
(rect.width, 0.0),
(rect.width, rect.height),
(0.0, rect.height),
(0.0, 0.0), (rect.width, 0.0), ];
let geometry = ShapeRenderable::polyline_geometry(&points, stroke_width);
let mesh = Mesh::with_color(default_shader(), geometry, Some(stroke));
ShapeRenderable::new(x, y, mesh, ShapeKind::Rectangle(rect))
}
fn rectangle_fill_and_stroke(
x: f32,
y: f32,
rect: Rectangle,
fill: Color,
stroke: Color,
stroke_width: f32,
) -> Self {
let fill_geometry = ShapeRenderable::rectangle_geometry(rect.width, rect.height);
let fill_mesh = Mesh::with_color(default_shader(), fill_geometry, Some(fill));
let points = vec![
(0.0, 0.0),
(rect.width, 0.0),
(rect.width, rect.height),
(0.0, rect.height),
(0.0, 0.0),
(rect.width, 0.0),
];
let stroke_geometry = ShapeRenderable::polyline_geometry(&points, stroke_width);
let stroke_mesh = Mesh::with_color(default_shader(), stroke_geometry, Some(stroke));
ShapeRenderable::new_with_stroke(x, y, fill_mesh, stroke_mesh, ShapeKind::Rectangle(rect))
}
fn rounded_rectangle(x: f32, y: f32, rr: RoundedRectangle, color: Color) -> Self {
let geometry =
ShapeRenderable::rounded_rectangle_geometry(rr.width, rr.height, rr.radius, 8);
let mesh = Mesh::with_color(default_shader(), geometry, Some(color));
ShapeRenderable::new(x, y, mesh, ShapeKind::RoundedRectangle(rr))
}
fn polygon(x: f32, y: f32, polygon: Polygon, color: Color) -> Self {
assert!(polygon.points.len() >= 3, "Polygon requires at least 3 points");
let abs_points: Vec<(f32, f32)> =
polygon.points.iter().map(|(px, py)| (x + px, y + py)).collect();
let (x0, y0) = abs_points[0]; let rel_points: Vec<(f32, f32)> = abs_points.iter().map(|(x, y)| (x - x0, y - y0)).collect();
let geometry = ShapeRenderable::polygon_geometry(&rel_points);
let mesh = Mesh::with_color(default_shader(), geometry, Some(color));
ShapeRenderable::new(x0, y0, mesh, ShapeKind::Polygon(polygon))
}
fn circle(x: f32, y: f32, circle: Circle, color: Color) -> Self {
let geometry = ShapeRenderable::circle_geometry(circle.radius, 100);
let mesh = Mesh::with_color(default_shader(), geometry, Some(color));
ShapeRenderable::new(x, y, mesh, ShapeKind::Circle(circle))
}
fn ellipse(x: f32, y: f32, ellipse: Ellipse, color: Color) -> Self {
let geometry = ShapeRenderable::ellipse_geometry(ellipse.radius_x, ellipse.radius_y, 64);
let mesh = Mesh::with_color(default_shader(), geometry, Some(color));
ShapeRenderable::new(x, y, mesh, ShapeKind::Ellipse(ellipse))
}
fn text(x: f32, y: f32, text: Text, color: Color) -> Self {
let font_atlas = get_or_create_font_atlas(&text.font_path, text.font_size);
let geometry = {
let mut atlas = font_atlas.borrow_mut();
ShapeRenderable::text_geometry(&text.content, &mut atlas)
};
let texture_id = font_atlas.borrow().texture_id();
let shader = text_shader();
let mut mesh = Mesh::with_texture(shader, geometry, Some(texture_id));
mesh.color = Some(color);
ShapeRenderable::new(x, y, mesh, ShapeKind::Text(text))
}
pub fn image_with_size(x: f32, y: f32, path: &str, width: f32, height: f32) -> ShapeRenderable {
let image = load_image(path);
let texture_id = generate_texture_from_image(&image);
let geometry = ShapeRenderable::image_geometry(width, height);
let shader = image_shader(); let mesh = Mesh::with_texture(shader, geometry, Some(texture_id));
ShapeRenderable::new(x, y, mesh, ShapeKind::Image(Image::new(width, height)))
}
pub fn image(x: f32, y: f32, path: &str) -> Self {
let image = load_image(path);
Self::image_with_size(x, y, path, image.width as f32, image.height as f32)
}
fn point_geometry() -> Geometry {
let vertex = vec![0.0, 0.0];
let mut geometry = Geometry::new(GL_POINTS);
geometry.add_buffer(&vertex, 2);
geometry.add_vertex_attribute(Attribute::new(0, 2, 2, 0));
geometry
}
fn point_list_geometry(points: &[(GLfloat, GLfloat)]) -> Geometry {
let mut vertices = Vec::with_capacity(points.len() * 2);
for &(x, y) in points {
vertices.push(x);
vertices.push(y);
}
let values_per_vertex = 2;
let mut geometry = Geometry::new(GL_POINTS);
geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0, values_per_vertex,
values_per_vertex as usize,
0,
));
geometry
}
fn line_geometry(
x1: GLfloat,
y1: GLfloat,
x2: GLfloat,
y2: GLfloat,
stroke_width: f32,
) -> Geometry {
let stroke_width = stroke_width.max(MIN_STROKE_WIDTH);
let dx = x2 - x1;
let dy = y2 - y1;
let length = (dx * dx + dy * dy).sqrt();
if length == 0.0 {
return Geometry::new(GL_TRIANGLES);
}
let nx = -dy / length;
let ny = dx / length;
let half_thickness = stroke_width / 2.0;
let ox = nx * half_thickness;
let oy = ny * half_thickness;
let v0 = [x1 - ox, y1 - oy];
let v1 = [x2 - ox, y2 - oy];
let v2 = [x2 + ox, y2 + oy];
let v3 = [x1 + ox, y1 + oy];
let vertices: Vec<GLfloat> = vec![
v0[0], v0[1], v1[0], v1[1], v2[0], v2[1], v2[0], v2[1], v3[0], v3[1], v0[0], v0[1],
];
let position_values_per_vertex = 2;
let mut geometry = Geometry::new(GL_TRIANGLES);
geometry.add_buffer(&vertices, position_values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0,
position_values_per_vertex,
position_values_per_vertex as usize,
0,
));
geometry
}
fn polyline_geometry(points: &[(GLfloat, GLfloat)], stroke_width: f32) -> Geometry {
const MITER_LIMIT: f32 = 4.0;
if points.len() < 2 {
return Geometry::new(GL_TRIANGLES);
}
let half_thickness = stroke_width.max(1.0) / 2.0;
let miter_limit_squared = (stroke_width * MITER_LIMIT).powi(2) / 4.0;
let mut vertices: Vec<GLfloat> = Vec::new();
let mut a = points[0];
let mut b = points[1];
let mut idx = 1;
while idx < points.len() && (b.0 - a.0).hypot(b.1 - a.1) == 0.0 {
idx += 1;
if idx < points.len() {
b = points[idx];
}
}
if (b.0 - a.0).hypot(b.1 - a.1) == 0.0 {
return Geometry::new(GL_TRIANGLES);
}
for i in idx + 1..=points.len() {
let c = if i < points.len() { points[i] } else { a };
let ab = (b.0 - a.0, b.1 - a.1);
let len_ab = (ab.0 * ab.0 + ab.1 * ab.1).sqrt();
let normal_ab = (
-ab.1 / len_ab * half_thickness,
ab.0 / len_ab * half_thickness,
);
let a1 = (a.0 + normal_ab.0, a.1 + normal_ab.1);
let a2 = (a.0 - normal_ab.0, a.1 - normal_ab.1);
let b1 = (b.0 + normal_ab.0, b.1 + normal_ab.1);
let b2 = (b.0 - normal_ab.0, b.1 - normal_ab.1);
vertices.extend_from_slice(&[
a1.0, a1.1, a2.0, a2.1, b1.0, b1.1, a2.0, a2.1, b1.0, b1.1, b2.0, b2.1,
]);
let bc = (c.0 - b.0, c.1 - b.1);
let len_bc = (bc.0 * bc.0 + bc.1 * bc.1).sqrt();
if len_bc > 0.0 {
let normal_bc = (
-bc.1 / len_bc * half_thickness,
bc.0 / len_bc * half_thickness,
);
let b3 = (b.0 + normal_bc.0, b.1 + normal_bc.1);
let b4 = (b.0 - normal_bc.0, b.1 - normal_bc.1);
let z = ab.0 * bc.1 - ab.1 * bc.0;
if z < 0.0 {
vertices.extend_from_slice(&[b.0, b.1, b1.0, b1.1, b3.0, b3.1]);
} else if z > 0.0 {
vertices.extend_from_slice(&[b.0, b.1, b2.0, b2.1, b4.0, b4.1]);
}
if z != 0.0 {
let (a_j, b_j, norm_j) = if z < 0.0 { (a1, b3, ab) } else { (a2, b4, ab) };
let denom = z;
let alpha = (bc.1 * (b_j.0 - a_j.0) + bc.0 * (a_j.1 - b_j.1)) / denom;
let mx = a_j.0 + alpha * norm_j.0;
let my = a_j.1 + alpha * norm_j.1;
let dist2 = (mx - b.0).powi(2) + (my - b.1).powi(2);
if dist2 <= miter_limit_squared {
if z < 0.0 {
vertices.extend_from_slice(&[mx, my, b1.0, b1.1, b3.0, b3.1]);
} else {
vertices.extend_from_slice(&[mx, my, b2.0, b2.1, b4.0, b4.1]);
}
}
}
}
a = b;
b = c;
}
let mut geometry = Geometry::new(GL_TRIANGLES);
geometry.add_buffer(&vertices, 2);
geometry.add_vertex_attribute(Attribute::new(0, 2, 2, 0));
geometry
}
fn triangle_geometry(vertices: &[(f32, f32); 3]) -> Geometry {
let mut geometry = Geometry::new(GL_TRIANGLES);
let flattened: Vec<f32> = vertices.iter().flat_map(|(x, y)| [*x, *y]).collect();
geometry.add_buffer(&flattened, 2);
geometry.add_vertex_attribute(Attribute::new(0, 2, 2, 0));
geometry
}
fn rectangle_geometry(width: GLfloat, height: GLfloat) -> Geometry {
let vertices: Vec<GLfloat> = vec![
0.0, 0.0, width, 0.0, 0.0, height, width, height,
];
let position_values_per_vertex = 2;
let values_per_vertex = position_values_per_vertex;
let mut geometry = Geometry::new(GL_TRIANGLE_STRIP);
geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0,
position_values_per_vertex,
values_per_vertex as usize,
0,
));
geometry
}
fn circle_geometry(radius: GLfloat, segments: usize) -> Geometry {
let mut vertices: Vec<GLfloat> = Vec::with_capacity((segments + 2) * 5);
vertices.extend_from_slice(&[0.0, 0.0]);
for i in 0..=segments {
let theta = (i as f32 / segments as f32) * std::f32::consts::TAU; let x = radius * theta.cos();
let y = radius * theta.sin();
vertices.extend_from_slice(&[x, y]);
}
let position_values_per_vertex = 2;
let values_per_vertex = position_values_per_vertex;
let mut geometry = Geometry::new(GL_TRIANGLE_FAN);
geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0,
position_values_per_vertex,
values_per_vertex as usize,
0,
));
geometry
}
fn ellipse_geometry(rx: f32, ry: f32, segments: usize) -> Geometry {
use std::f32::consts::PI;
let mut vertices: Vec<GLfloat> = Vec::with_capacity((segments + 2) * 2);
vertices.extend_from_slice(&[0.0, 0.0]);
for i in 0..=segments {
let angle = 2.0 * PI * (i as f32) / (segments as f32);
let x = rx * angle.cos();
let y = ry * angle.sin();
vertices.extend_from_slice(&[x, y]);
}
let values_per_vertex = 2;
let mut geometry = Geometry::new(GL_TRIANGLE_FAN);
geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0, 2,
values_per_vertex as usize,
0,
));
geometry
}
pub fn rounded_rectangle_geometry(
width: f32,
height: f32,
radius: f32,
segments: usize,
) -> Geometry {
assert!(radius * 2.0 <= width && radius * 2.0 <= height);
let mut vertices: Vec<GLfloat> = Vec::new();
let center_x = width / 2.0;
let center_y = height / 2.0;
vertices.push(center_x);
vertices.push(center_y);
let corners = [
(radius, radius, PI, 1.5 * PI), (width - radius, radius, 1.5 * PI, 2.0 * PI), (width - radius, height - radius, 0.0, 0.5 * PI), (radius, height - radius, 0.5 * PI, PI), ];
let mut first_arc_x = 0.0;
let mut first_arc_y = 0.0;
let mut is_first = true;
for &(cx, cy, start_angle, end_angle) in &corners {
for i in 0..=segments {
let theta =
start_angle + (end_angle - start_angle) * (i as f32) / (segments as f32);
let x = cx + radius * theta.cos();
let y = cy + radius * theta.sin();
if is_first {
first_arc_x = x;
first_arc_y = y;
is_first = false;
}
vertices.push(x);
vertices.push(y);
}
}
vertices.push(first_arc_x);
vertices.push(first_arc_y);
let values_per_vertex = 2;
let mut geometry = Geometry::new(GL_TRIANGLE_FAN);
geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0, 2, values_per_vertex as usize,
0,
));
geometry
}
fn polygon_geometry(points: &[(GLfloat, GLfloat)]) -> Geometry {
assert!(points.len() >= 3, "Polygon requires at least 3 points");
let mut vertices = Vec::with_capacity(points.len() * 2);
for &(x, y) in points {
vertices.extend_from_slice(&[x, y]);
}
let values_per_vertex = 2;
let mut geometry = Geometry::new(GL_TRIANGLE_FAN); geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(0, 2, values_per_vertex as usize, 0));
geometry
}
pub fn image_geometry(width: f32, height: f32) -> Geometry {
let hw = width / 2.0;
let hh = height / 2.0;
let vertices: Vec<f32> = vec![
-hw, -hh, 0.0, 0.0, hw, -hh, 1.0, 0.0, hw, hh, 1.0, 1.0, -hw, -hh, 0.0, 0.0, hw, hh, 1.0, 1.0, -hw, hh, 0.0, 1.0, ];
let values_per_vertex = 4;
let mut geometry = Geometry::new(GL_TRIANGLES);
geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0, 2, values_per_vertex as usize,
0,
));
geometry.add_vertex_attribute(Attribute::new(
1, 2, values_per_vertex as usize,
2, ));
geometry
}
fn text_geometry(text: &str, font_atlas: &mut FontAtlas) -> Geometry {
let mut vertices: Vec<f32> = Vec::new();
let mut cursor_x: f32 = 0.0;
let baseline_y: f32 = font_atlas.font_size() as f32;
for ch in text.chars() {
if let Some(glyph) = font_atlas.get_glyph(ch) {
if glyph.width == 0 || glyph.height == 0 {
cursor_x += glyph.advance;
continue;
}
let x0 = cursor_x + glyph.bearing_x as f32;
let y0 = baseline_y - glyph.bearing_y as f32; let x1 = x0 + glyph.width as f32;
let y1 = y0 + glyph.height as f32;
let u0 = glyph.uv_x;
let v0 = glyph.uv_y;
let u1 = glyph.uv_x + glyph.uv_width;
let v1 = glyph.uv_y + glyph.uv_height;
vertices.extend_from_slice(&[
x0, y1, u0, v1, x1, y1, u1, v1, x1, y0, u1, v0, ]);
vertices.extend_from_slice(&[
x0, y1, u0, v1, x1, y0, u1, v0, x0, y0, u0, v0, ]);
cursor_x += glyph.advance;
}
}
let values_per_vertex = 4;
let mut geometry = Geometry::new(GL_TRIANGLES);
geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0, 2, values_per_vertex as usize,
0,
));
geometry.add_vertex_attribute(Attribute::new(
1, 2, values_per_vertex as usize,
2, ));
geometry
}
fn svg_color(&self) -> String {
self.mesh
.color
.as_ref()
.map(|c| c.to_hex())
.unwrap_or_else(|| "#000000".to_string())
}
}
impl ToSvg for ShapeRenderable {
fn to_svg(&self) -> String {
match &self.shape {
ShapeKind::Line(line) => {
format!(
r#"<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="{color}" stroke-width="1"/>"#,
x1 = self.x + line.start.0,
y1 = self.y + line.start.1,
x2 = self.x + line.end.0,
y2 = self.y + line.end.1,
color = self.svg_color(),
)
}
ShapeKind::Rectangle(rect) => {
format!(
r#"<rect x="{x}" y="{y}" width="{w}" height="{h}" fill="{color}"/>"#,
x = self.x,
y = self.y,
w = rect.width,
h = rect.height,
color = self.svg_color(),
)
}
ShapeKind::RoundedRectangle(rect) => {
format!(
r#"<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="{r}" ry="{r}" fill="{color}"/>"#,
x = self.x,
y = self.y,
w = rect.width,
h = rect.height,
r = rect.radius,
color = self.svg_color(),
)
}
ShapeKind::Polygon(polygon) => {
let path = polygon
.points
.iter()
.map(|(px, py)| format!("{},{}", px + self.x, py + self.y))
.collect::<Vec<_>>()
.join(" ");
format!(
r#"<polygon points="{path}" fill="{color}" stroke="{color}" stroke-width="1"/>"#,
path = path,
color = self.svg_color(),
)
}
ShapeKind::Circle(circle) => {
format!(
r#"<circle cx="{cx}" cy="{cy}" r="{r}" fill="{color}"/>"#,
cx = self.x + circle.radius,
cy = self.y + circle.radius,
r = circle.radius,
color = self.svg_color(),
)
}
ShapeKind::Ellipse(ellipse) => {
format!(
r#"<ellipse cx="{cx}" cy="{cy}" rx="{rx}" ry="{ry}" fill="{color}"/>"#,
cx = self.x + ellipse.radius_x,
cy = self.y + ellipse.radius_y,
rx = ellipse.radius_x,
ry = ellipse.radius_y,
color = self.svg_color(),
)
}
ShapeKind::Polyline(polyline) => {
let path = polyline
.points
.iter()
.map(|(px, py)| format!("{},{}", px + self.x, py + self.y))
.collect::<Vec<_>>()
.join(" ");
format!(
r#"<polyline points="{path}" fill="none" stroke="{color}" stroke-width="1"/>"#,
path = path,
color = self.svg_color(),
)
}
ShapeKind::MultiPoint(multi_point) => {
let mut out = String::new();
for (px, py) in &multi_point.points {
let cx = px + self.x;
let cy = py + self.y;
out.push_str(&format!(
r#"<circle cx="{cx}" cy="{cy}" r="2" fill="{color}"/>"#,
cx = cx,
cy = cy,
color = self.svg_color(),
));
}
out
}
ShapeKind::Point => {
format!(
r#"<circle cx="{cx}" cy="{cy}" r="2" fill="{color}"/>"#,
cx = self.x,
cy = self.y,
color = self.svg_color(),
)
}
ShapeKind::Image(_) => String::new(),
ShapeKind::Triangle(tri) => {
let points: String = tri
.vertices
.iter()
.map(|(vx, vy)| format!("{:.2},{:.2}", vx + self.x, vy + self.y))
.collect::<Vec<_>>()
.join(" ");
format!(
r#"<polygon points="{points}" fill="{color}"/>"#,
points = points,
color = self.svg_color(),
)
}
ShapeKind::Arc(_) => {
unimplemented!("Arc SVG export is not yet implemented")
}
ShapeKind::Text(text) => {
format!(
r#"<text x="{x}" y="{y}" fill="{color}" font-size="{size}">{content}</text>"#,
x = self.x,
y = self.y,
color = self.svg_color(),
size = text.font_size,
content = text.content,
)
}
}
}
}