Grid matrix math fix

This commit is contained in:
Synthasmagoria 2026-08-30 14:01:17 +02:00
commit c0d84bae43
4 changed files with 80 additions and 34 deletions

View file

@ -24,10 +24,10 @@ rectangle_corners_from_rect :: proc "contextless" (rect: rect2) -> RectangleCorn
rectangle_corners_transform_mat3 :: proc "contextless" (transform: mat3, corners: RectangleCorners) -> RectangleCorners { rectangle_corners_transform_mat3 :: proc "contextless" (transform: mat3, corners: RectangleCorners) -> RectangleCorners {
return { return {
matrix3_transform_v2(transform, corners.tl), matrix3_transform_xy(transform, corners.tl),
matrix3_transform_v2(transform, corners.tr), matrix3_transform_xy(transform, corners.tr),
matrix3_transform_v2(transform, corners.br), matrix3_transform_xy(transform, corners.br),
matrix3_transform_v2(transform, corners.bl), matrix3_transform_xy(transform, corners.bl),
} }
} }

View file

@ -35,37 +35,36 @@ grid_get_line_separation :: proc(zoom: v2, min_line_distance: v2) -> v2 {
grid_world_to_grid_matrix :: proc(zoom, offset: v2) -> mat3 { grid_world_to_grid_matrix :: proc(zoom, offset: v2) -> mat3 {
zoom_amount := grid_get_zoom(zoom) zoom_amount := grid_get_zoom(zoom)
return matrix3_translate2(offset) * linalg.matrix3_scale(v3{**zoom_amount, 1.0}) return matrix3_translate2(offset) * matrix3_scale2(zoom_amount)
} }
draw_grid :: proc(grid: Grid, area, text_draw, text_clip: rect2) { draw_grid :: proc(grid: Grid, area, text_draw, text_clip: rect2, color: rl.Color) {
world_to_grid_matrix := grid_world_to_grid_matrix(grid.zoom, grid.offset) world_to_grid_matrix := grid_world_to_grid_matrix(grid.zoom, grid.offset)
grid_to_world_matrix := linalg.inverse(world_to_grid_matrix) grid_to_world_matrix := linalg.inverse(world_to_grid_matrix)
position := rect_get_tl(area) position := rect_get_tl(area)
size := rect_get_size(area) size := rect_get_size(area)
grid_world_br := position + size grid_world_br := position + size
grid_color := rl.Color{64, 64, 64, 255}
sep := grid_get_line_separation(grid.zoom, grid.cell_size_px) sep := grid_get_line_separation(grid.zoom, grid.cell_size_px)
tl := matrix3_transform_v2(world_to_grid_matrix, position) tl := matrix3_transform_xy(world_to_grid_matrix, position)
tl = linalg.floor(tl / sep) * sep tl = linalg.floor(tl / sep) * sep
br := matrix3_transform_v2(world_to_grid_matrix, grid_world_br) br := matrix3_transform_xy(world_to_grid_matrix, grid_world_br)
for x: f32 = tl.x; x < br.x; x += sep.x { for x: f32 = tl.x; x < br.x; x += sep.x {
world_position_x := (v3{x, 0.0, 1.0} * grid_to_world_matrix).x world_position_x := (grid_to_world_matrix * v3{x, 0.0, 1.0}).x
p1 := v2{world_position_x, position.y} p1 := v2{world_position_x, position.y}
p2 := v2{world_position_x, grid_world_br.y} p2 := v2{world_position_x, grid_world_br.y}
rl.DrawLineV(p1, p2, grid_color) rl.DrawLineV(p1, p2, color)
if p1.x >= text_clip.x && p1.x < text_clip.x + text_clip.width { if p1.x >= text_clip.x && p1.x < text_clip.x + text_clip.width {
draw_float_aligned(x, {world_position_x, text_draw.y}, program.font_style, .Middle, .Top, rl.GRAY) draw_float_aligned(x, {world_position_x, text_draw.y}, program.font_style, .Middle, .Top, rl.GRAY)
} }
} }
for y: f32 = tl.y; y < br.y; y += sep.y { for y: f32 = tl.y; y < br.y; y += sep.y {
world_position_y := (v3{0.0, y, 1.0} * grid_to_world_matrix).y world_position_y := (grid_to_world_matrix * v3{0.0, y, 1.0}).y
p1 := v2{position.x, world_position_y} p1 := v2{position.x, world_position_y}
p2 := v2{grid_world_br.x, world_position_y} p2 := v2{grid_world_br.x, world_position_y}
rl.DrawLineV(p1, p2, grid_color) rl.DrawLineV(p1, p2, color)
if p1.y >= text_clip.y && p1.y < text_clip.y + text_clip.height { if p1.y >= text_clip.y && p1.y < text_clip.y + text_clip.height {
draw_float_aligned(y, {text_draw.x, world_position_y}, program.font_style, .Left, .Center, rl.GRAY) draw_float_aligned(y, {text_draw.x, world_position_y}, program.font_style, .Left, .Center, rl.GRAY)
} }

View file

@ -1,5 +1,32 @@
package main package main
/*
Rules of matrices:
1) Order matters when multiplying with vectors
- m * v = Column vector x matrix
- v * m = Row vector x matrix
2) Odin matrices are column-major for math efficiency
fmt.println(matrix[3, 3]f32 {
a, -b, 0,
b, a, 0,
0, 0, 1,
})
>> [0.8660254, 0.5, 0, -0.5, 0.8660254, 0, 0, 0, 1]
Additionally this means that the first index of a matrix is the column.
mat[<column>, <row>].
If you had an array of 9 floats ([9]f32) and you accessed the first one
It'd be the same as accessing the first colmun of a matrix.
array[1] = mat[1, 0]
3) Matrix multiplication order matters
// scale first then translate
transformation_a := translation * scale
// translate first then scale
transformation_b := scale * translation
*/
import rl "libraries/raylib" import rl "libraries/raylib"
import ui "libraries/snths_ui" import ui "libraries/snths_ui"
import "core:mem" import "core:mem"
@ -275,8 +302,10 @@ matrix_pool_get_mat3 :: proc() -> (value_strings: []InputFloatField, values: []f
assert(program.matrix_count + 1 < matrix_pool_max(), "Max matrix count exceeded") assert(program.matrix_count + 1 < matrix_pool_max(), "Max matrix count exceeded")
value_strings = program.matrix_value_string_pool[program.matrix_count * 9 : program.matrix_count * 9 + 9] value_strings = program.matrix_value_string_pool[program.matrix_count * 9 : program.matrix_count * 9 + 9]
values = program.matrix_float_pool[program.matrix_count * 9 : program.matrix_count * 9 + 9] values = program.matrix_float_pool[program.matrix_count * 9 : program.matrix_count * 9 + 9]
for &str in value_strings { for i in 0..<len(value_strings) {
str[0] = '0' mem.set(raw_data(&value_strings[i]), 0, len(InputFloatField))
value_strings[i][0] = '0'
values[i] = 0.0
} }
program.matrix_count += 1 program.matrix_count += 1
return value_strings, values return value_strings, values
@ -508,7 +537,7 @@ program_update :: proc() {
matrix_container_br := rect_get_br(rect2(matrix_container.area)) matrix_container_br := rect_get_br(rect2(matrix_container.area))
grid_text_clip := rect_from_area(v2{matrix_container_br.x, top_bar_container_br.y}, program_screen_size()) grid_text_clip := rect_from_area(v2{matrix_container_br.x, top_bar_container_br.y}, program_screen_size())
grid_area := rect2{0.0, 0.0, **program_screen_size()} grid_area := rect2{0.0, 0.0, **program_screen_size()}
draw_grid(program.grid, grid_area, rect_grow(grid_text_clip, -4.0), rect_grow(grid_text_clip, -32.0)) draw_grid(program.grid, grid_area, rect_grow(grid_text_clip, -4.0), rect_grow(grid_text_clip, -32.0), {255, 255, 255, 64})
{ {
animation := &program.animation animation := &program.animation
@ -545,10 +574,10 @@ program_update :: proc() {
for draggable in program.mouse_draggables { for draggable in program.mouse_draggables {
switch draggable in draggable { switch draggable in draggable {
case MouseDraggable_Point: case MouseDraggable_Point:
position_world := matrix3_transform_v2(grid_to_world_matrix, draggable.ref^) position_world := matrix3_transform_xy(grid_to_world_matrix, draggable.ref^)
rl.DrawCircleV(position_world, program.mouse_draggable_radius, ANIMATION_COLOR_ORIGIN) rl.DrawCircleV(position_world, program.mouse_draggable_radius, ANIMATION_COLOR_ORIGIN)
case MouseDraggable_RectangleCornerData: case MouseDraggable_RectangleCornerData:
position_world := matrix3_transform_v2(grid_to_world_matrix, draggable.corner_ref^) position_world := matrix3_transform_xy(grid_to_world_matrix, draggable.corner_ref^)
rl.DrawCircleV(position_world, program.mouse_draggable_radius, ANIMATION_COLOR_ORIGIN) rl.DrawCircleV(position_world, program.mouse_draggable_radius, ANIMATION_COLOR_ORIGIN)
case MouseDraggable_RectangleData: case MouseDraggable_RectangleData:
} }
@ -611,15 +640,15 @@ program_add_rotation_matrix :: proc(angle: f32) {
a := linalg.cos(radians) a := linalg.cos(radians)
b := linalg.sin(radians) b := linalg.sin(radians)
fmt.bprint(value_strings[0][:], a) fmt.bprintf(value_strings[0][:], "%.3f", a)
values[0] = a values[0] = a
fmt.bprint(value_strings[1][:], -b) fmt.bprintf(value_strings[1][:], "%.3f", -b)
values[1] = b values[1] = -b
fmt.bprint(value_strings[3][:], b) fmt.bprintf(value_strings[3][:], "%.3f", b)
values[3] = a values[3] = b
fmt.bprint(value_strings[4][:], a) fmt.bprintf(value_strings[4][:], "%.3f", a)
values[4] = a values[4] = a
fmt.bprint(value_strings[8][:], 1) fmt.bprint(value_strings[8][:], f32(1.0))
values[8] = 1.0 values[8] = 1.0
// rotmat := linalg.matrix2_rotate_f32(angle) // rotmat := linalg.matrix2_rotate_f32(angle)
@ -861,14 +890,14 @@ program_handle_mouse :: proc(mouse_state: MouseState, top_bar_container, matrix_
for draggable in program.mouse_draggables { for draggable in program.mouse_draggables {
switch draggable in draggable { switch draggable in draggable {
case MouseDraggable_Point: case MouseDraggable_Point:
circle_position := matrix3_transform_v2(grid_to_world_matrix, draggable.ref^) circle_position := matrix3_transform_xy(grid_to_world_matrix, draggable.ref^)
if linalg.distance(mouse, circle_position) <= program.mouse_draggable_radius { if linalg.distance(mouse, circle_position) <= program.mouse_draggable_radius {
program.mouse_current_draggable = draggable program.mouse_current_draggable = draggable
mouse_state = .DraggingDraggable mouse_state = .DraggingDraggable
break state_label break state_label
} }
case MouseDraggable_RectangleCornerData: case MouseDraggable_RectangleCornerData:
circle_position := matrix3_transform_v2(grid_to_world_matrix, draggable.corner_ref^) circle_position := matrix3_transform_xy(grid_to_world_matrix, draggable.corner_ref^)
if linalg.distance(mouse, circle_position) <= program.mouse_draggable_radius { if linalg.distance(mouse, circle_position) <= program.mouse_draggable_radius {
program.mouse_current_draggable = draggable program.mouse_current_draggable = draggable
mouse_state = .DraggingDraggable mouse_state = .DraggingDraggable
@ -898,9 +927,9 @@ program_handle_mouse :: proc(mouse_state: MouseState, top_bar_container, matrix_
draggable := program.mouse_current_draggable draggable := program.mouse_current_draggable
switch draggable in draggable { switch draggable in draggable {
case MouseDraggable_Point: case MouseDraggable_Point:
draggable.ref^ = matrix3_transform_v2(world_to_grid_matrix, mouse) draggable.ref^ = matrix3_transform_xy(world_to_grid_matrix, mouse)
case MouseDraggable_RectangleCornerData: case MouseDraggable_RectangleCornerData:
position := matrix3_transform_v2(world_to_grid_matrix, mouse) position := matrix3_transform_xy(world_to_grid_matrix, mouse)
corners := rectangle_corners_move_corner(draggable.corners_ref^, position, draggable.corner) corners := rectangle_corners_move_corner(draggable.corners_ref^, position, draggable.corner)
draggable.corners_ref^ = corners draggable.corners_ref^ = corners
case MouseDraggable_RectangleData: case MouseDraggable_RectangleData:
@ -908,7 +937,7 @@ program_handle_mouse :: proc(mouse_state: MouseState, top_bar_container, matrix_
draggable.corners^ = rectangle_corners_add(draggable.corners^, mouse_delta_grid) draggable.corners^ = rectangle_corners_add(draggable.corners^, mouse_delta_grid)
} }
case .DraggingGrid: case .DraggingGrid:
program.grid.offset -= rl.GetMouseDelta() program.grid.offset -= rl.GetMouseDelta() * grid_get_zoom(program.grid.zoom)
if !rl.IsMouseButtonDown(.LEFT) { if !rl.IsMouseButtonDown(.LEFT) {
mouse_state = .Hover mouse_state = .Hover
break state_label break state_label

View file

@ -289,13 +289,31 @@ rect_get_size :: proc {rect2_get_size, irect2_get_size}
/// ## LINEAR ALGEBRA ## /// /// ## LINEAR ALGEBRA ## ///
matrix3_translate2 :: proc "contextless" (translation: v2) -> mat3 { matrix3_translate2 :: proc "contextless" (translation: v2) -> mat3 {
return { return {
1.0, 0.0, 0.0, 1.0, 0.0, translation.x,
0.0, 1.0, 0.0, 0.0, 1.0, translation.y,
translation.x, translation.y, 1.0, 0.0, 0.0, 1.0,
} }
} }
matrix3_transform_v2 :: proc "contextless" (m: mat3, v: v2) -> v2 { matrix3_scale2 :: proc "contextless" (scale: v2) -> mat3 {
return {
scale.x, 0.0, 0.0,
0.0, scale.y, 0.0,
0.0, 0.0, 1.0,
}
}
matrix3_rotate2 :: proc "contextless" (ang: f32) -> mat3 {
a := linalg.cos(ang)
b := linalg.sin(ang)
return {
a, b, 0,
-b, a, 0,
0, 0, 1,
}
}
matrix3_transform_xy :: proc "contextless" (m: mat3, v: v2) -> v2 {
return (m * v3{**v, 1.0}).xy return (m * v3{**v, 1.0}).xy
} }