1#![cfg_attr(not(feature = "std"), no_std)]
41
42extern crate alloc;
43
44use alloc::{sync::Arc, vec::Vec};
45use core::cmp::Ordering;
46
47use nalgebra as na;
48
49pub mod dithering;
50use dithering::Dithering;
51pub mod color;
52use color::{PixelText, QuantizePixel};
53pub mod extra;
54pub mod material;
55use material::*;
56
57pub type Transform = na::Transform3<f32>;
58pub type Vector2 = na::Vector2<f32>;
59pub type Vector3 = na::Vector3<f32>;
60pub type Vector4 = na::Vector4<f32>;
61pub type Matrix4 = na::Matrix4<f32>;
62pub type Matrix<const R: usize, const C: usize> =
63    na::Matrix<f32, na::Const<R>, na::Const<C>, na::ArrayStorage<f32, R, C>>;
64
65fn clip_to_ndc(clip_space: Vector4) -> Vector3 {
66    Vector3::new(
67        clip_space.x / clip_space.w,
68        clip_space.y / clip_space.w,
69        clip_space.z / clip_space.w,
70    )
71}
72
73fn ndc_to_screen(mut p: Vector3, w: usize, h: usize) -> Vector3 {
74    let half_w = (w - 1) as f32 / 2.0;
75    let half_h = (h - 1) as f32 / 2.0;
76    p.x = (p.x + 1.0) * half_w;
77    p.y = (1.0 - p.y) * half_h;
78    p.z = (p.z + 1.0) / 2.0;
79    p
80}
81
82fn edge_function(a: Vector2, b: Vector2, c: Vector2) -> f32 {
83    (c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)
84}
85
86#[allow(unused)]
87fn float_sort(a: f32, b: f32) -> Ordering {
88    if a > b {
89        Ordering::Greater
90    } else if a < b {
91        Ordering::Less
92    } else {
93        Ordering::Equal
94    }
95}
96
97fn bounding_box<const R: usize, const C: usize>(
103    v: &[Matrix<R, C>],
104) -> (Matrix<R, C>, Matrix<R, C>) {
105    let mut min = v[0];
106    let mut max = v[0];
107
108    for v in v {
109        for (v, (min, max)) in v.iter().zip(min.iter_mut().zip(max.iter_mut())) {
110            *min = v.min(*min);
111            *max = v.max(*max);
112        }
113    }
114
115    (min, max)
116}
117
118pub fn term_char_aspect() -> (usize, usize) {
123    (1, 2)
124}
125
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
131pub struct Background {
132    pub color: Vector3,
133}
134
135impl Default for Background {
136    fn default() -> Self {
137        Self {
138            color: Vector3::new(0.05, 0.23, 0.4),
139        }
140    }
141}
142
143#[derive(Default, Debug)]
171pub struct Renderer {
172    vertex_state: VertexState,
173    fragment_state: RasterState,
174}
175
176#[derive(Debug, Clone, Copy)]
177struct PrimitiveId {
178    mat_idx: usize,
179    obj_idx: usize,
180    pri_idx: usize,
181}
182
183#[derive(Default, Debug)]
184struct VertexState {
185    primitives: Vec<(Primitive, PrimitiveId)>,
190    obj_clip_center: Vec<Vector4>,
195}
196
197fn plane_intersect(inside: Vector4, outside: Vector4, dim: usize, clip: f32) -> Vector4 {
198    let t = (clip - inside[dim]) / (outside[dim] - inside[dim]);
199    let ret = inside + t * (outside - inside);
200    ret
201}
202
203impl VertexState {
204    pub fn reset(&mut self) {
205        self.primitives.clear();
206        self.obj_clip_center.clear();
207    }
208
209    pub fn clip_and_push_primitive(&mut self, primitive: Primitive, id: PrimitiveId) {
210        match primitive {
211            Primitive::Triangle(t) => self.clip_and_push_triangle(t, id),
212            Primitive::Line(l) => self.clip_and_push_line(l, id),
213        }
214    }
215
216    pub fn clip_and_push_line(&mut self, mut line: Line, id: PrimitiveId) {
217        for dim in 2..3 {
220            let Line { start, end } = line;
221            let clip = [start, end].map(|v| v[dim] / libm::fabsf(v.w) < 0.0);
222            let clip_cnt = clip.iter().filter(|v| **v).count();
223
224            line = if clip_cnt == 0 {
225                Line { start, end }
227            } else if clip_cnt == 1 {
228                if clip[0] {
230                    Line {
231                        start: plane_intersect(end, start, dim, 0.0),
232                        end,
233                    }
234                } else {
235                    Line {
236                        start,
237                        end: plane_intersect(start, end, dim, 0.0),
238                    }
239                }
240            } else {
241                return;
243            };
244        }
245
246        self.primitives.push((Primitive::Line(line), id));
247    }
248
249    pub fn clip_and_push_triangle(&mut self, Triangle { a, b, c }: Triangle, id: PrimitiveId) {
254        let dim = 2;
255
256        let clip = [a, b, c].map(|v| v[dim] / libm::fabsf(v.w) < 0.0);
257        let clip_cnt = clip.iter().filter(|v| **v).count();
258
259        if clip_cnt == 2 {
260            let unclipped_idx = clip.iter().enumerate().find(|(_, v)| !**v).unwrap().0;
262            let mut cnt = 0;
263            let verts = [a, b, c];
264            let [a, b, c] = verts.map(|v| {
265                let i = cnt;
266                cnt += 1;
267                if i == unclipped_idx {
268                    v
269                } else {
270                    plane_intersect(verts[unclipped_idx], v, dim, 0.0)
271                }
272            });
273            self.primitives
274                .push((Primitive::Triangle(Triangle { a, b, c }), id));
275        } else if clip_cnt == 1 {
276            let clipped_idx = clip.iter().enumerate().find(|(_, v)| **v).unwrap().0;
278            let verts = [a, b, c];
279
280            let (i1, i2) = if clipped_idx == 0 {
281                (1, 2)
282            } else {
283                (0, 1 + (clipped_idx - 1) % 2)
284            };
285
286            let c1 = plane_intersect(verts[i1], verts[clipped_idx], dim, 0.0);
287            let c2 = plane_intersect(verts[i2], verts[clipped_idx], dim, 0.0);
288
289            {
290                let mut verts1 = verts;
291                verts1[clipped_idx] = c1;
292                let [a, b, c] = verts1;
293                self.primitives
294                    .push((Primitive::Triangle(Triangle { a, b, c }), id));
295            }
296
297            {
298                let mut verts2 = verts;
299                verts2[i1] = c1;
300                verts2[clipped_idx] = c2;
301                let [a, b, c] = verts2;
302                self.primitives
303                    .push((Primitive::Triangle(Triangle { a, b, c }), id));
304            }
305        } else if clip_cnt == 0 {
306            self.primitives
307                .push((Primitive::Triangle(Triangle { a, b, c }), id));
308        }
309    }
310}
311
312#[derive(Default, Debug)]
313struct RasterOutput {
314    obj_bb: Vec<Option<(usize, usize, usize, usize)>>,
315}
316
317#[derive(Default, Debug)]
318struct RasterState {
319    w: usize,
320    h: usize,
321    depth: Vec<f32>,
323    objs: Vec<usize>,
325    output: RasterOutput,
327}
328
329impl RasterState {
330    pub fn ndc_to_screen(&self, p: Vector3) -> Vector3 {
331        ndc_to_screen(p, self.w, self.h)
332    }
333
334    fn clear_screen<T: QuantizePixel>(
335        &mut self,
336        bg: &Background,
337        conv_params: &T::Params,
338        dithering: &mut impl Dithering,
339        buf: &mut Vec<T>,
340        w: usize,
341        h: usize,
342    ) {
343        self.w = w;
344        self.h = h;
345
346        let len = w * h;
347        buf.clear();
348        dithering.new_frame(w, h);
349
350        for y in 0..h {
351            for x in 0..w {
352                buf.push(T::quantize_color(conv_params, bg.color, dithering, x, y));
353            }
354        }
355
356        self.depth.clear();
357        self.depth.resize(len, 1f32);
358        self.objs.resize(len, !0usize);
359    }
360
361    fn rasterize<T: QuantizePixel, M: Material + ?Sized>(
362        &mut self,
363        vs: &VertexState,
364        mats: &mut [impl AsMut<M>],
365        max_obj: usize,
366        conv_params: &T::Params,
367        dithering: &mut impl Dithering,
368        buf: &mut Vec<T>,
369    ) {
370        let len = self.w * self.h;
371        assert_eq!(buf.len(), len);
372
373        self.output.obj_bb.clear();
374        self.output.obj_bb.resize(max_obj, None);
375
376        for (
377            p,
378            PrimitiveId {
379                mat_idx,
380                obj_idx,
381                pri_idx,
382            },
383        ) in vs.primitives.iter()
384        {
385            let mat = mats[*mat_idx].as_mut();
386
387            let mut shade_pixel = |x, y, depth| {
388                assert!(x < self.w);
389                assert!(y < self.h);
390                let bidx = y * self.w + x;
391
392                if let Some(bb) = self.output.obj_bb.get_mut(*obj_idx) {
395                    if let Some((min_x, min_y, max_x, max_y)) = bb.as_mut() {
396                        *min_x = core::cmp::min(*min_x, x);
397                        *min_y = core::cmp::min(*min_y, y);
398                        *max_x = core::cmp::max(*max_x, x);
399                        *max_y = core::cmp::max(*max_y, y);
400                    } else {
401                        *bb = Some((x, y, x, y));
402                    }
403                }
404
405                if depth >= 0.0 && self.depth[bidx] >= depth {
406                    if let Some(color) = mat.fragment_shade(
407                        *pri_idx,
408                        Vector2::new((x as f32) / self.w as f32, (y as f32) / self.h as f32),
409                        depth,
410                    ) {
411                        self.depth[bidx] = depth;
412                        self.objs[bidx] = *obj_idx;
413                        if color.w >= 0.5 {
416                            buf[bidx] =
417                                T::quantize_color(conv_params, color.xyz(), dithering, x, y);
418                        }
419                    }
420                }
421            };
422
423            match p {
424                Primitive::Triangle(t) => {
425                    let t = [t.a, t.b, t.c]
426                        .map(clip_to_ndc)
427                        .map(|v| ndc_to_screen(v, self.w, self.h));
428
429                    let (bbmin, bbmax) = bounding_box(&t);
430
431                    let [a, b, c] = t;
432
433                    let area = edge_function(a.xy(), b.xy(), c.xy());
434
435                    if area <= 0.0 {
436                        continue;
437                    }
438
439                    for y in
440                        (bbmin.y.max(0.) as usize)..(libm::ceilf(bbmax.y.min(self.h as _)) as usize)
441                    {
442                        for x in (bbmin.x.max(0.) as usize)
443                            ..(libm::ceilf(bbmax.x.min(self.w as _)) as usize)
444                        {
445                            let p = Vector2::new(x as f32, y as f32);
446
447                            let wa = edge_function(b.xy(), c.xy(), p) / area;
448                            let wb = edge_function(c.xy(), a.xy(), p) / area;
449                            let wc = edge_function(a.xy(), b.xy(), p) / area;
450
451                            if wa >= 0.0 && wb >= 0.0 && wc >= 0.0 {
452                                let depth = wa * a.z + wb * b.z + wc * c.z;
453                                shade_pixel(x, y, depth);
454                            }
455                        }
456                    }
457                }
458                Primitive::Line(l) => {
459                    let l = [l.start, l.end]
460                        .map(clip_to_ndc)
461                        .map(|v| ndc_to_screen(v, self.w, self.h));
462
463                    let [a, b] = l;
464
465                    fn plot_line_low(
466                        w: usize,
467                        h: usize,
468                        mut x0: usize,
469                        y0: usize,
470                        x1: usize,
471                        y1: usize,
472                        mut plot: impl FnMut(usize, usize),
473                    ) {
474                        let dx = x1 as isize - x0 as isize;
475                        let mut dy = y1 as isize - y0 as isize;
476                        let mut yi = 1;
477
478                        if dy < 0 {
479                            yi = -1;
480                            dy = -dy;
481                        }
482
483                        let mut d = (2 * dy) - dx;
484                        let mut y = y0;
485
486                        while x0 <= x1 && x0 < w && y < h {
487                            plot(x0, y);
488                            if d > 0 {
489                                y = y.saturating_add_signed(yi);
490                                d += 2 * (dy - dx);
491                            } else {
492                                d += 2 * dy;
493                            }
494                            x0 += 1;
495                        }
496                    }
497
498                    fn plot_line_high(
499                        w: usize,
500                        h: usize,
501                        x0: usize,
502                        mut y0: usize,
503                        x1: usize,
504                        y1: usize,
505                        mut plot: impl FnMut(usize, usize),
506                    ) {
507                        let mut dx = x1 as isize - x0 as isize;
508                        let dy = y1 as isize - y0 as isize;
509                        let mut xi = 1;
510
511                        if dx < 0 {
512                            xi = -1;
513                            dx = -dx;
514                        }
515
516                        let mut d = (2 * dx) - dy;
517                        let mut x = x0;
518
519                        while y0 <= y1 && y0 < h && x < w {
520                            plot(x, y0);
521                            if d > 0 {
522                                x = x.saturating_add_signed(xi);
523                                d += 2 * (dx - dy);
524                            } else {
525                                d += 2 * dx;
526                            }
527                            y0 += 1;
528                        }
529                    }
530
531                    fn plot_line(
532                        w: usize,
533                        h: usize,
534                        x0: usize,
535                        y0: usize,
536                        x1: usize,
537                        y1: usize,
538                        plot: impl FnMut(usize, usize),
539                    ) {
540                        if y1.abs_diff(y0) < x1.abs_diff(x0) {
541                            if x0 > x1 {
542                                plot_line_low(w, h, x1, y1, x0, y0, plot);
543                            } else {
544                                plot_line_low(w, h, x0, y0, x1, y1, plot);
545                            }
546                        } else {
547                            if y0 > y1 {
548                                plot_line_high(w, h, x1, y1, x0, y0, plot);
549                            } else {
550                                plot_line_high(w, h, x0, y0, x1, y1, plot);
551                            }
552                        }
553                    }
554
555                    plot_line(
556                        self.w,
557                        self.h,
558                        libm::roundf(a.x) as usize,
559                        libm::roundf(a.y) as usize,
560                        libm::roundf(b.x) as usize,
561                        libm::roundf(b.y) as usize,
562                        |x, y| {
563                            let da = (a.xy() - Vector2::new(x as f32, y as f32)).magnitude();
565                            let db = (b.xy() - Vector2::new(x as f32, y as f32)).magnitude();
566                            let total = da + db;
567                            let lerp = da / total;
568                            let depth = a.z + (b.z - a.z) * lerp;
569                            shade_pixel(x, y, depth);
570                        },
571                    );
572                }
573            }
574        }
575    }
576}
577
578#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
585pub struct Camera {
586    pub transform: Transform,
587    pub proj: na::Projective3<f32>,
588}
589
590impl Default for Camera {
591    fn default() -> Self {
592        Self::new(na::Perspective3::new(1f32, 90f32.to_radians(), 0.1, 500.0).to_projective())
593    }
594}
595
596impl Camera {
597    pub fn new(proj: na::Projective3<f32>) -> Self {
598        Self {
599            transform: Transform::default(),
600            proj,
601        }
602    }
603}
604
605#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
607pub struct Object {
608    pub transform: Transform,
612    pub material: usize,
616    pub ty: ObjType,
618    pub text: Option<Arc<str>>,
624}
625
626#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
628pub enum ObjType {
629    Cube { size: Vector3 },
630    Primitive(Primitive),
631}
632
633impl ObjType {
634    fn gen(
635        &self,
636        proj: Matrix4,
637        model: Matrix4,
638        state: &mut VertexState,
639        material: &mut (impl Material + ?Sized),
640        obj_idx: usize,
641        mat_idx: usize,
642    ) {
643        match self {
645            Self::Cube { size, .. } => {
646                const CUBE_VERTICES: [Vector4; 8] = [
647                    Vector4::new(-0.5, -0.5, -0.5, 1.0),
648                    Vector4::new(0.5, -0.5, -0.5, 1.0),
649                    Vector4::new(0.5, 0.5, -0.5, 1.0),
650                    Vector4::new(-0.5, 0.5, -0.5, 1.0),
651                    Vector4::new(-0.5, -0.5, 0.5, 1.0),
652                    Vector4::new(0.5, -0.5, 0.5, 1.0),
653                    Vector4::new(0.5, 0.5, 0.5, 1.0),
654                    Vector4::new(-0.5, 0.5, 0.5, 1.0),
655                ];
656
657                const CUBE_INDICES: [[usize; 3]; 12] = [
658                    [0, 2, 1],
659                    [0, 3, 2],
660                    [1, 2, 6],
661                    [6, 5, 1],
662                    [4, 5, 6],
663                    [6, 7, 4],
664                    [2, 3, 6],
665                    [6, 3, 7],
666                    [0, 7, 3],
667                    [0, 4, 7],
668                    [0, 1, 5],
669                    [0, 5, 4],
670                ];
671
672                for [a, b, c] in CUBE_INDICES.map(|v| {
673                    v.map(|v| {
674                        CUBE_VERTICES[v].component_mul(&Vector4::new(size.x, size.y, size.z, 1.0))
677                    })
678                }) {
679                    let triangle = Triangle { a, b, c };
680                    let (pri_idx, primitive) =
681                        material.primitive_shade(Primitive::Triangle(triangle), proj, model);
682                    state.clip_and_push_primitive(
683                        primitive,
684                        PrimitiveId {
685                            mat_idx,
686                            obj_idx,
687                            pri_idx,
688                        },
689                    );
690                }
691            }
692            Self::Primitive(primitive) => {
693                let (pri_idx, primitive) = material.primitive_shade(*primitive, proj, model);
694                state.clip_and_push_primitive(
695                    primitive,
696                    PrimitiveId {
697                        mat_idx,
698                        obj_idx,
699                        pri_idx,
700                    },
701                );
702            }
703        }
704    }
705}
706
707#[derive(Debug, Clone, Copy)]
709#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
710pub enum Primitive {
711    Triangle(Triangle),
712    Line(Line),
713}
714
715#[derive(Debug, Clone, Copy)]
720#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
721pub struct Triangle {
722    pub a: Vector4,
723    pub b: Vector4,
724    pub c: Vector4,
725}
726
727#[derive(Debug, Clone, Copy, Default)]
732#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
733pub struct Line {
734    pub start: Vector4,
735    pub end: Vector4,
736}
737
738impl Renderer {
739    pub fn clear_screen<T: QuantizePixel>(
747        &mut self,
748        bg: &Background,
749        conv_params: &T::Params,
750        dithering: &mut impl Dithering,
751        buf: &mut Vec<T>,
752        w: usize,
753        h: usize,
754    ) {
755        self.fragment_state
756            .clear_screen(bg, conv_params, dithering, buf, w, h)
757    }
758
759    pub fn render<T: QuantizePixel, M: Material + ?Sized>(
764        &mut self,
765        camera: &Camera,
766        conv_params: &T::Params,
767        mats: &mut [impl AsMut<M>],
768        objects: &[Object],
769        dithering: &mut impl Dithering,
770        buf: &mut Vec<T>,
771    ) {
772        let pos = camera.transform.transform_point(&Vector3::default().into());
773        let dir = camera
774            .transform
775            .transform_vector(&na::vector![0.0, 1.0, 0.0]);
776        let view = Matrix4::look_at_rh(&pos, &(pos + dir), &na::vector![0.0, 0.0, 1.0]);
777
778        let proj = camera.proj.matrix() * view;
779
780        self.vertex_state.reset();
782
783        for mat in mats.iter_mut() {
784            mat.as_mut().new_frame();
785        }
786
787        for (i, obj) in objects.iter().enumerate() {
788            self.vertex_state
789                .obj_clip_center
790                .push(proj * obj.transform.matrix() * na::vector![0.0, 0.0, 0.0, 1.0]);
791            obj.ty.gen(
792                proj,
793                *obj.transform.matrix(),
794                &mut self.vertex_state,
795                mats[obj.material].as_mut(),
796                i,
797                obj.material,
798            );
799        }
800
801        self.fragment_state.rasterize(
803            &self.vertex_state,
804            mats,
805            objects.len(),
806            conv_params,
807            dithering,
808            buf,
809        );
810    }
811
812    pub fn text_pass<T: PixelText + QuantizePixel>(
817        &mut self,
818        objects: &[Object],
819        buf: &mut Vec<T>,
820    ) {
821        for (i, obj) in objects.iter().enumerate() {
822            let Some((min_x, min_y, mut max_x, mut max_y)) =
823                self.fragment_state.output.obj_bb.get(i).and_then(|v| *v)
824            else {
825                continue;
826            };
827
828            max_x += 1;
829            max_y += 1;
830
831            if max_y - min_y < 3 {
832                continue;
833            }
834
835            let Some(text) = obj.text.as_deref() else {
836                continue;
837            };
838
839            let w = text.len();
841
842            let max_chars = max_x - min_x - 2;
843
844            let (chars, width) = if max_chars < w {
845                (text.chars().take(max_chars), max_chars)
846            } else {
847                (text.chars().take(w), w)
848            };
849
850            let mut mid_x = (max_x + min_x) / 2;
853            let mut mid_y = (max_y + min_y) / 2;
854
855            let left_chars = width / 2;
856            let right_chars = (width - left_chars).saturating_sub(1);
857
858            if let Some(coords) = self.vertex_state.obj_clip_center.get(i) {
861                let ndc = clip_to_ndc(*coords);
862                let screen = self.fragment_state.ndc_to_screen(ndc);
863                let mx = libm::roundf(screen.x) as usize;
864                let my = libm::roundf(screen.y) as usize;
865                if max_y != min_y {
866                    mid_y = core::cmp::min(core::cmp::max(my, min_y + 1), max_y - 2);
867                }
868
869                if max_x != min_x {
870                    mid_x = core::cmp::min(
871                        core::cmp::max(mx, min_x + left_chars + 1),
872                        max_x - right_chars - 2,
873                    );
874                }
875            }
876
877            let mut darken = |x: usize, y: usize| {
878                let bidx = y * self.fragment_state.w + x;
879                if self.fragment_state.objs[bidx] == i {
881                    buf[bidx].darken();
882                }
883            };
884
885            for y in ((mid_y - 1)..=(mid_y + 1)).step_by(2) {
887                for x in (mid_x - left_chars - 1)..=(mid_x + right_chars + 1) {
888                    darken(x, y);
889                }
890            }
891            darken(mid_x - left_chars - 1, mid_y);
892            darken(mid_x + right_chars + 1, mid_y);
893
894            for (o, c) in chars.enumerate() {
896                let x = mid_x - left_chars + o;
897                let bidx = mid_y * self.fragment_state.w + x;
898                if self.fragment_state.objs[bidx] == i {
899                    buf[bidx].embed(c);
900                }
901            }
902        }
903    }
904}