Files
2026-03-28 13:42:24 +05:45

1608 lines
34 KiB
Odin

package edit2draw
import e2_glyph "shared:Edit2D/glyph"
import "core:strings"
import "base:runtime"
import "core:fmt"
import "core:math"
import "core:math/linalg"
import gl "vendor:OpenGL"
CameraType :: enum
{
BOX_2D,
ORTHO,
}
Camera :: struct {
center : [2]f32,
width, height : i32,
zoom, rotation : f32,
type : CameraType,
}
RGBA8 :: [4]u8
/*
just coping the box2d here because the shaders depends on these structure but we don't want to depend on it
*/
Rot :: struct
{
c, s : f32, //cosine and sine
}
Transform :: struct
{
p: [2]f32,
q: Rot,
}
make_rgba_i32 :: proc(color : i32, alpha : f32) -> RGBA8 {
c := i32(color)
return {
u8((c >> 16) & 0xFF),
u8((c >> 8) & 0xFF),
u8(c & 0xFF),
u8(0xFF * alpha),
}
}
camera_reset_view :: proc(camera : ^Camera) {
camera.center = {0, 0}
camera.zoom = 1
}
camera_init :: proc() -> Camera {
c : Camera = {
width = 1920,
height = 1080,
zoom = 1,
}
camera_reset_view(&c)
return c
}
//Takes in a vector that is screen's pixel coordinate and converts it to world's coordinate (according to the camera)
camera_convert_screen_to_world_64 :: proc(
cam : ^Camera,
ps : [2]f64,
) -> [2]f32 {
ps_32 :[2]f32= {f32(ps.x), f32(ps.y)}
return camera_convert_screen_to_world_32(cam, ps_32)
}
camera_convert_screen_to_world_32 :: proc(
cam : ^Camera,
ps : [2]f32,
) -> [2]f32 {
ps :[2]f32= {f32(ps.x), f32(ps.y)}
w := f32(cam.width)
h := f32(cam.height)
u := ps.x / w
v := (h - ps.y) / h
ratio := w / h
extents : [2]f32 = {cam.zoom * ratio, cam.zoom}
lower := cam.center - extents
upper := cam.center + extents
pw : [2]f32 = {
(1.0 - u) * lower.x + u * upper.x,
(1.0 - v) * lower.y + v * upper.y,
}
return pw
}
camera_convert_screen_to_world :: proc {
camera_convert_screen_to_world_32,
camera_convert_screen_to_world_64
}
camera_convert_world_to_screen :: proc(
cam : ^Camera,
pw : [2]f32,
) -> [2]f32
{
cam := cam
pw := pw
w := f32(cam.width)
h := f32(cam.height)
switch cam.rotation {
case 1 ..= 90, 180 ..= 270:
pw = swizzle(pw, 1, 0)
pw.y = -pw.y
case 180:
pw.y = -pw.y
}
ratio := w / h
extents : [2]f32 = {cam.zoom * ratio, cam.zoom}
rotated_pw := pw
lower := cam.center - extents
upper := cam.center + extents
u := (rotated_pw.x - lower.x) / (upper.x - lower.x)
v := (rotated_pw.y - lower.y) / (upper.y - lower.y)
ps : [2]f32 = {u * w, (1.0 - v) * h}
return ps
}
PI :: 3.14159265358979323846
DEG2RAD :: PI / 180.0
RAD2DEG :: 180.0 / PI
orthographic_matrix :: proc(cam: ^Camera) -> matrix[4,4]f32{
left :f32= 0
right :f32= f32(cam.width)
bottom :f32= f32(cam.height)
top :f32= 0
near : f32 = 1
far : f32 = -1
m: matrix[4,4]f32
m[0][0] = 2.0 / (right - left)
m[0][1] = 0.0
m[0][2] = 0.0
m[0][3] = 0.0
m[1][0] = 0.0
m[1][1] = 2.0 / (top - bottom)
m[1][2] = 0.0
m[1][3] = 0.0
m[2][0] = 0.0
m[2][1] = 0.0
m[2][2] = -2.0 / (far - near)
m[2][3] = 0.0
m[3][0] = -(right + left) / (right - left)
m[3][1] = -(top + bottom) / (top - bottom)
m[3][2] = -(far + near) / (far - near)
m[3][3] = 1.0
return m
}
//Convert from world coordinates to normalized device coordinates
// http://www.songho.ca/opengl/gl_projectionmatrix.html
camera_build_project_matrix :: proc(
cam : ^Camera,
z_bias : f32,
) -> matrix[4, 4]f32 {
if cam.type == .ORTHO do return orthographic_matrix(cam)
m : matrix[4, 4]f32
mat_rot := linalg.matrix4_rotate_f32(DEG2RAD * cam.rotation, {0, 0, 1})
ratio := f32(cam.width) / f32(cam.height)
extents : [2]f32 = {cam.zoom * ratio, cam.zoom}
lower := cam.center - extents
upper := cam.center + extents
w := upper.x - lower.x
h := upper.y - lower.y
m[0][0] = 2.0 / w
m[1][1] = 2.0 / h
m[2][2] = -1
m[3][0] = -2.0 * cam.center.x / w
m[3][1] = -2.0 * cam.center.y / h
m[3][2] = z_bias
m[3][3] = 1
return m * mat_rot
}
/*
camera_get_view_bounds :: proc(cam : ^Camera) -> b2.AABB {
return b2.AABB {
lowerBound = camera_convert_screen_to_world(cam, [2]f32{0, f32(cam.height)}),
upperBound = camera_convert_screen_to_world(cam, [2]f32{f32(cam.width), 0}),
}
}
*/
Background :: struct {
vao, vbo, program : u32,
uniforms : gl.Uniforms,
}
check_opengl :: proc() {
err := gl.GetError()
if err != gl.NO_ERROR {
fmt.eprintf("OpenGL error = %d\n", err)
assert(false)
}
}
background_create :: proc(back : ^Background) {
ok : bool
back.program, ok = gl.load_shaders_source(
#load("shaders/background.vs"),
#load("shaders/background.fs"),
)
check_opengl()
back.uniforms = gl.get_uniforms_from_program(back.program)
vertex_attribute : u32
//Generate
gl.GenVertexArrays(1, &back.vao)
gl.GenBuffers(1, &back.vbo)
gl.BindVertexArray(back.vao)
gl.EnableVertexAttribArray(vertex_attribute)
//Single quad
vertices : [4][2]f32 = {{-1.0, 1.0}, {-1.0, -1.0}, {1.0, 1.0}, {1.0, -1}}
gl.BindBuffer(gl.ARRAY_BUFFER, back.vbo)
gl.BufferData(
gl.ARRAY_BUFFER,
size_of(vertices),
&vertices[0],
gl.STATIC_DRAW,
)
gl.VertexAttribPointer(vertex_attribute, 2, gl.FLOAT, false, 0, 0)
check_opengl()
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
}
background_destroy :: proc(back : ^Background) {
if bool(back.vao) {
gl.DeleteVertexArrays(1, &back.vao)
gl.DeleteBuffers(1, &back.vbo)
back.vao = 0
back.vbo = 0
}
if bool(back.program) {
gl.DeleteProgram(back.program)
back.program = 0
}
}
background_draw :: proc(back : ^Background, cam : ^Camera) {
gl.UseProgram(back.program)
gl.Uniform2f(
back.uniforms["resolution"].location,
f32(cam.width),
f32(cam.height),
)
gl.Uniform3f(back.uniforms["baseColor"].location, 0.4, 0.4, 0.2)
gl.BindVertexArray(back.vao)
gl.BindBuffer(gl.ARRAY_BUFFER, back.vbo)
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
gl.UseProgram(0)
}
PointData :: struct {
pos : [2]f32,
size : f32,
rgba : RGBA8,
}
Point :: struct {
vao, vbo, program : u32,
uniforms : gl.Uniforms,
points : [dynamic]PointData,
}
points_create :: proc(point : ^Point) {
vs : string = `
#version 330
uniform mat4 projectionMatrix;
layout(location = 0) in vec2 v_position;
layout(location = 1) in float v_size;
layout(location = 2) in vec4 v_color;
out vec4 f_color;
void main(void) {
f_color = v_color;
gl_Position = projectionMatrix * vec4(v_position, 0.0f, 1.0f);
gl_PointSize = v_size;
}
`
fs : string = `
#version 330
in vec4 f_color;
out vec4 color;
void main(void){
color = f_color;
}
`
point.program, _ = gl.load_shaders_source(vs, fs)
point.uniforms = gl.get_uniforms_from_program(point.program)
vertex_attribute : u32 = 0
size_attribute : u32 = 1
color_attribute : u32 = 2
gl.GenVertexArrays(1, &point.vao)
gl.GenBuffers(1, &point.vbo)
gl.BindVertexArray(point.vao)
gl.EnableVertexAttribArray(vertex_attribute)
gl.EnableVertexAttribArray(size_attribute)
gl.EnableVertexAttribArray(color_attribute)
//Vertex buffer
gl.BindBuffer(gl.ARRAY_BUFFER, point.vbo)
gl.BufferData(
gl.ARRAY_BUFFER,
2048 * size_of(PointData),
nil,
gl.DYNAMIC_DRAW,
)
gl.VertexAttribPointer(
vertex_attribute,
2,
gl.FLOAT,
gl.FALSE,
size_of(PointData),
offset_of(PointData, pos),
)
gl.VertexAttribPointer(
size_attribute,
1,
gl.FLOAT,
gl.FALSE,
size_of(PointData),
offset_of(PointData, size),
)
gl.VertexAttribPointer(
color_attribute,
4,
gl.UNSIGNED_BYTE,
gl.TRUE,
size_of(PointData),
offset_of(PointData, rgba),
)
check_opengl()
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
}
points_destroy :: proc(point : ^Point) {
if point.vao != 0 {
gl.DeleteVertexArrays(1, &point.vao)
gl.DeleteBuffers(1, &point.vbo)
point.vao = 0
point.vbo = 0
}
if point.program != 0 {
gl.DeleteProgram(point.program)
point.program = 0
}
}
points_add :: proc(point : ^Point, v : [2]f32, size : f32, rgba : RGBA8) {
append(&point.points, PointData{v, size, rgba})
}
//Flush means draw
points_flush :: proc(point : ^Point, cam : ^Camera) {
count := i32(len(point.points))
if count == 0 do return
gl.UseProgram(point.program)
proj := camera_build_project_matrix(cam, 0)
gl.UniformMatrix4fv(point.uniforms["projectionMatrix"].location, 1, gl.FALSE, &proj[0][0])
gl.BindVertexArray(point.vao)
gl.BindBuffer(gl.ARRAY_BUFFER, point.vbo)
gl.Enable(gl.PROGRAM_POINT_SIZE)
base := 0
for count > 0 {
batch_count : i32 = min(count, 2048)
gl.BufferSubData(
gl.ARRAY_BUFFER, 0, int(batch_count * size_of(PointData)), &point.points[base])
gl.DrawArrays(gl.POINTS, 0, batch_count)
check_opengl()
count -= 2048
base += 2048
}
gl.Disable(gl.PROGRAM_POINT_SIZE)
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
gl.UseProgram(0)
clear(&point.points)
}
VertexData :: struct {
pos : [2]f32,
rgba : RGBA8,
}
Lines :: struct {
points : [dynamic]VertexData,
vao, vbo, program : u32,
uniforms : gl.Uniforms,
}
lines_create :: proc(line : ^Lines) {
vs : string = `
#version 330
uniform mat4 projectionMatrix;
layout(location = 0) in vec2 v_position;
layout(location = 1) in vec4 v_color;
out vec4 f_color;
void main(void){
f_color = v_color;
gl_Position = projectionMatrix * vec4(v_position , 0.0f, 1.0f);
}
`
fs : string = `
#version 330
in vec4 f_color;
out vec4 color;
void main(void){
color = f_color;
}
`
ok := false
line.program, ok = gl.load_shaders_source(vs, fs)
check_opengl()
line.uniforms = gl.get_uniforms_from_program(line.program)
vertex_attribute : u32 = 0
color_attribute : u32 = 1
gl.GenVertexArrays(1, &line.vao)
gl.GenBuffers(1, &line.vbo)
gl.BindVertexArray(line.vao)
gl.EnableVertexAttribArray(vertex_attribute)
gl.EnableVertexAttribArray(color_attribute)
//Vertex buffer
gl.BindBuffer(gl.ARRAY_BUFFER, line.vbo)
gl.BufferData(
gl.ARRAY_BUFFER,
2048 * size_of(VertexData),
nil,
gl.DYNAMIC_DRAW,
)
gl.VertexAttribPointer(
vertex_attribute,
2,
gl.FLOAT,
gl.FALSE,
size_of(VertexData),
offset_of(VertexData, pos),
)
gl.VertexAttribPointer(
color_attribute,
4,
gl.UNSIGNED_BYTE,
gl.TRUE,
size_of(VertexData),
offset_of(VertexData, rgba),
)
check_opengl()
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
}
lines_destroy :: proc(line : ^Lines) {
if line.vao != 0 {
gl.DeleteVertexArrays(1, &line.vao)
gl.DeleteBuffers(1, &line.vbo)
line.vao = 0
line.vbo = 0
}
if line.program != 0 {
gl.DeleteProgram(line.program)
line.program = 0
}
}
lines_add :: proc(line : ^Lines, p1, p2 : [2]f32, rgba : RGBA8) {
append(&line.points, VertexData{p1, rgba})
append(&line.points, VertexData{p2, rgba})
}
lines_flush :: proc(line : ^Lines, cam : ^Camera) {
count := i32(len(line.points))
batch_size : i32 = 2 * 2048
if count == 0 do return
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.UseProgram(line.program)
proj := camera_build_project_matrix(cam, 0.1)
gl.UniformMatrix4fv(
line.uniforms["projectionMatrix"].location,
1,
gl.FALSE,
&proj[0][0],
)
gl.BindVertexArray(line.vao)
gl.BindBuffer(gl.ARRAY_BUFFER, line.vbo)
base : i32 = 0
for count > 0 {
batch_count := min(count, batch_size)
size := int(batch_count * size_of(VertexData))
gl.BufferSubData(gl.ARRAY_BUFFER, 0, size, &line.points[base])
gl.DrawArrays(gl.LINES, 0, batch_count)
check_opengl()
count -= batch_size
base += batch_size
}
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
gl.UseProgram(0)
gl.Disable(gl.BLEND)
clear(&line.points)
}
CircleData :: struct {
pos : [2]f32,
radius : f32,
rgba : RGBA8,
}
Circles :: struct {
circles : [dynamic]CircleData,
vao, program : u32,
vbos : [2]u32,
uniforms : gl.Uniforms,
}
circle_create :: proc(circle : ^Circles) {
batch_size := 2048
circle.program, _ = gl.load_shaders_source(
#load("shaders/circle.vs"),
#load("shaders/circle.fs"),
)
check_opengl()
circle.uniforms = gl.get_uniforms_from_program(circle.program)
vertex_attribute : u32 = 0
position_instance : u32 = 1
radiusInstance : u32 = 2
colorInstance : u32 = 3
gl.GenVertexArrays(1, &circle.vao)
gl.GenBuffers(2, &circle.vbos[0])
gl.BindVertexArray(circle.vao)
gl.EnableVertexAttribArray(vertex_attribute)
gl.EnableVertexAttribArray(position_instance)
gl.EnableVertexAttribArray(radiusInstance)
gl.EnableVertexAttribArray(colorInstance)
//vertex buffer for single quad
a : f32 = 1.1
vertices : [][2]f32 = {
{-a, -a},
{a, -a},
{-a, a},
{a, -a},
{a, a},
{-a, a},
}
gl.BindBuffer(gl.ARRAY_BUFFER, circle.vbos[0])
gl.BufferData(
gl.ARRAY_BUFFER,
size_of([2]f32) * 6,
&vertices[0],
gl.STATIC_DRAW,
)
gl.VertexAttribPointer(vertex_attribute, 2, gl.FLOAT, gl.FALSE, 0, 0)
//
gl.BindBuffer(gl.ARRAY_BUFFER, circle.vbos[1])
gl.BufferData(
gl.ARRAY_BUFFER,
batch_size * size_of(CircleData),
nil,
gl.DYNAMIC_DRAW,
)
gl.VertexAttribPointer(
position_instance,
2,
gl.FLOAT,
gl.FALSE,
size_of(CircleData),
offset_of(CircleData, pos),
)
gl.VertexAttribPointer(
radiusInstance,
1,
gl.FLOAT,
gl.FALSE,
size_of(CircleData),
offset_of(CircleData, radius),
)
gl.VertexAttribPointer(
colorInstance,
4,
gl.UNSIGNED_BYTE,
gl.TRUE,
size_of(CircleData),
offset_of(CircleData, rgba),
)
gl.VertexAttribDivisor(position_instance, 1)
gl.VertexAttribDivisor(radiusInstance, 1)
gl.VertexAttribDivisor(colorInstance, 1)
check_opengl()
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
}
circle_destroy :: proc(circle : ^Circles) {
if circle.vao != 0 {
gl.DeleteVertexArrays(1, &circle.vao)
gl.DeleteBuffers(2, &circle.vbos[0])
circle.vao = 0
circle.vbos[0] = 0
circle.vbos[1] = 0
}
if circle.program != 0 {
gl.DeleteProgram(circle.program)
circle.program = 0
}
}
circle_add :: proc(
circle : ^Circles,
center : [2]f32,
radius : f32,
rgba : RGBA8,
)
{
append(&circle.circles, CircleData{center, radius, rgba})
}
circle_flush :: proc(circle : ^Circles, cam : ^Camera) {
count := i32(len(circle.circles))
if count == 0 do return
batch_size : i32 = 2048
gl.UseProgram(circle.program)
proj := camera_build_project_matrix(cam, 0.2)
gl.UniformMatrix4fv(
circle.uniforms["projectionMatrix"].location,
1,
gl.FALSE,
&proj[0][0],
)
gl.Uniform1f(
circle.uniforms["pixelScale"].location,
f32(cam.height) / cam.zoom,
)
gl.BindVertexArray(circle.vao)
gl.BindBuffer(gl.ARRAY_BUFFER, circle.vbos[1])
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
base : i32 = 0
for count > 0 {
batch_count := min(count, batch_size)
gl.BufferSubData(
gl.ARRAY_BUFFER,
0,
int(batch_count * size_of(CircleData)),
&circle.circles[base],
)
gl.DrawArraysInstanced(gl.TRIANGLES, 0, 6, batch_count)
check_opengl()
count -= batch_size
base += batch_size
}
gl.Disable(gl.BLEND)
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
gl.UseProgram(0)
clear(&circle.circles)
}
SolidCircleData :: struct
{
transform : Transform,
radius : f32,
rgba : RGBA8,
}
SolidCircle :: struct
{
circles : [dynamic]SolidCircleData,
program, vao : u32,
vbo : [2]u32,
uniforms : gl.Uniforms,
}
solid_circle_create :: proc(circle : ^SolidCircle)
{
circle.program, _ = gl.load_shaders_source(#load("shaders/solid_circle.vs"), #load("shaders/solid_circle.fs"))
circle.uniforms = gl.get_uniforms_from_program(circle.program)
gl.GenVertexArrays(1, &circle.vao)
gl.GenBuffers(2, &circle.vbo[0])
gl.BindVertexArray(circle.vao)
vertex_attribute : u32 = 0
transform_instance : u32 = 1
radius_instance : u32 = 2
color_instance : u32 = 3
gl.EnableVertexAttribArray(vertex_attribute)
gl.EnableVertexAttribArray(transform_instance)
gl.EnableVertexAttribArray(radius_instance)
gl.EnableVertexAttribArray(color_instance)
batch_size : i32 = 2048
//Vertex buffer for single quad
a : f32 = 1.1
vertices : [][2]f32 = {
{-a, -a},
{a, -a},
{-a, a},
{a, -a},
{a, a},
{-a, a},
}
gl.BindBuffer(gl.ARRAY_BUFFER, circle.vbo[0])
gl.BufferData(gl.ARRAY_BUFFER, size_of([2]f32) * 6, &vertices[0], gl.STATIC_DRAW)
gl.VertexAttribPointer(vertex_attribute, 2, gl.FLOAT, gl.FALSE, 0, 0)
//
gl.BindBuffer(gl.ARRAY_BUFFER, circle.vbo[1])
gl.BufferData(gl.ARRAY_BUFFER, int(batch_size * size_of(SolidCircleData)), nil, gl.DYNAMIC_DRAW)
gl.VertexAttribPointer(transform_instance, 4, gl.FLOAT, gl.FALSE, size_of(SolidCircleData), offset_of(SolidCircleData, transform))
gl.VertexAttribPointer(radius_instance, 1, gl.FLOAT, gl.FALSE, size_of(SolidCircleData), offset_of(SolidCircleData, radius))
gl.VertexAttribPointer(color_instance, 4, gl.UNSIGNED_BYTE, gl.TRUE, size_of(SolidCircleData), offset_of(SolidCircleData, rgba))
gl.VertexAttribDivisor(transform_instance, 1)
gl.VertexAttribDivisor(radius_instance, 1)
gl.VertexAttribDivisor(color_instance, 1)
check_opengl()
//Cleanup
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
}
solid_circle_destroy :: proc(circle : ^SolidCircle) {
if circle.vao != 0 {
gl.DeleteVertexArrays(1, &circle.vao)
gl.DeleteBuffers(2, &circle.vbo[0])
circle.vao = 0
circle.vbo[0] = 0
circle.vbo[1] = 0
}
if circle.program != 0 {
gl.DeleteProgram(circle.program)
circle.program = 0
}
}
solid_circle_add :: proc(
circle : ^SolidCircle,
transform : Transform,
radius : f32,
color : RGBA8,
) {
append(&circle.circles, SolidCircleData{transform, radius, color})
}
solid_circle_flush :: proc(circle : ^SolidCircle, cam : ^Camera) {
count : i32 = i32(len(circle.circles))
if count == 0 do return
batch_size : i32 = 2048
gl.UseProgram(circle.program)
proj := camera_build_project_matrix(cam, 0.2)
gl.UniformMatrix4fv(
circle.uniforms["projectionMatrix"].location,
1,
gl.FALSE,
&proj[0][0],
)
gl.Uniform1f(
circle.uniforms["pixelScale"].location,
f32(cam.height) / cam.zoom,
)
gl.BindVertexArray(circle.vao)
gl.BindBuffer(gl.ARRAY_BUFFER, circle.vbo[1])
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
base : i32 = 0
for count > 0 {
batch_count := min(count, batch_size)
gl.BufferSubData(
gl.ARRAY_BUFFER,
0,
int(batch_count * size_of(SolidCircleData)),
&circle.circles[base],
)
gl.DrawArraysInstanced(gl.TRIANGLES, 0, 6, batch_count)
check_opengl()
count -= batch_size
base += batch_size
}
gl.Disable(gl.BLEND)
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
gl.UseProgram(0)
clear(&circle.circles)
}
CapsuleData :: struct {
transform : Transform,
radius, length : f32,
rgba : RGBA8,
}
SolidCapsules :: struct {
capsules : [dynamic]CapsuleData,
vao, program : u32,
vbo : [2]u32,
uniforms : gl.Uniforms,
}
//Draw capsules using SDF-based shaders
solid_capsules_create :: proc(capsule : ^SolidCapsules) {
capsule.program, _ = gl.load_shaders_source(
#load("shaders/solid_capsule.vs"),
#load("shaders/solid_capsule.fs"),
)
check_opengl()
capsule.uniforms = gl.get_uniforms_from_program(capsule.program)
//batch_size := i32(len(capsules))
batch_size : i32 = 512
vertex_attribute : u32 = 0
transform_instance : u32 = 1
radius_instance : u32 = 2
length_instance : u32 = 3
color_instance : u32 = 4
gl.GenVertexArrays(1, &capsule.vao)
gl.GenBuffers(2, &capsule.vbo[0])
gl.BindVertexArray(capsule.vao)
gl.EnableVertexAttribArray(vertex_attribute)
gl.EnableVertexAttribArray(transform_instance)
gl.EnableVertexAttribArray(radius_instance)
gl.EnableVertexAttribArray(length_instance)
gl.EnableVertexAttribArray(color_instance)
//Vertex buffer for single quad
a : f32 = 1.1
vertices : [][2]f32 = {
{-a, -a},
{a, -a},
{-a, a},
{a, -a},
{a, a},
{-a, a},
}
gl.BindBuffer(gl.ARRAY_BUFFER, capsule.vbo[0])
gl.BufferData(
gl.ARRAY_BUFFER,
size_of([2]f32) * 6,
&vertices[0],
gl.STATIC_DRAW,
)
gl.VertexAttribPointer(vertex_attribute, 2, gl.FLOAT, gl.FALSE, 0, 0)
//
gl.BindBuffer(gl.ARRAY_BUFFER, capsule.vbo[1])
gl.BufferData(
gl.ARRAY_BUFFER,
int(batch_size * size_of(CapsuleData)),
nil,
gl.DYNAMIC_DRAW,
)
gl.VertexAttribPointer(
transform_instance,
4,
gl.FLOAT,
gl.FALSE,
size_of(CapsuleData),
offset_of(CapsuleData, transform),
)
gl.VertexAttribPointer(
radius_instance,
1,
gl.FLOAT,
gl.FALSE,
size_of(CapsuleData),
offset_of(CapsuleData, radius),
)
gl.VertexAttribPointer(
length_instance,
1,
gl.FLOAT,
gl.FALSE,
size_of(CapsuleData),
offset_of(CapsuleData, length),
)
gl.VertexAttribPointer(
color_instance,
4,
gl.UNSIGNED_BYTE,
gl.TRUE,
size_of(CapsuleData),
offset_of(CapsuleData, rgba),
)
gl.VertexAttribDivisor(transform_instance, 1)
gl.VertexAttribDivisor(radius_instance, 1)
gl.VertexAttribDivisor(length_instance, 1)
gl.VertexAttribDivisor(color_instance, 1)
check_opengl()
//Cleanup
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
}
solid_capsules_destroy :: proc(capsule : ^SolidCapsules) {
if capsule.vao != 0 {
gl.DeleteVertexArrays(1, &capsule.vao)
gl.DeleteBuffers(2, &capsule.vbo[0])
capsule.vao = 0
capsule.vbo = {0, 0}
}
if capsule.program != 0 {
gl.DeleteProgram(capsule.program)
capsule.program = 0
}
}
solid_capsules_add :: proc(
capsule : ^SolidCapsules,
p1, p2 : [2]f32,
radius : f32,
rgba : RGBA8,
) {
d := p2 - p1
length := math.sqrt(d.x * d.x + d.y * d.y)
if length < 0.001 do return
axis := d / length
transform : Transform = {
p = 0.5 * (p1 + p2),
q = {c = axis.x, s = axis.y},
}
append(&capsule.capsules, CapsuleData{transform, radius, length, rgba})
}
solid_capsules_flush :: proc(capsule : ^SolidCapsules, cam : ^Camera) {
count := i32(len(capsule.capsules))
if count == 0 do return
//batch :i32= 2048
gl.UseProgram(capsule.program)
proj := camera_build_project_matrix(cam, 0.2)
gl.UniformMatrix4fv(
capsule.uniforms["projectionMatrix"].location,
1,
gl.FALSE,
&proj[0][0],
)
gl.Uniform1f(
capsule.uniforms["pixelScale"].location,
f32(cam.height) / cam.zoom,
)
gl.BindVertexArray(capsule.vao)
gl.BindBuffer(gl.ARRAY_BUFFER, capsule.vbo[1])
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
base : i32 = 0
for count > 0 {
batch_count := min(count, 2048)
gl.BufferSubData(gl.ARRAY_BUFFER, 0, int(batch_count * size_of(CapsuleData)), &capsule.capsules[base])
gl.DrawArraysInstanced(gl.TRIANGLES, 0, 6, batch_count)
check_opengl()
count -= 2048
base += 2048
}
gl.Disable(gl.BLEND)
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
gl.UseProgram(0)
clear(&capsule.capsules)
}
PolygonData :: struct #packed {
transform : Transform,
p1, p2, p3, p4, p5, p6, p7, p8 : [2]f32,
count : i32,
radius : f32,
//Keep color small
color : RGBA8,
}
SolidPolygon :: struct {
polygons : [dynamic]PolygonData,
vao, program : u32,
vbo : [2]u32,
uniforms : gl.Uniforms,
}
solid_polygon_create :: proc(polygon : ^SolidPolygon) {
polygon.program, _ = gl.load_shaders_source(
#load("shaders/solid_polygons.vs"),
#load("shaders/solid_polygons.fs"),
)
batch_size : i32 = 512
polygon.uniforms = gl.get_uniforms_from_program(polygon.program)
vertex_attribute : u32 = 0
instance_transform : u32 = 1
instance_point12 : u32 = 2
instance_point34 : u32 = 3
instance_point56 : u32 = 4
instance_point78 : u32 = 5
instance_point_count : u32 = 6
instance_radius : u32 = 7
instance_color : u32 = 8
gl.GenVertexArrays(1, &polygon.vao)
gl.GenBuffers(2, &polygon.vbo[0])
gl.BindVertexArray(polygon.vao)
gl.EnableVertexAttribArray(vertex_attribute)
gl.EnableVertexAttribArray(instance_transform)
gl.EnableVertexAttribArray(instance_point12)
gl.EnableVertexAttribArray(instance_point34)
gl.EnableVertexAttribArray(instance_point56)
gl.EnableVertexAttribArray(instance_point78)
gl.EnableVertexAttribArray(instance_point_count)
gl.EnableVertexAttribArray(instance_radius)
gl.EnableVertexAttribArray(instance_color)
a : f32 = 1.1
vertices : [][2]f32 = {
{-a, -a},
{a, -a},
{-a, a},
{a, -a},
{a, a},
{-a, a},
}
gl.BindBuffer(gl.ARRAY_BUFFER, polygon.vbo[0])
gl.BufferData(gl.ARRAY_BUFFER, size_of([2]f32) * 6, &vertices[0], gl.STATIC_DRAW)
gl.VertexAttribPointer(vertex_attribute, 2, gl.FLOAT, gl.FALSE, 0, 0)
//
gl.BindBuffer(gl.ARRAY_BUFFER, polygon.vbo[1])
gl.BufferData(gl.ARRAY_BUFFER, int(batch_size * size_of(PolygonData)), nil, gl.DYNAMIC_DRAW)
gl.VertexAttribPointer(instance_transform, 4, gl.FLOAT, false, size_of(PolygonData), offset_of(PolygonData, transform))
gl.VertexAttribPointer(instance_point12, 4, gl.FLOAT, false, size_of(PolygonData), offset_of(PolygonData, p1))
gl.VertexAttribPointer(instance_point34, 4, gl.FLOAT, false, size_of(PolygonData), offset_of(PolygonData, p3))
gl.VertexAttribPointer(instance_point56, 4, gl.FLOAT, false, size_of(PolygonData), offset_of(PolygonData, p5))
gl.VertexAttribPointer(instance_point78, 4, gl.FLOAT, false, size_of(PolygonData), offset_of(PolygonData, p7))
gl.VertexAttribIPointer(instance_point_count, 1, gl.INT, size_of(PolygonData), offset_of(PolygonData, count))
gl.VertexAttribPointer(instance_radius, 1, gl.FLOAT, false, size_of(PolygonData), offset_of(PolygonData, radius))
gl.VertexAttribPointer(instance_color, 4, gl.UNSIGNED_BYTE, true, size_of(PolygonData), offset_of(PolygonData, color))
gl.VertexAttribDivisor(instance_transform, 1)
gl.VertexAttribDivisor(instance_point12, 1)
gl.VertexAttribDivisor(instance_point34, 1)
gl.VertexAttribDivisor(instance_point56, 1)
gl.VertexAttribDivisor(instance_point78, 1)
gl.VertexAttribDivisor(instance_point_count, 1)
gl.VertexAttribDivisor(instance_radius, 1)
gl.VertexAttribDivisor(instance_color, 1)
check_opengl()
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
}
solid_polygon_add :: proc(
polygon : ^SolidPolygon,
transform : Transform,
points : [^][2]f32,
count : i32,
radius : f32,
color : RGBA8,
) {
data : PolygonData
data.transform = transform
n := min(count, 8)
ps := cast([^][2]f32)&data.p1
for i in 0 ..< count {
ps[i] = points[i]
}
data.count = n
data.radius = f32(radius)
data.color = color
append(&polygon.polygons, data)
}
solid_polygon_flush :: proc(polygon : ^SolidPolygon, cam : ^Camera) {
count := i32(len(polygon.polygons))
if count == 0 do return
batch_size : i32 = 512
gl.UseProgram(polygon.program)
proj := camera_build_project_matrix(cam, 0.2)
gl.UniformMatrix4fv(polygon.uniforms["projectionMatrix"].location, 1, gl.FALSE, &proj[0][0])
gl.Uniform1f(polygon.uniforms["pixelScale"].location, f32(cam.height) / cam.zoom)
gl.BindVertexArray(polygon.vao)
gl.BindBuffer(gl.ARRAY_BUFFER, polygon.vbo[1])
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
base : i32 = 0
for count > 0 {
batch_count := min(count, batch_size)
gl.BufferSubData(gl.ARRAY_BUFFER, 0, int(batch_count * size_of(PolygonData)), &polygon.polygons[base])
gl.DrawArraysInstanced(gl.TRIANGLES, 0, 6, batch_count)
check_opengl()
count -= batch_size
base += batch_size
}
gl.Disable(gl.BLEND)
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindVertexArray(0)
gl.UseProgram(0)
clear(&polygon.polygons)
}
TextItem :: struct
{
str : string,
pos : [2]f32,
color : [4]u8,
}
Draw :: struct
{
show_ui : bool,
cam : Camera,
background : Background,
points : Point,
lines : Lines,
circles : Circles,
solid_circles : SolidCircle,
solid_capsules : SolidCapsules,
polygons : SolidPolygon,
textures : Texture,
drawCounters : bool,
//regular_font : im.Font,
frame_buffer : u32,
//There should be option to draw without glyph? maybe
glyph : e2_glyph.GlyphState,
font_path : string,
texts : [dynamic]TextItem,
}
/*
draw_aabb :: proc(draw : ^Draw, aabb : [2][2]f32, rgba : RGBA8) {
p1 := aabb.lowerBound
p2 : [2]f32 = {aabb.upperBound.x, aabb.lowerBound.y}
p3 := aabb.upperBound
p4 : [2]f32 = {aabb.lowerBound.x, aabb.upperBound.y}
lines_add(&draw.lines, p1, p2, rgba)
lines_add(&draw.lines, p2, p3, rgba)
lines_add(&draw.lines, p3, p4, rgba)
lines_add(&draw.lines, p4, p1, rgba)
}
*/
DrawSolidCircle :: proc(
circle : ^SolidCircle,
transform : Transform,
center : [2]f32,
radius : f32,
rgba: RGBA8,
) {
context = runtime.default_context()
transform := transform
solid_circle_add(circle, transform, radius, rgba)
}
DrawString :: proc(draw : ^Draw, x, y : int, cstr: cstring)
{
ps := camera_convert_world_to_screen(&draw.cam, {f32(x), f32(y)})
str := strings.clone_from_cstring(cstr)
str = strings.trim_left_space(str)
str = strings.trim_right_space(str)
str = strings.trim_null(str)
append(&draw.texts, TextItem{"0.00", {f32(ps.x), f32(ps.y)}, {1,1,1,1}})
/*
draw_list := im.GetForegroundDrawList()
im.DrawList_AddText(draw_list, {f32(x), f32(y)},im.GetColorU32(.Text), str)
*/
}
DrawStringVec :: proc(draw : ^Draw, p : [2]f32, cstr: cstring)
{
p := p
ps := camera_convert_world_to_screen(&draw.cam, p)
str := strings.clone_from_cstring(cstr)
str = strings.trim_left_space(str)
str = strings.trim_right_space(str)
str = strings.trim_null(str)
append(&draw.texts, TextItem{str, {f32(ps.x), f32(ps.y)}, {200,200,200,255}})
}
text_flush :: proc(draw: ^Draw, flush_glyph := false)
{
for &t in &draw.texts{
e2_glyph.glyph_draw_font(&draw.glyph, t.str, t.pos, t.color, flush_glyph)
}
clear(&draw.texts)
}
draw_flush :: proc(draw : ^Draw, flush_glyph := false) {
//background_draw(&draw.background, &draw.cam)
solid_circle_flush(&draw.solid_circles, &draw.cam)
solid_polygon_flush(&draw.polygons, &draw.cam)
solid_capsules_flush(&draw.solid_capsules, &draw.cam)
lines_flush(&draw.lines, &draw.cam)
points_flush(&draw.points, &draw.cam)
circle_flush(&draw.circles, &draw.cam)
textures_flush(draw)
text_flush(draw, flush_glyph)
check_opengl()
}
Rect :: struct {
x, y, w, h : f32,
}
TextureData :: struct
{
texture_id : u32, //opengl texture id
width, height : i32,
src_rect : Rect, // in pixel format
dst_rect : Rect, // in pixel format
}
//Contains multiple textures
Texture :: struct
{
textures : [dynamic]TextureData,
vao, vbo, ebo: u32,
uniforms : gl.Uniforms,
program_id : u32,
}
/*
Create texture and render texture
create opengl texture when a buffer data is provided and store the textuer
The function returns the actual opengl texture_id
render_texture :: gets a texture_id and rect and displays the texture (generic but convinent for micorui)
*/
textures_create :: proc(texture: ^Texture)
{
texture.program_id, _ = gl.load_shaders_source(#load("shaders/texture.vs"), #load("shaders/texture.fs"))
gl.UseProgram(texture.program_id)
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
texture.uniforms = gl.get_uniforms_from_program(texture.program_id)
vertices : []f32 = {
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 0.0, 0.0,
1.0, 0.0, 1.0, 0.0,
1.0, 1.0, 1.0, 1.0,
}
indices := []u32 {
0, 1, 2,
0, 2, 3,
}
gl.GenVertexArrays(1, &texture.vao)
gl.GenBuffers(1, &texture.vbo)
gl.GenBuffers(1, &texture.ebo)
gl.BindVertexArray(texture.vao)
gl.BindBuffer(gl.ARRAY_BUFFER, texture.vbo)
gl.BufferData(gl.ARRAY_BUFFER, len(vertices) * size_of(f32), raw_data(vertices), gl.DYNAMIC_DRAW)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, texture.ebo)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(indices) * size_of(u32), raw_data(indices), gl.DYNAMIC_DRAW)
gl.VertexAttribPointer(0, 2, gl.FLOAT, false, 4 * size_of(f32), 0)
gl.EnableVertexAttribArray(0)
gl.VertexAttribPointer(1, 2, gl.FLOAT, false, 4 * size_of(f32), 2 * size_of(f32))
gl.EnableVertexAttribArray(1)
//gl.BindBuffer(gl.ARRAY_BUFFER, 0)
//gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
gl.UseProgram(0)
}
create_texture :: proc(data: []u8, width, height: i32) -> u32
{
texture : u32
gl.GenTextures(1, &texture)
gl.BindTexture(gl.TEXTURE_2D, texture)
gl.TexImage2D( gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, raw_data(data))
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.BindTexture( gl.TEXTURE_2D, 0)
return texture
}
textures_flush :: proc(d: ^Draw)
{
texture := &d.textures
gl.UseProgram(texture.program_id)
gl.Uniform2f(texture.uniforms["u_resolution"].location, f32(d.cam.width), f32(d.cam.height))
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.ActiveTexture(gl.TEXTURE0)
for &t in &texture.textures
{
vertices := []f32{
0.0, 1.0, t.src_rect.x , (t.src_rect.y+ f32(t.src_rect.h)),
0.0, 0.0, t.src_rect.x , (t.src_rect.y ),
1.0, 0.0, (t.src_rect.x + f32(t.src_rect.w)), (t.src_rect.y ),
1.0, 1.0, (t.src_rect.x + f32(t.src_rect.w)), (t.src_rect.y+ f32(t.src_rect.h))
}
gl.BindBuffer(gl.ARRAY_BUFFER, texture.vbo)
gl.BufferData(gl.ARRAY_BUFFER, len(vertices) * size_of(f32), raw_data(vertices), gl.DYNAMIC_DRAW)
gl.Uniform2f(texture.uniforms["u_position"].location, t.dst_rect.x, t.dst_rect.y)
gl.Uniform2f(texture.uniforms["u_size"].location, t.dst_rect.w, t.dst_rect.h)
gl.BindTexture(gl.TEXTURE_2D, t.texture_id)
gl.Uniform1i(texture.uniforms["ourTexture"].location, 0)
gl.BindVertexArray(texture.vao)
gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, nil)
}
gl.Disable(gl.BLEND)
gl.UseProgram(0)
check_opengl()
clear(&texture.textures)
}
//This should be handled in engine code separately
draw_create :: proc(draw : ^Draw, camera : ^Camera, font_path : string = "")
{
background_create(&draw.background)
points_create(&draw.points)
solid_capsules_create(&draw.solid_capsules)
lines_create(&draw.lines)
circle_create(&draw.circles)
solid_circle_create(&draw.solid_circles)
solid_polygon_create(&draw.polygons)
textures_create(&draw.textures)
{
draw.glyph.atlas_width = draw.cam.width
draw.glyph.atlas_height = draw.cam.height
draw.glyph.font_size_pt = 9
draw.glyph.width = draw.cam.width
draw.glyph.height = draw.cam.height
draw.font_path = font_path
e2_glyph.glyph_init(&draw.glyph, draw.font_path)
}
check_opengl()
}