add c and jai
This commit is contained in:
10
jai/modules/Console_Render/console.jai
Normal file
10
jai/modules/Console_Render/console.jai
Normal file
@@ -0,0 +1,10 @@
|
||||
swap_buffer :: (screen: *Screen) {
|
||||
using screen;
|
||||
write_string(cast(string)buffer);
|
||||
}
|
||||
|
||||
clear_screen :: (screen: *Screen) {
|
||||
for 0..screen.height * screen.width {
|
||||
print_color(" ", color = .BLACK);
|
||||
}
|
||||
}
|
||||
107
jai/modules/Console_Render/draw.jai
Normal file
107
jai/modules/Console_Render/draw.jai
Normal file
@@ -0,0 +1,107 @@
|
||||
// "Squashes" two dimensional coordinates onto the one dimensional screen buffer
|
||||
draw :: (using screen: *Screen, p: Vec2s64, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||
if p.x >= 0 && p.x < width && p.y >= 0 && p.y < height {
|
||||
buffer[p.y * width + p.x] = char;
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of the Bresenham line algorithm
|
||||
// https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
|
||||
draw_line :: (screen: *Screen, p1: Vec2s64, p2: Vec2s64, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||
p1_c := p1;
|
||||
p2_c := p2;
|
||||
|
||||
dx := abs(p2_c.x - p1_c.x);
|
||||
dy := -abs(p2_c.y - p1_c.y);
|
||||
sx := ifx p1_c.x < p2_c.x then 1 else -1;
|
||||
sy := ifx p1_c.y < p2_c.y then 1 else -1;
|
||||
|
||||
err := dx + dy;
|
||||
e2 : s64;
|
||||
|
||||
while true {
|
||||
draw(screen, Vec2s64.{p1_c.x, p1_c.y}, char);
|
||||
|
||||
if p1_c.x == p2_c.x && p1_c.y == p2_c.y then break;
|
||||
|
||||
e2 = 2 * err;
|
||||
|
||||
if e2 >= dy {
|
||||
err += dy;
|
||||
p1_c.x += sx;
|
||||
}
|
||||
|
||||
if e2 <= dx {
|
||||
err += dx;
|
||||
p1_c.y += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw_triangle :: (screen: *Screen, p1: Vec2s64, p2: Vec2s64, p3: Vec2s64, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||
draw_line(screen, p1, p2, char);
|
||||
draw_line(screen, p2, p3, char);
|
||||
draw_line(screen, p3, p1, char);
|
||||
}
|
||||
|
||||
draw_triangle :: (screen: *Screen, t: Triangle, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||
draw_triangle(screen, t.p1, t.p2, t.p3, char);
|
||||
}
|
||||
|
||||
draw_quad :: (screen: *Screen, p1: Vec2s64, p2: Vec2s64, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||
// (x1, y1) => top left
|
||||
// (x2, y2) => bottom right
|
||||
|
||||
draw_line(screen, .{p1.x, p1.y}, .{p1.x, p2.y}, char);
|
||||
draw_line(screen, .{p1.x, p2.y}, .{p2.x, p2.y}, char);
|
||||
draw_line(screen, .{p2.x, p2.y}, .{p2.x, p1.y}, char);
|
||||
draw_line(screen, .{p2.x, p1.y}, .{p1.x, p1.y}, char);
|
||||
}
|
||||
|
||||
draw_quad :: (screen: *Screen, p1: Vec2s64, p2: Vec2s64, p3: Vec2s64, p4: Vec2s64, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||
// (x1, y1) => top left
|
||||
// (x2, y2) => bottom left
|
||||
// (x3, y3) => bottom right
|
||||
// (x4, y4) => top right
|
||||
|
||||
draw_line(screen, .{p1.x, p1.y}, .{p2.x, p2.y}, char);
|
||||
draw_line(screen, .{p2.x, p2.y}, .{p3.x, p3.y}, char);
|
||||
draw_line(screen, .{p3.x, p3.y}, .{p4.x, p4.y}, char);
|
||||
draw_line(screen, .{p4.x, p4.y}, .{p1.x, p1.y}, char);
|
||||
}
|
||||
|
||||
draw_quad :: (screen: *Screen, q: Quad, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||
draw_quad(screen, q.p1, q.p2, q.p3, q.p4, char);
|
||||
}
|
||||
|
||||
draw_text :: (using screen: *Screen, p: Vec2s64, s: string) {
|
||||
if p.x >= 0 && p.x < width && p.y >= 0 && p.y < height {
|
||||
for cast([]u8)s {
|
||||
buffer[(p.y * width + p.x) + it_index] = it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When attempting to draw outside of the visible screen, "clip" the pixels
|
||||
// by setting their position to the maximum width / height available
|
||||
clip :: (using screen: *Screen, p: *Vec2s64) {
|
||||
if p.x < 0 then p.x = 0;
|
||||
if p.x >= width then p.x = width;
|
||||
if p.y < 0 then p.y = 0;
|
||||
if p.y >= height then p.y = height;
|
||||
}
|
||||
|
||||
fill :: (screen: *Screen, p1: Vec2s64, p2: Vec2s64, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||
clip(screen, *p1);
|
||||
clip(screen, *p2);
|
||||
|
||||
for x: p1.x..p2.x {
|
||||
for y: p1.y..p2.y {
|
||||
draw(screen, .{x, y}, char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fill_entire_screen :: (screen: *Screen, char: u8 = DEFAULT_PIXEL_CHAR) {
|
||||
fill(screen, .{0,0}, .{screen.width, screen.height}, char);
|
||||
}
|
||||
8
jai/modules/Console_Render/file_operations.jai
Normal file
8
jai/modules/Console_Render/file_operations.jai
Normal file
@@ -0,0 +1,8 @@
|
||||
// Render current screen state to a file
|
||||
render_to_file :: (screen: *Screen, preserve_color: bool, filepath: string) {
|
||||
assert(false, "Not implemented");
|
||||
}
|
||||
|
||||
#scope_file
|
||||
|
||||
#import "File";
|
||||
42
jai/modules/Console_Render/macos/console.jai
Normal file
42
jai/modules/Console_Render/macos/console.jai
Normal file
@@ -0,0 +1,42 @@
|
||||
get_console_size :: () -> s64, s64 {
|
||||
//@Hack - This is terrible, but this is typically called only once, so maybe it doesn't matter...
|
||||
width := string_to_int(get_stdout_from_cmd("tput cols"));
|
||||
height := string_to_int(get_stdout_from_cmd("tput lines"));
|
||||
|
||||
return cast(s64)width, cast(s64)height;
|
||||
}
|
||||
|
||||
#scope_file
|
||||
|
||||
get_stdout_from_cmd :: (cmd: string) -> string {
|
||||
buffer: [256]u8;
|
||||
stream : *FILE;
|
||||
data : string;
|
||||
|
||||
// m_cmd := tprint("% %", cmd, " 2>&1");
|
||||
|
||||
stream = popen(to_c_string(cmd), to_c_string("r"));
|
||||
|
||||
if stream {
|
||||
while !feof(stream) {
|
||||
if fgets(buffer.data, buffer.count, stream) {
|
||||
data = tprint("%1%2", data, cast(string)(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
pclose(stream);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
to_c_string :: (s: string) -> *u8 {
|
||||
result := cast(*u8) alloc(s.count + 1);
|
||||
memcpy(result, s.data, s.count);
|
||||
result[s.count] = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#import "POSIX";
|
||||
19
jai/modules/Console_Render/math_extras.jai
Normal file
19
jai/modules/Console_Render/math_extras.jai
Normal file
@@ -0,0 +1,19 @@
|
||||
get_triangle_centroid :: (t: Triangle) -> Vec2s64 {
|
||||
return get_triangle_centroid(t.p1, t.p2, t.p3);
|
||||
}
|
||||
|
||||
get_triangle_centroid :: (p1: Vec2s64, p2: Vec2s64, p3: Vec2s64) -> Vec2s64 {
|
||||
x := (p1.x + p2.x + p3.x) / 3;
|
||||
y := (p1.y + p2.y + p3.y) / 3;
|
||||
|
||||
return .{x, y};
|
||||
}
|
||||
|
||||
make_quad_from_rect :: (p1: Vec2s64, p2: Vec2s64) -> Quad {
|
||||
return .{
|
||||
.{ p1.x, p1.y },
|
||||
.{ p2.x, p1.y },
|
||||
.{ p2.x, p2.y },
|
||||
.{ p1.x, p2.y },
|
||||
};
|
||||
}
|
||||
122
jai/modules/Console_Render/module.jai
Normal file
122
jai/modules/Console_Render/module.jai
Normal file
@@ -0,0 +1,122 @@
|
||||
/******************************************************
|
||||
* Copyright 2024 Max Amundsen - All Rights Reserved
|
||||
******************************************************
|
||||
|
||||
Console Screen Rendering:
|
||||
|
||||
Terminal emulators, or consoles, are programs that display text from a sequence of characters.
|
||||
These characters are usually fixed-width, meaning each character takes up the same amount of space on screen.
|
||||
The dimensions of your console window determines how the console will display the text in rows and columns.
|
||||
|
||||
This module is designed to draw simple 2d, or 3d shapes, inside a standard terminal emulator program.
|
||||
|
||||
--------------------------------------------------------------
|
||||
Suppose the following sequence of characters is sent to the console:
|
||||
|
||||
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U']
|
||||
|
||||
For a console displaying 7 characters horizontally, and 3 characters vertically, the result would be the following:
|
||||
|
||||
0123456
|
||||
_________
|
||||
0 |ABCDEFG|
|
||||
1 |HIJKLMN|
|
||||
2 |OPQRSTU|
|
||||
---------
|
||||
|
||||
As displayed in the above figure, the 0th element, 'A', in the sequence represents (0,0) in 2D space, while the 9th element, 'J' represents (2,1).
|
||||
|
||||
=> Calculating the two dimensional area of the console output yields the number of elements in the initial 1D sequence:
|
||||
|
||||
(7 * 3) = 21
|
||||
|
||||
This may appear obvious, however this is an important connection to make, when considering how the console represents its output.
|
||||
|
||||
The terminal automatically transforms our 1D sequence, to a 2D sequence displayed on your monitor, based on the size of the window.
|
||||
|
||||
Since the console is responsible for transforming our 1D array into a 2D output, we must provide the console with a 1D array.
|
||||
In order to draw 2D and 3D elements on the screen, we must construct procedures in the code to represent the transformations from
|
||||
higher dimensions, down to the first dimension.
|
||||
|
||||
This simple 2d -> 1d transformation is implemented as the `draw` procedure found in the `draw.jai` file in this module.
|
||||
|
||||
*/
|
||||
|
||||
BLANK_PIXEL : u8 : 0x20;
|
||||
DEFAULT_PIXEL_CHAR : u8 : #char "#";
|
||||
|
||||
Vec2s64 :: struct {
|
||||
x: s64;
|
||||
y: s64;
|
||||
}
|
||||
|
||||
Triangle :: struct {
|
||||
p1: Vec2s64;
|
||||
p3: Vec2s64;
|
||||
p2: Vec2s64;
|
||||
}
|
||||
|
||||
Quad :: struct {
|
||||
p1: Vec2s64;
|
||||
p2: Vec2s64;
|
||||
p3: Vec2s64;
|
||||
p4: Vec2s64;
|
||||
}
|
||||
|
||||
Screen :: struct {
|
||||
width : s64;
|
||||
height: s64;
|
||||
buffer: [..] u8;
|
||||
}
|
||||
|
||||
init_screen :: (screen: *Screen, width: s64, height: s64) {
|
||||
buffer : [..] u8;
|
||||
|
||||
array_resize(*buffer, width * height);
|
||||
|
||||
screen.width = width;
|
||||
screen.height = height;
|
||||
screen.buffer = buffer;
|
||||
}
|
||||
|
||||
resize_screen :: (screen: *Screen, width: s64, height: s64) {
|
||||
screen.width = width;
|
||||
screen.height = height;
|
||||
|
||||
array_resize(*screen.buffer, width * height);
|
||||
}
|
||||
|
||||
maybe_resize_screen :: (screen: *Screen) {
|
||||
w, h := get_console_size();
|
||||
|
||||
should_update := false;
|
||||
|
||||
if w != screen.width {
|
||||
should_update = true;
|
||||
}
|
||||
|
||||
if h != screen.height {
|
||||
should_update = true;
|
||||
}
|
||||
|
||||
if should_update {
|
||||
resize_screen(screen, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
#load "draw.jai";
|
||||
#load "transform.jai";
|
||||
#load "file_operations.jai";
|
||||
#load "console.jai";
|
||||
#load "math_extras.jai";
|
||||
|
||||
#if OS == .MACOS {
|
||||
#load "macos/console.jai";
|
||||
} else {
|
||||
#assert false "Platform unsupported. Sorry.";
|
||||
}
|
||||
|
||||
#scope_module
|
||||
|
||||
#import "Basic";
|
||||
#import "Math";
|
||||
67
jai/modules/Console_Render/transform.jai
Normal file
67
jai/modules/Console_Render/transform.jai
Normal file
@@ -0,0 +1,67 @@
|
||||
// for n points
|
||||
translate :: (amount: Vec2s64, pn: ..*Vec2s64) {
|
||||
for pn {
|
||||
it.x += amount.x;
|
||||
it.y += amount.y;
|
||||
}
|
||||
}
|
||||
|
||||
// for a triangle
|
||||
translate :: (amount: Vec2s64, t: *Triangle) {
|
||||
translate(amount, *t.p1, *t.p2, *t.p3);
|
||||
}
|
||||
|
||||
|
||||
// for a quad
|
||||
translate :: (amount: Vec2s64, q: *Quad) {
|
||||
translate(amount, *q.p1, *q.p2, *q.p3, *q.p4);
|
||||
}
|
||||
|
||||
// single point rotation
|
||||
rotate :: (angle: s64, center: Vec2s64, p: *Vec2s64) {
|
||||
rad := cast(float64)angle * (PI / 180.0);
|
||||
|
||||
// translate to center
|
||||
tx := p.x - center.x;
|
||||
ty := p.y - center.y;
|
||||
|
||||
// apply rotation
|
||||
rx := cast(s64)(tx * cos(rad) - ty * sin(rad));
|
||||
ry := cast(s64)(tx * sin(rad) + ty * cos(rad));
|
||||
|
||||
// translate back
|
||||
p.x = rx + center.x;
|
||||
p.y = ry + center.y;
|
||||
}
|
||||
|
||||
// rotate n points
|
||||
rotate :: (angle: s64, center: Vec2s64, pn: ..*Vec2s64) {
|
||||
for pn {
|
||||
rotate(angle, center, it);
|
||||
}
|
||||
}
|
||||
|
||||
// rotate 3 points via triangle struct
|
||||
rotate :: (angle: s64, center: Vec2s64, t: *Triangle) {
|
||||
rotate(angle, center, *t.p1, *t.p2, *t.p3);
|
||||
}
|
||||
|
||||
// rotate 4 points via quad struct
|
||||
rotate :: (angle: s64, center: Vec2s64, q: *Quad) {
|
||||
rotate(angle, center, *q.p1, *q.p2, *q.p3, *q.p4);
|
||||
}
|
||||
|
||||
scale :: (factor: Vector2, pn: ..*Vec2s64) {
|
||||
for pn {
|
||||
it.x = cast(s64)(it.x * factor.x);
|
||||
it.y = cast(s64)(it.y * factor.y);
|
||||
}
|
||||
}
|
||||
|
||||
scale :: (factor: Vector2, t: *Triangle) {
|
||||
scale(factor, *t.p1, *t.p2, *t.p3);
|
||||
}
|
||||
|
||||
scale :: (factor: Vector2, q: *Quad) {
|
||||
scale(factor, *q.p1, *q.p2, *q.p3, *q.p4);
|
||||
}
|
||||
Reference in New Issue
Block a user