mirror of
https://github.com/vlang/v.git
synced 2025-09-09 15:27:05 -04:00
math,examples: make 2048 use sliding animation for the tile movement (#23268)
This commit is contained in:
parent
66caf949a0
commit
7eec8b1cd7
@ -1,10 +1,22 @@
|
|||||||
import gg
|
import gg
|
||||||
import gx
|
import gx
|
||||||
import math
|
import math
|
||||||
|
import math.easing
|
||||||
import os.asset
|
import os.asset
|
||||||
import rand
|
import rand
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
const zooming_percent_per_frame = 5
|
||||||
|
const movement_percent_per_frame = 10
|
||||||
|
|
||||||
|
const window_title = 'V 2048'
|
||||||
|
const default_window_width = 544
|
||||||
|
const default_window_height = 560
|
||||||
|
|
||||||
|
const possible_moves = [Direction.up, .right, .down, .left]
|
||||||
|
const predictions_per_move = 300
|
||||||
|
const prediction_depth = 8
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
mut:
|
mut:
|
||||||
gg &gg.Context = unsafe { nil }
|
gg &gg.Context = unsafe { nil }
|
||||||
@ -14,7 +26,8 @@ mut:
|
|||||||
theme_idx int
|
theme_idx int
|
||||||
board Board
|
board Board
|
||||||
undo []Undo
|
undo []Undo
|
||||||
atickers [4][4]int
|
atickers [4][4]f64
|
||||||
|
mtickers [4][4]f64
|
||||||
state GameState = .play
|
state GameState = .play
|
||||||
tile_format TileFormat = .normal
|
tile_format TileFormat = .normal
|
||||||
moves int
|
moves int
|
||||||
@ -111,14 +124,6 @@ const themes = [
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
const window_title = 'V 2048'
|
|
||||||
const default_window_width = 544
|
|
||||||
const default_window_height = 560
|
|
||||||
const animation_length = 10 // frames
|
|
||||||
|
|
||||||
const possible_moves = [Direction.up, .right, .down, .left]
|
|
||||||
const predictions_per_move = 300
|
|
||||||
const prediction_depth = 8
|
|
||||||
|
|
||||||
struct Pos {
|
struct Pos {
|
||||||
x int = -1
|
x int = -1
|
||||||
@ -128,6 +133,7 @@ struct Pos {
|
|||||||
struct Board {
|
struct Board {
|
||||||
mut:
|
mut:
|
||||||
field [4][4]int
|
field [4][4]int
|
||||||
|
oidxs [4][4]u32 // old indexes of the fields, when != 0; each index is an encoding of its y,x coordinates = y << 16 | x
|
||||||
points int
|
points int
|
||||||
shifts int
|
shifts int
|
||||||
}
|
}
|
||||||
@ -141,6 +147,7 @@ struct TileLine {
|
|||||||
ypos int
|
ypos int
|
||||||
mut:
|
mut:
|
||||||
field [5]int
|
field [5]int
|
||||||
|
oidxs [5]u32
|
||||||
points int
|
points int
|
||||||
shifts int
|
shifts int
|
||||||
}
|
}
|
||||||
@ -201,6 +208,7 @@ fn (b Board) transpose() Board {
|
|||||||
for y in 0 .. 4 {
|
for y in 0 .. 4 {
|
||||||
for x in 0 .. 4 {
|
for x in 0 .. 4 {
|
||||||
res.field[y][x] = b.field[x][y]
|
res.field[y][x] = b.field[x][y]
|
||||||
|
res.oidxs[y][x] = b.oidxs[x][y]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
@ -211,6 +219,7 @@ fn (b Board) hmirror() Board {
|
|||||||
for y in 0 .. 4 {
|
for y in 0 .. 4 {
|
||||||
for x in 0 .. 4 {
|
for x in 0 .. 4 {
|
||||||
res.field[y][x] = b.field[y][3 - x]
|
res.field[y][x] = b.field[y][3 - x]
|
||||||
|
res.oidxs[y][x] = b.oidxs[y][3 - x]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
@ -241,6 +250,7 @@ fn (t TileLine) to_left() TileLine {
|
|||||||
res.shifts++
|
res.shifts++
|
||||||
for k := x; k < right_border_idx; k++ {
|
for k := x; k < right_border_idx; k++ {
|
||||||
res.field[k] = res.field[k + 1]
|
res.field[k] = res.field[k + 1]
|
||||||
|
res.oidxs[k] = res.oidxs[k + 1]
|
||||||
}
|
}
|
||||||
remaining_zeros--
|
remaining_zeros--
|
||||||
}
|
}
|
||||||
@ -255,6 +265,7 @@ fn (t TileLine) to_left() TileLine {
|
|||||||
if res.field[x] == res.field[x + 1] {
|
if res.field[x] == res.field[x + 1] {
|
||||||
for k := x; k < right_border_idx; k++ {
|
for k := x; k < right_border_idx; k++ {
|
||||||
res.field[k] = res.field[k + 1]
|
res.field[k] = res.field[k + 1]
|
||||||
|
res.oidxs[k] = res.oidxs[k + 1]
|
||||||
}
|
}
|
||||||
res.shifts++
|
res.shifts++
|
||||||
res.field[x]++
|
res.field[x]++
|
||||||
@ -272,18 +283,29 @@ fn (b Board) to_left() Board {
|
|||||||
}
|
}
|
||||||
for x in 0 .. 4 {
|
for x in 0 .. 4 {
|
||||||
hline.field[x] = b.field[y][x]
|
hline.field[x] = b.field[y][x]
|
||||||
|
hline.oidxs[x] = b.oidxs[y][x]
|
||||||
}
|
}
|
||||||
reshline := hline.to_left()
|
reshline := hline.to_left()
|
||||||
res.shifts += reshline.shifts
|
res.shifts += reshline.shifts
|
||||||
res.points += reshline.points
|
res.points += reshline.points
|
||||||
for x in 0 .. 4 {
|
for x in 0 .. 4 {
|
||||||
res.field[y][x] = reshline.field[x]
|
res.field[y][x] = reshline.field[x]
|
||||||
|
res.oidxs[y][x] = reshline.oidxs[x]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (b Board) move(d Direction) (Board, bool) {
|
fn yx2i(y int, x int) u32 {
|
||||||
|
return u32(y) << 16 | u32(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut b Board) move(d Direction) (Board, bool) {
|
||||||
|
for y in 0 .. 4 {
|
||||||
|
for x in 0 .. 4 {
|
||||||
|
b.oidxs[y][x] = yx2i(y, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
new := match d {
|
new := match d {
|
||||||
.left { b.to_left() }
|
.left { b.to_left() }
|
||||||
.right { b.hmirror().to_left().hmirror() }
|
.right { b.hmirror().to_left().hmirror() }
|
||||||
@ -291,9 +313,9 @@ fn (b Board) move(d Direction) (Board, bool) {
|
|||||||
.down { b.transpose().hmirror().to_left().hmirror().transpose() }
|
.down { b.transpose().hmirror().to_left().hmirror().transpose() }
|
||||||
}
|
}
|
||||||
// If the board hasn't changed, it's an illegal move, don't allow it.
|
// If the board hasn't changed, it's an illegal move, don't allow it.
|
||||||
for x in 0 .. 4 {
|
for y in 0 .. 4 {
|
||||||
for y in 0 .. 4 {
|
for x in 0 .. 4 {
|
||||||
if b.field[x][y] != new.field[x][y] {
|
if b.field[y][x] != new.field[y][x] {
|
||||||
return new, true
|
return new, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -324,11 +346,10 @@ fn (mut b Board) is_game_over() bool {
|
|||||||
fn (mut app App) update_tickers() {
|
fn (mut app App) update_tickers() {
|
||||||
for y in 0 .. 4 {
|
for y in 0 .. 4 {
|
||||||
for x in 0 .. 4 {
|
for x in 0 .. 4 {
|
||||||
mut old := app.atickers[y][x]
|
app.atickers[y][x] = math.clip(app.atickers[y][x] - f64(zooming_percent_per_frame) / 100.0,
|
||||||
if old > 0 {
|
0.0, 1.0)
|
||||||
old--
|
app.mtickers[y][x] = math.clip(app.mtickers[y][x] - f64(movement_percent_per_frame) / 100.0,
|
||||||
app.atickers[y][x] = old
|
0.0, 1.0)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -339,6 +360,7 @@ fn (mut app App) new_game() {
|
|||||||
for x in 0 .. 4 {
|
for x in 0 .. 4 {
|
||||||
app.board.field[y][x] = 0
|
app.board.field[y][x] = 0
|
||||||
app.atickers[y][x] = 0
|
app.atickers[y][x] = 0
|
||||||
|
app.mtickers[y][x] = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.state = .play
|
app.state = .play
|
||||||
@ -387,23 +409,26 @@ fn (mut b Board) place_random_tile() (Pos, int) {
|
|||||||
value := rand.f64n(1.0) or { 0.0 }
|
value := rand.f64n(1.0) or { 0.0 }
|
||||||
random_value := if value < 0.9 { 1 } else { 2 }
|
random_value := if value < 0.9 { 1 } else { 2 }
|
||||||
b.field[empty_pos.y][empty_pos.x] = random_value
|
b.field[empty_pos.y][empty_pos.x] = random_value
|
||||||
|
b.oidxs[empty_pos.y][empty_pos.x] = yx2i(empty_pos.y, empty_pos.x)
|
||||||
return empty_pos, random_value
|
return empty_pos, random_value
|
||||||
}
|
}
|
||||||
return Pos{}, 0
|
return Pos{}, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut app App) new_random_tile() {
|
fn (mut app App) new_random_tile() {
|
||||||
|
// do not animate empty fields:
|
||||||
for y in 0 .. 4 {
|
for y in 0 .. 4 {
|
||||||
for x in 0 .. 4 {
|
for x in 0 .. 4 {
|
||||||
fidx := app.board.field[y][x]
|
fidx := app.board.field[y][x]
|
||||||
if fidx == 0 {
|
if fidx == 0 {
|
||||||
app.atickers[y][x] = 0
|
app.atickers[y][x] = 0
|
||||||
|
app.board.oidxs[y][x] = 0xFFFF_FFFF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
empty_pos, random_value := app.board.place_random_tile()
|
empty_pos, random_value := app.board.place_random_tile()
|
||||||
if random_value > 0 {
|
if random_value > 0 {
|
||||||
app.atickers[empty_pos.y][empty_pos.x] = animation_length
|
app.atickers[empty_pos.y][empty_pos.x] = 1.0
|
||||||
}
|
}
|
||||||
if app.state != .freeplay {
|
if app.state != .freeplay {
|
||||||
app.check_for_victory()
|
app.check_for_victory()
|
||||||
@ -414,6 +439,13 @@ fn (mut app App) new_random_tile() {
|
|||||||
fn (mut app App) apply_new_board(new Board) {
|
fn (mut app App) apply_new_board(new Board) {
|
||||||
old := app.board
|
old := app.board
|
||||||
app.moves++
|
app.moves++
|
||||||
|
for y in 0 .. 4 {
|
||||||
|
for x in 0 .. 4 {
|
||||||
|
if old.oidxs[y][x] != new.oidxs[y][x] {
|
||||||
|
app.mtickers[y][x] = 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
app.board = new
|
app.board = new
|
||||||
app.undo << Undo{old, app.state}
|
app.undo << Undo{old, app.state}
|
||||||
app.new_random_tile()
|
app.new_random_tile()
|
||||||
@ -623,62 +655,114 @@ fn (app &App) draw_tiles() {
|
|||||||
// Draw the padding around the tiles
|
// Draw the padding around the tiles
|
||||||
app.gg.draw_rounded_rect_filled(xstart, ystart, tiles_size, tiles_size, tiles_size / 24,
|
app.gg.draw_rounded_rect_filled(xstart, ystart, tiles_size, tiles_size, tiles_size / 24,
|
||||||
app.theme.padding_color)
|
app.theme.padding_color)
|
||||||
// Draw the actual tiles
|
|
||||||
|
// Draw empty tiles:
|
||||||
|
for y in 0 .. 4 {
|
||||||
|
for x in 0 .. 4 {
|
||||||
|
tw := app.ui.tile_size
|
||||||
|
th := tw // square tiles, w == h
|
||||||
|
xoffset := xstart + app.ui.padding_size + x * toffset
|
||||||
|
yoffset := ystart + app.ui.padding_size + y * toffset
|
||||||
|
app.gg.draw_rounded_rect_filled(xoffset, yoffset, tw, th, tw / 8, app.theme.tile_colors[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the already placed and potentially moving tiles:
|
||||||
for y in 0 .. 4 {
|
for y in 0 .. 4 {
|
||||||
for x in 0 .. 4 {
|
for x in 0 .. 4 {
|
||||||
tidx := app.board.field[y][x]
|
tidx := app.board.field[y][x]
|
||||||
tile_color := if tidx < app.theme.tile_colors.len {
|
oidx := app.board.oidxs[y][x]
|
||||||
app.theme.tile_colors[tidx]
|
if tidx == 0 || oidx == 0xFFFF_FFFF {
|
||||||
} else {
|
continue
|
||||||
// If there isn't a specific color for this tile, reuse the last color available
|
|
||||||
app.theme.tile_colors.last()
|
|
||||||
}
|
}
|
||||||
anim_size := animation_length - app.atickers[y][x]
|
app.draw_one_tile(x, y, tidx)
|
||||||
tw := int(f64(app.ui.tile_size) / animation_length * anim_size)
|
}
|
||||||
th := tw // square tiles, w == h
|
}
|
||||||
xoffset := xstart + app.ui.padding_size + x * toffset + (app.ui.tile_size - tw) / 2
|
|
||||||
yoffset := ystart + app.ui.padding_size + y * toffset + (app.ui.tile_size - th) / 2
|
// Draw the newly placed random tiles on top of everything else:
|
||||||
app.gg.draw_rounded_rect_filled(xoffset, yoffset, tw, th, tw / 8, tile_color)
|
for y in 0 .. 4 {
|
||||||
if tidx != 0 { // 0 == blank spot
|
for x in 0 .. 4 {
|
||||||
xpos := xoffset + tw / 2
|
tidx := app.board.field[y][x]
|
||||||
ypos := yoffset + th / 2
|
oidx := app.board.oidxs[y][x]
|
||||||
mut fmt := app.label_format(.tile)
|
if oidx == 0xFFFF_FFFF && tidx != 0 {
|
||||||
fmt = gx.TextCfg{
|
app.draw_one_tile(x, y, tidx)
|
||||||
...fmt
|
|
||||||
size: int(f32(fmt.size - 1) / animation_length * anim_size)
|
|
||||||
}
|
|
||||||
match app.tile_format {
|
|
||||||
.normal {
|
|
||||||
app.gg.draw_text(xpos, ypos, '${1 << tidx}', fmt)
|
|
||||||
}
|
|
||||||
.log {
|
|
||||||
app.gg.draw_text(xpos, ypos, '${tidx}', fmt)
|
|
||||||
}
|
|
||||||
.exponent {
|
|
||||||
app.gg.draw_text(xpos, ypos, '2', fmt)
|
|
||||||
fs2 := int(f32(fmt.size) * 0.67)
|
|
||||||
app.gg.draw_text(xpos + app.ui.tile_size / 10, ypos - app.ui.tile_size / 8,
|
|
||||||
'${tidx}', gx.TextCfg{
|
|
||||||
...fmt
|
|
||||||
size: fs2
|
|
||||||
align: gx.HorizontalAlign.left
|
|
||||||
})
|
|
||||||
}
|
|
||||||
.shifts {
|
|
||||||
fs2 := int(f32(fmt.size) * 0.6)
|
|
||||||
app.gg.draw_text(xpos, ypos, '2<<${tidx - 1}', gx.TextCfg{
|
|
||||||
...fmt
|
|
||||||
size: fs2
|
|
||||||
})
|
|
||||||
}
|
|
||||||
.none {} // Don't draw any text here, colors only
|
|
||||||
.end {} // Should never get here
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (app &App) draw_one_tile(x int, y int, tidx int) {
|
||||||
|
xstart := app.ui.x_padding + app.ui.border_size
|
||||||
|
ystart := app.ui.y_padding + app.ui.border_size + app.ui.header_size
|
||||||
|
toffset := app.ui.tile_size + app.ui.padding_size
|
||||||
|
oidx := app.board.oidxs[y][x]
|
||||||
|
oy := oidx >> 16
|
||||||
|
ox := oidx & 0xFFFF
|
||||||
|
mut dx := 0
|
||||||
|
mut dy := 0
|
||||||
|
if oidx != 0xFFFF_FFFF {
|
||||||
|
scaling := app.ui.tile_size * easing.in_out_quint(app.mtickers[y][x])
|
||||||
|
if ox != x {
|
||||||
|
dx = math.clip(int(scaling * (f64(ox) - f64(x))), -4 * app.ui.tile_size, 4 * app.ui.tile_size)
|
||||||
|
}
|
||||||
|
if oy != y {
|
||||||
|
dy = math.clip(int(scaling * (f64(oy) - f64(y))), -4 * app.ui.tile_size, 4 * app.ui.tile_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tile_color := if tidx < app.theme.tile_colors.len {
|
||||||
|
app.theme.tile_colors[tidx]
|
||||||
|
} else {
|
||||||
|
// If there isn't a specific color for this tile, reuse the last color available
|
||||||
|
app.theme.tile_colors.last()
|
||||||
|
}
|
||||||
|
anim_size := 1.0 - app.atickers[y][x]
|
||||||
|
tw := int(f64(anim_size * app.ui.tile_size))
|
||||||
|
th := tw // square tiles, w == h
|
||||||
|
xoffset := dx + xstart + app.ui.padding_size + x * toffset + (app.ui.tile_size - tw) / 2
|
||||||
|
yoffset := dy + ystart + app.ui.padding_size + y * toffset + (app.ui.tile_size - th) / 2
|
||||||
|
app.gg.draw_rounded_rect_filled(xoffset, yoffset, tw, th, tw / 8, tile_color)
|
||||||
|
if tidx != 0 { // 0 == blank spot
|
||||||
|
xpos := xoffset + tw / 2
|
||||||
|
ypos := yoffset + th / 2
|
||||||
|
mut fmt := app.label_format(.tile)
|
||||||
|
fmt = gx.TextCfg{
|
||||||
|
...fmt
|
||||||
|
size: int(anim_size * (fmt.size - 1))
|
||||||
|
}
|
||||||
|
match app.tile_format {
|
||||||
|
.normal {
|
||||||
|
app.gg.draw_text(xpos, ypos, '${1 << tidx}', fmt)
|
||||||
|
}
|
||||||
|
.log {
|
||||||
|
app.gg.draw_text(xpos, ypos, '${tidx}', fmt)
|
||||||
|
}
|
||||||
|
.exponent {
|
||||||
|
app.gg.draw_text(xpos, ypos, '2', fmt)
|
||||||
|
fs2 := int(f32(fmt.size) * 0.67)
|
||||||
|
app.gg.draw_text(xpos + app.ui.tile_size / 10, ypos - app.ui.tile_size / 8,
|
||||||
|
'${tidx}', gx.TextCfg{
|
||||||
|
...fmt
|
||||||
|
size: fs2
|
||||||
|
align: gx.HorizontalAlign.left
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.shifts {
|
||||||
|
fs2 := int(f32(fmt.size) * 0.6)
|
||||||
|
app.gg.draw_text(xpos, ypos, '2<<${tidx - 1}', gx.TextCfg{
|
||||||
|
...fmt
|
||||||
|
size: fs2
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.none {} // Don't draw any text here, colors only
|
||||||
|
.end {} // Should never get here
|
||||||
|
}
|
||||||
|
// oidx_fmt := gx.TextCfg{...fmt,size: 14}
|
||||||
|
// app.gg.draw_text(xoffset + 50, yoffset + 15, 'y:${oidx >> 16}|x:${oidx & 0xFFFF}|m:${app.mtickers[y][x]:5.3f}', oidx_fmt)
|
||||||
|
// app.gg.draw_text(xoffset + 52, yoffset + 30, 'ox:${ox}|oy:${oy}', oidx_fmt)
|
||||||
|
// app.gg.draw_text(xoffset + 52, yoffset + 85, 'dx:${dx}|dy:${dy}', oidx_fmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut app App) handle_touches() {
|
fn (mut app App) handle_touches() {
|
||||||
s, e := app.touch.start, app.touch.end
|
s, e := app.touch.start, app.touch.end
|
||||||
adx, ady := math.abs(e.pos.x - s.pos.x), math.abs(e.pos.y - s.pos.y)
|
adx, ady := math.abs(e.pos.x - s.pos.x), math.abs(e.pos.y - s.pos.y)
|
||||||
@ -733,7 +817,7 @@ fn (mut app App) handle_swipe() {
|
|||||||
adx, ady := math.abs(dx), math.abs(dy)
|
adx, ady := math.abs(dx), math.abs(dy)
|
||||||
dmin := if math.min(adx, ady) > 0 { math.min(adx, ady) } else { 1 }
|
dmin := if math.min(adx, ady) > 0 { math.min(adx, ady) } else { 1 }
|
||||||
dmax := if math.max(adx, ady) > 0 { math.max(adx, ady) } else { 1 }
|
dmax := if math.max(adx, ady) > 0 { math.max(adx, ady) } else { 1 }
|
||||||
tdiff := int(e.time.unix_milli() - s.time.unix_milli())
|
tdiff := (e.time - s.time).milliseconds()
|
||||||
// TODO: make this calculation more accurate (don't use arbitrary numbers)
|
// TODO: make this calculation more accurate (don't use arbitrary numbers)
|
||||||
min_swipe_distance := int(math.sqrt(math.min(w, h) * tdiff / 100)) + 20
|
min_swipe_distance := int(math.sqrt(math.min(w, h) * tdiff / 100)) + 20
|
||||||
if dmax < min_swipe_distance {
|
if dmax < min_swipe_distance {
|
||||||
|
@ -135,10 +135,6 @@ pub fn (x f32) strlong() string {
|
|||||||
return strconv.f32_to_str_l(x)
|
return strconv.f32_to_str_l(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
-----------------------
|
|
||||||
----- C functions -----
|
|
||||||
*/
|
|
||||||
// f32_abs returns the absolute value of `a` as a `f32` value.
|
// f32_abs returns the absolute value of `a` as a `f32` value.
|
||||||
// Example: assert f32_abs(-2.0) == 2.0
|
// Example: assert f32_abs(-2.0) == 2.0
|
||||||
@[inline]
|
@[inline]
|
||||||
@ -149,38 +145,38 @@ pub fn f32_abs(a f32) f32 {
|
|||||||
// f64_abs returns the absolute value of `a` as a `f64` value.
|
// f64_abs returns the absolute value of `a` as a `f64` value.
|
||||||
// Example: assert f64_abs(-2.0) == f64(2.0)
|
// Example: assert f64_abs(-2.0) == f64(2.0)
|
||||||
@[inline]
|
@[inline]
|
||||||
fn f64_abs(a f64) f64 {
|
pub fn f64_abs(a f64) f64 {
|
||||||
return if a < 0 { -a } else { a }
|
return if a < 0 { -a } else { a }
|
||||||
}
|
}
|
||||||
|
|
||||||
// f32_max returns the largest `f32` of input `a` and `b`.
|
// f32_min returns the smaller `f32` of input `a` and `b`.
|
||||||
// Example: assert f32_max(2.0,3.0) == 3.0
|
|
||||||
@[inline]
|
|
||||||
pub fn f32_max(a f32, b f32) f32 {
|
|
||||||
return if a > b { a } else { b }
|
|
||||||
}
|
|
||||||
|
|
||||||
// f32_min returns the smallest `f32` of input `a` and `b`.
|
|
||||||
// Example: assert f32_min(2.0,3.0) == 2.0
|
// Example: assert f32_min(2.0,3.0) == 2.0
|
||||||
@[inline]
|
@[inline]
|
||||||
pub fn f32_min(a f32, b f32) f32 {
|
pub fn f32_min(a f32, b f32) f32 {
|
||||||
return if a < b { a } else { b }
|
return if a < b { a } else { b }
|
||||||
}
|
}
|
||||||
|
|
||||||
// f64_max returns the largest `f64` of input `a` and `b`.
|
// f32_max returns the larger `f32` of input `a` and `b`.
|
||||||
|
// Example: assert f32_max(2.0,3.0) == 3.0
|
||||||
|
@[inline]
|
||||||
|
pub fn f32_max(a f32, b f32) f32 {
|
||||||
|
return if a > b { a } else { b }
|
||||||
|
}
|
||||||
|
|
||||||
|
// f64_min returns the smaller `f64` of input `a` and `b`.
|
||||||
|
// Example: assert f64_min(2.0,3.0) == 2.0
|
||||||
|
@[inline]
|
||||||
|
pub fn f64_min(a f64, b f64) f64 {
|
||||||
|
return if a < b { a } else { b }
|
||||||
|
}
|
||||||
|
|
||||||
|
// f64_max returns the larger `f64` of input `a` and `b`.
|
||||||
// Example: assert f64_max(2.0,3.0) == 3.0
|
// Example: assert f64_max(2.0,3.0) == 3.0
|
||||||
@[inline]
|
@[inline]
|
||||||
pub fn f64_max(a f64, b f64) f64 {
|
pub fn f64_max(a f64, b f64) f64 {
|
||||||
return if a > b { a } else { b }
|
return if a > b { a } else { b }
|
||||||
}
|
}
|
||||||
|
|
||||||
// f64_min returns the smallest `f64` of input `a` and `b`.
|
|
||||||
// Example: assert f64_min(2.0,3.0) == 2.0
|
|
||||||
@[inline]
|
|
||||||
fn f64_min(a f64, b f64) f64 {
|
|
||||||
return if a < b { a } else { b }
|
|
||||||
}
|
|
||||||
|
|
||||||
// eq_epsilon returns true if the `f32` is equal to input `b`.
|
// eq_epsilon returns true if the `f32` is equal to input `b`.
|
||||||
// using an epsilon of typically 1E-5 or higher (backend/compiler dependent).
|
// using an epsilon of typically 1E-5 or higher (backend/compiler dependent).
|
||||||
// Example: assert f32(2.0).eq_epsilon(2.0)
|
// Example: assert f32(2.0).eq_epsilon(2.0)
|
||||||
|
26
vlib/math/interpolation.v
Normal file
26
vlib/math/interpolation.v
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
module math
|
||||||
|
|
||||||
|
// mix performs a linear interpolation (LERP) mix between `start` and `end`, using `t` to weight between them.
|
||||||
|
// `t` should be in the closed interval [0, 1].
|
||||||
|
// For `t` == 0, the output is `x`.
|
||||||
|
// Note: mix is calculated in such a way, that the output *will* be `y`, when `t` == 1.0 .
|
||||||
|
// See: https://registry.khronos.org/OpenGL-Refpages/gl4/html/mix.xhtml
|
||||||
|
// Also: https://en.wikipedia.org/wiki/Linear_interpolation .
|
||||||
|
@[inline]
|
||||||
|
pub fn mix[T](start T, end T, t T) T {
|
||||||
|
return start * (1 - t) + end * t
|
||||||
|
}
|
||||||
|
|
||||||
|
// clip constrain the given value `x`, to lie between two further values `min_value` and `max_value`.
|
||||||
|
// See: https://registry.khronos.org/OpenGL-Refpages/gl4/html/clamp.xhtml
|
||||||
|
// Also: https://en.wikipedia.org/wiki/Clamp_(function)
|
||||||
|
@[inline]
|
||||||
|
pub fn clip[T](x T, min_value T, max_value T) T {
|
||||||
|
return if x > max_value {
|
||||||
|
max_value
|
||||||
|
} else if x < min_value {
|
||||||
|
min_value
|
||||||
|
} else {
|
||||||
|
x
|
||||||
|
}
|
||||||
|
}
|
69
vlib/math/interpolation_bezier.v
Normal file
69
vlib/math/interpolation_bezier.v
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
module math
|
||||||
|
|
||||||
|
// BezierPoint represents point coordinates as floating point numbers.
|
||||||
|
// This type is used as the output of the cubic_bezier family of functions.
|
||||||
|
pub struct BezierPoint {
|
||||||
|
pub mut:
|
||||||
|
x f64
|
||||||
|
y f64
|
||||||
|
}
|
||||||
|
|
||||||
|
// cubic_bezier returns a linear interpolation between the control points,
|
||||||
|
// specified by their X and Y coordinates in a single array of points `p`, and given the parameter t,
|
||||||
|
// varying between 0.0 and 1.0 .
|
||||||
|
// When `t` == 0.0, the output is P[0] .
|
||||||
|
// When `t` == 1.0, the output is P[3] .
|
||||||
|
// The points x[1],y[1] and x[2],y[2], serve as attractors.
|
||||||
|
@[direct_array_access; inline]
|
||||||
|
pub fn cubic_bezier(t f64, p []BezierPoint) BezierPoint {
|
||||||
|
if p.len != 4 {
|
||||||
|
panic('invalid p.len')
|
||||||
|
}
|
||||||
|
return cubic_bezier_coords(t, p[0].x, p[1].x, p[2].x, p[3].x, p[0].y, p[1].y, p[2].y,
|
||||||
|
p[3].y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cubic_bezier_a returns a linear interpolation between the control points,
|
||||||
|
// specified by their X and Y coordinates in 2 arrays, and given the parameter t,
|
||||||
|
// varying between 0.0 and 1.0 .
|
||||||
|
// When `t` == 0.0, the output is x[0],y[0] .
|
||||||
|
// When `t` == 1.0, the output is x[3],y[3] .
|
||||||
|
// The points x[1],y[1] and x[2],y[2], serve as attractors.
|
||||||
|
@[direct_array_access; inline]
|
||||||
|
pub fn cubic_bezier_a(t f64, x []f64, y []f64) BezierPoint {
|
||||||
|
if x.len != 4 {
|
||||||
|
panic('invalid x.len')
|
||||||
|
}
|
||||||
|
if y.len != 4 {
|
||||||
|
panic('invalid y.len')
|
||||||
|
}
|
||||||
|
return cubic_bezier_coords(t, x[0], x[1], x[2], x[3], y[0], y[1], y[2], y[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
// cubic_bezier_fa returns a linear interpolation between the control points,
|
||||||
|
// specified by their X and Y coordinates in 2 fixed arrays, and given the parameter t,
|
||||||
|
// varying between 0.0 and 1.0 .
|
||||||
|
// When `t` == 0.0, the output is x[0],y[0] .
|
||||||
|
// When `t` == 1.0, the output is x[3],y[3] .
|
||||||
|
// The points x[1],y[1] and x[2],y[2], serve as attractors.
|
||||||
|
@[direct_array_access; inline]
|
||||||
|
pub fn cubic_bezier_fa(t f64, x [4]f64, y [4]f64) BezierPoint {
|
||||||
|
return cubic_bezier_coords(t, x[0], x[1], x[2], x[3], y[0], y[1], y[2], y[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
// cubic_bezier_coords returns a linear interpolation between the control points,
|
||||||
|
// specified by their X and Y coordinates, and given the parameter t,
|
||||||
|
// varying between 0.0 and 1.0 .
|
||||||
|
// When `t` == 0.0, the output is x0,y0 .
|
||||||
|
// When `t` == 1.0, the output is x3,y3 .
|
||||||
|
// The points x1,y1 and x2,y2, serve as attractors.
|
||||||
|
@[inline]
|
||||||
|
pub fn cubic_bezier_coords(t f64, x0 f64, x1 f64, x2 f64, x3 f64, y0 f64, y1 f64, y2 f64, y3 f64) BezierPoint {
|
||||||
|
p0 := pow(1 - t, 3)
|
||||||
|
p1 := 3 * t * pow(1 - t, 2)
|
||||||
|
p2 := 3 * (1 - t) * pow(t, 2)
|
||||||
|
p3 := pow(t, 3)
|
||||||
|
xt := p0 * x0 + p1 * x1 + p2 * x2 + p3 * x3
|
||||||
|
yt := p0 * y0 + p1 * y1 + p2 * y2 + p3 * y3
|
||||||
|
return BezierPoint{xt, yt}
|
||||||
|
}
|
67
vlib/math/interpolation_test.v
Normal file
67
vlib/math/interpolation_test.v
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
fn test_mix() {
|
||||||
|
assert math.mix(0.0, 100.0, 0.0) == 0.0
|
||||||
|
assert math.mix(0.0, 100.0, 0.1) == 10.0
|
||||||
|
assert math.mix(0.0, 100.0, 0.2) == 20.0
|
||||||
|
assert math.mix(0.0, 100.0, 0.5) == 50.0
|
||||||
|
assert math.mix(0.0, 100.0, 0.8) == 80.0
|
||||||
|
assert math.mix(0.0, 100.0, 0.9) == 90.0
|
||||||
|
assert math.mix(0.0, 100.0, 1.0) == 100.0
|
||||||
|
|
||||||
|
assert math.mix(100.0, 500.0, 0.0) == 100.0
|
||||||
|
assert math.mix(100.0, 500.0, 0.1) == 140.0
|
||||||
|
assert math.mix(100.0, 500.0, 0.2) == 180.0
|
||||||
|
assert math.mix(100.0, 500.0, 0.5) == 300.0
|
||||||
|
assert math.mix(100.0, 500.0, 0.8) == 420.0
|
||||||
|
assert math.mix(100.0, 500.0, 0.9) == 460.0
|
||||||
|
assert math.mix(100.0, 500.0, 1.0) == 500.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_clip() {
|
||||||
|
assert math.clip(0.0, 10.0, 50.0) == 10.0
|
||||||
|
assert math.clip(5.5, 10.0, 50.0) == 10.0
|
||||||
|
assert math.clip(10.0, 10.0, 50.0) == 10.0
|
||||||
|
assert math.clip(20.0, 10.0, 50.0) == 20.0
|
||||||
|
assert math.clip(50.0, 10.0, 50.0) == 50.0
|
||||||
|
assert math.clip(80.0, 10.0, 50.0) == 50.0
|
||||||
|
assert math.clip(90.5, 10.0, 50.0) == 50.0
|
||||||
|
|
||||||
|
assert math.clip(0, 10, 50) == 10
|
||||||
|
assert math.clip(5, 10, 50) == 10
|
||||||
|
assert math.clip(10, 10, 50) == 10
|
||||||
|
assert math.clip(20, 10, 50) == 20
|
||||||
|
assert math.clip(50, 10, 50) == 50
|
||||||
|
assert math.clip(80, 10, 50) == 50
|
||||||
|
assert math.clip(90, 10, 50) == 50
|
||||||
|
}
|
||||||
|
|
||||||
|
// The test curve control points are taken from: https://cubic-bezier.com/#.19,-0.09,.42,1.19
|
||||||
|
const b = [
|
||||||
|
math.BezierPoint{0, 0},
|
||||||
|
math.BezierPoint{0.19, -0.09},
|
||||||
|
math.BezierPoint{0.42, 1.19},
|
||||||
|
math.BezierPoint{1.0, 1.0},
|
||||||
|
]
|
||||||
|
const bx = b.map(it.x)
|
||||||
|
const by = b.map(it.y)
|
||||||
|
const bx_fa = [4]f64{init: b[index].x}
|
||||||
|
const by_fa = [4]f64{init: b[index].y}
|
||||||
|
|
||||||
|
fn test_cubic_bezier() {
|
||||||
|
assert math.cubic_bezier(0.0, b) == math.BezierPoint{b[0].x, b[0].x}
|
||||||
|
assert math.cubic_bezier(0.5, b) == math.BezierPoint{0.35375, 0.5375}
|
||||||
|
assert math.cubic_bezier(1.0, b) == math.BezierPoint{b[3].x, b[3].x}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_cubic_bezier_a() {
|
||||||
|
assert math.cubic_bezier_a(0.0, bx, by) == math.BezierPoint{bx[0], by[0]}
|
||||||
|
assert math.cubic_bezier_a(0.5, bx, by) == math.BezierPoint{0.35375, 0.5375}
|
||||||
|
assert math.cubic_bezier_a(1.0, bx, by) == math.BezierPoint{bx[3], by[3]}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_cubic_bezier_fa() {
|
||||||
|
assert math.cubic_bezier_fa(0.0, bx_fa, by_fa) == math.BezierPoint{bx_fa[0], by_fa[0]}
|
||||||
|
assert math.cubic_bezier_fa(0.5, bx_fa, by_fa) == math.BezierPoint{0.35375, 0.5375}
|
||||||
|
assert math.cubic_bezier_fa(1.0, bx_fa, by_fa) == math.BezierPoint{bx_fa[3], by_fa[3]}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user