Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-renderer/src/vertex.rs
1//! Vertex data structures and layouts for wgpu rendering
2 
3use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, VertexFormat, VertexStepMode};
4 
5/// Vertex data for UI rendering
6#[repr(C)]
7#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
8pub struct Vertex {
9 pub position: [f32; 2], // Changed from 3D to 2D to match shader
10 pub color: [f32; 4],
11 pub uv: [f32; 2], // Renamed from tex_coords to match shader
12 pub params: [f32; 4], // Added params field to match shader
13 pub flags: u32, // For different rendering modes (solid, textured, etc.)
14}
15 
16impl Vertex {
17 /// Create a new vertex
18 pub fn new(position: [f32; 2], color: [f32; 4], uv: [f32; 2]) -> Self {
19 Self {
20 position,
21 color,
22 uv,
23 params: [0.0, 0.0, 0.0, 0.0],
24 flags: 0,
25 }
26 }
27 
28 /// Create a vertex with solid color (no texture)
29 pub fn solid(position: [f32; 2], color: [f32; 4]) -> Self {
30 Self {
31 position,
32 color,
33 uv: [0.0, 0.0],
34 params: [0.0, 0.0, 0.0, 0.0],
35 flags: 0, // FLAG_TYPE_SOLID = 0 (matches shader)
36 }
37 }
38 
39 /// Create a vertex with texture coordinates
40 pub fn textured(position: [f32; 2], uv: [f32; 2], color: [f32; 4]) -> Self {
41 Self {
42 position,
43 color,
44 uv,
45 params: [0.0, 0.0, 0.0, 0.0],
46 flags: 1, // FLAG_TYPE_TEXTURED = 1 (matches shader)
47 }
48 }
49 
50 /// Get the vertex buffer layout descriptor
51 pub fn desc() -> VertexBufferLayout<'static> {
52 VertexBufferLayout {
53 array_stride: std::mem::size_of::<Vertex>() as BufferAddress,
54 step_mode: VertexStepMode::Vertex,
55 attributes: &[
56 // Position
57 VertexAttribute {
58 offset: 0,
59 shader_location: 0,
60 format: VertexFormat::Float32x2,
61 },
62 // Color
63 VertexAttribute {
64 offset: std::mem::size_of::<[f32; 2]>() as BufferAddress,
65 shader_location: 1,
66 format: VertexFormat::Float32x4,
67 },
68 // UV coordinates
69 VertexAttribute {
70 offset: std::mem::size_of::<[f32; 6]>() as BufferAddress,
71 shader_location: 2,
72 format: VertexFormat::Float32x2,
73 },
74 // Params
75 VertexAttribute {
76 offset: std::mem::size_of::<[f32; 8]>() as BufferAddress,
77 shader_location: 3,
78 format: VertexFormat::Float32x4,
79 },
80 // Flags
81 VertexAttribute {
82 offset: std::mem::size_of::<[f32; 12]>() as BufferAddress,
83 shader_location: 4,
84 format: VertexFormat::Uint32,
85 },
86 ],
87 }
88 }
89}
90 
91/// Text vertex for specialized text rendering
92#[repr(C)]
93#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
94pub struct TextVertex {
95 pub position: [f32; 3],
96 pub tex_coords: [f32; 2],
97 pub color: [f32; 4],
98 pub glyph_index: u32,
99}
100 
101impl TextVertex {
102 /// Create a new text vertex
103 pub fn new(
104 position: [f32; 3],
105 tex_coords: [f32; 2],
106 color: [f32; 4],
107 glyph_index: u32,
108 ) -> Self {
109 Self {
110 position,
111 tex_coords,
112 color,
113 glyph_index,
114 }
115 }
116 
117 /// Get the vertex buffer layout descriptor for text
118 pub fn desc() -> VertexBufferLayout<'static> {
119 VertexBufferLayout {
120 array_stride: std::mem::size_of::<TextVertex>() as BufferAddress,
121 step_mode: VertexStepMode::Vertex,
122 attributes: &[
123 // Position
124 VertexAttribute {
125 offset: 0,
126 shader_location: 0,
127 format: VertexFormat::Float32x3,
128 },
129 // Texture coordinates
130 VertexAttribute {
131 offset: std::mem::size_of::<[f32; 3]>() as BufferAddress,
132 shader_location: 1,
133 format: VertexFormat::Float32x2,
134 },
135 // Color
136 VertexAttribute {
137 offset: std::mem::size_of::<[f32; 5]>() as BufferAddress,
138 shader_location: 2,
139 format: VertexFormat::Float32x4,
140 },
141 // Glyph index
142 VertexAttribute {
143 offset: std::mem::size_of::<[f32; 9]>() as BufferAddress,
144 shader_location: 3,
145 format: VertexFormat::Uint32,
146 },
147 ],
148 }
149 }
150}
151 
152/// Vertex builder for creating common shapes
153pub struct VertexBuilder;
154 
155impl VertexBuilder {
156 /// Create vertices for a rectangle
157 pub fn rectangle(
158 x: f32,
159 y: f32,
160 width: f32,
161 height: f32,
162 color: [f32; 4],
163 ) -> (Vec<Vertex>, Vec<u16>) {
164 let vertices = vec![
165 Vertex::solid([x, y], color), // Top-left
166 Vertex::solid([x + width, y], color), // Top-right
167 Vertex::solid([x + width, y + height], color), // Bottom-right
168 Vertex::solid([x, y + height], color), // Bottom-left
169 ];
170 
171 let indices = vec![0, 1, 2, 2, 3, 0];
172 
173 (vertices, indices)
174 }
175 
176 /// Create vertices for a textured rectangle
177 pub fn textured_rectangle(
178 x: f32,
179 y: f32,
180 width: f32,
181 height: f32,
182 color: [f32; 4],
183 ) -> (Vec<Vertex>, Vec<u16>) {
184 let vertices = vec![
185 Vertex::textured([x, y], [0.0, 0.0], color), // Top-left
186 Vertex::textured([x + width, y], [1.0, 0.0], color), // Top-right
187 Vertex::textured([x + width, y + height], [1.0, 1.0], color), // Bottom-right
188 Vertex::textured([x, y + height], [0.0, 1.0], color), // Bottom-left
189 ];
190 
191 let indices = vec![0, 1, 2, 2, 3, 0];
192 
193 (vertices, indices)
194 }
195 
196 /// Create vertices for a circle (approximated with triangles)
197 pub fn circle(
198 center_x: f32,
199 center_y: f32,
200 radius: f32,
201 color: [f32; 4],
202 segments: u32,
203 ) -> (Vec<Vertex>, Vec<u16>) {
204 let mut vertices = Vec::with_capacity((segments + 1) as usize);
205 let mut indices = Vec::with_capacity((segments * 3) as usize);
206 
207 // Center vertex
208 vertices.push(Vertex::solid([center_x, center_y], color));
209 
210 // Create vertices around the circle
211 for i in 0..segments {
212 let angle = (i as f32) * 2.0 * std::f32::consts::PI / (segments as f32);
213 let x = center_x + radius * angle.cos();
214 let y = center_y + radius * angle.sin();
215 vertices.push(Vertex::solid([x, y], color));
216 }
217 
218 // Create triangles
219 for i in 0..segments {
220 let next = if i == segments - 1 { 1 } else { i + 2 };
221 indices.extend_from_slice(&[0, (i + 1) as u16, next as u16]);
222 }
223 
224 (vertices, indices)
225 }
226 
227 /// Create vertices for a rounded rectangle using SDF
228 pub fn rounded_rectangle(
229 x: f32,
230 y: f32,
231 width: f32,
232 height: f32,
233 radius: f32,
234 color: [f32; 4],
235 _corner_segments: u32, // Unused for SDF
236 ) -> (Vec<Vertex>, Vec<u16>) {
237 let mut vertices = Vec::with_capacity(4);
238 let indices = vec![0, 1, 2, 2, 3, 0];
239 
240 // Params: width, height, radius, unused
241 let params = [width, height, radius, 0.0];
242 // Flag: 3 = FLAG_TYPE_ROUNDED_RECT
243 let flags = 3;
244 
245 vertices.push(Vertex {
246 position: [x, y],
247 color,
248 uv: [0.0, 0.0],
249 params,
250 flags,
251 });
252 vertices.push(Vertex {
253 position: [x + width, y],
254 color,
255 uv: [1.0, 0.0],
256 params,
257 flags,
258 });
259 vertices.push(Vertex {
260 position: [x + width, y + height],
261 color,
262 uv: [1.0, 1.0],
263 params,
264 flags,
265 });
266 vertices.push(Vertex {
267 position: [x, y + height],
268 color,
269 uv: [0.0, 1.0],
270 params,
271 flags,
272 });
273 
274 (vertices, indices)
275 }
276 
277 /// Create vertices for a rounded rectangle outline (border)
278 pub fn rounded_rectangle_outline(
279 x: f32,
280 y: f32,
281 width: f32,
282 height: f32,
283 radius: f32,
284 color: [f32; 4],
285 thickness: f32,
286 corner_segments: u32,
287 ) -> (Vec<Vertex>, Vec<u16>) {
288 let mut vertices = Vec::new();
289 let mut indices = Vec::new();
290 
291 // Create the four border lines
292 let half_thickness = thickness / 2.0;
293 
294 // Top line
295 let (top_verts, top_indices) = Self::line(
296 x + radius,
297 y - half_thickness,
298 x + width - radius,
299 y - half_thickness,
300 thickness,
301 color,
302 );
303 vertices.extend(top_verts);
304 indices.extend(top_indices);
305 
306 // Right line
307 let offset = vertices.len() as u16;
308 let (right_verts, right_indices) = Self::line(
309 x + width + half_thickness,
310 y + radius,
311 x + width + half_thickness,
312 y + height - radius,
313 thickness,
314 color,
315 );
316 vertices.extend(right_verts);
317 indices.extend(right_indices.iter().map(|&i| i + offset));
318 
319 // Bottom line
320 let offset = vertices.len() as u16;
321 let (bottom_verts, bottom_indices) = Self::line(
322 x + width - radius,
323 y + height + half_thickness,
324 x + radius,
325 y + height + half_thickness,
326 thickness,
327 color,
328 );
329 vertices.extend(bottom_verts);
330 indices.extend(bottom_indices.iter().map(|&i| i + offset));
331 
332 // Left line
333 let offset = vertices.len() as u16;
334 let (left_verts, left_indices) = Self::line(
335 x - half_thickness,
336 y + height - radius,
337 x - half_thickness,
338 y + radius,
339 thickness,
340 color,
341 );
342 vertices.extend(left_verts);
343 indices.extend(left_indices.iter().map(|&i| i + offset));
344 
345 // Add rounded corners (outline arcs)
346 let corners = [
347 (x + radius, y + radius), // Top-left
348 (x + width - radius, y + radius), // Top-right
349 (x + width - radius, y + height - radius), // Bottom-right
350 (x + radius, y + height - radius), // Bottom-left
351 ];
352 
353 for (i, &(cx, cy)) in corners.iter().enumerate() {
354 let start_angle = (i as f32) * std::f32::consts::PI / 2.0 + std::f32::consts::PI;
355 
356 // Create arc outline using multiple line segments
357 for j in 0..corner_segments {
358 let angle1 = start_angle
359 + (j as f32) * (std::f32::consts::PI / 2.0) / (corner_segments as f32);
360 let angle2 = start_angle
361 + ((j + 1) as f32) * (std::f32::consts::PI / 2.0) / (corner_segments as f32);
362 
363 let x1 = cx + radius * angle1.cos();
364 let y1 = cy + radius * angle1.sin();
365 let x2 = cx + radius * angle2.cos();
366 let y2 = cy + radius * angle2.sin();
367 
368 let offset = vertices.len() as u16;
369 let (arc_verts, arc_indices) = Self::line(x1, y1, x2, y2, thickness, color);
370 vertices.extend(arc_verts);
371 indices.extend(arc_indices.iter().map(|&i| i + offset));
372 }
373 }
374 
375 (vertices, indices)
376 }
377 
378 /// Create vertices for a circle sector
379 fn circle_sector(
380 center_x: f32,
381 center_y: f32,
382 radius: f32,
383 color: [f32; 4],
384 segments: u32,
385 start_angle: f32,
386 angle_span: f32,
387 ) -> (Vec<Vertex>, Vec<u16>) {
388 let mut vertices = Vec::with_capacity((segments + 1) as usize);
389 let mut indices = Vec::with_capacity((segments * 3) as usize);
390 
391 // Center vertex
392 vertices.push(Vertex::solid([center_x, center_y], color));
393 
394 // Create vertices around the sector
395 for i in 0..=segments {
396 let angle = start_angle + (i as f32) * angle_span / (segments as f32);
397 let x = center_x + radius * angle.cos();
398 let y = center_y + radius * angle.sin();
399 vertices.push(Vertex::solid([x, y], color));
400 }
401 
402 // Create triangles
403 for i in 0..segments {
404 indices.extend_from_slice(&[0, (i + 1) as u16, (i + 2) as u16]);
405 }
406 
407 (vertices, indices)
408 }
409 
410 /// Create vertices for a line with thickness
411 pub fn line(
412 start_x: f32,
413 start_y: f32,
414 end_x: f32,
415 end_y: f32,
416 thickness: f32,
417 color: [f32; 4],
418 ) -> (Vec<Vertex>, Vec<u16>) {
419 let dx = end_x - start_x;
420 let dy = end_y - start_y;
421 let length = (dx * dx + dy * dy).sqrt();
422 
423 if length == 0.0 {
424 return (Vec::new(), Vec::new());
425 }
426 
427 // Normalize and get perpendicular vector
428 let nx = -dy / length;
429 let ny = dx / length;
430 
431 let half_thickness = thickness * 0.5;
432 
433 let vertices = vec![
434 Vertex::solid(
435 [start_x + nx * half_thickness, start_y + ny * half_thickness],
436 color,
437 ),
438 Vertex::solid(
439 [start_x - nx * half_thickness, start_y - ny * half_thickness],
440 color,
441 ),
442 Vertex::solid(
443 [end_x - nx * half_thickness, end_y - ny * half_thickness],
444 color,
445 ),
446 Vertex::solid(
447 [end_x + nx * half_thickness, end_y + ny * half_thickness],
448 color,
449 ),
450 ];
451 
452 let indices = vec![0, 1, 2, 2, 3, 0];
453 
454 (vertices, indices)
455 }
456}
457 
458#[cfg(test)]
459mod tests {
460 use super::*;
461 
462 #[test]
463 fn test_vertex_creation() {
464 let vertex = Vertex::new([1.0, 2.0], [1.0, 0.0, 0.0, 1.0], [0.5, 0.5]);
465 assert_eq!(vertex.position, [1.0, 2.0]);
466 assert_eq!(vertex.color, [1.0, 0.0, 0.0, 1.0]);
467 assert_eq!(vertex.uv, [0.5, 0.5]);
468 }
469 
470 #[test]
471 fn test_vertex_solid() {
472 let vertex = Vertex::solid([0.0, 0.0], [1.0, 1.0, 1.0, 1.0]);
473 assert_eq!(vertex.flags, 0);
474 assert_eq!(vertex.uv, [0.0, 0.0]);
475 }
476 
477 #[test]
478 fn test_vertex_textured() {
479 let vertex = Vertex::textured([0.0, 0.0], [1.0, 1.0], [1.0, 1.0, 1.0, 1.0]);
480 assert_eq!(vertex.flags, 1);
481 assert_eq!(vertex.uv, [1.0, 1.0]);
482 }
483 
484 #[test]
485 fn test_rectangle_builder() {
486 let (vertices, indices) =
487 VertexBuilder::rectangle(0.0, 0.0, 100.0, 50.0, [1.0, 0.0, 0.0, 1.0]);
488 assert_eq!(vertices.len(), 4);
489 assert_eq!(indices.len(), 6);
490 assert_eq!(vertices[0].position, [0.0, 0.0]);
491 assert_eq!(vertices[2].position, [100.0, 50.0]);
492 }
493 
494 #[test]
495 fn test_circle_builder() {
496 let (vertices, indices) = VertexBuilder::circle(50.0, 50.0, 25.0, [0.0, 1.0, 0.0, 1.0], 8);
497 assert_eq!(vertices.len(), 9); // Center + 8 segments
498 assert_eq!(indices.len(), 24); // 8 triangles * 3 indices
499 }
500}
501