diff options
Diffstat (limited to 'v_windows/v/examples')
203 files changed, 19684 insertions, 0 deletions
diff --git a/v_windows/v/examples/.gitignore b/v_windows/v/examples/.gitignore new file mode 100644 index 0000000..8299fd7 --- /dev/null +++ b/v_windows/v/examples/.gitignore @@ -0,0 +1,2 @@ +*.ppm +*.js
\ No newline at end of file diff --git a/v_windows/v/examples/2048/.gitignore b/v_windows/v/examples/2048/.gitignore new file mode 100644 index 0000000..e324642 --- /dev/null +++ b/v_windows/v/examples/2048/.gitignore @@ -0,0 +1,2 @@ +2048 +main diff --git a/v_windows/v/examples/2048/2048.v b/v_windows/v/examples/2048/2048.v new file mode 100644 index 0000000..d237226 --- /dev/null +++ b/v_windows/v/examples/2048/2048.v @@ -0,0 +1,939 @@ +import gg +import gx +import math +import math.mathutil as mu +import os +import rand +import time + +struct App { +mut: + gg &gg.Context = 0 + touch TouchInfo + ui Ui + theme &Theme = themes[0] + theme_idx int + board Board + undo []Undo + atickers [4][4]int + state GameState = .play + tile_format TileFormat = .normal + moves int + perf &Perf = 0 + is_ai_mode bool +} + +struct Ui { +mut: + dpi_scale f32 + tile_size int + border_size int + padding_size int + header_size int + font_size int + window_width int + window_height int + x_padding int + y_padding int +} + +struct Theme { + bg_color gx.Color + padding_color gx.Color + text_color gx.Color + game_over_color gx.Color + victory_color gx.Color + tile_colors []gx.Color +} + +const ( + themes = [ + &Theme{ + bg_color: gx.rgb(250, 248, 239) + padding_color: gx.rgb(143, 130, 119) + victory_color: gx.rgb(100, 160, 100) + game_over_color: gx.rgb(190, 50, 50) + text_color: gx.black + tile_colors: [ + gx.rgb(205, 193, 180), /* Empty / 0 tile */ + gx.rgb(238, 228, 218), /* 2 */ + gx.rgb(237, 224, 200), /* 4 */ + gx.rgb(242, 177, 121), /* 8 */ + gx.rgb(245, 149, 99), /* 16 */ + gx.rgb(246, 124, 95), /* 32 */ + gx.rgb(246, 94, 59), /* 64 */ + gx.rgb(237, 207, 114), /* 128 */ + gx.rgb(237, 204, 97), /* 256 */ + gx.rgb(237, 200, 80), /* 512 */ + gx.rgb(237, 197, 63), /* 1024 */ + gx.rgb(237, 194, 46), + ] + }, + &Theme{ + bg_color: gx.rgb(55, 55, 55) + padding_color: gx.rgb(68, 60, 59) + victory_color: gx.rgb(100, 160, 100) + game_over_color: gx.rgb(190, 50, 50) + text_color: gx.white + tile_colors: [ + gx.rgb(123, 115, 108), + gx.rgb(142, 136, 130), + gx.rgb(142, 134, 120), + gx.rgb(145, 106, 72), + gx.rgb(147, 89, 59), + gx.rgb(147, 74, 57), + gx.rgb(147, 56, 35), + gx.rgb(142, 124, 68), + gx.rgb(142, 122, 58), + gx.rgb(142, 120, 48), + gx.rgb(142, 118, 37), + gx.rgb(142, 116, 27), + ] + }, + &Theme{ + bg_color: gx.rgb(38, 38, 66) + padding_color: gx.rgb(58, 50, 74) + victory_color: gx.rgb(100, 160, 100) + game_over_color: gx.rgb(190, 50, 50) + text_color: gx.white + tile_colors: [ + gx.rgb(92, 86, 140), + gx.rgb(106, 99, 169), + gx.rgb(106, 97, 156), + gx.rgb(108, 79, 93), + gx.rgb(110, 66, 76), + gx.rgb(110, 55, 74), + gx.rgb(110, 42, 45), + gx.rgb(106, 93, 88), + gx.rgb(106, 91, 75), + gx.rgb(106, 90, 62), + gx.rgb(106, 88, 48), + gx.rgb(106, 87, 35), + ] + }, + ] + window_title = 'V 2048' + default_window_width = 544 + default_window_height = 560 + animation_length = 10 // frames + frames_per_ai_move = 8 + possible_moves = [Direction.up, .right, .down, .left] + predictions_per_move = 200 + prediction_depth = 8 +) + +// Used for performance monitoring when `-d showfps` is passed, unused / optimized out otherwise +struct Perf { +mut: + frame int + frame_old int + frame_sw time.StopWatch = time.new_stopwatch() + second_sw time.StopWatch = time.new_stopwatch() +} + +struct Pos { + x int = -1 + y int = -1 +} + +struct Board { +mut: + field [4][4]int + points int + shifts int +} + +struct Undo { + board Board + state GameState +} + +struct TileLine { + ypos int +mut: + field [5]int + points int + shifts int +} + +struct TouchInfo { +mut: + start Touch + end Touch +} + +struct Touch { +mut: + pos Pos + time time.Time +} + +enum TileFormat { + normal + log + exponent + shifts + none_ + end_ // To know when to wrap around +} + +enum GameState { + play + over + victory + freeplay +} + +enum LabelKind { + points + moves + tile + victory + game_over + score_end +} + +enum Direction { + up + down + left + right +} + +// Utility functions +[inline] +fn avg(a int, b int) int { + return (a + b) / 2 +} + +fn (b Board) transpose() Board { + mut res := b + for y in 0 .. 4 { + for x in 0 .. 4 { + res.field[y][x] = b.field[x][y] + } + } + return res +} + +fn (b Board) hmirror() Board { + mut res := b + for y in 0 .. 4 { + for x in 0 .. 4 { + res.field[y][x] = b.field[y][3 - x] + } + } + return res +} + +fn (t TileLine) to_left() TileLine { + right_border_idx := 4 + mut res := t + mut zeros := 0 + mut nonzeros := 0 + // gather meta info about the line: + for x in res.field { + if x == 0 { + zeros++ + } else { + nonzeros++ + } + } + if nonzeros == 0 { + // when all the tiles are empty, there is nothing left to do + return res + } + if zeros > 0 { + // we have some 0s, do shifts to compact them: + mut remaining_zeros := zeros + for x := 0; x < right_border_idx - 1; x++ { + for res.field[x] == 0 && remaining_zeros > 0 { + res.shifts++ + for k := x; k < right_border_idx; k++ { + res.field[k] = res.field[k + 1] + } + remaining_zeros-- + } + } + } + // At this point, the non 0 tiles are all on the left, with no empty spaces + // between them. we can safely merge them, when they have the same value: + for x := 0; x < right_border_idx - 1; x++ { + if res.field[x] == 0 { + break + } + if res.field[x] == res.field[x + 1] { + for k := x; k < right_border_idx; k++ { + res.field[k] = res.field[k + 1] + } + res.shifts++ + res.field[x]++ + res.points += 1 << res.field[x] + } + } + return res +} + +fn (b Board) to_left() Board { + mut res := b + for y in 0 .. 4 { + mut hline := TileLine{ + ypos: y + } + for x in 0 .. 4 { + hline.field[x] = b.field[y][x] + } + reshline := hline.to_left() + res.shifts += reshline.shifts + res.points += reshline.points + for x in 0 .. 4 { + res.field[y][x] = reshline.field[x] + } + } + return res +} + +fn (b Board) move(d Direction) (Board, bool) { + new := match d { + .left { b.to_left() } + .right { b.hmirror().to_left().hmirror() } + .up { b.transpose().to_left().transpose() } + .down { b.transpose().hmirror().to_left().hmirror().transpose() } + } + // 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 { + if b.field[x][y] != new.field[x][y] { + return new, true + } + } + } + return new, false +} + +fn (mut b Board) is_game_over() bool { + for y in 0 .. 4 { + for x in 0 .. 4 { + fidx := b.field[y][x] + if fidx == 0 { + // there are remaining zeros + return false + } + if (x > 0 && fidx == b.field[y][x - 1]) + || (x < 4 - 1 && fidx == b.field[y][x + 1]) + || (y > 0 && fidx == b.field[y - 1][x]) + || (y < 4 - 1 && fidx == b.field[y + 1][x]) { + // there are remaining merges + return false + } + } + } + return true +} + +fn (mut app App) update_tickers() { + for y in 0 .. 4 { + for x in 0 .. 4 { + mut old := app.atickers[y][x] + if old > 0 { + old-- + app.atickers[y][x] = old + } + } + } +} + +fn (mut app App) new_game() { + app.board = Board{} + for y in 0 .. 4 { + for x in 0 .. 4 { + app.board.field[y][x] = 0 + app.atickers[y][x] = 0 + } + } + app.state = .play + app.undo = []Undo{cap: 4096} + app.moves = 0 + app.new_random_tile() + app.new_random_tile() +} + +[inline] +fn (mut app App) check_for_victory() { + for y in 0 .. 4 { + for x in 0 .. 4 { + fidx := app.board.field[y][x] + if fidx == 11 { + app.state = .victory + return + } + } + } +} + +[inline] +fn (mut app App) check_for_game_over() { + if app.board.is_game_over() { + app.state = .over + } +} + +fn (mut b Board) place_random_tile() (Pos, int) { + mut etiles := [16]Pos{} + mut empty_tiles_max := 0 + for y in 0 .. 4 { + for x in 0 .. 4 { + fidx := b.field[y][x] + if fidx == 0 { + etiles[empty_tiles_max] = Pos{x, y} + empty_tiles_max++ + } + } + } + if empty_tiles_max > 0 { + new_random_tile_index := rand.intn(empty_tiles_max) + empty_pos := etiles[new_random_tile_index] + // 10% chance of getting a `4` tile + random_value := if rand.f64n(1.0) < 0.9 { 1 } else { 2 } + b.field[empty_pos.y][empty_pos.x] = random_value + return empty_pos, random_value + } + return Pos{}, 0 +} + +fn (mut app App) new_random_tile() { + for y in 0 .. 4 { + for x in 0 .. 4 { + fidx := app.board.field[y][x] + if fidx == 0 { + app.atickers[y][x] = 0 + } + } + } + empty_pos, random_value := app.board.place_random_tile() + if random_value > 0 { + app.atickers[empty_pos.y][empty_pos.x] = animation_length + } + if app.state != .freeplay { + app.check_for_victory() + } + app.check_for_game_over() +} + +fn (mut app App) apply_new_board(new Board) { + old := app.board + app.moves++ + app.board = new + app.undo << Undo{old, app.state} + app.new_random_tile() +} + +fn (mut app App) move(d Direction) { + new, is_valid := app.board.move(d) + if !is_valid { + return + } + app.apply_new_board(new) +} + +struct Prediction { +mut: + move Direction + mpoints f64 + mcmoves f64 +} + +fn (p Prediction) str() string { + return '{ move: ${p.move:5}, mpoints: ${p.mpoints:6.2f}, mcmoves: ${p.mcmoves:6.2f} }' +} + +fn (mut app App) ai_move() { + mut predictions := [4]Prediction{} + mut is_valid := false + think_watch := time.new_stopwatch() + for move in possible_moves { + move_idx := int(move) + predictions[move_idx].move = move + mut mpoints := 0 + mut mcmoves := 0 + for _ in 0 .. predictions_per_move { + mut cboard := app.board + cboard, is_valid = cboard.move(move) + if !is_valid || cboard.is_game_over() { + continue + } + mpoints += cboard.points + cboard.place_random_tile() + mut cmoves := 0 + for !cboard.is_game_over() { + nmove := possible_moves[rand.intn(possible_moves.len)] + cboard, is_valid = cboard.move(nmove) + if !is_valid { + continue + } + cboard.place_random_tile() + cmoves++ + if cmoves > prediction_depth { + break + } + } + mpoints += cboard.points + mcmoves += cmoves + } + predictions[move_idx].mpoints = f64(mpoints) / predictions_per_move + predictions[move_idx].mcmoves = f64(mcmoves) / predictions_per_move + } + think_time := think_watch.elapsed().milliseconds() + mut bestprediction := Prediction{ + mpoints: -1 + } + for move_idx in 0 .. possible_moves.len { + if bestprediction.mpoints < predictions[move_idx].mpoints { + bestprediction = predictions[move_idx] + } + } + eprintln('Simulation time: ${think_time:4}ms | best $bestprediction') + app.move(bestprediction.move) +} + +fn (app &App) label_format(kind LabelKind) gx.TextCfg { + match kind { + .points { + return gx.TextCfg{ + color: if app.state in [.over, .victory] { gx.white } else { app.theme.text_color } + align: .left + size: app.ui.font_size / 2 + } + } + .moves { + return gx.TextCfg{ + color: if app.state in [.over, .victory] { gx.white } else { app.theme.text_color } + align: .right + size: app.ui.font_size / 2 + } + } + .tile { + return gx.TextCfg{ + color: app.theme.text_color + align: .center + vertical_align: .middle + size: app.ui.font_size + } + } + .victory { + return gx.TextCfg{ + color: app.theme.victory_color + align: .center + vertical_align: .middle + size: app.ui.font_size * 2 + } + } + .game_over { + return gx.TextCfg{ + color: app.theme.game_over_color + align: .center + vertical_align: .middle + size: app.ui.font_size * 2 + } + } + .score_end { + return gx.TextCfg{ + color: gx.white + align: .center + vertical_align: .middle + size: app.ui.font_size * 3 / 4 + } + } + } +} + +[inline] +fn (mut app App) set_theme(idx int) { + theme := themes[idx] + app.theme_idx = idx + app.theme = theme + app.gg.set_bg_color(theme.bg_color) +} + +fn (mut app App) resize() { + mut s := gg.dpi_scale() + if s == 0.0 { + s = 1.0 + } + window_size := gg.window_size() + w := window_size.width + h := window_size.height + m := f32(mu.min(w, h)) + app.ui.dpi_scale = s + app.ui.window_width = w + app.ui.window_height = h + app.ui.padding_size = int(m / 38) + app.ui.header_size = app.ui.padding_size + app.ui.border_size = app.ui.padding_size * 2 + app.ui.tile_size = int((m - app.ui.padding_size * 5 - app.ui.border_size * 2) / 4) + app.ui.font_size = int(m / 10) + // If the window's height is greater than its width, center the board vertically. + // If not, center it horizontally + if w > h { + app.ui.y_padding = 0 + app.ui.x_padding = (app.ui.window_width - app.ui.window_height) / 2 + } else { + app.ui.y_padding = (app.ui.window_height - app.ui.window_width - app.ui.header_size) / 2 + app.ui.x_padding = 0 + } +} + +fn (app &App) draw() { + xpad, ypad := app.ui.x_padding, app.ui.y_padding + ww := app.ui.window_width + wh := app.ui.window_height + m := mu.min(ww, wh) + labelx := xpad + app.ui.border_size + labely := ypad + app.ui.border_size / 2 + app.draw_tiles() + // TODO: Make transparency work in `gg` + if app.state == .over { + app.gg.draw_rect(0, 0, ww, wh, gx.rgba(10, 0, 0, 180)) + app.gg.draw_text(ww / 2, (m * 4 / 10) + ypad, 'Game Over', app.label_format(.game_over)) + f := app.label_format(.tile) + msg := $if android { 'Tap to restart' } $else { 'Press `r` to restart' } + app.gg.draw_text(ww / 2, (m * 6 / 10) + ypad, msg, gx.TextCfg{ + ...f + color: gx.white + size: f.size * 3 / 4 + }) + } + if app.state == .victory { + app.gg.draw_rect(0, 0, ww, wh, gx.rgba(0, 10, 0, 180)) + app.gg.draw_text(ww / 2, (m * 4 / 10) + ypad, 'Victory!', app.label_format(.victory)) + // f := app.label_format(.tile) + msg1 := $if android { 'Tap to continue' } $else { 'Press `space` to continue' } + msg2 := $if android { 'Tap to restart' } $else { 'Press `r` to restart' } + app.gg.draw_text(ww / 2, (m * 6 / 10) + ypad, msg1, app.label_format(.score_end)) + app.gg.draw_text(ww / 2, (m * 8 / 10) + ypad, msg2, app.label_format(.score_end)) + } + // Draw at the end, so that it's on top of the victory / game over overlays + app.gg.draw_text(labelx, labely, 'Points: $app.board.points', app.label_format(.points)) + app.gg.draw_text(ww - labelx, labely, 'Moves: $app.moves', app.label_format(.moves)) +} + +fn (app &App) draw_tiles() { + 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 + tiles_size := mu.min(app.ui.window_width, app.ui.window_height) - app.ui.border_size * 2 + // Draw the padding around the tiles + app.gg.draw_rounded_rect(xstart, ystart, tiles_size, tiles_size, tiles_size / 24, + app.theme.padding_color) + // Draw the actual tiles + for y in 0 .. 4 { + for x in 0 .. 4 { + tidx := app.board.field[y][x] + 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 := animation_length - app.atickers[y][x] + 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 + app.gg.draw_rounded_rect(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(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 (mut app App) handle_touches() { + s, e := app.touch.start, app.touch.end + adx, ady := mu.abs(e.pos.x - s.pos.x), mu.abs(e.pos.y - s.pos.y) + if mu.max(adx, ady) < 10 { + app.handle_tap() + } else { + app.handle_swipe() + } +} + +fn (mut app App) handle_tap() { + _, ypad := app.ui.x_padding, app.ui.y_padding + w, h := app.ui.window_width, app.ui.window_height + m := mu.min(w, h) + s, e := app.touch.start, app.touch.end + avgx, avgy := avg(s.pos.x, e.pos.x), avg(s.pos.y, e.pos.y) + // TODO: Replace "touch spots" with actual buttons + // bottom left -> change theme + if avgx < 50 && h - avgy < 50 { + app.next_theme() + } + // bottom right -> change tile format + if w - avgx < 50 && h - avgy < 50 { + app.next_tile_format() + } + if app.state == .victory { + if avgy > (m / 2) + ypad { + if avgy < (m * 7 / 10) + ypad { + app.state = .freeplay + } else if avgy < (m * 9 / 10) + ypad { + app.new_game() + } else { + // TODO remove and implement an actual way to toggle themes on mobile + } + } + } else if app.state == .over { + if avgy > (m / 2) + ypad && avgy < (m * 7 / 10) + ypad { + app.new_game() + } + } +} + +fn (mut app App) handle_swipe() { + // Currently, swipes are only used to move the tiles. + // If the user's not playing, exit early to avoid all the unnecessary calculations + if app.state !in [.play, .freeplay] { + return + } + s, e := app.touch.start, app.touch.end + w, h := app.ui.window_width, app.ui.window_height + dx, dy := e.pos.x - s.pos.x, e.pos.y - s.pos.y + adx, ady := mu.abs(dx), mu.abs(dy) + dmin := if mu.min(adx, ady) > 0 { mu.min(adx, ady) } else { 1 } + dmax := if mu.max(adx, ady) > 0 { mu.max(adx, ady) } else { 1 } + tdiff := int(e.time.unix_time_milli() - s.time.unix_time_milli()) + // TODO: make this calculation more accurate (don't use arbitrary numbers) + min_swipe_distance := int(math.sqrt(mu.min(w, h) * tdiff / 100)) + 20 + if dmax < min_swipe_distance { + return + } + // Swipe was too short + if dmax / dmin < 2 { + return + } + // Swiped diagonally + if adx > ady { + if dx < 0 { + app.move(.left) + } else { + app.move(.right) + } + } else { + if dy < 0 { + app.move(.up) + } else { + app.move(.down) + } + } +} + +[inline] +fn (mut app App) next_theme() { + app.set_theme(if app.theme_idx == themes.len - 1 { 0 } else { app.theme_idx + 1 }) +} + +[inline] +fn (mut app App) next_tile_format() { + app.tile_format = TileFormat(int(app.tile_format) + 1) + if app.tile_format == .end_ { + app.tile_format = .normal + } +} + +[inline] +fn (mut app App) undo() { + if app.undo.len > 0 { + undo := app.undo.pop() + app.board = undo.board + app.state = undo.state + app.moves-- + } +} + +fn (mut app App) on_key_down(key gg.KeyCode) { + // these keys are independent from the game state: + match key { + .a { app.is_ai_mode = !app.is_ai_mode } + .escape { exit(0) } + .n, .r { app.new_game() } + .backspace { app.undo() } + .enter { app.next_tile_format() } + .j { app.state = .over } + .t { app.next_theme() } + else {} + } + if app.state in [.play, .freeplay] { + match key { + .w, .up { app.move(.up) } + .a, .left { app.move(.left) } + .s, .down { app.move(.down) } + .d, .right { app.move(.right) } + else {} + } + } + if app.state == .victory { + if key == .space { + app.state = .freeplay + } + } +} + +fn on_event(e &gg.Event, mut app App) { + match e.typ { + .key_down { + app.on_key_down(e.key_code) + } + .resized, .restored, .resumed { + app.resize() + } + .touches_began { + if e.num_touches > 0 { + t := e.touches[0] + app.touch.start = Touch{ + pos: Pos{ + x: int(t.pos_x / app.ui.dpi_scale) + y: int(t.pos_y / app.ui.dpi_scale) + } + time: time.now() + } + } + } + .touches_ended { + if e.num_touches > 0 { + t := e.touches[0] + app.touch.end = Touch{ + pos: Pos{ + x: int(t.pos_x / app.ui.dpi_scale) + y: int(t.pos_y / app.ui.dpi_scale) + } + time: time.now() + } + app.handle_touches() + } + } + .mouse_down { + app.touch.start = Touch{ + pos: Pos{ + x: int(e.mouse_x / app.ui.dpi_scale) + y: int(e.mouse_y / app.ui.dpi_scale) + } + time: time.now() + } + } + .mouse_up { + app.touch.end = Touch{ + pos: Pos{ + x: int(e.mouse_x / app.ui.dpi_scale) + y: int(e.mouse_y / app.ui.dpi_scale) + } + time: time.now() + } + app.handle_touches() + } + else {} + } +} + +fn frame(mut app App) { + $if showfps ? { + app.perf.frame_sw.restart() + } + app.gg.begin() + app.update_tickers() + app.draw() + app.perf.frame++ + if app.is_ai_mode && app.state in [.play, .freeplay] && app.perf.frame % frames_per_ai_move == 0 { + app.ai_move() + } + $if showfps ? { + app.showfps() + } + app.gg.end() +} + +fn init(mut app App) { + app.resize() + $if showfps ? { + app.perf.frame_sw.restart() + app.perf.second_sw.restart() + } +} + +fn (mut app App) showfps() { + println(app.perf.frame_sw.elapsed().microseconds()) + f := app.perf.frame + if (f & 127) == 0 { + last_frame_us := app.perf.frame_sw.elapsed().microseconds() + ticks := f64(app.perf.second_sw.elapsed().milliseconds()) + fps := f64(app.perf.frame - app.perf.frame_old) * ticks / 1000 / 4.5 + last_fps := 128000.0 / ticks + eprintln('frame ${f:-5} | avg. fps: ${fps:-5.1f} | avg. last 128 fps: ${last_fps:-5.1f} | last frame time: ${last_frame_us:-4}µs') + app.perf.second_sw.restart() + app.perf.frame_old = f + } +} + +fn main() { + mut app := &App{} + app.new_game() + mut font_path := os.resource_abs_path(os.join_path('../assets/fonts/', 'RobotoMono-Regular.ttf')) + $if android { + font_path = 'fonts/RobotoMono-Regular.ttf' + } + mut window_title_ := 'V 2048' + // TODO: Make emcc a real platform ifdef + $if emscripten ? { + // in emscripten, sokol uses `window_title` as the selector to the canvas it'll render to, + // and since `document.querySelector('V 2048')` isn't valid JS, we use `canvas` instead + window_title_ = 'canvas' + } + app.perf = &Perf{} + app.gg = gg.new_context( + bg_color: app.theme.bg_color + width: default_window_width + height: default_window_height + sample_count: 4 // higher quality curves + create_window: true + window_title: window_title_ + frame_fn: frame + event_fn: on_event + init_fn: init + user_data: app + font_path: font_path + ) + app.gg.run() +} diff --git a/v_windows/v/examples/2048/LICENSE b/v_windows/v/examples/2048/LICENSE new file mode 100644 index 0000000..7897d4d --- /dev/null +++ b/v_windows/v/examples/2048/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Delyan Angelov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/v_windows/v/examples/2048/README.md b/v_windows/v/examples/2048/README.md new file mode 100644 index 0000000..15516fb --- /dev/null +++ b/v_windows/v/examples/2048/README.md @@ -0,0 +1,25 @@ +# V 2048 + +This is a simple 2048 game, written in [the V programming language](https://vlang.io/). + +WebAssembly demo: https://v2048.vercel.app + +![screenshot](demo.png) + +## Description: +Merge tiles by moving them. +After each move, a new random tile is added (2 or 4). +The goal of the game is to create a tile with a value of 2048. + +## Keys: +Escape - exit the game +Backspace - undo last move +n - restart the game +t - toggle the UI theme +Enter - toggle the tile text format + +UP,LEFT,DOWN,RIGHT / W,A,S,D / touchscreen swipes - move the tiles + +## Running instructions: +Compile & run the game with `./v run examples/2048` + diff --git a/v_windows/v/examples/2048/demo.png b/v_windows/v/examples/2048/demo.png Binary files differnew file mode 100644 index 0000000..fcd4684 --- /dev/null +++ b/v_windows/v/examples/2048/demo.png diff --git a/v_windows/v/examples/2048/v.mod b/v_windows/v/examples/2048/v.mod new file mode 100644 index 0000000..5c146f8 --- /dev/null +++ b/v_windows/v/examples/2048/v.mod @@ -0,0 +1,7 @@ +Module { + name: 'v2048', + description: 'A simple 2048 game written in V.', + version: '0.0.2', + repo_url: 'https://github.com/spytheman/v2048', + dependencies: [] +} diff --git a/v_windows/v/examples/asm.v b/v_windows/v/examples/asm.v new file mode 100644 index 0000000..88c75ec --- /dev/null +++ b/v_windows/v/examples/asm.v @@ -0,0 +1,18 @@ +fn main() { + a := 100 + b := 20 + mut c := 0 + $if amd64 { + asm amd64 { + mov eax, a + add eax, b + mov c, eax + ; =r (c) // output + ; r (a) // input + r (b) + } + } + println('a: $a') // 100 + println('b: $b') // 20 + println('c: $c') // 120 +} diff --git a/v_windows/v/examples/assets/fonts/LICENSE b/v_windows/v/examples/assets/fonts/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/v_windows/v/examples/assets/fonts/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/v_windows/v/examples/assets/fonts/RobotoMono-Regular.ttf b/v_windows/v/examples/assets/fonts/RobotoMono-Regular.ttf Binary files differnew file mode 100644 index 0000000..b158a33 --- /dev/null +++ b/v_windows/v/examples/assets/fonts/RobotoMono-Regular.ttf diff --git a/v_windows/v/examples/bfs.v b/v_windows/v/examples/bfs.v new file mode 100644 index 0000000..ff20fdf --- /dev/null +++ b/v_windows/v/examples/bfs.v @@ -0,0 +1,41 @@ +// Breadth-First Search (BFS) allows you to find the shortest distance between two nodes in the graph. +fn breadth_first_search_path(graph map[string][]string, vertex string, target string) []string { + mut path := []string{} + mut visited := []string{init: vertex} + mut queue := [][][]string{} + queue << [[vertex], path] + for queue.len > 0 { + mut idx := queue.len - 1 + node := queue[idx][0][0] + path = queue[idx][1] + queue.delete(idx) + if node == target { + path << node + return path + } + for child in graph[node] { + mut tmp := path.clone() + if child !in visited { + visited << child + tmp << node + queue << [[child], tmp] + } + } + } + return path +} + +fn main() { + graph := { + 'A': ['B', 'C'] + 'B': ['A', 'D', 'E'] + 'C': ['A', 'F'] + 'D': ['B'] + 'E': ['B', 'F'] + 'F': ['C', 'E'] + } + println('Graph: $graph') + path := breadth_first_search_path(graph, 'A', 'F') + println('The shortest path from node A to node F is: $path') + assert path == ['A', 'C', 'F'] +} diff --git a/v_windows/v/examples/binary_search_tree.v b/v_windows/v/examples/binary_search_tree.v new file mode 100644 index 0000000..514426a --- /dev/null +++ b/v_windows/v/examples/binary_search_tree.v @@ -0,0 +1,171 @@ +// Binary Search Tree example by @SleepyRoy + +// TODO: make Node.value generic once it's robust enough +struct Empty {} + +struct Node { + value f64 + left Tree + right Tree +} + +type Tree = Empty | Node + +// return size(number of nodes) of BST +fn size(tree Tree) int { + return match tree { + Empty { 0 } + Node { 1 + size(tree.left) + size(tree.right) } + } +} + +// insert a value to BST +fn insert(tree Tree, x f64) Tree { + return match tree { + Empty { + Node{x, tree, tree} + } + Node { + if x == tree.value { + tree + } else if x < tree.value { + Node{ + ...tree + left: insert(tree.left, x) + } + } else { + Node{ + ...tree + right: insert(tree.right, x) + } + } + } + } +} + +// whether able to find a value in BST +fn search(tree Tree, x f64) bool { + return match tree { + Empty { + false + } + Node { + if x == tree.value { + true + } else if x < tree.value { + search(tree.left, x) + } else { + search(tree.right, x) + } + } + } +} + +// find the minimal value of a BST +fn min(tree Tree) f64 { + return match tree { + Empty { + 1e100 + } + Node { + if tree.value < min(tree.left) { + tree.value + } else { + min(tree.left) + } + } + } +} + +// delete a value in BST (if nonexistant do nothing) +fn delete(tree Tree, x f64) Tree { + return match tree { + Empty { + tree + } + Node { + if tree.left is Node && tree.right is Node { + if x < tree.value { + Node{ + ...tree + left: delete(tree.left, x) + } + } else if x > tree.value { + Node{ + ...tree + right: delete(tree.right, x) + } + } else { + Node{ + ...tree + value: min(tree.right) + right: delete(tree.right, min(tree.right)) + } + } + } else if tree.left is Node { + if x == tree.value { + tree.left + } else { + Node{ + ...tree + left: delete(tree.left, x) + } + } + } else { + if x == tree.value { + tree.right + } else { + Node{ + ...tree + right: delete(tree.right, x) + } + } + } + } + } +} + +fn main() { + $if !freestanding { + mut tree := Tree(Empty{}) + input := [0.3, 0.2, 0.5, 0.0, 0.6, 0.8, 0.9, 1.0, 0.1, 0.4, 0.7] + for i in input { + tree = insert(tree, i) + } + println('[1] after insertion tree size is ${size(tree)}') // 11 + del := [-0.3, 0.0, 0.3, 0.6, 1.0, 1.5] + for i in del { + tree = delete(tree, i) + } + print('[2] after deletion tree size is ${size(tree)}, ') // 7 + print('and these elements were deleted: ') // 0.0 0.3 0.6 1.0 + for i in input { + if !search(tree, i) { + print('$i ') + } + } + println('') + } $else { + mut tree := Tree(Empty{}) + input := [0.3, 0.2, 0.5, 0.0, 0.6, 0.8, 0.9, 1.0, 0.1, 0.4, 0.7] + for i in input { + tree = insert(tree, i) + } + print('[1] after insertion tree size is ') // 11 + println(size(tree)) + del := [-0.3, 0.0, 0.3, 0.6, 1.0, 1.5] + for i in del { + tree = delete(tree, i) + } + print('[2] after deletion tree size is ') // 7 + print(size(tree)) + print(', and these elements were deleted: ') // 0.0 0.3 0.6 1.0 + for i in input { + if !search(tree, i) { + print(i) + print(' ') + } + } + println('') + } +} diff --git a/v_windows/v/examples/buf_reader.v b/v_windows/v/examples/buf_reader.v new file mode 100644 index 0000000..e754f61 --- /dev/null +++ b/v_windows/v/examples/buf_reader.v @@ -0,0 +1,19 @@ +// Simple raw HTTP head request +import net +import time +import io + +fn main() { + // Make a new connection + mut conn := net.dial_tcp('google.com:80') ? + // Simple http HEAD request for a file + conn.write_string('GET /index.html HTTP/1.0\r\n\r\n') ? + // Wrap in a buffered reader + mut r := io.new_buffered_reader(reader: conn) + for { + l := r.read_line() or { break } + println('$l') + // Make it nice and obvious that we are doing this line by line + time.sleep(100 * time.millisecond) + } +} diff --git a/v_windows/v/examples/c_interop_wkhtmltopdf.v b/v_windows/v/examples/c_interop_wkhtmltopdf.v new file mode 100644 index 0000000..ddd56b5 --- /dev/null +++ b/v_windows/v/examples/c_interop_wkhtmltopdf.v @@ -0,0 +1,96 @@ +import os + +// Example of C interop for a very handy task. +// +// wkhtmltopdf and wkhtmltoimage are open source (LGPLv3) command line tools to +// render HTML into PDF and various image formats using the Qt WebKit rendering +// engine. These run entirely "headless" and do not require a display or display +// service. +// +// https://github.com/wkhtmltopdf/wkhtmltopdf +// https://wkhtmltopdf.org/downloads.html +// https://wkhtmltopdf.org/libwkhtmltox/ +#flag -lwkhtmltox +#include "wkhtmltox/pdf.h" # You can install the C package for your system from the wkhtmltopdf.org/downloads.html page + +struct C.wkhtmltopdf_global_settings {} + +struct C.wkhtmltopdf_object_settings {} + +struct C.wkhtmltopdf_converter {} + +fn C.wkhtmltopdf_init(use_graphics bool) int + +fn C.wkhtmltopdf_deinit() int + +fn C.wkhtmltopdf_version() &char + +fn C.wkhtmltopdf_create_global_settings() &C.wkhtmltopdf_global_settings + +fn C.wkhtmltopdf_destroy_global_settings(global_settings &C.wkhtmltopdf_global_settings) + +fn wkhtmltopdf_set_global_setting(global_settings &C.wkhtmltopdf_global_settings, name &char, value &char) bool + +fn C.wkhtmltopdf_create_object_settings() &C.wkhtmltopdf_object_settings + +fn C.wkhtmltopdf_destroy_object_settings(object_settings &C.wkhtmltopdf_object_settings) + +fn C.wkhtmltopdf_set_object_setting(object_settings &C.wkhtmltopdf_object_settings, name &char, value &char) bool + +fn C.wkhtmltopdf_create_converter(global_settings &C.wkhtmltopdf_global_settings) &C.wkhtmltopdf_converter + +fn C.wkhtmltopdf_destroy_converter(converter &C.wkhtmltopdf_converter) + +fn C.wkhtmltopdf_add_object(converter &C.wkhtmltopdf_converter, object_settings &C.wkhtmltopdf_object_settings, data &char) + +fn C.wkhtmltopdf_convert(converter &C.wkhtmltopdf_converter) bool + +fn C.wkhtmltopdf_http_error_code(converter &C.wkhtmltopdf_converter) int + +fn C.wkhtmltopdf_get_output(converter &C.wkhtmltopdf_converter, data &&char) int + +fn main() { + // init + init := C.wkhtmltopdf_init(0) + println('wkhtmltopdf_init: $init') + version := unsafe { cstring_to_vstring(&char(C.wkhtmltopdf_version())) } + println('wkhtmltopdf_version: $version') + global_settings := C.wkhtmltopdf_create_global_settings() + println('wkhtmltopdf_create_global_settings: ${voidptr(global_settings)}') + object_settings := C.wkhtmltopdf_create_object_settings() + println('wkhtmltopdf_create_object_settings') + converter := C.wkhtmltopdf_create_converter(global_settings) + println('wkhtmltopdf_create_converter: ${voidptr(converter)}') + // convert + mut result := C.wkhtmltopdf_set_object_setting(object_settings, c'page', c'http://www.google.com.br') + println('wkhtmltopdf_set_object_setting: $result [page = http://www.google.com.br]') + C.wkhtmltopdf_add_object(converter, object_settings, 0) + println('wkhtmltopdf_add_object') + result = C.wkhtmltopdf_convert(converter) + println('wkhtmltopdf_convert: $result') + error_code := C.wkhtmltopdf_http_error_code(converter) + println('wkhtmltopdf_http_error_code: $error_code') + if result { + pdata := &char(0) + ppdata := &pdata + size := C.wkhtmltopdf_get_output(converter, voidptr(ppdata)) + println('wkhtmltopdf_get_output: $size bytes') + mut file := os.open_file('./google.pdf', 'w+', 0o666) or { + println('ERR: $err') + return + } + wrote := unsafe { file.write_ptr(pdata, size) } + println('write_bytes: $wrote [./google.pdf]') + file.flush() + file.close() + } + // destroy + C.wkhtmltopdf_destroy_converter(converter) + println('wkhtmltopdf_destroy_converter') + C.wkhtmltopdf_destroy_object_settings(object_settings) + println('wkhtmltopdf_destroy_object_settings: ${voidptr(object_settings)}') + C.wkhtmltopdf_destroy_global_settings(global_settings) + println('wkhtmltopdf_destroy_global_settings') + deinit := C.wkhtmltopdf_deinit() + println('wkhtmltopdf_deinit: $deinit') +} diff --git a/v_windows/v/examples/cli.v b/v_windows/v/examples/cli.v new file mode 100644 index 0000000..9232aac --- /dev/null +++ b/v_windows/v/examples/cli.v @@ -0,0 +1,78 @@ +module main + +import cli { Command, Flag } +import os + +fn main() { + mut cmd := Command{ + name: 'cli' + description: 'An example of the cli library.' + version: '1.0.0' + } + mut greet_cmd := Command{ + name: 'greet' + description: 'Prints greeting in different languages.' + usage: '<name>' + required_args: 1 + pre_execute: greet_pre_func + execute: greet_func + post_execute: greet_post_func + } + greet_cmd.add_flag(Flag{ + flag: .string + required: true + name: 'language' + abbrev: 'l' + description: 'Language of the message.' + }) + greet_cmd.add_flag(Flag{ + flag: .int + name: 'times' + default_value: ['3'] + description: 'Number of times the message gets printed.' + }) + greet_cmd.add_flag(Flag{ + flag: .string_array + name: 'fun' + description: 'Just a dumby flags to show multiple.' + }) + cmd.add_command(greet_cmd) + cmd.setup() + cmd.parse(os.args) +} + +fn greet_func(cmd Command) ? { + language := cmd.flags.get_string('language') or { panic('Failed to get `language` flag: $err') } + times := cmd.flags.get_int('times') or { panic('Failed to get `times` flag: $err') } + name := cmd.args[0] + for _ in 0 .. times { + match language { + 'english', 'en' { + println('Welcome $name') + } + 'german', 'de' { + println('Willkommen $name') + } + 'dutch', 'nl' { + println('Welkom $name') + } + else { + println('Unsupported language') + println('Supported languages are `english`, `german` and `dutch`.') + break + } + } + } + fun := cmd.flags.get_strings('fun') or { panic('Failed to get `fun` flag: $err') } + for f in fun { + println('fun: $f') + } +} + +fn greet_pre_func(cmd Command) ? { + println('This is a function running before the main function.\n') +} + +fn greet_post_func(cmd Command) ? { + println('\nThis is a function running after the main function.') +} diff --git a/v_windows/v/examples/clock/clock.v b/v_windows/v/examples/clock/clock.v new file mode 100644 index 0000000..61e4430 --- /dev/null +++ b/v_windows/v/examples/clock/clock.v @@ -0,0 +1,168 @@ +// An X11 clock modeled after https://en.wikipedia.org/wiki/Station_clock +// This is a small V example that was based off of the fireworks example. +// Written by Stefan Schroeder in 2021 for the v project examples. +// See LICENSE for license information. +import os +import gg +import gx +import math +import time + +const ( + // All coordinates are designed for a clock size of this many pixel. + // You cannot change the size of the clock by adjusting this value. + design_size = 700 + center = 350 + + // Half the width of a tic-mark. + tw = 9 + // Height of a minute tic-mark. (hour is twice, 3-hour is thrice) + th = 25 + // Padding of tic-mark to window border + tp = 10 + + tic_color = gx.Color{ + r: 50 + g: 50 + b: 50 + } + hand_color = gx.black + second_hand_color = gx.red +) + +struct App { + minutes_tic []f32 = [f32(center - tw), tp, center + tw, tp, center + tw, tp, center + tw, + tp + + 1 * th, center - tw, tp + 1 * th] + hours_tic []f32 = [f32(center - tw), tp, center + tw, tp, center + tw, tp, center + tw, tp + 2 * th, + center - tw, tp + 2 * th] + hours3_tic []f32 = [f32(center - tw), tp, center + tw, tp, center + tw, tp, center + tw, tp + 3 * th, + center - tw, tp + 3 * th] + + hour_hand []f32 = [f32(329), 161, 350, 140, 371, 161, 371, 413, 329, 413] + minute_hand []f32 = [f32(334.25), 40.25, 350, 24.5, 365.75, 40.25, 365.75, 427, 334.25, 427] + second_hand []f32 = [f32(345.8), 38.5, 350, 34.3, 354.2000, 38.5, 358.75, 427, 341.25, 427] +mut: + gg &gg.Context = 0 + draw_flag bool = true + dpi_scale f32 = 1.0 +} + +fn on_frame(mut app App) { + if !app.draw_flag { + return + } + app.gg.begin() + + for i in 0 .. 60 { // draw minute tics + draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.minutes_tic, tic_color, + i * 6) + } + for i in 0 .. 12 { // hours + draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.hours_tic, tic_color, i * 30) + } + for i in 0 .. 4 { // 3 hours + draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.hours3_tic, tic_color, + i * 90) + } + + n := time.now() + + // draw hour hand + i := f32(n.hour) + f32(n.minute) / 60.0 + draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.hour_hand, hand_color, i * 30) + + // draw minute hand + mut j := f32(n.minute) + if n.second == 59 { // make minute hand move smoothly + j += f32(math.sin(f32(n.microsecond) / 1e6 * math.pi / 2.0)) + } + draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.minute_hand, hand_color, j * 6) + + // draw second hand with smooth transition + k := f32(n.second) + f32(math.sin(f32(n.microsecond) / 1e6 * math.pi / 2.0)) + draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.second_hand, second_hand_color, + 0 + k * 6) + + app.gg.end() +} + +// Rotate a polygon round the centerpoint +fn draw_convex_poly_rotate(mut ctx gg.Context, dpi_scale f32, points []f32, c gx.Color, angle f32) { + sa := math.sin(math.pi * angle / 180.0) + ca := math.cos(math.pi * angle / 180.0) + + mut rotated_points := []f32{} + for i := 0; i < points.len / 2; i++ { + x := points[2 * i] + y := points[2 * i + 1] + xn := f32((x - center) * ca - (y - center) * sa) + yn := f32((x - center) * sa + (y - center) * ca) + rotated_points << (xn + center) * dpi_scale + rotated_points << (yn + center) * dpi_scale + } + ctx.draw_convex_poly(rotated_points, c) +} + +fn (mut app App) resize() { + size := gg.window_size() + // avoid calls when minimized + if size.width < 2 && size.height < 2 { + return + } + w := f32(size.width) / design_size + h := f32(size.height) / design_size + app.dpi_scale = if w < h { w } else { h } +} + +fn on_event(e &gg.Event, mut app App) { + match e.typ { + .resized, .resumed { + app.resize() + } + .iconified { + app.draw_flag = false + } + .restored { + app.draw_flag = true + app.resize() + } + else { + if e.typ == .key_down { + match e.key_code { + .q { + println('Good bye.') + // do we need to free anything here? + exit(0) + } + else {} + } + } + } + } +} + +// is needed for easier diagnostics on windows +[console] +fn main() { + println("Press 'q' to quit.") + mut font_path := os.resource_abs_path(os.join_path('../assets/fonts/', 'RobotoMono-Regular.ttf')) + $if android { + font_path = 'fonts/RobotoMono-Regular.ttf' + } + + mut app := &App{} + + app.gg = gg.new_context( + width: design_size + height: design_size + window_title: 'Clock!' + bg_color: gx.white + user_data: app + frame_fn: on_frame + event_fn: on_event + font_path: font_path + ) + + app.gg.run() +} diff --git a/v_windows/v/examples/compiletime/compile-time-for.v b/v_windows/v/examples/compiletime/compile-time-for.v new file mode 100644 index 0000000..f3f90fa --- /dev/null +++ b/v_windows/v/examples/compiletime/compile-time-for.v @@ -0,0 +1,38 @@ +struct App {} + +fn (mut app App) method_one() {} + +fn (mut app App) method_two() int { + return 0 +} + +fn (mut app App) method_three(s string) string { + return s +} + +fn main() { + $for method in App.methods { + $if method.typ is fn (string) string { + println('$method.name IS `fn(string) string`') + } $else { + println('$method.name is NOT `fn(string) string`') + } + $if method.return_type !is int { + println('$method.name does NOT return `int`') + } $else { + println('$method.name DOES return `int`') + } + $if method.args[0].typ !is string { + println("$method.name's first arg is NOT `string`") + } $else { + println("$method.name's first arg IS `string`") + } + // TODO: Double inversion, should this even be allowed? + $if method.typ is fn () { + println('$method.name IS a void method') + } $else { + println('$method.name is NOT a void method') + } + println('') + } +} diff --git a/v_windows/v/examples/concurrency/concurrency.v b/v_windows/v/examples/concurrency/concurrency.v new file mode 100644 index 0000000..92a5517 --- /dev/null +++ b/v_windows/v/examples/concurrency/concurrency.v @@ -0,0 +1,18 @@ +import time + +// Simulate expensive computing using sleep function +fn expensive_computing(id int, duration int) { + println('Executing expensive computing task ($id)...') + time.sleep(duration * time.millisecond) + println('Finish task $id on $duration ms') +} + +fn main() { + mut threads := []thread{} + threads << go expensive_computing(1, 100) + threads << go expensive_computing(2, 500) + threads << go expensive_computing(3, 1000) + // Join all tasks + threads.wait() + println('All jobs finished!') +} diff --git a/v_windows/v/examples/concurrency/concurrency_http.v b/v_windows/v/examples/concurrency/concurrency_http.v new file mode 100644 index 0000000..b5b0b58 --- /dev/null +++ b/v_windows/v/examples/concurrency/concurrency_http.v @@ -0,0 +1,32 @@ +import net.http +import sync +import time + +fn vlang_time(mut wg sync.WaitGroup) ?string { + start := time.ticks() + data := http.get('https://vlang.io/utc_now') ? + finish := time.ticks() + println('Finish getting time ${finish - start} ms') + println(data.text) + wg.done() + return data.text +} + +fn remote_ip(mut wg sync.WaitGroup) ?string { + start := time.ticks() + data := http.get('https://api.ipify.org') ? + finish := time.ticks() + println('Finish getting ip ${finish - start} ms') + println(data.text) + wg.done() + return data.text +} + +fn main() { + mut wg := sync.new_waitgroup() + wg.add(2) + // Run tasks async + go vlang_time(mut wg) + go remote_ip(mut wg) + wg.wait() +} diff --git a/v_windows/v/examples/concurrency/concurrency_returns.v b/v_windows/v/examples/concurrency/concurrency_returns.v new file mode 100644 index 0000000..6d687e4 --- /dev/null +++ b/v_windows/v/examples/concurrency/concurrency_returns.v @@ -0,0 +1,13 @@ +fn expensive_computing(i int) int { + return i * i +} + +fn main() { + mut threads := []thread int{} + for i in 1 .. 10 { + threads << go expensive_computing(i) + } + // Join all tasks + r := threads.wait() + println('All jobs finished: $r') +} diff --git a/v_windows/v/examples/database/mysql.v b/v_windows/v/examples/database/mysql.v new file mode 100644 index 0000000..97e0888 --- /dev/null +++ b/v_windows/v/examples/database/mysql.v @@ -0,0 +1,17 @@ +import mysql + +fn main() { + mut conn := mysql.Connection{ + host: 'localhost' + port: 3306 + username: 'root' + password: '' + dbname: 'mysql' + } + conn.connect() ? + res := conn.query('show tables') ? + for row in res.rows() { + println(row.vals.join(', ')) + } + conn.close() +} diff --git a/v_windows/v/examples/database/orm.v b/v_windows/v/examples/database/orm.v new file mode 100644 index 0000000..b308e91 --- /dev/null +++ b/v_windows/v/examples/database/orm.v @@ -0,0 +1,257 @@ +import sqlite +import mysql +import pg + +[table: 'modules'] +struct Module { + id int [primary; sql: serial] + name string + nr_downloads int [sql: u64] + creator User +} + +struct User { + id int [primary; sql: serial] + age int [unique: 'user'] + name string [sql: 'username'; unique] + is_customer bool [sql: 'abc'; unique: 'user'] + skipped_string string [skip] +} + +struct Parent { + id int [primary; sql: serial] + name string + children []Child [fkey: 'parent_id'] +} + +struct Child { + id int [primary; sql: serial] + parent_id int + name string +} + +fn main() { + sqlite3_array() + mysql_array() + psql_array() + + sqlite3() + mysql() + psql() +} + +fn sqlite3_array() { + mut db := sqlite.connect(':memory:') or { panic(err) } + sql db { + create table Parent + } + + par := Parent{ + name: 'test' + children: [ + Child{ + name: 'abc' + }, + Child{ + name: 'def' + }, + ] + } + + sql db { + insert par into Parent + } + + parent := sql db { + select from Parent where id == 1 + } + + sql db { + drop table Parent + } + + eprintln(parent) +} + +fn mysql_array() { + mut db := mysql.Connection{ + host: 'localhost' + port: 3306 + username: 'root' + password: 'abc' + dbname: 'test' + } + db.connect() or { panic(err) } + + sql db { + create table Parent + } + + par := Parent{ + name: 'test' + children: [ + Child{ + name: 'abc' + }, + Child{ + name: 'def' + }, + ] + } + + sql db { + insert par into Parent + } + + parent := sql db { + select from Parent where id == 1 + } + + eprintln(parent) + + sql db { + drop table Parent + } + + db.close() +} + +fn psql_array() { + mut db := pg.connect(host: 'localhost', user: 'test', password: 'abc', dbname: 'test') or { + panic(err) + } + + sql db { + create table Parent + } + + par := Parent{ + name: 'test' + children: [ + Child{ + name: 'abc' + }, + Child{ + name: 'def' + }, + ] + } + + sql db { + insert par into Parent + } + + parent := sql db { + select from Parent where id == 1 + } + + eprintln(parent) + + sql db { + drop table Parent + } + + db.close() +} + +fn sqlite3() { + mut db := sqlite.connect(':memory:') or { panic(err) } + sql db { + create table Module + } + + mod := Module{ + name: 'test' + nr_downloads: 10 + creator: User{ + age: 21 + name: 'VUser' + is_customer: true + } + } + sql db { + insert mod into Module + } + + modul := sql db { + select from Module where id == 1 + } + + sql db { + drop table Module + } + + eprintln(modul) + db.close() or { panic(err) } +} + +fn mysql() { + mut conn := mysql.Connection{ + host: 'localhost' + port: 3306 + username: 'root' + password: 'abc' + dbname: 'test' + } + conn.connect() or { panic(err) } + + sql conn { + create table Module + } + + mod := Module{ + name: 'test' + nr_downloads: 10 + creator: User{ + age: 21 + name: 'VUser' + is_customer: true + } + } + + sql conn { + insert mod into Module + } + + m := sql conn { + select from Module where id == 1 + } + + eprintln(m) + conn.close() +} + +fn psql() { + mut db := pg.connect(host: 'localhost', user: 'test', password: 'abc', dbname: 'test') or { + panic(err) + } + + mod := Module{ + name: 'test' + nr_downloads: 10 + creator: User{ + age: 21 + name: 'VUser' + is_customer: true + } + } + + sql db { + create table Module + } + + sql db { + insert mod into Module + } + + modul := sql db { + select from Module where id == 1 + } + + sql db { + drop table Module + } + + eprintln(modul) + db.close() +} diff --git a/v_windows/v/examples/database/psql/.gitignore b/v_windows/v/examples/database/psql/.gitignore new file mode 100644 index 0000000..23830aa --- /dev/null +++ b/v_windows/v/examples/database/psql/.gitignore @@ -0,0 +1 @@ +customer diff --git a/v_windows/v/examples/database/psql/customer.v b/v_windows/v/examples/database/psql/customer.v new file mode 100644 index 0000000..4538a94 --- /dev/null +++ b/v_windows/v/examples/database/psql/customer.v @@ -0,0 +1,63 @@ +module main + +import pg + +const dash = '----------------------------------------------------------------' + +struct Customer { + id int + name string + nr_orders int + country string +} + +fn main() { + /* + db := pg.connect(pg.Config{ + host: 'localhost' //'127.0.0.1' + user: 'postgres' + dbname: 'customerdb' + }) or { + println('failed to connect') + println(err) + return + } + + nr_customers := db.select count from Customer + println('Total customers: $nr_customers') + + // V syntax can be used to build queries + println(dash) + bg_country := 'Bulgaria' + bg_customers := db.select from Customer where country == bg_country && id != 2 + for customer in bg_customers { + println('$customer.country | $customer.id - $customer.name') + } + + println(dash) + ru_customers := db.select from Customer where country == 'Russia' + for customer in ru_customers { + println('$customer.country | $customer.id - $customer.name') + } + + // by adding `limit 1` we tell V that there will be only one object + println(dash) + existing := db.select from Customer where id == 1 limit 1 or { panic(err) } + println('Existing customer name: $existing.name') + println('Existing customer full information:') + println(existing) + + println(dash) + q := Customer{} + // It's easy to handle queries that don't return any data + if anon := db.select from Customer where id == 12345 && name == q.name && + nr_orders > q.nr_orders limit 1 { + println('Non existing customer name: $anon.name') + } + // Insert a new customer + nc := Customer{ + name: 'John Doe' + nr_orders: 10 + } + db.insert(nc)*/ +} diff --git a/v_windows/v/examples/database/psql/mydb.sql b/v_windows/v/examples/database/psql/mydb.sql new file mode 100644 index 0000000..a7cbb39 --- /dev/null +++ b/v_windows/v/examples/database/psql/mydb.sql @@ -0,0 +1,122 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 9.5.19 +-- Dumped by pg_dump version 9.5.19 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: +-- + +CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; + + +-- +-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; + + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: customers; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.customers ( + id integer NOT NULL, + name text DEFAULT ''::text, + nr_orders integer DEFAULT 0, + country text DEFAULT 'England'::text, + created_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.customers OWNER TO myuser; + +-- +-- Name: customers_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.customers_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.customers_id_seq OWNER TO myuser; + +-- +-- Name: customers_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.customers_id_seq OWNED BY public.customers.id; + + +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.customers ALTER COLUMN id SET DEFAULT nextval('public.customers_id_seq'::regclass); + + +-- +-- Data for Name: customers; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.customers (id, name, nr_orders, country, created_at) FROM stdin; +2 Pippi Långstrump 3 Bulgaria 2019-08-19 09:41:30.78888 +1 Bilbo Begins 11 Bulgaria 2019-08-19 09:40:31.396807 +3 Viktualia Rullgardina 0 Bulgaria 2019-08-19 09:42:52.723223 +4 Krusmynta Efraimsdotter 5 Bulgaria 2019-08-19 09:43:04.083209 +5 Ana Karenina 0 Russia 2019-08-20 15:41:50.244971 +7 Jiji Lolobridgida 0 Italy 2019-08-20 15:42:26.020113 +6 Viktor Savashkin 8 Russia 2019-08-20 15:42:07.213557 +\. + + +-- +-- Name: customers_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.customers_id_seq', 1, true); + + +-- +-- Name: customers_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.customers + ADD CONSTRAINT customers_pkey PRIMARY KEY (id); + + +-- +-- Name: SCHEMA public; Type: ACL; Schema: -; Owner: postgres +-- + +REVOKE ALL ON SCHEMA public FROM PUBLIC; +REVOKE ALL ON SCHEMA public FROM postgres; +GRANT ALL ON SCHEMA public TO postgres; +GRANT ALL ON SCHEMA public TO PUBLIC; + + +-- +-- PostgreSQL database dump complete +-- + diff --git a/v_windows/v/examples/database/sqlite.v b/v_windows/v/examples/database/sqlite.v new file mode 100644 index 0000000..a3c7176 --- /dev/null +++ b/v_windows/v/examples/database/sqlite.v @@ -0,0 +1,22 @@ +import sqlite + +fn main() { + db := sqlite.connect(':memory:') ? + db.exec("create table users (id integer primary key, name text default '');") + + db.exec("insert into users (name) values ('Sam')") + db.exec("insert into users (name) values ('Peter')") + db.exec("insert into users (name) values ('Kate')") + + nr_users := db.q_int('select count(*) from users') + println('nr users = $nr_users') + + name := db.q_string('select name from users where id = 1') + assert name == 'Sam' + + users, code := db.exec('select * from users') + println('SQL Result code: $code') + for row in users { + println(row.vals) + } +} diff --git a/v_windows/v/examples/dump_factorial.v b/v_windows/v/examples/dump_factorial.v new file mode 100644 index 0000000..9b5fc0f --- /dev/null +++ b/v_windows/v/examples/dump_factorial.v @@ -0,0 +1,10 @@ +fn factorial(n u32) u32 { + if dump(n <= 1) { + return dump(1) + } + return dump(n * factorial(n - 1)) +} + +fn main() { + println(factorial(5)) +} diff --git a/v_windows/v/examples/dynamic_library_loading/modules/library/library.v b/v_windows/v/examples/dynamic_library_loading/modules/library/library.v new file mode 100644 index 0000000..ed3658c --- /dev/null +++ b/v_windows/v/examples/dynamic_library_loading/modules/library/library.v @@ -0,0 +1,19 @@ +module library + +// add_1 is exported with the C name `add_1`. +// It can be called by external programs, when the module is compiled +// as a shared library. +// It is exported, because the function is declared as public with `pub`. +// The exported C name is `add_1`, because of the export: tag. +// (Normally, the exported name is a V mangled version based on the module +// name followed by __, followed by the fn name, i.e. it would have been +// `library__add_1`, if not for the export: tag). +[export: 'add_1'] +pub fn add_1(x int, y int) int { + return my_private_function(x + y) +} + +// this function is not exported and will not be visible to external programs. +fn my_private_function(x int) int { + return 1 + x +} diff --git a/v_windows/v/examples/dynamic_library_loading/use.v b/v_windows/v/examples/dynamic_library_loading/use.v new file mode 100644 index 0000000..71a0099 --- /dev/null +++ b/v_windows/v/examples/dynamic_library_loading/use.v @@ -0,0 +1,17 @@ +module main + +import os +import dl + +type FNAdder = fn (int, int) int + +fn main() { + library_file_path := os.join_path(os.getwd(), dl.get_libname('library')) + handle := dl.open_opt(library_file_path, dl.rtld_lazy) ? + eprintln('handle: ${ptr_str(handle)}') + mut f := FNAdder(0) + f = dl.sym_opt(handle, 'add_1') ? + eprintln('f: ${ptr_str(f)}') + res := f(1, 2) + eprintln('res: $res') +} diff --git a/v_windows/v/examples/dynamic_library_loading/use_test.v b/v_windows/v/examples/dynamic_library_loading/use_test.v new file mode 100644 index 0000000..a224b2a --- /dev/null +++ b/v_windows/v/examples/dynamic_library_loading/use_test.v @@ -0,0 +1,56 @@ +module main + +import os +import dl + +const ( + vexe = os.real_path(os.getenv('VEXE')) + cfolder = os.dir(@FILE) + so_ext = dl.dl_ext + library_file_name = os.join_path(cfolder, dl.get_libname('library')) +) + +fn test_vexe() { + // dump(vexe) + assert vexe != '' + // dump(os.executable()) + // dump(@FILE) + // dump(cfolder) + // dump(so_ext) + // dump(library_file_name) +} + +fn test_can_compile_library() { + os.chdir(cfolder) or {} + os.rm(library_file_name) or {} + v_compile('-d no_backtrace -o library -shared modules/library/library.v') + assert os.is_file(library_file_name) +} + +fn test_can_compile_main_program() { + os.chdir(cfolder) or {} + assert os.is_file(library_file_name) + result := v_compile('run use.v') + // dump(result) + assert result.output.contains('res: 4') + os.rm(library_file_name) or {} +} + +fn test_can_compile_and_use_library_with_skip_unused() { + os.chdir(cfolder) or {} + os.rm(library_file_name) or {} + v_compile('-skip-unused -d no_backtrace -o library -shared modules/library/library.v') + assert os.is_file(library_file_name) + result := v_compile('run use.v') + assert result.output.contains('res: 4') + os.rm(library_file_name) or {} +} + +fn v_compile(vopts string) os.Result { + cmd := '"$vexe" -showcc $vopts' + // dump(cmd) + res := os.execute_or_exit(cmd) + // dump(res) + assert res.exit_code == 0 + return res +} diff --git a/v_windows/v/examples/errors.v b/v_windows/v/examples/errors.v new file mode 100644 index 0000000..e763fcf --- /dev/null +++ b/v_windows/v/examples/errors.v @@ -0,0 +1,20 @@ +import semver + +fn main() { + semver.from('asd') or { check_error(err) } + semver.from('') or { check_error(err) } +} + +fn check_error(err IError) { + match err { + semver.InvalidVersionFormatError { + println('wrong format') + } + semver.EmptyInputError { + println('empty input') + } + else { + println('unknown error') + } + } +} diff --git a/v_windows/v/examples/eventbus/eventbus.v b/v_windows/v/examples/eventbus/eventbus.v new file mode 100644 index 0000000..042feb8 --- /dev/null +++ b/v_windows/v/examples/eventbus/eventbus.v @@ -0,0 +1,13 @@ +module main + +import some_module + +fn main() { + mut sub := some_module.get_subscriber() + sub.subscribe('error', on_error) + some_module.do_work() +} + +fn on_error(sender voidptr, e &some_module.MyError, x voidptr) { + println(e.message) +} diff --git a/v_windows/v/examples/eventbus/modules/some_module/some_module.v b/v_windows/v/examples/eventbus/modules/some_module/some_module.v new file mode 100644 index 0000000..01231fb --- /dev/null +++ b/v_windows/v/examples/eventbus/modules/some_module/some_module.v @@ -0,0 +1,34 @@ +module some_module + +import eventbus + +const ( + eb = eventbus.new() +) + +pub struct Work { +pub: + hours int +} + +pub struct MyError { +pub: + message string +} + +pub fn do_work() { + work := Work{20} + for i in 0 .. 20 { + println('working...') + if i == 15 { + error := &MyError{'There was an error.'} + some_module.eb.publish('error', work, error) + some_module.eb.publish('error', work, error) + return + } + } +} + +pub fn get_subscriber() eventbus.Subscriber { + return *some_module.eb.subscriber +} diff --git a/v_windows/v/examples/fetch.v b/v_windows/v/examples/fetch.v new file mode 100644 index 0000000..e1a7132 --- /dev/null +++ b/v_windows/v/examples/fetch.v @@ -0,0 +1,12 @@ +import time +import net.http + +fn main() { + resp := http.get('https://vlang.io/utc_now') or { + println('failed to fetch data from the server') + return + } + + t := time.unix(resp.text.int()) + println(t.format()) +} diff --git a/v_windows/v/examples/fibonacci.v b/v_windows/v/examples/fibonacci.v new file mode 100644 index 0000000..13056d2 --- /dev/null +++ b/v_windows/v/examples/fibonacci.v @@ -0,0 +1,37 @@ +// This program displays the fibonacci sequence +// import os + +fn main() { + // Check for user input + // if os.args.len != 2 { + // println('usage: fibonacci [rank]') + + // Exit + // return + // } + + // Parse first argument and cast it to int + // stop := os.args[1].int() + stop := 23 + // Can only calculate correctly until rank 92 + if stop > 92 { + println('rank must be 92 or less') + return + } + + // Three consecutive terms of the sequence + mut a := 0 + mut b := 0 + mut c := 1 + println(a + c + c) + for _ in 0 .. stop { + // Set a and b to the next term + a = b + b = c + // Compute the new term + c = a + b + + // Print the new term + println(c) + } +} diff --git a/v_windows/v/examples/file_list.v b/v_windows/v/examples/file_list.v new file mode 100644 index 0000000..b1dc295 --- /dev/null +++ b/v_windows/v/examples/file_list.v @@ -0,0 +1,18 @@ +import os + +fn main() { + files := os.ls('.') or { + println(err) + return + } + mut f := os.create('file_list.txt') or { + println(err) + return + } + for file in files { + if os.is_file(file) { + f.write_string(file + '\r\n') or { println(err) } + } + } + f.close() +} diff --git a/v_windows/v/examples/fireworks/fireworks.v b/v_windows/v/examples/fireworks/fireworks.v new file mode 100644 index 0000000..9581cc5 --- /dev/null +++ b/v_windows/v/examples/fireworks/fireworks.v @@ -0,0 +1,119 @@ +import os +import objects +import gg +import gx +import rand + +struct App { +mut: + gg &gg.Context = 0 + ui &objects.UIParams = 0 + rockets []objects.Rocket + frames [][]objects.Rocket + // i thought about using a fixed fifo queue for the frames but the array + // seemed to work fine, if you'd like a challenge try implementing it with the queue :) + draw_flag bool = true +} + +fn on_frame(mut app App) { + if !app.draw_flag { + return + } + app.gg.begin() + + // drawing previous frames + for mut frame in app.frames { + for mut rocket in frame { + if !rocket.exploded { + rocket.color.a = byte(f32_max(rocket.color.a - 8, 0)) + rocket.draw(mut app.gg) + } + } + } + + // chance of firing new rocket + if rand.intn(30) == 0 { + app.rockets << objects.new_rocket() + } + // simulating rockets + app.rockets = app.rockets.filter(!it.dead) + + for mut rocket in app.rockets { + rocket.tick(mut app.gg) + } + + // adding frame + mut frame := app.rockets.clone() + + for mut rocket in frame { + rocket.particles = [] + } + + app.frames << frame + + // trimming out frames + if app.frames.len > 30 { + app.frames.delete(0) + } + + app.gg.end() +} + +fn on_event(e &gg.Event, mut app App) { + match e.typ { + .resized, .resumed { + app.resize() + } + .iconified { + app.draw_flag = false + } + .restored { + app.draw_flag = true + app.resize() + } + else { + // println("Type ${e.typ}") + } + } +} + +fn (mut app App) resize() { + size := gg.window_size() + // avoid calls when minimized + if size.width < 2 && size.height < 2 { + return + } + mut s := gg.dpi_scale() + + if s == 0.0 { + s = 1.0 + } + app.ui.dpi_scale = s + app.ui.width = size.width + app.ui.height = size.height +} + +// is needed for easier diagnostics on windows +[console] +fn main() { + mut font_path := os.resource_abs_path(os.join_path('../assets/fonts/', 'RobotoMono-Regular.ttf')) + $if android { + font_path = 'fonts/RobotoMono-Regular.ttf' + } + + mut app := &App{} + app.ui = objects.get_params() + + app.gg = gg.new_context( + width: app.ui.width + height: app.ui.height + window_title: 'Fireworks!' + bg_color: gx.black + user_data: app + frame_fn: on_frame + event_fn: on_event + font_path: font_path + ) + + app.gg.run() +} diff --git a/v_windows/v/examples/fireworks/modules/objects/color.v b/v_windows/v/examples/fireworks/modules/objects/color.v new file mode 100644 index 0000000..6761f26 --- /dev/null +++ b/v_windows/v/examples/fireworks/modules/objects/color.v @@ -0,0 +1,12 @@ +module objects + +import gx +import rand + +pub fn random_color() gx.Color { + return gx.Color{ + r: byte(rand.int_in_range(0, 256)) + g: byte(rand.int_in_range(0, 256)) + b: byte(rand.int_in_range(0, 256)) + } +} diff --git a/v_windows/v/examples/fireworks/modules/objects/constants.v b/v_windows/v/examples/fireworks/modules/objects/constants.v new file mode 100644 index 0000000..a8e5abd --- /dev/null +++ b/v_windows/v/examples/fireworks/modules/objects/constants.v @@ -0,0 +1,20 @@ +module objects + +pub struct UIParams { +pub mut: + dpi_scale f32 = 1.0 + width int = 800 + height int = 800 + gravity Vector = Vector{0, -0.03} + age_rate int = 1 + offspring_count int = 100 + rocket_radius int = 5 + particle_radius f32 = 2.5 + drag f32 = 0.98 +} + +const params = &UIParams{} + +pub fn get_params() &UIParams { + return objects.params +} diff --git a/v_windows/v/examples/fireworks/modules/objects/particle.v b/v_windows/v/examples/fireworks/modules/objects/particle.v new file mode 100644 index 0000000..8f84a39 --- /dev/null +++ b/v_windows/v/examples/fireworks/modules/objects/particle.v @@ -0,0 +1,36 @@ +module objects + +import gg +import gx + +pub struct Particle { +pub mut: + color gx.Color + pos Vector + vel Vector + accel Vector + lifespan f32 = 255 +} + +pub fn (particle Particle) draw(mut ctx gg.Context) { + ctx.draw_circle(particle.pos.x, get_params().height - particle.pos.y, get_params().particle_radius, + particle.color) +} + +pub fn (mut particle Particle) tick(mut rocket Rocket, mut ctx gg.Context) { + particle.lifespan -= get_params().age_rate + particle.color.a = byte(particle.lifespan) + + if particle.lifespan <= 0 { + rocket.dead = true + return + } + + particle.accel += get_params().gravity + particle.vel += particle.accel + particle.vel = particle.vel.mult(get_params().drag) + particle.pos += particle.vel + particle.draw(mut ctx) + + particle.accel = Vector{} +} diff --git a/v_windows/v/examples/fireworks/modules/objects/rocket.v b/v_windows/v/examples/fireworks/modules/objects/rocket.v new file mode 100644 index 0000000..ebdce0a --- /dev/null +++ b/v_windows/v/examples/fireworks/modules/objects/rocket.v @@ -0,0 +1,69 @@ +module objects + +import gg +import gx +import rand + +pub struct Rocket { +pub mut: + color gx.Color + pos Vector + vel Vector + accel Vector + exploded bool + particles []Particle + dead bool +} + +pub fn (rocket Rocket) draw(mut ctx gg.Context) { + ctx.draw_circle(rocket.pos.x, get_params().height - rocket.pos.y, get_params().rocket_radius, + rocket.color) +} + +pub fn (mut rocket Rocket) explode() { + rocket.exploded = true + + for _ in 0 .. get_params().offspring_count { + rocket.spawn_particle() + } +} + +pub fn (mut rocket Rocket) tick(mut ctx gg.Context) { + if !rocket.exploded { + if rocket.vel.y <= 1 { + rocket.explode() + } + + rocket.accel += get_params().gravity + rocket.vel += rocket.accel + rocket.pos += rocket.vel + rocket.draw(mut ctx) + + rocket.accel = Vector{} + } + + for mut particle in rocket.particles { + particle.tick(mut rocket, mut ctx) + } +} + +pub fn new_rocket() Rocket { + return Rocket{ + color: random_color() + pos: Vector{ + x: rand.f32_in_range(50, get_params().width - 50) + } + vel: Vector{ + x: rand.f32_in_range(-1.5, 1.5) + y: rand.f32_in_range(5, 7) + } + } +} + +pub fn (mut rocket Rocket) spawn_particle() { + rocket.particles << Particle{ + color: rocket.color + pos: rocket.pos + accel: random_vector_in_circle().mult(2) + } +} diff --git a/v_windows/v/examples/fireworks/modules/objects/vector.v b/v_windows/v/examples/fireworks/modules/objects/vector.v new file mode 100644 index 0000000..0e29283 --- /dev/null +++ b/v_windows/v/examples/fireworks/modules/objects/vector.v @@ -0,0 +1,28 @@ +module objects + +import math +import rand + +pub struct Vector { +pub mut: + x f32 + y f32 +} + +pub fn (a Vector) + (b Vector) Vector { + return Vector{a.x + b.x, a.y + b.y} +} + +pub fn (vector Vector) mult(scalar f32) Vector { + return Vector{vector.x * scalar, vector.y * scalar} +} + +pub fn random_vector_in_circle() Vector { + theta := rand.f32n(2 * math.pi) + y := rand.f32() + + return Vector{ + x: f32(y * math.sin(theta)) + y: f32(y * math.cos(theta)) + } +} diff --git a/v_windows/v/examples/fizz_buzz.v b/v_windows/v/examples/fizz_buzz.v new file mode 100644 index 0000000..3310460 --- /dev/null +++ b/v_windows/v/examples/fizz_buzz.v @@ -0,0 +1,17 @@ +fn main() { + mut s := '' + for n in 1 .. 101 { + if n % 3 == 0 { + s += 'Fizz' + } + if n % 5 == 0 { + s += 'Buzz' + } + if s == '' { + println(n) + } else { + println(s) + } + s = '' + } +} diff --git a/v_windows/v/examples/flappylearning/.gitignore b/v_windows/v/examples/flappylearning/.gitignore new file mode 100644 index 0000000..dc22e61 --- /dev/null +++ b/v_windows/v/examples/flappylearning/.gitignore @@ -0,0 +1 @@ +game diff --git a/v_windows/v/examples/flappylearning/LICENSE b/v_windows/v/examples/flappylearning/LICENSE new file mode 100644 index 0000000..87cfb6f --- /dev/null +++ b/v_windows/v/examples/flappylearning/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 uxnow + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/v_windows/v/examples/flappylearning/README.md b/v_windows/v/examples/flappylearning/README.md new file mode 100644 index 0000000..57e2889 --- /dev/null +++ b/v_windows/v/examples/flappylearning/README.md @@ -0,0 +1,16 @@ +# flappylearning-v +flappy learning implemented by vlang + +## get started + +```sh +v run game.v +``` + +![flappy.png](img/flappy.png) + +## thanks +https://github.com/xviniette/FlappyLearning + +## license +MIT diff --git a/v_windows/v/examples/flappylearning/assets/img/background.png b/v_windows/v/examples/flappylearning/assets/img/background.png Binary files differnew file mode 100644 index 0000000..22b3070 --- /dev/null +++ b/v_windows/v/examples/flappylearning/assets/img/background.png diff --git a/v_windows/v/examples/flappylearning/assets/img/bird.png b/v_windows/v/examples/flappylearning/assets/img/bird.png Binary files differnew file mode 100644 index 0000000..ca3a159 --- /dev/null +++ b/v_windows/v/examples/flappylearning/assets/img/bird.png diff --git a/v_windows/v/examples/flappylearning/assets/img/flappy.png b/v_windows/v/examples/flappylearning/assets/img/flappy.png Binary files differnew file mode 100644 index 0000000..c7085aa --- /dev/null +++ b/v_windows/v/examples/flappylearning/assets/img/flappy.png diff --git a/v_windows/v/examples/flappylearning/assets/img/pipebottom.png b/v_windows/v/examples/flappylearning/assets/img/pipebottom.png Binary files differnew file mode 100644 index 0000000..99d27b2 --- /dev/null +++ b/v_windows/v/examples/flappylearning/assets/img/pipebottom.png diff --git a/v_windows/v/examples/flappylearning/assets/img/pipetop.png b/v_windows/v/examples/flappylearning/assets/img/pipetop.png Binary files differnew file mode 100644 index 0000000..1fcd52b --- /dev/null +++ b/v_windows/v/examples/flappylearning/assets/img/pipetop.png diff --git a/v_windows/v/examples/flappylearning/game.v b/v_windows/v/examples/flappylearning/game.v new file mode 100644 index 0000000..15cdc28 --- /dev/null +++ b/v_windows/v/examples/flappylearning/game.v @@ -0,0 +1,291 @@ +module main + +import gg +import gx +import os +import time +import math +import rand +import neuroevolution + +const ( + win_width = 500 + win_height = 512 +) + +struct Bird { +mut: + x f64 = 80 + y f64 = 250 + width f64 = 40 + height f64 = 30 + alive bool = true + gravity f64 + velocity f64 = 0.3 + jump f64 = -6 +} + +fn (mut b Bird) flap() { + b.gravity = b.jump +} + +fn (mut b Bird) update() { + b.gravity += b.velocity + b.y += b.gravity +} + +fn (b Bird) is_dead(height f64, pipes []Pipe) bool { + if b.y >= height || b.y + b.height <= 0 { + return true + } + for pipe in pipes { + if !(b.x > pipe.x + pipe.width || b.x + b.width < pipe.x || b.y > pipe.y + pipe.height + || b.y + b.height < pipe.y) { + return true + } + } + return false +} + +struct Pipe { +mut: + x f64 = 80 + y f64 = 250 + width f64 = 40 + height f64 = 30 + speed f64 = 3 +} + +fn (mut p Pipe) update() { + p.x -= p.speed +} + +fn (p Pipe) is_out() bool { + return p.x + p.width < 0 +} + +struct App { +mut: + gg &gg.Context + background gg.Image + bird gg.Image + pipetop gg.Image + pipebottom gg.Image + pipes []Pipe + birds []Bird + score int + max_score int + width f64 = win_width + height f64 = win_height + spawn_interval f64 = 90 + interval f64 + nv neuroevolution.Generations + gen []neuroevolution.Network + alives int + generation int + background_speed f64 = 0.5 + background_x f64 + timer_period_ms int = 24 +} + +fn (mut app App) start() { + app.interval = 0 + app.score = 0 + app.pipes = [] + app.birds = [] + app.gen = app.nv.generate() + for _ in 0 .. app.gen.len { + app.birds << Bird{} + } + app.generation++ + app.alives = app.birds.len +} + +fn (app &App) is_it_end() bool { + for i in 0 .. app.birds.len { + if app.birds[i].alive { + return false + } + } + return true +} + +fn (mut app App) update() { + app.background_x += app.background_speed + mut next_holl := f64(0) + if app.birds.len > 0 { + for i := 0; i < app.pipes.len; i += 2 { + if app.pipes[i].x + app.pipes[i].width > app.birds[0].x { + next_holl = app.pipes[i].height / app.height + break + } + } + } + for j, mut bird in app.birds { + if bird.alive { + inputs := [ + bird.y / app.height, + next_holl, + ] + res := app.gen[j].compute(inputs) + if res[0] > 0.5 { + bird.flap() + } + bird.update() + if bird.is_dead(app.height, app.pipes) { + bird.alive = false + app.alives-- + app.nv.network_score(app.gen[j], app.score) + if app.is_it_end() { + app.start() + } + } + } + } + for k := 0; k < app.pipes.len; k++ { + app.pipes[k].update() + if app.pipes[k].is_out() { + app.pipes.delete(k) + k-- + } + } + if app.interval == 0 { + delta_bord := f64(50) + pipe_holl := f64(120) + holl_position := math.round(rand.f64() * (app.height - delta_bord * 2.0 - pipe_holl)) + + delta_bord + app.pipes << Pipe{ + x: app.width + y: 0 + height: holl_position + } + app.pipes << Pipe{ + x: app.width + y: holl_position + pipe_holl + height: app.height + } + } + app.interval++ + if app.interval == app.spawn_interval { + app.interval = 0 + } + app.score++ + app.max_score = if app.score > app.max_score { app.score } else { app.max_score } +} + +fn main() { + mut app := &App{ + gg: 0 + } + mut font_path := os.resource_abs_path(os.join_path('../assets/fonts/', 'RobotoMono-Regular.ttf')) + $if android { + font_path = 'fonts/RobotoMono-Regular.ttf' + } + app.gg = gg.new_context( + bg_color: gx.white + width: win_width + height: win_height + create_window: true + window_title: 'flappylearning-v' + frame_fn: frame + event_fn: on_event + user_data: app + init_fn: init_images + font_path: font_path + ) + app.nv = neuroevolution.Generations{ + population: 50 + network: [2, 2, 1] + } + app.start() + go app.run() + app.gg.run() +} + +fn (mut app App) run() { + for { + app.update() + time.sleep(app.timer_period_ms * time.millisecond) + } +} + +fn init_images(mut app App) { + $if android { + background := os.read_apk_asset('img/background.png') or { panic(err) } + app.background = app.gg.create_image_from_byte_array(background) + bird := os.read_apk_asset('img/bird.png') or { panic(err) } + app.bird = app.gg.create_image_from_byte_array(bird) + pipetop := os.read_apk_asset('img/pipetop.png') or { panic(err) } + app.pipetop = app.gg.create_image_from_byte_array(pipetop) + pipebottom := os.read_apk_asset('img/pipebottom.png') or { panic(err) } + app.pipebottom = app.gg.create_image_from_byte_array(pipebottom) + } $else { + app.background = app.gg.create_image(os.resource_abs_path('assets/img/background.png')) + app.bird = app.gg.create_image(os.resource_abs_path('assets/img/bird.png')) + app.pipetop = app.gg.create_image(os.resource_abs_path('assets/img/pipetop.png')) + app.pipebottom = app.gg.create_image(os.resource_abs_path('assets/img/pipebottom.png')) + } +} + +fn frame(app &App) { + app.gg.begin() + app.draw() + app.gg.end() +} + +fn (app &App) display() { + for i := 0; i < int(math.ceil(app.width / app.background.width) + 1.0); i++ { + background_x := i * app.background.width - math.floor(int(app.background_x) % int(app.background.width)) + app.gg.draw_image(f32(background_x), 0, app.background.width, app.background.height, + app.background) + } + for i, pipe in app.pipes { + if i % 2 == 0 { + app.gg.draw_image(f32(pipe.x), f32(pipe.y + pipe.height - app.pipetop.height), + app.pipetop.width, app.pipetop.height, app.pipetop) + } else { + app.gg.draw_image(f32(pipe.x), f32(pipe.y), app.pipebottom.width, app.pipebottom.height, + app.pipebottom) + } + } + for bird in app.birds { + if bird.alive { + app.gg.draw_image(f32(bird.x), f32(bird.y), app.bird.width, app.bird.height, + app.bird) + } + } + app.gg.draw_text_def(10, 25, 'Score: $app.score') + app.gg.draw_text_def(10, 50, 'Max Score: $app.max_score') + app.gg.draw_text_def(10, 75, 'Generation: $app.generation') + app.gg.draw_text_def(10, 100, 'Alive: $app.alives / $app.nv.population') +} + +fn (app &App) draw() { + app.display() +} + +fn on_event(e &gg.Event, mut app App) { + if e.typ == .key_down { + app.key_down(e.key_code) + } +} + +fn (mut app App) key_down(key gg.KeyCode) { + // global keys + match key { + .escape { + exit(0) + } + ._0 { + app.timer_period_ms = 0 + } + .space { + if app.timer_period_ms == 24 { + app.timer_period_ms = 4 + } else { + app.timer_period_ms = 24 + } + } + else {} + } +} diff --git a/v_windows/v/examples/flappylearning/modules/neuroevolution/neuronevolution.v b/v_windows/v/examples/flappylearning/modules/neuroevolution/neuronevolution.v new file mode 100644 index 0000000..f3839ee --- /dev/null +++ b/v_windows/v/examples/flappylearning/modules/neuroevolution/neuronevolution.v @@ -0,0 +1,287 @@ +module neuroevolution + +import rand +import math + +fn random_clamped() f64 { + return rand.f64() * 2 - 1 +} + +pub fn activation(a f64) f64 { + ap := (-a) / 1 + return (1 / (1 + math.exp(ap))) +} + +fn round(a int, b f64) int { + return int(math.round(f64(a) * b)) +} + +struct Neuron { +mut: + value f64 + weights []f64 +} + +fn (mut n Neuron) populate(nb int) { + for _ in 0 .. nb { + n.weights << random_clamped() + } +} + +struct Layer { + id int +mut: + neurons []Neuron +} + +fn (mut l Layer) populate(nb_neurons int, nb_inputs int) { + for _ in 0 .. nb_neurons { + mut n := Neuron{} + n.populate(nb_inputs) + l.neurons << n + } +} + +struct Network { +mut: + layers []Layer +} + +fn (mut n Network) populate(network []int) { + assert network.len >= 2 + input := network[0] + hiddens := network[1..network.len - 1] + output := network[network.len - 1] + mut index := 0 + mut previous_neurons := 0 + mut input_layer := Layer{ + id: index + } + input_layer.populate(input, previous_neurons) + n.layers << input_layer + previous_neurons = input + index++ + for hidden in hiddens { + mut hidden_layer := Layer{ + id: index + } + hidden_layer.populate(hidden, previous_neurons) + previous_neurons = hidden + n.layers << hidden_layer + index++ + } + mut output_layer := Layer{ + id: index + } + output_layer.populate(output, previous_neurons) + n.layers << output_layer +} + +fn (n Network) get_save() Save { + mut save := Save{} + for layer in n.layers { + save.neurons << layer.neurons.len + for neuron in layer.neurons { + for weight in neuron.weights { + save.weights << weight + } + } + } + return save +} + +fn (mut n Network) set_save(save Save) { + mut previous_neurons := 0 + mut index := 0 + mut index_weights := 0 + n.layers = [] + for save_neuron in save.neurons { + mut layer := Layer{ + id: index + } + layer.populate(save_neuron, previous_neurons) + for mut neuron in layer.neurons { + for i in 0 .. neuron.weights.len { + neuron.weights[i] = save.weights[index_weights] + index_weights++ + } + } + previous_neurons = save_neuron + index++ + n.layers << layer + } +} + +pub fn (mut n Network) compute(inputs []f64) []f64 { + assert n.layers.len > 0 + assert inputs.len == n.layers[0].neurons.len + for i, input in inputs { + n.layers[0].neurons[i].value = input + } + mut prev_layer := n.layers[0] + for i in 1 .. n.layers.len { + for j, neuron in n.layers[i].neurons { + mut sum := f64(0) + for k, prev_layer_neuron in prev_layer.neurons { + sum += prev_layer_neuron.value * neuron.weights[k] + } + n.layers[i].neurons[j].value = activation(sum) + } + prev_layer = n.layers[i] + } + mut outputs := []f64{} + mut last_layer := n.layers[n.layers.len - 1] + for neuron in last_layer.neurons { + outputs << neuron.value + } + return outputs +} + +struct Save { +mut: + neurons []int + weights []f64 +} + +fn (s Save) clone() Save { + mut save := Save{} + save.neurons << s.neurons + save.weights << s.weights + return save +} + +struct Genome { + score int + network Save +} + +struct Generation { +mut: + genomes []Genome +} + +fn (mut g Generation) add_genome(genome Genome) { + mut i := 0 + for gg in g.genomes { + if genome.score > gg.score { + break + } + i++ + } + g.genomes.insert(i, genome) +} + +fn (g1 Genome) breed(g2 Genome, nb_child int) []Save { + mut datas := []Save{} + for _ in 0 .. nb_child { + mut data := g1.network.clone() + for i, weight in g2.network.weights { + if rand.f64() <= 0.5 { + data.weights[i] = weight + } + } + for i, _ in data.weights { + if rand.f64() <= 0.1 { + data.weights[i] += (rand.f64() * 2 - 1) * 0.5 + } + } + datas << data + } + return datas +} + +fn (g Generation) next(population int) []Save { + mut nexts := []Save{} + if population == 0 { + return nexts + } + keep := round(population, 0.2) + for i in 0 .. keep { + if nexts.len < population { + nexts << g.genomes[i].network.clone() + } + } + random := round(population, 0.2) + for _ in 0 .. random { + if nexts.len < population { + mut n := g.genomes[0].network.clone() + for k, _ in n.weights { + n.weights[k] = random_clamped() + } + nexts << n + } + } + mut max := 0 + out: for { + for i in 0 .. max { + mut childs := g.genomes[i].breed(g.genomes[max], 1) + for c in childs { + nexts << c + if nexts.len >= population { + break out + } + } + } + max++ + if max >= g.genomes.len - 1 { + max = 0 + } + } + return nexts +} + +pub struct Generations { +pub: + population int + network []int +mut: + generations []Generation +} + +fn (mut gs Generations) first() []Save { + mut out := []Save{} + for _ in 0 .. gs.population { + mut nn := Network{} + nn.populate(gs.network) + out << nn.get_save() + } + gs.generations << Generation{} + return out +} + +fn (mut gs Generations) next() []Save { + assert gs.generations.len > 0 + gen := gs.generations[gs.generations.len - 1].next(gs.population) + gs.generations << Generation{} + return gen +} + +fn (mut gs Generations) add_genome(genome Genome) { + assert gs.generations.len > 0 + gs.generations[gs.generations.len - 1].add_genome(genome) +} + +fn (mut gs Generations) restart() { + gs.generations = [] +} + +pub fn (mut gs Generations) generate() []Network { + saves := if gs.generations.len == 0 { gs.first() } else { gs.next() } + mut nns := []Network{} + for save in saves { + mut nn := Network{} + nn.set_save(save) + nns << nn + } + if gs.generations.len >= 2 { + gs.generations.delete(0) + } + return nns +} + +pub fn (mut gs Generations) network_score(network Network, score int) { + gs.add_genome(Genome{ + score: score + network: network.get_save() + }) +} diff --git a/v_windows/v/examples/function_types.v b/v_windows/v/examples/function_types.v new file mode 100644 index 0000000..fa924cb --- /dev/null +++ b/v_windows/v/examples/function_types.v @@ -0,0 +1,32 @@ +// Function signatures can be declared as types: + +type Filter = fn (string) string + +// Functions can accept function types as arguments: + +fn filter(s string, f Filter) string { + return f(s) +} + +// Declare a function with a matching signature: + +fn uppercase(s string) string { + return s.to_upper() +} + +fn main() { + // A function can be assigned to a matching type: + + my_filter := Filter(uppercase) + + // You don't strictly need the `Filter` cast - it's only used + // here to illustrate how these types are compatible. + + // All of the following prints "HELLO WORLD": + + println(filter('Hello world', my_filter)) + println(filter('Hello world', uppercase)) + println(filter('Hello world', fn (s string) string { + return s.to_upper() + })) +} diff --git a/v_windows/v/examples/game_of_life/README.md b/v_windows/v/examples/game_of_life/README.md new file mode 100644 index 0000000..02969ff --- /dev/null +++ b/v_windows/v/examples/game_of_life/README.md @@ -0,0 +1,10 @@ +# Conway's Game of Life + +![](https://github.com/fuyutarow/Conways-Game-of-Life-with-Vlang/raw/master/v-gun.gif) + + +``` +v run life.v +``` + +Created by fuyutarow: https://github.com/fuyutarow/Conways-Game-of-Life-with-Vlang diff --git a/v_windows/v/examples/game_of_life/life.v b/v_windows/v/examples/game_of_life/life.v new file mode 100644 index 0000000..0f3b7c7 --- /dev/null +++ b/v_windows/v/examples/game_of_life/life.v @@ -0,0 +1,25 @@ +module main + +import time +import automaton + +fn print_automaton(a &automaton.Automaton) { + for y := 1; y < a.field.maxy; y++ { + mut s := ' ' + for x := 1; x < a.field.maxx; x++ { + cell := a.field.get(x, y) + s += if cell == 1 { '@' } else { '.' } + } + println(s) + } + println('') +} + +fn main() { + mut a := automaton.gun() + for { + a.update() + print_automaton(a) + time.sleep(100 * time.millisecond) + } +} diff --git a/v_windows/v/examples/game_of_life/life_gg.v b/v_windows/v/examples/game_of_life/life_gg.v new file mode 100644 index 0000000..2f45017 --- /dev/null +++ b/v_windows/v/examples/game_of_life/life_gg.v @@ -0,0 +1,56 @@ +module main + +import gg +import gx +import automaton + +const ( + screen_width = 800 + screen_height = 600 + filled_color = gx.blue +) + +[live] +fn print_automaton(app &App) { + square_size := 18 + for y := 1; y < app.a.field.maxy; y++ { + for x := 1; x < app.a.field.maxx; x++ { + cell := app.a.field.get(x, y) + if cell == 1 { + app.gg.draw_rect(f32(square_size * x), f32(square_size * y), f32(square_size), + f32(square_size), filled_color) + } + } + } +} + +struct App { +mut: + gg &gg.Context + a automaton.Automaton +} + +fn frame(mut app App) { + app.gg.begin() + app.a.update() + print_automaton(app) + app.gg.end() +} + +fn main() { + mut app := App{ + gg: 0 + a: automaton.gun() + } + app.gg = gg.new_context( + bg_color: gx.white + frame_fn: frame + user_data: &app + width: screen_width + height: screen_height + create_window: true + resizable: false + window_title: 'v life (with gg, gx)' + ) + app.gg.run() +} diff --git a/v_windows/v/examples/game_of_life/modules/automaton/automaton.v b/v_windows/v/examples/game_of_life/modules/automaton/automaton.v new file mode 100644 index 0000000..5c0bf06 --- /dev/null +++ b/v_windows/v/examples/game_of_life/modules/automaton/automaton.v @@ -0,0 +1,132 @@ +module automaton + +// /////////////////////////////////////////////////////////// +pub struct A2D { +pub mut: + maxx int + maxy int + data &int +} + +fn new_a2d(maxx int, maxy int) &A2D { + size := int(sizeof(int)) * (maxx * maxy) + return &A2D{ + maxx: maxx + maxy: maxy + data: unsafe { &int(vcalloc(size)) } + } +} + +[inline] +pub fn (a &A2D) set(x int, y int, newval int) { + unsafe { + mut e := &int(0) + e = a.data + y * a.maxx + x + *e = newval + _ = e // TODO compiler bug, this is not necessary + } +} + +[inline] +pub fn (a &A2D) get(x int, y int) int { + unsafe { + mut e := &int(0) + e = a.data + y * a.maxx + x + _ = e + return *e + } +} + +[inline] +pub fn (a &A2D) clear() { + for y := 0; y < a.maxy; y++ { + for x := 0; x < a.maxx; x++ { + a.set(x, y, 0) + } + } +} + +// /////////////////////////////////////////////////////////// +pub struct Automaton { +pub mut: + field &A2D + new_field &A2D +} + +fn new_automaton(ftext string) Automaton { + f := ftext.split('\n').map(it.trim_space()).filter(it.len > 0) + maxy := f.len + mut maxx := 0 + for y := 0; y < f.len; y++ { + if maxx < f[y].len { + maxx = f[y].len + } + } + field := new_a2d(maxx, maxy) + new_field := new_a2d(maxx, maxy) + for y in 0 .. field.maxy { + for x in 0 .. field.maxx { + val := if x < f[y].len && f[y][x] == `#` { 1 } else { 0 } + field.set(x, y, val) + } + } + return Automaton{ + field: field + new_field: new_field + } +} + +pub fn (mut aa Automaton) update() { + aa.new_field.clear() + for y := 1; y < aa.field.maxy; y++ { + for x := 1; x < aa.field.maxx; x++ { + moore_sum := (0 + aa.field.get(x - 1, y - 1) + aa.field.get(x, y - 1) + aa.field.get(x + + 1, y - 1) + aa.field.get(x - 1, y) + 0 + aa.field.get(x + 1, y) + + aa.field.get(x - 1, y + 1) + aa.field.get(x, y + 1) + aa.field.get(x + 1, y + 1)) + cell := aa.field.get(x, y) + v := if cell == 1 { moore_sum in [2, 3] } else { moore_sum == 3 } + aa.new_field.set(x, y, if v { 1 } else { 0 }) + } + } + tmp := aa.field + aa.field = aa.new_field + aa.new_field = tmp +} + +pub fn gun() Automaton { + field := ' +******************************************* +* * +* A shooting gun: * +* # * +* # # * +* ## ## ## * +* # # ## ## * +* ## # # ## * +* ## # # ## # # * +* # # # * +* # # * +* ## * +* * +* Tetris Life: * +* * +* ## #### * +* ## * +* * +* * +* * +* # ## * +* ### ## * +* * +* * +* * +* # * +* ### * +* * +* * +* * +* * +******************************************* +' + return new_automaton(field) +} diff --git a/v_windows/v/examples/get_weather/README.md b/v_windows/v/examples/get_weather/README.md new file mode 100644 index 0000000..be2695d --- /dev/null +++ b/v_windows/v/examples/get_weather/README.md @@ -0,0 +1,23 @@ +# get_weather +get_weather is a very simple web crawler. +Its goal is to get a weather forecast from caiyunapp.com. + +# Compile and Run + +Use this to generate an executable and then launch the web crawler. +```bash +v get_weather.v +./get_weather +``` + +As a convenience, you can also compile and launch the web crawler directly. +```bash +v run get_weather.v +``` + +In this project we use http.fetch() to get a http.Response, with a +custom user-agent and then we use json.decode() to decode the json +response to struct. +We also use a `[skip]` attribute to skip certain fields in the response, +that we don't need and use a `[json: result]` attribute to specify that +our struct field is named differently from the incoming json response. diff --git a/v_windows/v/examples/get_weather/get_weather.v b/v_windows/v/examples/get_weather/get_weather.v new file mode 100644 index 0000000..4de0572 --- /dev/null +++ b/v_windows/v/examples/get_weather/get_weather.v @@ -0,0 +1,55 @@ +import json +import rand +import net.http + +struct Weather { + status string [skip] // drop this field + api_version string [skip] + api_status string [skip] + lang string [skip] + unit string [skip] + tzshift int [skip] + timezone string [skip] + server_time u32 [skip] + location []f32 [skip] + result Result //[json: result] if the field name is different in JSON, it can be specified +} + +struct Result { + realtime Realtime [skip] + minutely Minutely [skip] + hourly Hourly [skip] + daily Daily [skip] + primary int [skip] + forecast_keypoint string +} + +struct Realtime {} + +struct Minutely {} + +struct Hourly {} + +struct Daily {} + +fn main() { + config := http.FetchConfig{ + user_agent: 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0' + } + + rnd := rand.f32() + url := 'https://api.caiyunapp.com/v2.5/96Ly7wgKGq6FhllM/116.391912,40.010711/weather.jsonp?hourlysteps=120&random=$rnd' + // println(url) + + resp := http.fetch(http.FetchConfig{ ...config, url: url }) or { + println('failed to fetch data from the server') + return + } + + weather := json.decode(Weather, resp.text) or { + println('failed to decode weather json') + return + } + + println('未来两小时天气:\n${weather.result.forecast_keypoint}.') +} diff --git a/v_windows/v/examples/gg/bezier.v b/v_windows/v/examples/gg/bezier.v new file mode 100644 index 0000000..e5cb675 --- /dev/null +++ b/v_windows/v/examples/gg/bezier.v @@ -0,0 +1,34 @@ +module main + +import gg +import gx + +const ( + points = [f32(200.0), 200.0, 200.0, 100.0, 400.0, 100.0, 400.0, 300.0] +) + +struct App { +mut: + gg &gg.Context +} + +fn main() { + mut app := &App{ + gg: 0 + } + app.gg = gg.new_context( + bg_color: gx.rgb(174, 198, 255) + width: 600 + height: 400 + window_title: 'Cubic Bézier curve' + frame_fn: frame + user_data: app + ) + app.gg.run() +} + +fn frame(mut app App) { + app.gg.begin() + app.gg.draw_cubic_bezier(points, gx.blue) + app.gg.end() +} diff --git a/v_windows/v/examples/gg/bezier_anim.v b/v_windows/v/examples/gg/bezier_anim.v new file mode 100644 index 0000000..2f54ac9 --- /dev/null +++ b/v_windows/v/examples/gg/bezier_anim.v @@ -0,0 +1,68 @@ +module main + +import gg +import gx + +const rate = f32(1) / 60 * 10 + +struct App { +mut: + gg &gg.Context + anim &Anim +} + +struct Anim { +mut: + time f32 + reverse bool +} + +fn (mut anim Anim) advance() { + if anim.reverse { + anim.time -= 1 * rate + } else { + anim.time += 1 * rate + } + // Use some arbitrary value that fits 60 fps + if anim.time > 80 * rate || anim.time < -80 * rate { + anim.reverse = !anim.reverse + } +} + +fn main() { + mut app := &App{ + gg: 0 + anim: &Anim{} + } + app.gg = gg.new_context( + bg_color: gx.rgb(174, 198, 255) + width: 600 + height: 400 + window_title: 'Animated cubic Bézier curve' + frame_fn: frame + user_data: app + ) + app.gg.run() +} + +fn frame(mut app App) { + time := app.anim.time + + p1_x := f32(200.0) + p1_y := f32(200.0) + (10 * time) + + p2_x := f32(400.0) + p2_y := f32(200.0) + (10 * time) + + ctrl_p1_x := f32(200.0) + (40 * time) + ctrl_p1_y := f32(100.0) + ctrl_p2_x := f32(400.0) + (-40 * time) + ctrl_p2_y := f32(100.0) + + points := [p1_x, p1_y, ctrl_p1_x, ctrl_p1_y, ctrl_p2_x, ctrl_p2_y, p2_x, p2_y] + + app.gg.begin() + app.gg.draw_cubic_bezier(points, gx.blue) + app.gg.end() + app.anim.advance() +} diff --git a/v_windows/v/examples/gg/logo.png b/v_windows/v/examples/gg/logo.png Binary files differnew file mode 100644 index 0000000..98ebfb1 --- /dev/null +++ b/v_windows/v/examples/gg/logo.png diff --git a/v_windows/v/examples/gg/polygons.v b/v_windows/v/examples/gg/polygons.v new file mode 100644 index 0000000..9ae2037 --- /dev/null +++ b/v_windows/v/examples/gg/polygons.v @@ -0,0 +1,34 @@ +module main + +import gg +import gx + +struct App { +mut: + gg &gg.Context +} + +fn main() { + mut app := &App{ + gg: 0 + } + app.gg = gg.new_context( + bg_color: gx.rgb(174, 198, 255) + width: 600 + height: 400 + window_title: 'Polygons' + frame_fn: frame + user_data: app + ) + app.gg.run() +} + +fn frame(mut app App) { + app.gg.begin() + app.gg.draw_convex_poly([f32(100.0), 100.0, 200.0, 100.0, 300.0, 200.0, 200.0, 300.0, 100.0, + 300.0, + ], gx.blue) + app.gg.draw_empty_poly([f32(50.0), 50.0, 70.0, 60.0, 90.0, 80.0, 70.0, 110.0], gx.black) + app.gg.draw_triangle(450, 142, 530, 280, 370, 280, gx.red) + app.gg.end() +} diff --git a/v_windows/v/examples/gg/random.v b/v_windows/v/examples/gg/random.v new file mode 100644 index 0000000..f9e963b --- /dev/null +++ b/v_windows/v/examples/gg/random.v @@ -0,0 +1,63 @@ +import gg +import time + +const pwidth = 800 + +const pheight = 600 + +const pbytes = 4 + +struct AppState { +mut: + gg &gg.Context = 0 + istream_idx int + pixels [pheight][pwidth]u32 +} + +[direct_array_access] +fn (mut state AppState) update() { + mut rcolor := u64(state.gg.frame) + for { + for y in 0 .. pheight { + for x in 0 .. pwidth { + rcolor = rcolor * 1664525 + 1013904223 + state.pixels[y][x] = u32(rcolor & 0x0000_0000_FFFF_FFFF) | 0x1010AFFF + } + } + time.sleep(33 * time.millisecond) + } +} + +fn (mut state AppState) draw() { + mut istream_image := state.gg.get_cached_image_by_idx(state.istream_idx) + istream_image.update_pixel_data(&state.pixels) + size := gg.window_size() + state.gg.draw_image(0, 0, size.width, size.height, istream_image) +} + +// gg callbacks: + +fn graphics_init(mut state AppState) { + state.istream_idx = state.gg.new_streaming_image(pwidth, pheight, pbytes, pixel_format: .rgba8) +} + +fn graphics_frame(mut state AppState) { + state.gg.begin() + state.draw() + state.gg.end() +} + +fn main() { + mut state := &AppState{} + state.gg = gg.new_context( + width: 800 + height: 600 + create_window: true + window_title: 'Random Static' + init_fn: graphics_init + frame_fn: graphics_frame + user_data: state + ) + go state.update() + state.gg.run() +} diff --git a/v_windows/v/examples/gg/raven_text_rendering.v b/v_windows/v/examples/gg/raven_text_rendering.v new file mode 100644 index 0000000..2007787 --- /dev/null +++ b/v_windows/v/examples/gg/raven_text_rendering.v @@ -0,0 +1,104 @@ +module main + +import gg +import gx +import os +import math + +const ( + win_width = 600 + win_height = 700 + bg_color = gx.white +) + +const ( + text = ' +Once upon a midnight dreary, while I pondered, weak and weary, +Over many a quaint and curious volume of forgotten lore— + While I nodded, nearly napping, suddenly there came a tapping, +As of some one gently rapping, rapping at my chamber door. +“’Tis some visitor,” I muttered, “tapping at my chamber door— + Only this and nothing more.” + + Ah, distinctly I remember it was in the bleak December; +And each separate dying ember wrought its ghost upon the floor. + Eagerly I wished the morrow;—vainly I had sought to borrow + From my books surcease of sorrow—sorrow for the lost Lenore— +For the rare and radiant maiden whom the angels name Lenore— + Nameless here for evermore. + + And the silken, sad, uncertain rustling of each purple curtain +Thrilled me—filled me with fantastic terrors never felt before; + So that now, to still the beating of my heart, I stood repeating + “’Tis some visitor entreating entrance at my chamber door— +Some late visitor entreating entrance at my chamber door;— + This it is and nothing more.” + + Presently my soul grew stronger; hesitating then no longer, +“Sir,” said I, “or Madam, truly your forgiveness I implore; + But the fact is I was napping, and so gently you came rapping, + And so faintly you came tapping, tapping at my chamber door, +That I scarce was sure I heard you”—here I opened wide the door;— + Darkness there and nothing more. + +Deep into that darkness peering, long I stood there wondering, fearing, +Doubting, dreaming dreams no mortal ever dared to dream before; + But the silence was unbroken, and the stillness gave no token, + And the only word there spoken was the whispered word, “Lenore?” +This I whispered, and an echo murmured back the word, “Lenore!”— + Merely this and nothing more. + + Back into the chamber turning, all my soul within me burning, +Soon again I heard a tapping somewhat louder than before. + “Surely,” said I, “surely that is something at my window lattice; + Let me see, then, what thereat is, and this mystery explore— +Let my heart be still a moment and this mystery explore;— + ’Tis the wind and nothing more!” +' + lines = text.split('\n') +) + +struct App { +mut: + gg &gg.Context +} + +fn main() { + mut app := &App{ + gg: 0 + } + mut font_path := os.resource_abs_path(os.join_path('..', 'assets', 'fonts', 'RobotoMono-Regular.ttf')) + $if android { + font_path = 'fonts/RobotoMono-Regular.ttf' + } + app.gg = gg.new_context( + width: win_width + height: win_height + create_window: true + window_title: 'Empty window' + user_data: app + bg_color: bg_color + frame_fn: frame + font_path: font_path // window_user_ptr: ctx + // native_rendering: true + ) + app.gg.run() +} + +fn frame(mut app App) { + app.gg.begin() + width := gg.window_size().width + mut scale_factor := math.round(f32(width) / win_width) + if scale_factor <= 0 { + scale_factor = 1 + } + text_cfg := gx.TextCfg{ + size: 16 * int(scale_factor) + } + mut y := 10 + for line in lines { + app.gg.draw_text(10, y, line, text_cfg) + y += 30 + } + app.gg.end() +} diff --git a/v_windows/v/examples/gg/rectangles.v b/v_windows/v/examples/gg/rectangles.v new file mode 100644 index 0000000..97dc222 --- /dev/null +++ b/v_windows/v/examples/gg/rectangles.v @@ -0,0 +1,63 @@ +module main + +import gg +import gx +import os +import net.http + +const ( + win_width = 600 + win_height = 300 +) + +struct App { +mut: + gg &gg.Context + image gg.Image +} + +fn main() { + mut app := &App{ + gg: 0 + } + app.gg = gg.new_context( + bg_color: gx.white + width: win_width + height: win_height + create_window: true + window_title: 'Rectangles' + frame_fn: frame + user_data: app + init_fn: init_images + ) + app.image = app.gg.create_image(os.resource_abs_path('logo.png')) + + http.download_file_with_progress("http://retrowave.ru/artwork/8b1a28d7f9a9322f44fe5f98f87229f1d2f6b883.jpg","download.png", download_finished, downloading) + app.gg.run() +} + +fn download_finished() { + println("Finished") +} + +fn downloading() { + println("downloding") +} + +fn init_images(mut app App) { + // app.image = gg.create_image('logo.png') +} + +fn frame(app &App) { + app.gg.begin() + app.draw() + app.gg.end() +} + +fn (app &App) draw() { + // app.gg.draw_text_def(200,20, 'hello world!') + // app.gg.draw_text_def(300,300, 'привет') + app.gg.draw_rect(10, 10, 100, 30, gx.blue) + app.gg.draw_empty_rect(110, 150, 80, 40, gx.black) + app.gg.draw_image(230, 30, app.image.width, app.image.height, app.image) +} diff --git a/v_windows/v/examples/gg/set_pixels.v b/v_windows/v/examples/gg/set_pixels.v new file mode 100644 index 0000000..69dc53d --- /dev/null +++ b/v_windows/v/examples/gg/set_pixels.v @@ -0,0 +1,39 @@ +module main + +import gg +import gx + +struct App { +mut: + gg &gg.Context +} + +fn main() { + mut app := &App{ + gg: 0 + } + app.gg = gg.new_context( + bg_color: gx.rgb(174, 198, 255) + width: 100 + height: 100 + window_title: 'Set Pixels' + frame_fn: frame + user_data: app + ) + app.gg.run() +} + +fn frame(mut app App) { + mut pixels := []f32{} + + for x in 30 .. 60 { + for y in 30 .. 60 { + pixels << f32(x) + pixels << f32(y) + } + } + + app.gg.begin() + app.gg.set_pixels(pixels, gx.red) + app.gg.end() +} diff --git a/v_windows/v/examples/gg/stars.v b/v_windows/v/examples/gg/stars.v new file mode 100644 index 0000000..815cfeb --- /dev/null +++ b/v_windows/v/examples/gg/stars.v @@ -0,0 +1,134 @@ +module main + +import os +import gg +import gx +import rand +import sokol.sgl + +const ( + win_width = 800 + win_height = 600 + max_stars = 5000 + max_v_letters = 5 +) + +struct Star { +mut: + x f32 + y f32 + z f32 + r f32 + g f32 + b f32 +} + +struct VLetter { +mut: + x f32 + y f32 + z f32 + w f32 + h f32 + angle f32 + dz f32 + dangle f32 +} + +struct App { +mut: + gg &gg.Context + image gg.Image + stars []Star + v_letters []VLetter +} + +fn main() { + mut app := &App{ + gg: 0 + stars: []Star{len: max_stars} + v_letters: []VLetter{len: max_v_letters} + } + app.gg = gg.new_context( + bg_color: gx.black + width: win_width + height: win_height + create_window: true + window_title: 'Star Vield' + frame_fn: frame + init_fn: init_images + user_data: app + ) + for i in 0 .. max_stars { + app.stars[i].x = rand.f32_in_range(-200.0, 200.0) + app.stars[i].y = rand.f32_in_range(-200.0, 200.0) + app.stars[i].z = rand.f32_in_range(-200.0, -100.0) + app.stars[i].r = rand.f32_in_range(0.1, 1.0) + app.stars[i].g = rand.f32_in_range(0.1, 1.0) + app.stars[i].b = rand.f32_in_range(0.1, 1.0) + } + for i in 0 .. max_v_letters { + app.v_letters[i].x = rand.f32_in_range(-20.0, 20.0) + app.v_letters[i].y = rand.f32_in_range(-20.0, 20.0) + app.v_letters[i].z = rand.f32_in_range(-5.0, -1.0) + app.v_letters[i].w = rand.f32_in_range(5, 20) + app.v_letters[i].h = app.v_letters[i].w + app.v_letters[i].angle = rand.f32_in_range(0, 6.283184) + app.v_letters[i].dangle = rand.f32_in_range(-0.05, 0.05) + app.v_letters[i].dz = rand.f32_in_range(-0.1, -0.01) + } + app.gg.run() +} + +fn init_images(mut app App) { + app.image = app.gg.create_image(os.resource_abs_path('logo.png')) +} + +fn frame(mut app App) { + app.gg.begin() + app.draw() + app.gg.end() +} + +// fn C.glPointSize(size f32) +fn (mut app App) draw() { + sgl.defaults() + sgl.perspective(sgl.rad(90), 1.0, 1.0, 100.0) + // C.glPointSize(3.0) + sgl.begin_points() + for i in 0 .. app.stars.len { + s := app.stars[i] + sgl.v3f_c3f(s.x, s.y, s.z, s.r, s.g, s.b) + app.stars[i].z += 0.3 + if app.stars[i].z > -1.0 { + app.stars[i].x = rand.f32_in_range(-200.0, 200.0) + app.stars[i].y = rand.f32_in_range(-200.0, 200.0) + app.stars[i].z = rand.f32_in_range(-200.0, -100.0) + } + } + sgl.end() + // //// + for i in 0 .. app.v_letters.len { + v := app.v_letters[i] + sgl.defaults() + sgl.perspective(sgl.rad(90), 1.0, 1.0, 100.0) + sgl.rotate(v.angle, 0, 0, 1) + app.gg.draw_image_3d(v.x, v.y, v.z, v.w, v.h, app.image) + // + app.v_letters[i].z += app.v_letters[i].dz + app.v_letters[i].angle += app.v_letters[i].dangle + if app.v_letters[i].z > -60.0 { + app.v_letters[i].x += rand.f32_in_range(-0.05, 0.05) + app.v_letters[i].y += rand.f32_in_range(-0.05, 0.05) + } + if app.v_letters[i].z < -95.0 { + app.v_letters[i].h *= 0.8 + app.v_letters[i].w *= 0.8 + } + if app.v_letters[i].z < -100.0 { + app.v_letters[i].z = rand.f32_in_range(-5.0, -1.0) + app.v_letters[i].h = 10.0 + app.v_letters[i].w = 10.0 + } + } +} diff --git a/v_windows/v/examples/gg/worker_thread.v b/v_windows/v/examples/gg/worker_thread.v new file mode 100644 index 0000000..a2c9852 --- /dev/null +++ b/v_windows/v/examples/gg/worker_thread.v @@ -0,0 +1,90 @@ +// Copyright(C) 2019 Lars Pontoppidan. All rights reserved. +// Use of this source code is governed by an MIT license file distributed with this software package. +module main + +// Example of how to send a value through a channel from a worker thread to the main/rendering thread. +// This can be useful to do long running computations while keeping your framerate high (60 fps in this example). +import gg +import gx +import math +import time + +const ( + win_width = 600 + win_height = 700 + bg_color = gx.white + count_color = gx.black +) + +struct App { +mut: + gg &gg.Context + ch chan i64 + counter i64 +} + +fn main() { + mut app := &App{ + gg: 0 + } + app.gg = gg.new_context( + width: win_width + height: win_height + create_window: true + window_title: 'Counter' + user_data: app + bg_color: bg_color + frame_fn: frame + init_fn: init + font_path: gg.system_font_path() + ) + app.gg.run() +} + +fn init(mut app App) { + // Spawn a new worker thread. + go worker(mut app) +} + +// worker simulates a workload. This should be run in a separate thread. +fn worker(mut app App) { + stopwatch := time.new_stopwatch() + mut elapsed := stopwatch.elapsed() + // Do heavy operations here - like invoking a path finding algorithm, load an image or similar. + for { + now := stopwatch.elapsed() + // When done - send the result through a channel to the main/rendering thread. + app.ch <- (now - elapsed) + elapsed = now + time.sleep(1 * time.second) + } +} + +fn frame(mut app App) { + app.gg.begin() + size := gg.window_size() + mut scale_factor := math.round(f32(size.width) / win_width) + if scale_factor <= 0 { + scale_factor = 1 + } + text_cfg := gx.TextCfg{ + size: 64 * int(scale_factor) + } + + // Try a pop from the channel + mut count := i64(0) + if app.ch.try_pop(mut count) == .success { + // A value was assigned - increase the counter + app.counter += i64(f64(count) / time.second) + } + + label := '$app.counter' + label_width := (f64(label.len * text_cfg.size) / 4.0) + label_height := (f64(1 * text_cfg.size) / 2.0) + mut x := f32(size.width) * 0.5 - label_width + mut y := f32(size.height) * 0.5 - label_height + + app.gg.draw_text(int(x), int(y), label, text_cfg) + + app.gg.end() +} diff --git a/v_windows/v/examples/hanoi.v b/v_windows/v/examples/hanoi.v new file mode 100644 index 0000000..0851ba0 --- /dev/null +++ b/v_windows/v/examples/hanoi.v @@ -0,0 +1,22 @@ +// hanoi tower +const ( + num = 7 +) + +fn main() { + hanoi(num, 'A', 'B', 'C') +} + +fn move(n int, a string, b string) { + println('Disc $n from $a to ${b}...') +} + +fn hanoi(n int, a string, b string, c string) { + if n == 1 { + move(1, a, c) + } else { + hanoi(n - 1, a, c, b) + move(n, a, c) + hanoi(n - 1, b, a, c) + } +} diff --git a/v_windows/v/examples/hello_v_js.v b/v_windows/v/examples/hello_v_js.v new file mode 100644 index 0000000..2286719 --- /dev/null +++ b/v_windows/v/examples/hello_v_js.v @@ -0,0 +1,5 @@ +fn main() { + for i in 0 .. 3 { + println('Hello from V.js ($i)') + } +} diff --git a/v_windows/v/examples/hello_world.v b/v_windows/v/examples/hello_world.v new file mode 100644 index 0000000..fb96416 --- /dev/null +++ b/v_windows/v/examples/hello_world.v @@ -0,0 +1 @@ +println('Hello, World!') diff --git a/v_windows/v/examples/hot_reload/.gitignore b/v_windows/v/examples/hot_reload/.gitignore new file mode 100644 index 0000000..fc656b9 --- /dev/null +++ b/v_windows/v/examples/hot_reload/.gitignore @@ -0,0 +1 @@ +!glfw3.dll diff --git a/v_windows/v/examples/hot_reload/bounce.v b/v_windows/v/examples/hot_reload/bounce.v new file mode 100644 index 0000000..135ad52 --- /dev/null +++ b/v_windows/v/examples/hot_reload/bounce.v @@ -0,0 +1,84 @@ +// Build this example with +// v -live bounce.v +module main + +import gx +import gg +import time + +struct Game { +mut: + gg &gg.Context + x int + y int + dy int + dx int + height int + width int + draw_fn voidptr +} + +const ( + window_width = 400 + window_height = 300 + width = 50 +) + +fn main() { + mut game := &Game{ + gg: 0 + dx: 2 + dy: 2 + height: window_height + width: window_width + draw_fn: 0 + } + game.gg = gg.new_context( + width: window_width + height: window_height + font_size: 20 + user_data: game + window_title: 'Hot code reloading demo' + create_window: true + frame_fn: frame + bg_color: gx.white + font_path: gg.system_font_path() + ) + // window.onkeydown(key_down) + println('Starting the game loop...') + go game.run() + game.gg.run() +} + +// Try uncommenting or changing the lines inside the live functions. +// Guess what will happen: +[live] +fn frame(mut game Game) { + game.gg.begin() + game.gg.draw_text_def(10, 5, 'Modify examples/hot_reload/bounce.v to get instant updates') + game.gg.draw_rect(game.x, game.y, width, width, gx.blue) + game.gg.draw_rect(window_width - width - game.x + 10, 200 - game.y + width, width, + width, gx.rgb(228, 10, 55)) + game.gg.draw_rect(game.x - 25, 250 - game.y, width, width, gx.rgb(28, 240, 55)) + game.gg.end() +} + +[live] +fn (mut game Game) update_model() { + speed := 2 + game.x += speed * game.dx + game.y += speed * game.dy + if game.y >= game.height - width || game.y <= 0 { + game.dy = -game.dy + } + if game.x >= game.width - width || game.x <= 0 { + game.dx = -game.dx + } +} + +fn (mut game Game) run() { + for { + game.update_model() + time.sleep(16 * time.millisecond) // 60fps + } +} diff --git a/v_windows/v/examples/hot_reload/graph.v b/v_windows/v/examples/hot_reload/graph.v new file mode 100644 index 0000000..53228f1 --- /dev/null +++ b/v_windows/v/examples/hot_reload/graph.v @@ -0,0 +1,86 @@ +module main + +import gx +import gg +import time +import math + +const ( + size = 700 + scale = 50.0 +) + +struct Context { +mut: + gg &gg.Context +} + +fn main() { + mut context := &Context{ + gg: 0 + } + context.gg = gg.new_context( + width: size + height: size + font_size: 20 + user_data: context + window_title: 'Graph builder' + create_window: true + frame_fn: frame + resizable: true + bg_color: gx.white + font_path: gg.system_font_path() + ) + context.gg.run() +} + +fn frame(mut ctx Context) { + ctx.gg.begin() + ctx.draw() + ctx.gg.end() +} + +[live] +fn (ctx &Context) draw() { + s := gg.window_size() + mut w := s.width + mut h := s.height + if gg.high_dpi() { + w /= 2 + h /= 2 + } + ctx.gg.draw_line(0, h / 2, w, h / 2, gx.gray) // x axis + ctx.gg.draw_line(w / 2, 0, w / 2, h, gx.gray) // y axis + atime := f64(time.ticks() / 10) + stime := math.sin(2.0 * math.pi * f64(time.ticks() % 6000) / 6000) + mut y := 0.0 + blue := gx.Color{ + r: 100 + g: 100 + b: 200 + } + red := gx.Color{ + r: 200 + g: 100 + b: 100 + } + y = 1.0 + max := f32(w) / (2 * scale) + min := -max + for x := min; x <= max; x += 0.01 { + // y = x*x + 2 + // y = x * x + stime * stime + // y = stime + // y = stime * h + y = stime * 1.0 * math.sin(x + stime + atime / 32) * ((h / 256) + x) + // y = (stime * x) * x + stime + // y = (x + 3) * (x + 3) / stime + stime*2.5 + // y = math.sqrt(30.0 - x * x) * stime + // y -= (stime-0.5) + stime + // ctx.gg.draw_rect(f32((w/2) + x * scale), f32((h/2) - y * scale), 2, 2, blue) + ctx.gg.draw_rect(f32((w / 2) + x * scale), f32((h / 2) - y * scale), 2, (f32(y) * scale), + blue) + ctx.gg.draw_rect(f32((w / 2) + x * scale), f32((h / 2) + y * scale), 2, (f32(y) * scale) + + 32, red) + } +} diff --git a/v_windows/v/examples/hot_reload/message.v b/v_windows/v/examples/hot_reload/message.v new file mode 100644 index 0000000..1f9ec2a --- /dev/null +++ b/v_windows/v/examples/hot_reload/message.v @@ -0,0 +1,18 @@ +module main + +// Build this example with `v -live message.v` +import time +import v.live + +[live] +fn print_message() { + info := live.info() + println('OK reloads: ${info.reloads_ok:4d} | Total reloads: ${info.reloads:4d} | Hello! Modify this message while the program is running.') +} + +fn main() { + for { + print_message() + time.sleep(500 * time.millisecond) + } +} diff --git a/v_windows/v/examples/http_server.v b/v_windows/v/examples/http_server.v new file mode 100644 index 0000000..ffcc299 --- /dev/null +++ b/v_windows/v/examples/http_server.v @@ -0,0 +1,28 @@ +module main + +import net.http { CommonHeader, Request, Response, Server } + +struct ExampleHandler {} + +fn (h ExampleHandler) handle(req Request) Response { + mut res := Response{ + header: http.new_header_from_map({ + CommonHeader.content_type: 'text/plain' + }) + } + res.text = match req.url { + '/foo' { 'bar\n' } + '/hello' { 'world\n' } + '/' { 'foo\nhello\n' } + else { 'Not found\n' } + } + res.status_code = if res.text == 'Not found' { 404 } else { 200 } + return res +} + +fn main() { + mut server := Server{ + handler: ExampleHandler{} + } + server.listen_and_serve() ? +} diff --git a/v_windows/v/examples/json.v b/v_windows/v/examples/json.v new file mode 100644 index 0000000..841d9e2 --- /dev/null +++ b/v_windows/v/examples/json.v @@ -0,0 +1,40 @@ +import json + +struct User { + name string + age int +mut: + is_registered bool +} + +fn main() { + s := '[{ "name":"Frodo", "age":25}, {"name":"Bobby", "age":10}]' + mut users := json.decode([]User, s) or { + eprintln('Failed to parse json') + return + } + for user in users { + println('$user.name: $user.age') + } + println('') + for i, user in users { + println('$i) $user.name') + if !user.can_register() { + println('Cannot register $user.name, they are too young') + } else { + users[i].register() + println('$user.name is registered') + } + } + // Let's encode users again just for fun + println('') + println(json.encode(users)) +} + +fn (u User) can_register() bool { + return u.age >= 16 +} + +fn (mut u User) register() { + u.is_registered = true +} diff --git a/v_windows/v/examples/lander.v b/v_windows/v/examples/lander.v new file mode 100644 index 0000000..ad2600d --- /dev/null +++ b/v_windows/v/examples/lander.v @@ -0,0 +1,62 @@ +// Example of sum types +// Models a landing craft leaving orbit and landing on a world +import rand +import time + +struct Moon { +} + +struct Mars { +} + +fn (m Mars) dust_storm() bool { + return rand.int() >= 0 +} + +struct Venus { +} + +type World = Mars | Moon | Venus + +struct Lander { +} + +fn (l Lander) deorbit() { + println('leaving orbit') +} + +fn (l Lander) open_parachutes(n int) { + println('opening $n parachutes') +} + +fn wait() { + println('waiting...') + time.sleep(1 * time.second) +} + +fn (l Lander) land(w World) { + if w is Mars { + for w.dust_storm() { + wait() + } + } + l.deorbit() + match w { + Moon {} // no atmosphere + Mars { + // light atmosphere + l.open_parachutes(3) + } + Venus { + // heavy atmosphere + l.open_parachutes(1) + } + } + println('landed') +} + +fn main() { + l := Lander{} + l.land(Venus{}) + l.land(Mars{}) +} diff --git a/v_windows/v/examples/linear_regression/simple_linear_regression.v b/v_windows/v/examples/linear_regression/simple_linear_regression.v new file mode 100644 index 0000000..5bce1dc --- /dev/null +++ b/v_windows/v/examples/linear_regression/simple_linear_regression.v @@ -0,0 +1,55 @@ +import math + +struct LinearResult { + r2 f64 + intercept f64 + slope f64 + dependent_variable_means f64 + independent_variable_means f64 +} + +fn linearrelationship(independent_variable []int, dependent_variable []int) LinearResult { + // Objective : + // Find what is the linear relationship between two dataset X and Y? + // x := independent variable + // y := dependent variable + mut sum_r2_x := 0 + mut sum_r2_y := 0 + mut sum_xy := 0 + mut sum_x := 0 + mut sum_y := 0 + for i in independent_variable { + sum_x += i + sum_r2_x += i * i + } + for yi in dependent_variable { + sum_y += yi + sum_r2_y += yi * yi + } + x_means := sum_x / independent_variable.len + y_means := sum_y / dependent_variable.len + for index, x_value in independent_variable { + sum_xy += x_value * dependent_variable[index] + } + // /Slope = (∑y)(∑x²) - (∑x)(∑xy) / n(∑x²) - (∑x)² + slope_value := f64((sum_y * sum_r2_x) - (sum_x * sum_xy)) / f64((sum_r2_x * independent_variable.len) - (sum_x * sum_x)) + // /Intercept = n(∑xy) - (∑x)(∑y) / n(∑x²) - (∑x)² + intercept_value := f64((independent_variable.len * sum_xy) - (sum_x * sum_y)) / f64((independent_variable.len * sum_r2_x) - (sum_x * sum_x)) + // Regression equation = Intercept + Slope x + // R2 = n(∑xy) - (∑x)(∑y) / sqrt([n(∑x²)-(∑x)²][n(∑y²)-(∑y)²] + r2_value := f64((independent_variable.len * sum_xy) - (sum_x * sum_y)) / math.sqrt(f64((sum_r2_x * independent_variable.len) - (sum_x * sum_x)) * f64((sum_r2_y * dependent_variable.len) - (sum_y * sum_y))) + return LinearResult{ + r2: r2_value + intercept: intercept_value + slope: slope_value + independent_variable_means: x_means + dependent_variable_means: y_means + } +} + +fn main() { + independent_variable := [4, 5, 6, 7, 10] + dependent_variable := [3, 8, 20, 30, 12] + result := linearrelationship(independent_variable, dependent_variable) + println(result) +} diff --git a/v_windows/v/examples/links_scraper.v b/v_windows/v/examples/links_scraper.v new file mode 100644 index 0000000..2a0b775 --- /dev/null +++ b/v_windows/v/examples/links_scraper.v @@ -0,0 +1,14 @@ +import net.http + +fn main() { + html := http.get_text('https://news.ycombinator.com') + mut pos := 0 + for { + pos = html.index_after('https://', pos + 1) + if pos == -1 { + break + } + end := html.index_after('"', pos) + println(html[pos..end]) + } +} diff --git a/v_windows/v/examples/log.v b/v_windows/v/examples/log.v new file mode 100644 index 0000000..a4c28e1 --- /dev/null +++ b/v_windows/v/examples/log.v @@ -0,0 +1,18 @@ +import log + +fn main() { + mut l := log.Log{} + l.set_level(.info) + // Make a new file called info.log in the current folder + l.set_full_logpath('./info.log') + l.log_to_console_too() + println('Please check the file: $l.output_file_name after this example crashes.') + + l.info('info') + l.warn('warn') + l.error('error') + l.debug('no debug') + l.set_level(.debug) + l.debug('debug') + l.fatal('fatal') +} diff --git a/v_windows/v/examples/mini_calculator.v b/v_windows/v/examples/mini_calculator.v new file mode 100644 index 0000000..0c22aa3 --- /dev/null +++ b/v_windows/v/examples/mini_calculator.v @@ -0,0 +1,135 @@ +// Q: What's this? +// A: This is a mini "home-made" calculator. You may also regard it as a very elementary version of "interpreter". +import os + +const numeric_char = [`0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `.`, `e`, `E`] + +// Convert expression to Reverse Polish Notation. +fn expr_to_rev_pol(expr string) ?[]string { + if expr == '' { + return error('err: empty expression') + } + mut stack := []string{} + mut rev_pol := []string{} + mut pos := 0 + for pos < expr.len { + mut end_pos := pos + for end_pos < expr.len && expr[end_pos] in numeric_char { + end_pos++ + } + if end_pos > pos { + stack << expr[pos..end_pos] + pos = end_pos + } else if end_pos == pos { + op := expr[pos].ascii_str() + match op { + '(' { + stack << op + } + '*', '/' { + for stack.len > 0 && stack.last() !in ['(', '+', '-'] { + rev_pol << stack.last() + stack.delete(stack.len - 1) + } + stack << op + } + '+', '-' { + for stack.len > 0 && stack.last() != '(' { + rev_pol << stack.last() + stack.delete(stack.len - 1) + } + stack << op + } + ')' { + for stack.len > 0 && stack.last() != '(' { + rev_pol << stack.last() + stack.delete(stack.len - 1) + } + stack.delete(stack.len - 1) + } + else { + return error('err: invalid character `$op`') + } + } + pos++ + } + } + for stack.len > 0 { + top := stack.last() + rev_pol << top + stack.delete(stack.len - 1) + } + return rev_pol +} + +// Evaluate the result of Reverse Polish Notation. +fn eval_rev_pol(rev_pol []string) ?f64 { + mut stack := []f64{} + for item in rev_pol { + if is_num_string(item) { + stack << item.f64() + } else { + if stack.len >= 2 { + oprand_r := stack.last() + stack.delete(stack.len - 1) + oprand_l := stack.last() + stack.delete(stack.len - 1) + match item { + '+' { + stack << oprand_l + oprand_r + } + '-' { + stack << oprand_l - oprand_r + } + '*' { + stack << oprand_l * oprand_r + } + '/' { + if oprand_r == 0 { + return error('err: divide by zero') + } + stack << oprand_l / oprand_r + } + else {} + } + } else { + return error('err: invalid expression') + } + } + } + return stack[0] +} + +fn is_num_string(str string) bool { + for c in str { + if c !in numeric_char { + return false + } + } + return true +} + +fn main() { + println('Please enter the expression you want to calculate, e.g. 1e2+(3-2.5)*6/1.5 .') + println("Enter 'exit' or 'EXIT' to quit.") + mut expr_count := 0 + for { + expr_count++ + expr := os.input_opt('[$expr_count] ') or { + println('') + break + }.trim_space() + if expr in ['exit', 'EXIT'] { + break + } + rev_pol := expr_to_rev_pol(expr) or { + eprintln(err) + continue + } + res := eval_rev_pol(rev_pol) or { + eprintln(err) + continue + } + println(res) + } +} diff --git a/v_windows/v/examples/native/hello_world.v b/v_windows/v/examples/native/hello_world.v new file mode 100644 index 0000000..f2be959 --- /dev/null +++ b/v_windows/v/examples/native/hello_world.v @@ -0,0 +1,20 @@ +// fn println(s string) { } + +// fn test_fn() { +// println('test fn') +//} + +fn main() { + println('native test') + // i := 0 + // for i < 5 { + for _ in 1 .. 5 { + println('Hello world from V native machine code generator!') + // i++ + } + /* + println('Hello again!') + //test_fn() + println('done') + */ +} diff --git a/v_windows/v/examples/nbody.v b/v_windows/v/examples/nbody.v new file mode 100644 index 0000000..0f719e8 --- /dev/null +++ b/v_windows/v/examples/nbody.v @@ -0,0 +1,129 @@ +// Ported based on https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/nbody-go-3.html +// Output: +// -0.169075164 +// -0.169059907 +import math + +const ( + solar_mass = 39.47841760435743197 // 4.0 * math.Pi * math.Pi + days_per_year = 365.24 + c_n = 5 +) + +struct Position { +pub mut: + x f64 + y f64 + z f64 +} + +struct Momentum { +pub mut: + x f64 + y f64 + z f64 + m f64 +} + +struct System { +pub mut: + v []Momentum + s []Position +} + +fn advance(mut sys System, dt f64) { + for i in 0 .. c_n - 1 { + mut vx := sys.v[i].x + mut vy := sys.v[i].y + mut vz := sys.v[i].z + + for j := i + 1; j < c_n; j++ { + dx := sys.s[i].x - sys.s[j].x + dy := sys.s[i].y - sys.s[j].y + dz := sys.s[i].z - sys.s[j].z + + dsquared := dx * dx + dy * dy + dz * dz + distance := math.sqrt(dsquared) + mag := (dt / (dsquared * distance)) + mi := sys.v[i].m + + vx -= dx * sys.v[j].m * mag + vy -= dy * sys.v[j].m * mag + vz -= dz * sys.v[j].m * mag + + sys.v[j].x += dx * mi * mag + sys.v[j].y += dy * mi * mag + sys.v[j].z += dz * mi * mag + } + sys.v[i].x = vx + sys.v[i].y = vy + sys.v[i].z = vz + } + + for i in 0 .. c_n { + sys.s[i].x += dt * sys.v[i].x + sys.s[i].y += dt * sys.v[i].y + sys.s[i].z += dt * sys.v[i].z + } +} + +fn offsetmomentum(mut sys System) { + mut px := f64(0) + mut py := f64(0) + mut pz := f64(0) + + for i in 0 .. c_n { + px += sys.v[i].x * sys.v[i].m + py += sys.v[i].y * sys.v[i].m + pz += sys.v[i].z * sys.v[i].m + } + sys.v[0].x = -px / solar_mass + sys.v[0].y = -py / solar_mass + sys.v[0].z = -pz / solar_mass +} + +fn energy(sys System) f64 { + mut e := f64(0) + for i in 0 .. c_n { + e += 0.5 * sys.v[i].m * (sys.v[i].x * sys.v[i].x + sys.v[i].y * sys.v[i].y + + sys.v[i].z * sys.v[i].z) + for j := i + 1; j < c_n; j++ { + dx := sys.s[i].x - sys.s[j].x + dy := sys.s[i].y - sys.s[j].y + dz := sys.s[i].z - sys.s[j].z + distance := math.sqrt(dx * dx + dy * dy + dz * dz) + e -= (sys.v[i].m * sys.v[j].m) / distance + } + } + return e +} + +fn arr_momentum() []Momentum { + return [ + Momentum{0.0, 0.0, 0.0, solar_mass}, + Momentum{1.66007664274403694e-03 * days_per_year, 7.69901118419740425e-03 * days_per_year, -6.90460016972063023e-05 * days_per_year, 9.54791938424326609e-04 * solar_mass}, + Momentum{-2.76742510726862411e-03 * days_per_year, 4.99852801234917238e-03 * days_per_year, 2.30417297573763929e-05 * days_per_year, 2.85885980666130812e-04 * solar_mass}, + Momentum{2.96460137564761618e-03 * days_per_year, 2.37847173959480950e-03 * days_per_year, -2.96589568540237556e-05 * days_per_year, 4.36624404335156298e-05 * solar_mass}, + Momentum{2.68067772490389322e-03 * days_per_year, 1.62824170038242295e-03 * days_per_year, -9.51592254519715870e-05 * days_per_year, 5.15138902046611451e-05 * solar_mass}, + ] +} + +fn arr_position() []Position { + return [ + Position{0.0, 0.0, 0.0}, + Position{4.84143144246472090e+00, -1.16032004402742839e+00, -1.03622044471123109e-01}, + Position{8.34336671824457987e+00, 4.12479856412430479e+00, -4.03523417114321381e-01}, + Position{1.28943695621391310e+01, -1.51111514016986312e+01, -2.23307578892655734e-01}, + Position{1.53796971148509165e+01, -2.59193146099879641e+01, 1.79258772950371181e-01}, + ] +} + +fn main() { + mut sys := &System{arr_momentum(), arr_position()} + offsetmomentum(mut sys) + println('${energy(sys):.9f}') //-0.169075164 + for _ in 0 .. 50_000_000 { + advance(mut sys, 0.01) + } + println('${energy(sys):.9f}') //-0.169059907 +} diff --git a/v_windows/v/examples/net_peer_ip.v b/v_windows/v/examples/net_peer_ip.v new file mode 100644 index 0000000..7f84deb --- /dev/null +++ b/v_windows/v/examples/net_peer_ip.v @@ -0,0 +1,5 @@ +import net + +conn := net.dial_tcp('google.com:80') ? +peer_addr := conn.peer_addr() ? +println('$peer_addr') diff --git a/v_windows/v/examples/net_raw_http.v b/v_windows/v/examples/net_raw_http.v new file mode 100644 index 0000000..85c88a9 --- /dev/null +++ b/v_windows/v/examples/net_raw_http.v @@ -0,0 +1,20 @@ +import net +import io + +fn main() { + // Make a new connection + mut conn := net.dial_tcp('google.com:80') ? + defer { + conn.close() or {} + } + + println(' peer: $conn.peer_addr()') + println('local: $conn.addr()') + + // Simple http HEAD request for a file + conn.write_string('HEAD /index.html HTTP/1.0\r\n\r\n') ? + // Read all the data that is waiting + result := io.read_all(reader: conn) ? + // Cast to string and print result + println(result.bytestr()) +} diff --git a/v_windows/v/examples/net_resolve.v b/v_windows/v/examples/net_resolve.v new file mode 100644 index 0000000..ddc058d --- /dev/null +++ b/v_windows/v/examples/net_resolve.v @@ -0,0 +1,24 @@ +import net + +for addr in [ + 'vlang.io:80', + 'google.com:80', + 'steampowered.com:80', + 'api.steampowered.com:80', +] { + println('$addr') + + for @type in [net.SocketType.tcp, .udp] { + family := net.AddrFamily.unspec + + addrs := net.resolve_addrs(addr, family, @type) or { + println('> None') + continue + } + + for a in addrs { + f := a.family() + println('> $a $f ${@type}') + } + } +} diff --git a/v_windows/v/examples/net_t.v b/v_windows/v/examples/net_t.v new file mode 100644 index 0000000..8e8255a --- /dev/null +++ b/v_windows/v/examples/net_t.v @@ -0,0 +1,21 @@ +import net.http +import sync +import time + +fn send_request(mut wg sync.WaitGroup) ?string { + start := time.ticks() + data := http.get('https://google.com') ? + finish := time.ticks() + println('Finish getting time ${finish - start} ms') + wg.done() + return data.text +} + +fn main() { + mut wg := sync.new_waitgroup() + for i := 0; i < 50; i++ { + wg.add(1) + go send_request(mut wg) + } + wg.wait() +} diff --git a/v_windows/v/examples/net_udp_server_and_client.v b/v_windows/v/examples/net_udp_server_and_client.v new file mode 100644 index 0000000..a69d178 --- /dev/null +++ b/v_windows/v/examples/net_udp_server_and_client.v @@ -0,0 +1,44 @@ +import os +import os.cmdline +import net + +fn main() { + println('Usage: net_udp_server_and_client [-l] [-p 5000]') + println(' -l - act as a server and listen') + println(' -p XXXX - custom port number') + println('------------------------------------------') + is_server := '-l' in os.args + port := cmdline.option(os.args, '-p', '40001').int() + mut buf := []byte{len: 100} + if is_server { + println('UDP echo server, listening for udp packets on port: $port') + mut c := net.listen_udp(':$port') ? + for { + read, addr := c.read(mut buf) or { continue } + println('received $read bytes from $addr') + c.write_to(addr, buf[..read]) or { + println('Server: connection dropped') + continue + } + } + } else { + println('UDP client, sending packets to port: ${port}.\nType `exit` to exit.') + mut c := net.dial_udp('localhost:$port') ? + for { + mut line := os.input('client > ') + match line { + '' { + line = '\n' + } + 'exit' { + println('goodbye.') + exit(0) + } + else {} + } + c.write_string(line) ? + read, _ := c.read(mut buf) ? + println('server : ' + buf[0..read].bytestr()) + } + } +} diff --git a/v_windows/v/examples/news_fetcher.v b/v_windows/v/examples/news_fetcher.v new file mode 100644 index 0000000..e724fba --- /dev/null +++ b/v_windows/v/examples/news_fetcher.v @@ -0,0 +1,49 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import net.http +import json +import sync.pool + +struct Story { + title string + url string +} + +fn worker_fetch(p &pool.PoolProcessor, cursor int, worker_id int) voidptr { + id := p.get_item<int>(cursor) + resp := http.get('https://hacker-news.firebaseio.com/v0/item/${id}.json') or { + println('failed to fetch data from /v0/item/${id}.json') + return pool.no_result + } + story := json.decode(Story, resp.text) or { + println('failed to decode a story') + return pool.no_result + } + println('# $cursor) $story.title | $story.url') + return pool.no_result +} + +// Fetches top HN stories in parallel, depending on how many cores you have +fn main() { + resp := http.get('https://hacker-news.firebaseio.com/v0/topstories.json') or { + println('failed to fetch data from /v0/topstories.json') + return + } + mut ids := json.decode([]int, resp.text) or { + println('failed to decode topstories.json') + return + } + if ids.len > 10 { + ids = ids[0..10] + } + mut fetcher_pool := pool.new_pool_processor( + callback: worker_fetch + ) + // NB: if you do not call set_max_jobs, the pool will try to use an optimal + // number of threads, one per each core in your system, which in most + // cases is what you want anyway... You can override the automatic choice + // by setting the VJOBS environment variable too. + // fetcher_pool.set_max_jobs( 4 ) + fetcher_pool.work_on_items(ids) +} diff --git a/v_windows/v/examples/path_tracing.v b/v_windows/v/examples/path_tracing.v new file mode 100644 index 0000000..8546c01 --- /dev/null +++ b/v_windows/v/examples/path_tracing.v @@ -0,0 +1,583 @@ +/********************************************************************** +* path tracing demo +* +* Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* This file contains a path tracer example in less of 500 line of codes +* 3 demo scenes included +* +* This code is inspired by: +* - "Realistic Ray Tracing" by Peter Shirley 2000 ISBN-13: 978-1568814612 +* - https://www.kevinbeason.com/smallpt/ +* +* Known limitations: +* - there are some approximation errors in the calculations +* - to speed-up the code a cos/sin table is used +* - the full precision code is present but commented, can be restored very easily +* - an higher number of samples ( > 60) can block the program on higher resolutions +* without a stack size increase +* - as a recursive program this code depend on the stack size, +* for higher number of samples increase the stack size +* in linux: ulimit -s byte_size_of_the_stack +* example: ulimit -s 16000000 +* - No OpenMP support +**********************************************************************/ +import os +import math +import rand +import time +import term + +const ( + inf = 1e+10 + eps = 1e-4 + f_0 = 0.0 +) + +//**************************** 3D Vector utility struct ********************* +struct Vec { +mut: + x f64 = 0.0 + y f64 = 0.0 + z f64 = 0.0 +} + +[inline] +fn (v Vec) + (b Vec) Vec { + return Vec{v.x + b.x, v.y + b.y, v.z + b.z} +} + +[inline] +fn (v Vec) - (b Vec) Vec { + return Vec{v.x - b.x, v.y - b.y, v.z - b.z} +} + +[inline] +fn (v Vec) * (b Vec) Vec { + return Vec{v.x * b.x, v.y * b.y, v.z * b.z} +} + +[inline] +fn (v Vec) dot(b Vec) f64 { + return v.x * b.x + v.y * b.y + v.z * b.z +} + +[inline] +fn (v Vec) mult_s(b f64) Vec { + return Vec{v.x * b, v.y * b, v.z * b} +} + +[inline] +fn (v Vec) cross(b Vec) Vec { + return Vec{v.y * b.z - v.z * b.y, v.z * b.x - v.x * b.z, v.x * b.y - v.y * b.x} +} + +[inline] +fn (v Vec) norm() Vec { + tmp_norm := 1.0 / math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) + return Vec{v.x * tmp_norm, v.y * tmp_norm, v.z * tmp_norm} +} + +//********************************Image************************************** +struct Image { + width int + height int + data &Vec +} + +fn new_image(w int, h int) Image { + vecsize := int(sizeof(Vec)) + return Image{ + width: w + height: h + data: unsafe { &Vec(vcalloc(vecsize * w * h)) } + } +} + +// write out a .ppm file +fn (image Image) save_as_ppm(file_name string) { + npixels := image.width * image.height + mut f_out := os.create(file_name) or { panic(err) } + f_out.writeln('P3') or { panic(err) } + f_out.writeln('$image.width $image.height') or { panic(err) } + f_out.writeln('255') or { panic(err) } + for i in 0 .. npixels { + c_r := to_int(unsafe { image.data[i] }.x) + c_g := to_int(unsafe { image.data[i] }.y) + c_b := to_int(unsafe { image.data[i] }.z) + f_out.write_string('$c_r $c_g $c_b ') or { panic(err) } + } + f_out.close() +} + +//********************************** Ray ************************************ +struct Ray { + o Vec + d Vec +} + +// material types, used in radiance() +enum Refl_t { + diff + spec + refr +} + +//******************************** Sphere *********************************** +struct Sphere { + rad f64 = 0.0 // radius + p Vec // position + e Vec // emission + c Vec // color + refl Refl_t // reflection type => [diffuse, specular, refractive] +} + +fn (sp Sphere) intersect(r Ray) f64 { + op := sp.p - r.o // Solve t^2*d.d + 2*t*(o-p).d + (o-p).(o-p)-R^2 = 0 + b := op.dot(r.d) + mut det := b * b - op.dot(op) + sp.rad * sp.rad + + if det < 0 { + return 0 + } + + det = math.sqrt(det) + + mut t := b - det + if t > eps { + return t + } + + t = b + det + if t > eps { + return t + } + return 0 +} + +/*********************************** Scenes ********************************** +* 0) Cornell Box with 2 spheres +* 1) Sunset +* 2) Psychedelic +* The sphere fileds are: Sphere{radius, position, emission, color, material} +******************************************************************************/ +const ( + cen = Vec{50, 40.8, -860} // used by scene 1 + spheres = [ + [/* scene 0 cornnel box */ Sphere{ + rad: 1e+5 + p: Vec{1e+5 + 1, 40.8, 81.6} + e: Vec{} + c: Vec{.75, .25, .25} + refl: .diff + }, /* Left */ Sphere{ + rad: 1e+5 + p: Vec{-1e+5 + 99, 40.8, 81.6} + e: Vec{} + c: Vec{.25, .25, .75} + refl: .diff + }, /* Rght */ Sphere{ + rad: 1e+5 + p: Vec{50, 40.8, 1e+5} + e: Vec{} + c: Vec{.75, .75, .75} + refl: .diff + }, /* Back */ Sphere{ + rad: 1e+5 + p: Vec{50, 40.8, -1e+5 + 170} + e: Vec{} + c: Vec{} + refl: .diff + }, /* Frnt */ Sphere{ + rad: 1e+5 + p: Vec{50, 1e+5, 81.6} + e: Vec{} + c: Vec{.75, .75, .75} + refl: .diff + }, /* Botm */ Sphere{ + rad: 1e+5 + p: Vec{50, -1e+5 + 81.6, 81.6} + e: Vec{} + c: Vec{.75, .75, .75} + refl: .diff + }, /* Top */ Sphere{ + rad: 16.5 + p: Vec{27, 16.5, 47} + e: Vec{} + c: Vec{1, 1, 1}.mult_s(.999) + refl: .spec + }, /* Mirr */ Sphere{ + rad: 16.5 + p: Vec{73, 16.5, 78} + e: Vec{} + c: Vec{1, 1, 1}.mult_s(.999) + refl: .refr + }, /* Glas */ Sphere{ + rad: 600 + p: Vec{50, 681.6 - .27, 81.6} + e: Vec{12, 12, 12} + c: Vec{} + refl: .diff + } /* Lite */], + [/* scene 1 sunset */ Sphere{ + rad: 1600 + p: Vec{1.0, 0.0, 2.0}.mult_s(3000) + e: Vec{1.0, .9, .8}.mult_s(1.2e+1 * 1.56 * 2) + c: Vec{} + refl: .diff + }, /* sun */ Sphere{ + rad: 1560 + p: Vec{1, 0, 2}.mult_s(3500) + e: Vec{1.0, .5, .05}.mult_s(4.8e+1 * 1.56 * 2) + c: Vec{} + refl: .diff + }, /* horizon sun2 */ Sphere{ + rad: 10000 + p: cen + Vec{0, 0, -200} + e: Vec{0.00063842, 0.02001478, 0.28923243}.mult_s(6e-2 * 8) + c: Vec{.7, .7, 1}.mult_s(.25) + refl: .diff + }, /* sky */ Sphere{ + rad: 100000 + p: Vec{50, -100000, 0} + e: Vec{} + c: Vec{.3, .3, .3} + refl: .diff + }, /* grnd */ Sphere{ + rad: 110000 + p: Vec{50, -110048.5, 0} + e: Vec{.9, .5, .05}.mult_s(4) + c: Vec{} + refl: .diff + }, /* horizon brightener */ Sphere{ + rad: 4e+4 + p: Vec{50, -4e+4 - 30, -3000} + e: Vec{} + c: Vec{.2, .2, .2} + refl: .diff + }, /* mountains */ Sphere{ + rad: 26.5 + p: Vec{22, 26.5, 42} + e: Vec{} + c: Vec{1, 1, 1}.mult_s(.596) + refl: .spec + }, /* white Mirr */ Sphere{ + rad: 13 + p: Vec{75, 13, 82} + e: Vec{} + c: Vec{.96, .96, .96}.mult_s(.96) + refl: .refr + }, /* Glas */ Sphere{ + rad: 22 + p: Vec{87, 22, 24} + e: Vec{} + c: Vec{.6, .6, .6}.mult_s(.696) + refl: .refr + } /* Glas2 */], + [/* scene 3 Psychedelic */ Sphere{ + rad: 150 + p: Vec{50 + 75, 28, 62} + e: Vec{1, 1, 1}.mult_s(0e-3) + c: Vec{1, .9, .8}.mult_s(.93) + refl: .refr + }, Sphere{ + rad: 28 + p: Vec{50 + 5, -28, 62} + e: Vec{1, 1, 1}.mult_s(1e+1) + c: Vec{1, 1, 1}.mult_s(0) + refl: .diff + }, Sphere{ + rad: 300 + p: Vec{50, 28, 62} + e: Vec{1, 1, 1}.mult_s(0e-3) + c: Vec{1, 1, 1}.mult_s(.93) + refl: .spec + }], + ] // end of scene array +) + +//********************************** Utilities ****************************** +[inline] +fn clamp(x f64) f64 { + if x < 0 { + return 0 + } + if x > 1 { + return 1 + } + return x +} + +[inline] +fn to_int(x f64) int { + p := math.pow(clamp(x), 1.0 / 2.2) + return int(p * 255.0 + 0.5) +} + +fn intersect(r Ray, spheres &Sphere, nspheres int) (bool, f64, int) { + mut d := 0.0 + mut t := inf + mut id := 0 + for i := nspheres - 1; i >= 0; i-- { + d = unsafe { spheres[i] }.intersect(r) + if d > 0 && d < t { + t = d + id = i + } + } + return (t < inf), t, id +} + +// some casual random function, try to avoid the 0 +fn rand_f64() f64 { + x := rand.u32() & 0x3FFF_FFFF + return f64(x) / f64(0x3FFF_FFFF) +} + +const ( + cache_len = 65536 // the 2*pi angle will be splitted in 65536 part + cache_mask = cache_len - 1 // mask to speed-up the module process +) + +struct Cache { +mut: + sin_tab [65536]f64 + cos_tab [65536]f64 +} + +fn new_tabs() Cache { + mut c := Cache{} + inv_len := 1.0 / f64(cache_len) + for i in 0 .. cache_len { + x := f64(i) * math.pi * 2.0 * inv_len + c.sin_tab[i] = math.sin(x) + c.cos_tab[i] = math.cos(x) + } + return c +} + +//************ Cache for sin/cos speed-up table and scene selector ********** +const ( + tabs = new_tabs() +) + +//****************** main function for the radiance calculation ************* +fn radiance(r Ray, depthi int, scene_id int) Vec { + if depthi > 1024 { + eprintln('depthi: $depthi') + eprintln('') + return Vec{} + } + mut depth := depthi // actual depth in the reflection tree + mut t := 0.0 // distance to intersection + mut id := 0 // id of intersected object + mut res := false // result of intersect + + v_1 := 1.0 + // v_2 := f64(2.0) + + scene := spheres[scene_id] + // res, t, id = intersect(r, id, tb.scene) + res, t, id = intersect(r, scene.data, scene.len) + if !res { + return Vec{} + } + // if miss, return black + + obj := scene[id] // the hit object + + x := r.o + r.d.mult_s(t) + n := (x - obj.p).norm() + + nl := if n.dot(r.d) < 0.0 { n } else { n.mult_s(-1) } + + mut f := obj.c + + // max reflection + mut p := f.z + if f.x > f.y && f.x > f.z { + p = f.x + } else { + if f.y > f.z { + p = f.y + } + } + + depth++ + if depth > 5 { + if rand_f64() < p { + f = f.mult_s(f64(1.0) / p) + } else { + return obj.e // R.R. + } + } + + if obj.refl == .diff { // Ideal DIFFUSE reflection + // **Full Precision** + // r1 := f64(2.0 * math.pi) * rand_f64() + + // tabbed speed-up + r1 := rand.u32() & cache_mask + + r2 := rand_f64() + r2s := math.sqrt(r2) + + w := nl + + mut u := if math.abs(w.x) > f64(0.1) { Vec{0, 1, 0} } else { Vec{1, 0, 0} } + u = u.cross(w).norm() + + v := w.cross(u) + + // **Full Precision** + // d := (u.mult_s(math.cos(r1) * r2s) + v.mult_s(math.sin(r1) * r2s) + w.mult_s(1.0 - r2)).norm() + + // tabbed speed-up + d := (u.mult_s(tabs.cos_tab[r1] * r2s) + v.mult_s(tabs.sin_tab[r1] * r2s) + + w.mult_s(math.sqrt(f64(1.0) - r2))).norm() + + return obj.e + f * radiance(Ray{x, d}, depth, scene_id) + } else { + if obj.refl == .spec { // Ideal SPECULAR reflection + return obj.e + f * radiance(Ray{x, r.d - n.mult_s(2.0 * n.dot(r.d))}, depth, scene_id) + } + } + + refl_ray := Ray{x, r.d - n.mult_s(2.0 * n.dot(r.d))} // Ideal dielectric REFRACTION + into := n.dot(nl) > 0 // Ray from outside going in? + + nc := f64(1.0) + nt := f64(1.5) + + nnt := if into { nc / nt } else { nt / nc } + + ddn := r.d.dot(nl) + cos2t := v_1 - nnt * nnt * (v_1 - ddn * ddn) + if cos2t < 0.0 { // Total internal reflection + return obj.e + f * radiance(refl_ray, depth, scene_id) + } + + dirc := if into { f64(1) } else { f64(-1) } + tdir := (r.d.mult_s(nnt) - n.mult_s(dirc * (ddn * nnt + math.sqrt(cos2t)))).norm() + + a := nt - nc + b := nt + nc + r0 := a * a / (b * b) + c := if into { v_1 + ddn } else { v_1 - tdir.dot(n) } + + re := r0 + (v_1 - r0) * c * c * c * c * c + tr := v_1 - re + pp := f64(.25) + f64(.5) * re + rp := re / pp + tp := tr / (v_1 - pp) + + mut tmp := Vec{} + if depth > 2 { + // Russian roulette + tmp = if rand_f64() < pp { + radiance(refl_ray, depth, scene_id).mult_s(rp) + } else { + radiance(Ray{x, tdir}, depth, scene_id).mult_s(tp) + } + } else { + tmp = (radiance(refl_ray, depth, scene_id).mult_s(re)) + + (radiance(Ray{x, tdir}, depth, scene_id).mult_s(tr)) + } + return obj.e + (f * tmp) +} + +//*********************** beam scan routine ********************************* +fn ray_trace(w int, h int, samps int, file_name string, scene_id int) Image { + image := new_image(w, h) + + // inverse costants + w1 := f64(1.0 / f64(w)) + h1 := f64(1.0 / f64(h)) + samps1 := f64(1.0 / f64(samps)) + + cam := Ray{Vec{50, 52, 295.6}, Vec{0, -0.042612, -1}.norm()} // cam position, direction + cx := Vec{f64(w) * 0.5135 / f64(h), 0, 0} + cy := cx.cross(cam.d).norm().mult_s(0.5135) + mut r := Vec{} + + // speed-up constants + v_1 := f64(1.0) + v_2 := f64(2.0) + + // OpenMP injection point! #pragma omp parallel for schedule(dynamic, 1) shared(c) + for y := 0; y < h; y++ { + term.cursor_up(1) + eprintln('Rendering (${samps * 4} spp) ${(100.0 * f64(y)) / (f64(h) - 1.0):5.2f}%') + for x in 0 .. w { + i := (h - y - 1) * w + x + mut ivec := unsafe { &image.data[i] } + // we use sx and sy to perform a square subsampling of 4 samples + for sy := 0; sy < 2; sy++ { + for sx := 0; sx < 2; sx++ { + r = Vec{0, 0, 0} + for _ in 0 .. samps { + r1 := v_2 * rand_f64() + dx := if r1 < v_1 { math.sqrt(r1) - v_1 } else { v_1 - math.sqrt(v_2 - r1) } + + r2 := v_2 * rand_f64() + dy := if r2 < v_1 { math.sqrt(r2) - v_1 } else { v_1 - math.sqrt(v_2 - r2) } + + d := cx.mult_s(((f64(sx) + 0.5 + dx) * 0.5 + f64(x)) * w1 - .5) + + cy.mult_s(((f64(sy) + 0.5 + dy) * 0.5 + f64(y)) * h1 - .5) + cam.d + r = r + radiance(Ray{cam.o + + d.mult_s(140.0), d.norm()}, 0, scene_id).mult_s(samps1) + } + tmp_vec := Vec{clamp(r.x), clamp(r.y), clamp(r.z)}.mult_s(.25) + (*ivec) = *ivec + tmp_vec + } + } + } + } + return image +} + +fn main() { + if os.args.len > 6 { + eprintln('Usage:\n path_tracing [samples] [image.ppm] [scene_n] [width] [height]') + exit(1) + } + mut width := 320 // width of the rendering in pixels + mut height := 200 // height of the rendering in pixels + mut samples := 4 // number of samples per pixel, increase for better quality + mut scene_id := 0 // scene to render [0 cornell box,1 sunset,2 psyco] + mut file_name := 'image.ppm' // name of the output file in .ppm format + + if os.args.len >= 2 { + samples = os.args[1].int() / 4 + } + if os.args.len >= 3 { + file_name = os.args[2] + } + if os.args.len >= 4 { + scene_id = os.args[3].int() + } + if os.args.len >= 5 { + width = os.args[4].int() + } + if os.args.len == 6 { + height = os.args[5].int() + } + // change the seed for a different result + rand.seed([u32(2020), 0]) + + t1 := time.ticks() + + eprintln('Path tracing samples: $samples, file_name: $file_name, scene_id: $scene_id, width: $width, height: $height') + eprintln('') + image := ray_trace(width, height, samples, file_name, scene_id) + t2 := time.ticks() + + eprintln('Rendering finished. Took: ${(t2 - t1):5}ms') + + image.save_as_ppm(file_name) + t3 := time.ticks() + + eprintln('Image saved as [$file_name]. Took: ${(t3 - t2):5}ms') +} diff --git a/v_windows/v/examples/pendulum_sim/sim.v b/v_windows/v/examples/pendulum_sim/sim.v new file mode 100644 index 0000000..f8ef11d --- /dev/null +++ b/v_windows/v/examples/pendulum_sim/sim.v @@ -0,0 +1,366 @@ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// sim.v * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// created by: jordan bonecutter * * * * * * * * * * * * * * * * * * * +// jpbonecutter@gmail.com * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// +// I wrote the pendulum simulator to learn V, I think it could be a +// good addition to the examples directory. +// Essentially, the pendulum sim runs a simulation of a pendulum with +// a metallic tip swinging over three magnets. +// I run this simulation with the initial position at each pixel in an +// image and color the pixel according to the magnet over which it +// finally rests. +// I used some fun features in V like coroutines, channels, +// struct embedding, mutability, methods, and the like. +import math +import os +import term +import runtime + +// customisable through setting VJOBS +const parallel_workers = runtime.nr_jobs() + +const width = 800 + +const height = 600 + +struct Vec3D { + x f64 + y f64 + z f64 +} + +fn (v Vec3D) add(v2 Vec3D) Vec3D { + return Vec3D{ + x: v.x + v2.x + y: v.y + v2.y + z: v.z + v2.z + } +} + +fn (v Vec3D) dot(v2 Vec3D) f64 { + return (v.x * v2.x) + (v.y * v2.y) + (v.z * v2.z) +} + +fn (v Vec3D) scale(scalar f64) Vec3D { + return Vec3D{ + x: v.x * scalar + y: v.y * scalar + z: v.z * scalar + } +} + +fn (v Vec3D) norm_squared() f64 { + return v.dot(v) +} + +fn (v Vec3D) norm() f64 { + return math.sqrt(v.norm_squared()) +} + +struct SimState { +mut: + position Vec3D + velocity Vec3D + accel Vec3D +} + +// magnets lie at [ +// math.cos(index * 2 * math.pi / 3) * magnet_spacing +// math.sin(index * 2 * math.pi / 3) * magnet_spacing +// -magnet_height +// ] +struct SimParams { + rope_length f64 + bearing_mass f64 + magnet_spacing f64 + magnet_height f64 + magnet_strength f64 + gravity f64 +} + +fn (params SimParams) get_rope_vector(state SimState) Vec3D { + rope_origin := Vec3D{ + x: 0 + y: 0 + z: params.rope_length + } + + return state.position.add(rope_origin.scale(-1)) +} + +fn (mut state SimState) satisfy_rope_constraint(params SimParams) { + mut rope_vector := params.get_rope_vector(state) + rope_vector = rope_vector.scale(params.rope_length / rope_vector.norm()) + state.position = Vec3D{ + x: 0 + y: 0 + z: params.rope_length + }.add(rope_vector) +} + +fn (params SimParams) get_grav_force(state SimState) Vec3D { + return Vec3D{ + x: 0 + y: 0 + z: -params.bearing_mass * params.gravity + } +} + +fn (params SimParams) get_magnet_position(theta f64) Vec3D { + return Vec3D{ + x: math.cos(theta) * params.magnet_spacing + y: math.sin(theta) * params.magnet_spacing + z: -params.magnet_height + } +} + +fn (params SimParams) get_magnet_force(theta f64, state SimState) Vec3D { + magnet_position := params.get_magnet_position(theta) + mut diff := magnet_position.add(state.position.scale(-1)) + distance_squared := diff.norm_squared() + diff = diff.scale(1.0 / math.sqrt(distance_squared)) + return diff.scale(params.magnet_strength / distance_squared) +} + +fn (params SimParams) get_magnet_dist(theta f64, state SimState) f64 { + return params.get_magnet_position(theta).add(state.position.scale(-1)).norm() +} + +fn (params SimParams) get_magnet1_force(state SimState) Vec3D { + return params.get_magnet_force(0.0 * math.pi / 3.0, state) +} + +fn (params SimParams) get_magnet2_force(state SimState) Vec3D { + return params.get_magnet_force(2.0 * math.pi / 3.0, state) +} + +fn (params SimParams) get_magnet3_force(state SimState) Vec3D { + return params.get_magnet_force(4.0 * math.pi / 3.0, state) +} + +fn (params SimParams) get_tension_force(state SimState, f_passive Vec3D) Vec3D { + rope_vector := params.get_rope_vector(state) + rope_vector_norm := rope_vector.scale(1.0 / rope_vector.norm()) + return rope_vector_norm.scale(-1.0 * rope_vector_norm.dot(f_passive)) +} + +fn (mut state SimState) increment(delta_t f64, params SimParams) { + // basically just add up all forces => + // get an accelleration => + // add to velocity => + // ensure rope constraint is satisfied + + // force due to gravity + f_gravity := params.get_grav_force(state) + + // force due to each magnet + f_magnet1 := params.get_magnet1_force(state) + + // force due to each magnet + f_magnet2 := params.get_magnet2_force(state) + + // force due to each magnet + f_magnet3 := params.get_magnet3_force(state) + + // passive forces + f_passive := f_gravity.add(f_magnet1.add(f_magnet2.add(f_magnet3))) + + // force due to tension of the rope + f_tension := params.get_tension_force(state, f_passive) + + // sum up all the fores + f_sum := f_tension.add(f_passive) + + // get the acceleration + accel := f_sum.scale(1.0 / params.bearing_mass) + state.accel = accel + + // update the velocity + state.velocity = state.velocity.add(accel.scale(delta_t)) + + // update the position + state.position = state.position.add(state.velocity.scale(delta_t)) + + // ensure the position satisfies rope constraint + state.satisfy_rope_constraint(params) +} + +fn (state SimState) done() bool { + return state.velocity.norm() < 0.05 && state.accel.norm() < 0.01 +} + +struct PPMWriter { +mut: + file os.File +} + +struct ImageSettings { + width int + height int +} + +struct Pixel { + r byte + g byte + b byte +} + +fn (mut writer PPMWriter) start_for_file(fname string, settings ImageSettings) { + writer.file = os.create(fname) or { panic("can't create file $fname") } + writer.file.writeln('P6 $settings.width $settings.height 255') or {} +} + +fn (mut writer PPMWriter) next_pixel(p Pixel) { + writer.file.write([p.r, p.g, p.b]) or {} +} + +fn (mut writer PPMWriter) finish() { + writer.file.close() +} + +fn sim_runner(mut state SimState, params SimParams) Pixel { + // do the simulation! + for _ in 0 .. 1000 { + state.increment(0.0005, params) + if state.done() { + println('done!') + break + } + } + + // find the closest magnet + m1_dist := params.get_magnet_dist(0, state) + m2_dist := params.get_magnet_dist(2.0 * math.pi / 3.0, state) + m3_dist := params.get_magnet_dist(4.0 * math.pi / 3.0, state) + + if m1_dist < m2_dist && m1_dist < m3_dist { + return Pixel{ + r: 255 + g: 0 + b: 0 + } + } else if m2_dist < m1_dist && m2_dist < m3_dist { + return Pixel{ + r: 0 + g: 255 + b: 0 + } + } else { + return Pixel{ + r: 0 + g: 0 + b: 255 + } + } +} + +struct SimResult { + id u64 + p Pixel +} + +struct SimRequest { + id u64 + params SimParams +mut: + initial SimState +} + +fn sim_worker(request_chan chan SimRequest, result_chan chan SimResult) { + // serve sim requests as they come in + for { + mut request := <-request_chan or { break } + + result_chan <- SimResult{ + id: request.id + p: sim_runner(mut request.initial, request.params) + } + } +} + +struct ValidPixel { + Pixel +mut: + valid bool +} + +fn image_worker(mut writer PPMWriter, result_chan chan SimResult, total_pixels u64) { + // as new pixels come in, write them to the image file + mut current_index := u64(0) + mut pixel_buf := []ValidPixel{len: int(total_pixels), init: ValidPixel{ + valid: false + }} + for { + result := <-result_chan or { break } + pixel_buf[result.id].Pixel = result.p + pixel_buf[result.id].valid = true + + for current_index < total_pixels && pixel_buf[current_index].valid { + writer.next_pixel(pixel_buf[current_index].Pixel) + current_index++ + } + + if current_index >= total_pixels { + break + } + } +} + +fn main() { + params := SimParams{ + rope_length: 0.25 + bearing_mass: 0.03 + magnet_spacing: 0.05 + magnet_height: 0.03 + magnet_strength: 10.0 + gravity: 4.9 + } + + mut writer := PPMWriter{} + writer.start_for_file('test.ppm', ImageSettings{ + width: width + height: height + }) + defer { + writer.finish() + } + + result_chan := chan SimResult{} + request_chan := chan SimRequest{} + + // start a worker on each core + for _ in 0 .. parallel_workers { + go sim_worker(request_chan, result_chan) + } + + go fn (request_chan chan SimRequest, params SimParams) { + mut index := u64(0) + println('') + for y in 0 .. height { + term.clear_previous_line() + println('Line: $y') + for x in 0 .. width { + // setup initial conditions + mut state := SimState{} + state.position = Vec3D{ + x: 0.1 * ((f64(x) - 0.5 * f64(width - 1)) / f64(width - 1)) + y: 0.1 * ((f64(y) - 0.5 * f64(height - 1)) / f64(height - 1)) + z: 0.0 + } + state.velocity = Vec3D{} + state.satisfy_rope_constraint(params) + request_chan <- SimRequest{ + id: index + initial: state + params: params + } + index++ + } + } + request_chan.close() + }(request_chan, params) + + image_worker(mut writer, result_chan, width * height) +} diff --git a/v_windows/v/examples/pico/pico.v b/v_windows/v/examples/pico/pico.v new file mode 100644 index 0000000..8a8a636 --- /dev/null +++ b/v_windows/v/examples/pico/pico.v @@ -0,0 +1,52 @@ +import json +import picoev +import picohttpparser + +const ( + port = 8088 +) + +struct Message { + message string +} + +[inline] +fn json_response() string { + msg := Message{ + message: 'Hello, World!' + } + return json.encode(msg) +} + +[inline] +fn hello_response() string { + return 'Hello, World!' +} + +fn callback(data voidptr, req picohttpparser.Request, mut res picohttpparser.Response) { + if picohttpparser.cmpn(req.method, 'GET ', 4) { + if picohttpparser.cmp(req.path, '/t') { + res.http_ok() + res.header_server() + res.header_date() + res.plain() + res.body(hello_response()) + } else if picohttpparser.cmp(req.path, '/j') { + res.http_ok() + res.header_server() + res.header_date() + res.json() + res.body(json_response()) + } else { + res.http_404() + } + } else { + res.http_405() + } + res.end() +} + +fn main() { + println('Starting webserver on http://127.0.0.1:$port/ ...') + picoev.new(port: port, cb: &callback).serve() +} diff --git a/v_windows/v/examples/process/.ignore b/v_windows/v/examples/process/.ignore new file mode 100644 index 0000000..e42252a --- /dev/null +++ b/v_windows/v/examples/process/.ignore @@ -0,0 +1 @@ +command
\ No newline at end of file diff --git a/v_windows/v/examples/process/command.v b/v_windows/v/examples/process/command.v new file mode 100644 index 0000000..718ce96 --- /dev/null +++ b/v_windows/v/examples/process/command.v @@ -0,0 +1,34 @@ +module main + +import os + +// basic example which shows how to use the Command function + +fn exec(path string) string { + mut out := '' + mut line := '' + mut cmd := os.Command{ + path: path + } + cmd.start() or { panic(err) } + + for { + line = cmd.read_line() + println(line) + out += line + if cmd.eof { + return out + } + } + return out +} + +fn main() { + mut out := '' + exec("bash -c 'find /tmp/'") + out = exec('echo to stdout') + out = exec('echo to stderr 1>&2') + println("'$out'") + // THIS DOES NOT WORK, is error, it goes to stderror of the command I run + assert out == 'to stderr' +} diff --git a/v_windows/v/examples/process/execve.v b/v_windows/v/examples/process/execve.v new file mode 100644 index 0000000..f840c8d --- /dev/null +++ b/v_windows/v/examples/process/execve.v @@ -0,0 +1,17 @@ +module main + +import os + +fn exec(args []string) { + os.execve('/bin/bash', args, []) or { + // eprintln(err) + panic(err) + } +} + +fn main() { + // exec(["-c","find /"]) //works + exec(['-c', 'find /tmp/']) // here it works as well + + // exec(["-c","find","/tmp/"]) // does not work I guess is normal +} diff --git a/v_windows/v/examples/process/process_script.v b/v_windows/v/examples/process/process_script.v new file mode 100644 index 0000000..fa415e7 --- /dev/null +++ b/v_windows/v/examples/process/process_script.v @@ -0,0 +1,59 @@ +module main + +import os + +// a test where we execute a bash script but work around where we put script in bash inside bash + +fn exec(path string, redirect bool) { + mut line := '' + mut line_err := '' + mut cmd := os.new_process('/bin/bash') + + if redirect { + cmd.set_args(['-c', '/bin/bash /tmp/test.sh 2>&1']) + } else { + cmd.set_args([path]) + } + + cmd.set_redirect_stdio() + cmd.run() + if cmd.is_alive() { + for { + line = cmd.stdout_read() + println('STDOUT: $line') + + if !redirect { + line_err = cmd.stderr_read() + println('STDERR: $line_err') + } + + if !cmd.is_alive() { + break + } + } + } + if cmd.code > 0 { + println('ERROR:') + println(cmd) + // println(cmd.stderr_read()) + } +} + +fn main() { + script := ' +echo line 1 +#will use some stderr now +echo redirect 1 to 2 1>&2 +echo line 3 +' + + os.write_file('/tmp/test.sh', script) or { panic(err) } + // os.chmod("/tmp/test.sh",0o700) //make executable + + // this will work because stderr/stdout are smaller than 4096 chars, once larger there can be deadlocks + // in other words this can never work reliably without being able to check if there is data on stderr or stdout + exec('/tmp/test.sh', false) + + // this will always work + exec('/tmp/test.sh', true) +} diff --git a/v_windows/v/examples/process/process_stdin_trick.v b/v_windows/v/examples/process/process_stdin_trick.v new file mode 100644 index 0000000..7a1455d --- /dev/null +++ b/v_windows/v/examples/process/process_stdin_trick.v @@ -0,0 +1,83 @@ +module main + +import os + +// this is a example script to show you stdin can be used and keep a process open + +fn exec(cmd string) (string, int) { + mut cmd2 := cmd + mut out := '' + mut line := '' + mut rc := 0 + mut p := os.new_process('/bin/bash') + + // there are methods missing to know if stderr/stdout has data as such its better to redirect bot on same FD + // not so nice trick to run bash in bash and redirect stderr, maybe someone has a better solution + p.set_args(['-c', 'bash 2>&1']) + p.set_redirect_stdio() + p.run() + + if !cmd2.ends_with('\n') { + cmd2 += '\n' + } + + p.stdin_write(cmd2) + p.stdin_write('\necho **OK**\n') + + for { + if !p.is_alive() { + break + } + line = p.stdout_read() + println(line) + // line_err = p.stderr_read() //IF WE CALL STDERR_READ will block + // we need a mechanism which allows us to check if stderr/stdout has data or it should never block + // is not a good way, need to use a string buffer, is slow like this + out += line + if out.ends_with('**OK**\n') { + out = out[0..(out.len - 7)] + break + } + } + + // println("read from stdout, should not block") + // is not really needed but good test to see behaviour + // out += p.stdout_read() + // println("read done") + + // println(cmd.stderr_read()) + + if p.code > 0 { + rc = 1 + println('ERROR:') + println(cmd2) + print(out) + } + // documentation says we need to call p.wait(), but this does not seem to work, will be process stop or become zombie? + // p.wait() + + return out, rc +} + +fn main() { + mut out := '' + mut rc := 0 + + // the following does not work, not sure why not + // out,rc = exec("find /tmp/ && echo '******'") + + out, rc = exec("find /tmp/ ; echo '******'") + println(out) + assert out.ends_with('******\n') + + out, rc = exec('echo to stdout') + assert out.contains('to stdout') + + out, rc = exec('echo to stderr 1>&2') + assert out.contains('to stderr') + + out, rc = exec('ls /sssss') + assert rc > 0 // THIS STILL GIVES AN ERROR ! + + println('test ok stderr & stdout is indeed redirected') +} diff --git a/v_windows/v/examples/quick_sort.v b/v_windows/v/examples/quick_sort.v new file mode 100644 index 0000000..f890749 --- /dev/null +++ b/v_windows/v/examples/quick_sort.v @@ -0,0 +1,42 @@ +import rand + +const ( + gen_len = 1000 // how many random numbers to generate + gen_max = 10000 // max of the generated numbers +) + +fn main() { + mut arr := []int{} + for _ in 0 .. gen_len { + arr << rand.intn(gen_max) + } + println('length of random array is $arr.len') + println('before quick sort whether array is sorted: ${is_sorted<int>(arr)}') + quick_sort<int>(mut arr, 0, arr.len - 1) + println('after quick sort whether array is sorted: ${is_sorted<int>(arr)}') +} + +fn quick_sort<T>(mut arr []T, l int, r int) { + if l >= r { + return + } + mut sep := l // what is sep: [...all_value<arr[sep]...sep...all_value>=arr[sep]...] + for i in l + 1 .. r + 1 { + if arr[i] < arr[l] { + sep++ + arr[i], arr[sep] = arr[sep], arr[i] + } + } + arr[l], arr[sep] = arr[sep], arr[l] + quick_sort<T>(mut arr, l, sep - 1) + quick_sort<T>(mut arr, sep + 1, r) +} + +fn is_sorted<T>(arr []T) bool { + for i in 0 .. arr.len - 1 { + if arr[i] > arr[i + 1] { + return false + } + } + return true +} diff --git a/v_windows/v/examples/random_ips.v b/v_windows/v/examples/random_ips.v new file mode 100644 index 0000000..59c4eff --- /dev/null +++ b/v_windows/v/examples/random_ips.v @@ -0,0 +1,7 @@ +import rand + +fn main() { + for _ in 0 .. 10 { + println('${rand.intn(255)}.${rand.intn(255)}.${rand.intn(255)}.${rand.intn(255)}') + } +} diff --git a/v_windows/v/examples/regex/pcre.vv b/v_windows/v/examples/regex/pcre.vv new file mode 100644 index 0000000..72beaf5 --- /dev/null +++ b/v_windows/v/examples/regex/pcre.vv @@ -0,0 +1,69 @@ +module main + +// NB: you need to `v install pcre` to be able to compile this example. + +import pcre + +fn example() { + r := pcre.new_regex('Match everything after this: (.+)', 0) or { + println('An error occured!') + return + } + + m := r.match_str('Match everything after this: "I ❤️ VLang!"', 0, 0) or { + println('No match!') + return + } + + // m.get(0) -> Match everything after this: "I ❤️ VLang!" + // m.get(1) -> "I ❤️ VLang!"' + // m.get(2) -> Error! + whole_match := m.get(0) or { + println('We matched nothing...') + return + } + + matched_str := m.get(1) or { + println('We matched nothing...') + return + } + + println(whole_match) // Match everything after this: "I ❤️ VLang!" + println(matched_str) // "I ❤️ VLang!" +} + +fn main() { + example() + + mut text := '[ an s. s! ]( wi4ki:something ) + [ an s. s! ]( wi4ki:something ) + [ an s. s! ](wiki:something) + [ an s. s! ](something)dd + d [ an s. s! ](something ) d + [ more text ]( something ) s [ something b ](something)dd + + ' + + // check the regex on https://regex101.com/r/HdYya8/1/ + + regex := r'(\[[a-z\.\! ]*\]\( *\w*\:*\w* *\))*' + + r := pcre.new_regex(regex, 0) or { + println('An error occured!') + return + } + + m := r.match_str(text, 0, 0) or { + println('No match!') + return + } + + whole_match1 := m.get(0) or { + println('We matched nothing 0...') + return + } + + println(whole_match1) + + println(m.get_all()) +} diff --git a/v_windows/v/examples/regex/readme.md b/v_windows/v/examples/regex/readme.md new file mode 100644 index 0000000..3559564 --- /dev/null +++ b/v_windows/v/examples/regex/readme.md @@ -0,0 +1,8 @@ +# regex + +There are 2 ways to do regex: +a) using the native module called `regex` +b) using an exteranl module called `pcre`, which wraps the C library pcre. +NB: you need to first do: `v install pcre`, for the `pcre` module to work. + +You can find examples of both in this directory. diff --git a/v_windows/v/examples/regex/regex_example.v b/v_windows/v/examples/regex/regex_example.v new file mode 100644 index 0000000..7469ef5 --- /dev/null +++ b/v_windows/v/examples/regex/regex_example.v @@ -0,0 +1,80 @@ +/********************************************************************** +* regex samples +* +* Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* This file contains a collection of regex samples +* +**********************************************************************/ +import regex + +/* +This simple function converts an HTML RGB value with 3 or 6 hex digits to a u32 value, +this function is not optimized and it is only for didatical purpose +example: #A0B0CC #A9F +*/ +fn convert_html_rgb(in_col string) u32 { + mut n_digit := if in_col.len == 4 { 1 } else { 2 } + mut col_mul := if in_col.len == 4 { 4 } else { 0 } + + // this is the regex query, it uses V string interpolation to customize the regex query + // NOTE: if you want use escaped code you must use the r"" (raw) strings, + // *** please remember that V interpoaltion doesn't work on raw strings. *** + + query := '#([a-fA-F0-9]{$n_digit})([a-fA-F0-9]{$n_digit})([a-fA-F0-9]{$n_digit})' + + mut re := regex.regex_opt(query) or { panic(err) } + start, end := re.match_string(in_col) + println('start: $start, end: $end') + mut res := u32(0) + if start >= 0 { + group_list := re.get_group_list() + r := ('0x' + in_col[group_list[0].start..group_list[0].end]).int() << col_mul + g := ('0x' + in_col[group_list[1].start..group_list[1].end]).int() << col_mul + b := ('0x' + in_col[group_list[2].start..group_list[2].end]).int() << col_mul + println('r: $r g: $g b: $b') + res = u32(r) << 16 | u32(g) << 8 | u32(b) + } + return res +} + +/* +This function demonstrates the use of the named groups +*/ +fn convert_html_rgb_n(in_col string) u32 { + mut n_digit := if in_col.len == 4 { 1 } else { 2 } + mut col_mul := if in_col.len == 4 { 4 } else { 0 } + + query := '#(?P<red>[a-fA-F0-9]{$n_digit})(?P<green>[a-fA-F0-9]{$n_digit})(?P<blue>[a-fA-F0-9]{$n_digit})' + + mut re := regex.regex_opt(query) or { panic(err) } + start, end := re.match_string(in_col) + println('start: $start, end: $end') + mut res := u32(0) + if start >= 0 { + red_s, red_e := re.get_group_bounds_by_name('red') + r := ('0x' + in_col[red_s..red_e]).int() << col_mul + + green_s, green_e := re.get_group_bounds_by_name('green') + g := ('0x' + in_col[green_s..green_e]).int() << col_mul + + blue_s, blue_e := re.get_group_bounds_by_name('blue') + b := ('0x' + in_col[blue_s..blue_e]).int() << col_mul + + println('r: $r g: $g b: $b') + res = u32(r) << 16 | u32(g) << 8 | u32(b) + } + return res +} + +fn main() { + // convert HTML rgb color using groups + println(convert_html_rgb('#A0b0Cc').hex()) + println(convert_html_rgb('#ABC').hex()) + + // convert HTML rgb color using named groups + println(convert_html_rgb_n('#A0B0CC').hex()) + println(convert_html_rgb_n('#ABC').hex()) +} diff --git a/v_windows/v/examples/regex/regex_with_memoization.v b/v_windows/v/examples/regex/regex_with_memoization.v new file mode 100644 index 0000000..28e346b --- /dev/null +++ b/v_windows/v/examples/regex/regex_with_memoization.v @@ -0,0 +1,127 @@ +import os + +fn regex_match(src string, pat string) bool { + src_size := src.len + 1 + pat_size := pat.len + 1 + mut memo := [][]int{len: src_size, init: []int{len: pat_size, init: -1}} + return regex_match_core(src, pat, 0, 0, mut memo) +} + +fn regex_match_core(src string, pat string, src_pos int, pat_pos int, mut memo [][]int) bool { + if memo[src_pos][pat_pos] != -1 { + return memo[src_pos][pat_pos] == 1 + } + mut spos := src_pos + mut ppos := pat_pos + if spos >= src.len && ppos >= pat.len { + memo[src_pos][pat_pos] = 1 + return true + } else if spos < src.len && ppos >= pat.len { + memo[src_pos][pat_pos] = 0 + return false + } else if spos >= src.len && ppos < pat.len { + if pat[ppos] == `\\` { + ppos++ + } + res := ppos + 1 < pat.len && pat[ppos + 1] in [`*`, `?`] + && regex_match_core(src, pat, spos, ppos + 2, mut memo) + memo[src_pos][pat_pos] = if res { 1 } else { 0 } + return res + } else { + first_is_bslash := pat[ppos] == `\\` + if first_is_bslash { + ppos++ + } + first_bslash_and_match := first_is_bslash && ppos < pat.len + && (((pat[ppos] == `d` && src[spos].is_digit()) + || (pat[ppos] == `D` && !src[spos].is_digit()) + || (pat[ppos] == `s` && src[spos].is_space()) + || (pat[ppos] == `S` && !src[spos].is_space()) + || (pat[ppos] == `w` && (src[spos].is_digit() || src[spos].is_letter() + || src[spos] == `_`)) || (pat[ppos] == `W` && !(src[spos].is_digit() + || src[spos].is_letter() || src[spos] == `_`))) + || (pat[ppos] in [`d`, `D`, `s`, `S`, `w`, `W`] && ppos + 1 < pat.len + && pat[ppos + 1] in [`*`, `?`, `+`]) + || (pat[ppos] !in [`d`, `D`, `s`, `S`, `w`, `W`] && src[spos] == pat[ppos])) + if ppos + 1 < pat.len { + match pat[ppos + 1] { + `*` { + if first_bslash_and_match { + res := regex_match_core(src, pat, spos + 1, ppos - 1, mut memo) + || regex_match_core(src, pat, spos, ppos + 2, mut memo) + memo[src_pos][pat_pos] = if res { 1 } else { 0 } + return res + } else if src[spos] == pat[ppos] || pat[ppos] == `.` { + res := regex_match_core(src, pat, spos + 1, ppos, mut memo) + || regex_match_core(src, pat, spos, ppos + 2, mut memo) + memo[src_pos][pat_pos] = if res { 1 } else { 0 } + return res + } else { + res := regex_match_core(src, pat, spos, ppos + 2, mut memo) + memo[src_pos][pat_pos] = if res { 1 } else { 0 } + return res + } + } + `+` { + if first_bslash_and_match { + res := regex_match_core(src, pat, spos + 1, ppos - 1, mut memo) + || regex_match_core(src, pat, spos + 1, ppos + 2, mut memo) + memo[src_pos][pat_pos] = if res { 1 } else { 0 } + return res + } else if src[spos] == pat[ppos] || pat[ppos] == `.` { + res := regex_match_core(src, pat, spos + 1, ppos, mut memo) + || regex_match_core(src, pat, spos + 1, ppos + 2, mut memo) + memo[src_pos][pat_pos] = if res { 1 } else { 0 } + return res + } else { + memo[src_pos][pat_pos] = 0 + return false + } + } + `?` { + if first_bslash_and_match || src[spos] == pat[ppos] || pat[ppos] == `.` { + res := regex_match_core(src, pat, spos + 1, ppos + 2, mut memo) + || regex_match_core(src, pat, spos, ppos + 2, mut memo) + memo[src_pos][pat_pos] = if res { 1 } else { 0 } + return res + } else { + res := regex_match_core(src, pat, spos, ppos + 2, mut memo) + memo[src_pos][pat_pos] = if res { 1 } else { 0 } + return res + } + } + else {} + } + } + if first_is_bslash { + res := first_bslash_and_match + && regex_match_core(src, pat, spos + 1, ppos + 1, mut memo) + memo[src_pos][pat_pos] = if res { 1 } else { 0 } + return res + } else { + res := (src[spos] == pat[ppos] || pat[ppos] == `.`) && pat[ppos] != `\\` + && regex_match_core(src, pat, spos + 1, ppos + 1, mut memo) + memo[src_pos][pat_pos] = if res { 1 } else { 0 } + return res + } + } +} + +fn main() { + mut cnt := 0 + println('currently supported patterns: . ? + * \\ \\d \\D \\s \\S \\w \\W') + println('example: source `address@domain.net` matches pattern `\\w+@domain\\.net`') + println('enter `exit` to quit\n') + for { + cnt++ + src := os.input('[$cnt] enter source string: ') + if src == 'exit' { + break + } + pat := os.input('[$cnt] enter pattern string: ') + if pat == 'exit' { + break + } + println('[$cnt] whether `$src` matches `$pat`: ${regex_match(src, pat)}') + } +} diff --git a/v_windows/v/examples/rune.v b/v_windows/v/examples/rune.v new file mode 100644 index 0000000..f30b671 --- /dev/null +++ b/v_windows/v/examples/rune.v @@ -0,0 +1,9 @@ +fn main() { + // GRINNING FACE😀 => f0 09 98 80 + grinning_face := rune(0xf09f9880) + println(grinning_face) + + // COMMERCIAL AT@ => 0x40 + commercial_at := rune(0x40000000) + println(commercial_at) +} diff --git a/v_windows/v/examples/smtp/mail.v b/v_windows/v/examples/smtp/mail.v new file mode 100644 index 0000000..c26be33 --- /dev/null +++ b/v_windows/v/examples/smtp/mail.v @@ -0,0 +1,36 @@ +// Creator: nedimf (07/2020) +import os +import net.smtp + +fn main() { + println('Hi, this is sample of how to send email trough net.smtp library in V, which is really easy using the net.smtp module.') + println('We are going to create a simple email client, that takes some arguments. and then sends email with an HTML body.') + println('To fully test email sending, I suggest using the mailtrap.io service, which is free and acts like a really nice mail server sandbox.') + println('') + println('V Email client') + println('') + mailserver := os.input('Mail server: ') + mailport := os.input('Mail server port: ').int() + println('Login') + username := os.input('Username: ') + password := os.input('Password: ') + from := os.input('From: ') + to := os.input('To: ') + subject := os.input('Subject: ') + body := os.input('Body: ') + client_cfg := smtp.Client{ + server: mailserver + from: from + port: mailport + username: username + password: password + } + send_cfg := smtp.Mail{ + to: to + subject: subject + body_type: .html + body: body + } + mut client := smtp.new_client(client_cfg) or { panic('Error configuring smtp') } + client.send(send_cfg) or { panic('Error resolving email address') } +} diff --git a/v_windows/v/examples/snek/snek.v b/v_windows/v/examples/snek/snek.v new file mode 100644 index 0000000..1d66fb1 --- /dev/null +++ b/v_windows/v/examples/snek/snek.v @@ -0,0 +1,221 @@ +import os +import gg +import gx +// import sokol.sapp +import time +import rand + +// constants +const ( + top_height = 100 + canvas_size = 700 + game_size = 17 + tile_size = canvas_size / game_size + tick_rate_ms = 100 +) + +const high_score_file_path = os.join_path(os.cache_dir(), 'v', 'examples', 'snek') + +// types +struct Pos { + x int + y int +} + +fn (a Pos) + (b Pos) Pos { + return Pos{a.x + b.x, a.y + b.y} +} + +fn (a Pos) - (b Pos) Pos { + return Pos{a.x - b.x, a.y - b.y} +} + +enum Direction { + up + down + left + right +} + +type HighScore = int + +fn (mut h HighScore) save() { + os.mkdir_all(os.dir(high_score_file_path)) or { return } + os.write_file(high_score_file_path, (*h).str()) or { return } +} + +fn (mut h HighScore) load() { + h = (os.read_file(high_score_file_path) or { '' }).int() +} + +struct App { +mut: + gg &gg.Context + score int + best HighScore + snake []Pos + dir Direction + food Pos + start_time i64 + last_tick i64 +} + +// utility +fn (mut app App) reset_game() { + app.score = 0 + app.snake = [ + Pos{3, 8}, + Pos{2, 8}, + Pos{1, 8}, + Pos{0, 8}, + ] + app.dir = .right + app.food = Pos{10, 8} + app.start_time = time.ticks() + app.last_tick = time.ticks() +} + +fn (mut app App) move_food() { + for { + x := rand.int_in_range(0, game_size) + y := rand.int_in_range(0, game_size) + app.food = Pos{x, y} + + if app.food !in app.snake { + return + } + } +} + +// events +fn on_keydown(key gg.KeyCode, mod gg.Modifier, mut app App) { + match key { + .w, .up { + if app.dir != .down { + app.dir = .up + } + } + .s, .down { + if app.dir != .up { + app.dir = .down + } + } + .a, .left { + if app.dir != .right { + app.dir = .left + } + } + .d, .right { + if app.dir != .left { + app.dir = .right + } + } + else {} + } +} + +fn on_frame(mut app App) { + app.gg.begin() + + now := time.ticks() + + if now - app.last_tick >= tick_rate_ms { + app.last_tick = now + + // finding delta direction + delta_dir := match app.dir { + .up { Pos{0, -1} } + .down { Pos{0, 1} } + .left { Pos{-1, 0} } + .right { Pos{1, 0} } + } + + // "snaking" along + mut prev := app.snake[0] + app.snake[0] = app.snake[0] + delta_dir + + for i in 1 .. app.snake.len { + tmp := app.snake[i] + app.snake[i] = prev + prev = tmp + } + + // adding last segment + if app.snake[0] == app.food { + app.move_food() + app.score++ + if app.score > app.best { + app.best = app.score + app.best.save() + } + app.snake << app.snake.last() + app.snake.last() - app.snake[app.snake.len - 2] + } + } + // drawing snake + for pos in app.snake { + app.gg.draw_rect(tile_size * pos.x, tile_size * pos.y + top_height, tile_size, + tile_size, gx.blue) + } + + // drawing food + app.gg.draw_rect(tile_size * app.food.x, tile_size * app.food.y + top_height, tile_size, + tile_size, gx.red) + + // drawing top + app.gg.draw_rect(0, 0, canvas_size, top_height, gx.black) + app.gg.draw_text(150, top_height / 2, 'Score: $app.score', gx.TextCfg{ + color: gx.white + align: .center + vertical_align: .middle + size: 65 + }) + app.gg.draw_text(canvas_size - 150, top_height / 2, 'Best: $app.best', gx.TextCfg{ + color: gx.white + align: .center + vertical_align: .middle + size: 65 + }) + + // checking if snake bit itself + if app.snake[0] in app.snake[1..] { + app.reset_game() + } + // checking if snake hit a wall + if app.snake[0].x < 0 || app.snake[0].x >= game_size || app.snake[0].y < 0 + || app.snake[0].y >= game_size { + app.reset_game() + } + + app.gg.end() +} + +const font = $embed_file('../assets/fonts/RobotoMono-Regular.ttf') + +// setup +fn main() { + mut app := App{ + gg: 0 + } + app.reset_game() + app.best.load() + + mut font_copy := font + font_bytes := unsafe { + font_copy.data().vbytes(font_copy.len) + } + + app.gg = gg.new_context( + bg_color: gx.white + frame_fn: on_frame + keydown_fn: on_keydown + user_data: &app + width: canvas_size + height: top_height + canvas_size + create_window: true + resizable: false + window_title: 'snek' + font_bytes_normal: font_bytes + ) + + app.gg.run() +} diff --git a/v_windows/v/examples/sokol/01_cubes/cube.v b/v_windows/v/examples/sokol/01_cubes/cube.v new file mode 100644 index 0000000..c933dbf --- /dev/null +++ b/v_windows/v/examples/sokol/01_cubes/cube.v @@ -0,0 +1,432 @@ +/********************************************************************** +* +* Sokol 3d cube demo +* +* Copyright (c) 2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* TODO: +* - add instancing +* - add an exampel with shaders +**********************************************************************/ +import gg +import gx +import math +import sokol.sapp +import sokol.gfx +import sokol.sgl + +const ( + win_width = 800 + win_height = 800 + bg_color = gx.white +) + +struct App { +mut: + gg &gg.Context + pip_3d C.sgl_pipeline + texture C.sg_image + init_flag bool + frame_count int + mouse_x int = -1 + mouse_y int = -1 +} + +/****************************************************************************** +* +* Texture functions +* +******************************************************************************/ +fn create_texture(w int, h int, buf &u8) C.sg_image { + sz := w * h * 4 + mut img_desc := C.sg_image_desc{ + width: w + height: h + num_mipmaps: 0 + min_filter: .linear + mag_filter: .linear + // usage: .dynamic + wrap_u: .clamp_to_edge + wrap_v: .clamp_to_edge + label: &byte(0) + d3d11_texture: 0 + } + // commen if .dynamic is enabled + img_desc.data.subimage[0][0] = C.sg_range{ + ptr: buf + size: size_t(sz) + } + + sg_img := C.sg_make_image(&img_desc) + return sg_img +} + +fn destroy_texture(sg_img C.sg_image) { + C.sg_destroy_image(sg_img) +} + +// Use only if usage: .dynamic is enabled +fn update_text_texture(sg_img C.sg_image, w int, h int, buf &byte) { + sz := w * h * 4 + mut tmp_sbc := C.sg_image_data{} + tmp_sbc.subimage[0][0] = C.sg_range{ + ptr: buf + size: size_t(sz) + } + C.sg_update_image(sg_img, &tmp_sbc) +} + +/****************************************************************************** +* +* Draw functions +* +******************************************************************************/ +fn draw_triangle() { + sgl.defaults() + sgl.begin_triangles() + sgl.v2f_c3b(0.0, 0.5, 255, 0, 0) + sgl.v2f_c3b(-0.5, -0.5, 0, 0, 255) + sgl.v2f_c3b(0.5, -0.5, 0, 255, 0) + sgl.end() +} + +// vertex specification for a cube with colored sides and texture coords +fn cube() { + sgl.begin_quads() + // edge color + sgl.c3f(1.0, 0.0, 0.0) + // edge coord + // x,y,z, texture cord: u,v + sgl.v3f_t2f(-1.0, 1.0, -1.0, -1.0, 1.0) + sgl.v3f_t2f(1.0, 1.0, -1.0, 1.0, 1.0) + sgl.v3f_t2f(1.0, -1.0, -1.0, 1.0, -1.0) + sgl.v3f_t2f(-1.0, -1.0, -1.0, -1.0, -1.0) + sgl.c3f(0.0, 1.0, 0.0) + sgl.v3f_t2f(-1.0, -1.0, 1.0, -1.0, 1.0) + sgl.v3f_t2f(1.0, -1.0, 1.0, 1.0, 1.0) + sgl.v3f_t2f(1.0, 1.0, 1.0, 1.0, -1.0) + sgl.v3f_t2f(-1.0, 1.0, 1.0, -1.0, -1.0) + sgl.c3f(0.0, 0.0, 1.0) + sgl.v3f_t2f(-1.0, -1.0, 1.0, -1.0, 1.0) + sgl.v3f_t2f(-1.0, 1.0, 1.0, 1.0, 1.0) + sgl.v3f_t2f(-1.0, 1.0, -1.0, 1.0, -1.0) + sgl.v3f_t2f(-1.0, -1.0, -1.0, -1.0, -1.0) + sgl.c3f(1.0, 0.5, 0.0) + sgl.v3f_t2f(1.0, -1.0, 1.0, -1.0, 1.0) + sgl.v3f_t2f(1.0, -1.0, -1.0, 1.0, 1.0) + sgl.v3f_t2f(1.0, 1.0, -1.0, 1.0, -1.0) + sgl.v3f_t2f(1.0, 1.0, 1.0, -1.0, -1.0) + sgl.c3f(0.0, 0.5, 1.0) + sgl.v3f_t2f(1.0, -1.0, -1.0, -1.0, 1.0) + sgl.v3f_t2f(1.0, -1.0, 1.0, 1.0, 1.0) + sgl.v3f_t2f(-1.0, -1.0, 1.0, 1.0, -1.0) + sgl.v3f_t2f(-1.0, -1.0, -1.0, -1.0, -1.0) + sgl.c3f(1.0, 0.0, 0.5) + sgl.v3f_t2f(-1.0, 1.0, -1.0, -1.0, 1.0) + sgl.v3f_t2f(-1.0, 1.0, 1.0, 1.0, 1.0) + sgl.v3f_t2f(1.0, 1.0, 1.0, 1.0, -1.0) + sgl.v3f_t2f(1.0, 1.0, -1.0, -1.0, -1.0) + sgl.end() +} + +fn draw_cubes(app App) { + rot := [f32(1.0) * (app.frame_count % 360), 0.5 * f32(app.frame_count % 360)] + // rot := [f32(app.mouse_x), f32(app.mouse_y)] + + sgl.defaults() + sgl.load_pipeline(app.pip_3d) + + sgl.matrix_mode_projection() + sgl.perspective(sgl.rad(45.0), 1.0, 0.1, 100.0) + + sgl.matrix_mode_modelview() + sgl.translate(0.0, 0.0, -12.0) + sgl.rotate(sgl.rad(rot[0]), 1.0, 0.0, 0.0) + sgl.rotate(sgl.rad(rot[1]), 0.0, 1.0, 0.0) + cube() + sgl.push_matrix() + sgl.translate(0.0, 0.0, 3.0) + sgl.scale(0.5, 0.5, 0.5) + sgl.rotate(-2.0 * sgl.rad(rot[0]), 1.0, 0.0, 0.0) + sgl.rotate(-2.0 * sgl.rad(rot[1]), 0.0, 1.0, 0.0) + cube() + sgl.push_matrix() + sgl.translate(0.0, 0.0, 3.0) + sgl.scale(0.5, 0.5, 0.5) + sgl.rotate(-3.0 * sgl.rad(2 * rot[0]), 1.0, 0.0, 0.0) + sgl.rotate(3.0 * sgl.rad(2 * rot[1]), 0.0, 0.0, 1.0) + cube() + sgl.pop_matrix() + sgl.pop_matrix() +} + +fn cube_t(r f32, g f32, b f32) { + sgl.begin_quads() + // edge color + sgl.c3f(r, g, b) + // edge coord + // x,y,z, texture cord: u,v + sgl.v3f_t2f(-1.0, 1.0, -1.0, 0.0, 0.25) + sgl.v3f_t2f(1.0, 1.0, -1.0, 0.25, 0.25) + sgl.v3f_t2f(1.0, -1.0, -1.0, 0.25, 0.0) + sgl.v3f_t2f(-1.0, -1.0, -1.0, 0.0, 0.0) + sgl.c3f(r, g, b) + sgl.v3f_t2f(-1.0, -1.0, 1.0, 0.0, 0.25) + sgl.v3f_t2f(1.0, -1.0, 1.0, 0.25, 0.25) + sgl.v3f_t2f(1.0, 1.0, 1.0, 0.25, 0.0) + sgl.v3f_t2f(-1.0, 1.0, 1.0, 0.0, 0.0) + sgl.c3f(r, g, b) + sgl.v3f_t2f(-1.0, -1.0, 1.0, 0.0, 0.25) + sgl.v3f_t2f(-1.0, 1.0, 1.0, 0.25, 0.25) + sgl.v3f_t2f(-1.0, 1.0, -1.0, 0.25, 0.0) + sgl.v3f_t2f(-1.0, -1.0, -1.0, 0.0, 0.0) + sgl.c3f(r, g, b) + sgl.v3f_t2f(1.0, -1.0, 1.0, 0.0, 0.25) + sgl.v3f_t2f(1.0, -1.0, -1.0, 0.25, 0.25) + sgl.v3f_t2f(1.0, 1.0, -1.0, 0.25, 0.0) + sgl.v3f_t2f(1.0, 1.0, 1.0, 0.0, 0.0) + sgl.c3f(r, g, b) + sgl.v3f_t2f(1.0, -1.0, -1.0, 0.0, 0.25) + sgl.v3f_t2f(1.0, -1.0, 1.0, 0.25, 0.25) + sgl.v3f_t2f(-1.0, -1.0, 1.0, 0.25, 0.0) + sgl.v3f_t2f(-1.0, -1.0, -1.0, 0.0, 0.0) + sgl.c3f(r, g, b) + sgl.v3f_t2f(-1.0, 1.0, -1.0, 0.0, 0.25) + sgl.v3f_t2f(-1.0, 1.0, 1.0, 0.25, 0.25) + sgl.v3f_t2f(1.0, 1.0, 1.0, 0.25, 0.0) + sgl.v3f_t2f(1.0, 1.0, -1.0, 0.0, 0.0) + sgl.end() +} + +fn draw_texture_cubes(app App) { + rot := [f32(app.mouse_x), f32(app.mouse_y)] + sgl.defaults() + sgl.load_pipeline(app.pip_3d) + + sgl.enable_texture() + sgl.texture(app.texture) + + sgl.matrix_mode_projection() + sgl.perspective(sgl.rad(45.0), 1.0, 0.1, 100.0) + + sgl.matrix_mode_modelview() + sgl.translate(0.0, 0.0, -12.0) + sgl.rotate(sgl.rad(rot[0]), 1.0, 0.0, 0.0) + sgl.rotate(sgl.rad(rot[1]), 0.0, 1.0, 0.0) + cube_t(1, 1, 1) + sgl.push_matrix() + sgl.translate(0.0, 0.0, 3.0) + sgl.scale(0.5, 0.5, 0.5) + sgl.rotate(-2.0 * sgl.rad(rot[0]), 1.0, 0.0, 0.0) + sgl.rotate(-2.0 * sgl.rad(rot[1]), 0.0, 1.0, 0.0) + cube_t(1, 1, 1) + sgl.push_matrix() + sgl.translate(0.0, 0.0, 3.0) + sgl.scale(0.5, 0.5, 0.5) + sgl.rotate(-3.0 * sgl.rad(2 * rot[0]), 1.0, 0.0, 0.0) + sgl.rotate(3.0 * sgl.rad(2 * rot[1]), 0.0, 0.0, 1.0) + cube_t(1, 1, 1) + sgl.pop_matrix() + sgl.pop_matrix() + + sgl.disable_texture() +} + +fn cube_field(app App) { + rot := [f32(app.mouse_x), f32(app.mouse_y)] + xyz_sz := f32(2.0) + field_size := 20 + + sgl.defaults() + sgl.load_pipeline(app.pip_3d) + + sgl.enable_texture() + sgl.texture(app.texture) + + sgl.matrix_mode_projection() + sgl.perspective(sgl.rad(45.0), 1.0, 0.1, 200.0) + + sgl.matrix_mode_modelview() + + sgl.translate(field_size, 0.0, -120.0) + sgl.rotate(sgl.rad(rot[0]), 0.0, 1.0, 0.0) + sgl.rotate(sgl.rad(rot[1]), 1.0, 0.0, 0.0) + + // draw field_size*field_size cubes + for y in 0 .. field_size { + for x in 0 .. field_size { + sgl.push_matrix() + z := f32(math.cos(f32(x * 2) / field_size) * math.sin(f32(y * 2) / field_size) * xyz_sz) * (xyz_sz * 5) + sgl.translate(x * xyz_sz, z, y * xyz_sz) + cube_t(f32(f32(x) / field_size), f32(f32(y) / field_size), 1) + sgl.pop_matrix() + } + } + sgl.disable_texture() +} + +fn frame(mut app App) { + ws := gg.window_size_real_pixels() + ratio := f32(ws.width) / ws.height + dw := ws.width + dh := ws.height + ww := int(dh / 3) // not a bug + hh := int(dh / 3) + x0 := int(f32(dw) * 0.05) + // x1 := dw/2 + y0 := 0 + y1 := int(f32(dh) * 0.5) + + app.gg.begin() + // sgl.defaults() + + // 2d triangle + sgl.viewport(x0, y0, ww, hh, true) + draw_triangle() + + // colored cubes with viewport + sgl.viewport(x0, y1, ww, hh, true) + draw_cubes(app) + + // textured cubed with viewport + sgl.viewport(0, int(dh / 5), dw, int(dh * ratio), true) + draw_texture_cubes(app) + + // textured field of cubes with viewport + sgl.viewport(0, int(dh / 5), dw, int(dh * ratio), true) + cube_field(app) + + app.frame_count++ + + app.gg.end() +} + +/****************************************************************************** +* +* Init / Cleanup +* +******************************************************************************/ +fn my_init(mut app App) { + app.init_flag = true + + // set max vertices, + // for a large number of the same type of object it is better use the instances!! + desc := sapp.create_desc() + gfx.setup(&desc) + sgl_desc := C.sgl_desc_t{ + max_vertices: 50 * 65536 + } + sgl.setup(&sgl_desc) + + // 3d pipeline + mut pipdesc := C.sg_pipeline_desc{} + unsafe { C.memset(&pipdesc, 0, sizeof(pipdesc)) } + + color_state := C.sg_color_state{ + blend: C.sg_blend_state{ + enabled: true + src_factor_rgb: gfx.BlendFactor(C.SG_BLENDFACTOR_SRC_ALPHA) + dst_factor_rgb: gfx.BlendFactor(C.SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA) + } + } + pipdesc.colors[0] = color_state + + pipdesc.depth = C.sg_depth_state{ + write_enabled: true + compare: gfx.CompareFunc(C.SG_COMPAREFUNC_LESS_EQUAL) + } + pipdesc.cull_mode = .back + app.pip_3d = sgl.make_pipeline(&pipdesc) + + // create chessboard texture 256*256 RGBA + w := 256 + h := 256 + sz := w * h * 4 + tmp_txt := unsafe { malloc(sz) } + mut i := 0 + for i < sz { + unsafe { + y := (i >> 0x8) >> 5 // 8 cell + x := (i & 0xFF) >> 5 // 8 cell + // upper left corner + if x == 0 && y == 0 { + tmp_txt[i] = byte(0xFF) + tmp_txt[i + 1] = byte(0) + tmp_txt[i + 2] = byte(0) + tmp_txt[i + 3] = byte(0xFF) + } + // low right corner + else if x == 7 && y == 7 { + tmp_txt[i] = byte(0) + tmp_txt[i + 1] = byte(0xFF) + tmp_txt[i + 2] = byte(0) + tmp_txt[i + 3] = byte(0xFF) + } else { + col := if ((x + y) & 1) == 1 { 0xFF } else { 0 } + tmp_txt[i] = byte(col) // red + tmp_txt[i + 1] = byte(col) // green + tmp_txt[i + 2] = byte(col) // blue + tmp_txt[i + 3] = byte(0xFF) // alpha + } + i += 4 + } + } + unsafe { + app.texture = create_texture(w, h, tmp_txt) + free(tmp_txt) + } +} + +fn cleanup(mut app App) { + gfx.shutdown() +} + +/****************************************************************************** +* +* event +* +******************************************************************************/ +fn my_event_manager(mut ev gg.Event, mut app App) { + if ev.typ == .mouse_move { + app.mouse_x = int(ev.mouse_x) + app.mouse_y = int(ev.mouse_y) + } + if ev.typ == .touches_began || ev.typ == .touches_moved { + if ev.num_touches > 0 { + touch_point := ev.touches[0] + app.mouse_x = int(touch_point.pos_x) + app.mouse_y = int(touch_point.pos_y) + } + } +} + +/****************************************************************************** +* +* Main +* +******************************************************************************/ +// is needed for easier diagnostics on windows +[console] +fn main() { + // App init + mut app := &App{ + gg: 0 + } + + app.gg = gg.new_context( + width: win_width + height: win_height + create_window: true + window_title: '3D Cube Demo' + user_data: app + bg_color: bg_color + frame_fn: frame + init_fn: my_init + cleanup_fn: cleanup + event_fn: my_event_manager + ) + + app.gg.run() +} diff --git a/v_windows/v/examples/sokol/02_cubes_glsl/cube_glsl.glsl b/v_windows/v/examples/sokol/02_cubes_glsl/cube_glsl.glsl new file mode 100644 index 0000000..e19e936 --- /dev/null +++ b/v_windows/v/examples/sokol/02_cubes_glsl/cube_glsl.glsl @@ -0,0 +1,95 @@ +//------------------------------------------------------------------------------ +// Shader code for texcube-sapp sample. +// +// NOTE: This source file also uses the '#pragma sokol' form of the +// custom tags. +//------------------------------------------------------------------------------ +//#pragma sokol @ctype mat4 my_mat4 + +#pragma sokol @vs vs +uniform vs_params { + mat4 mvp; +}; + +in vec4 pos; +in vec4 color0; +in vec2 texcoord0; + +out vec4 color; +out vec2 uv; + +void main() { + gl_Position = mvp * pos; + color = color0; + uv = texcoord0; +} +#pragma sokol @end + +#pragma sokol @fs fs +uniform sampler2D tex; +uniform fs_params { + vec2 text_res; + float iTime; +}; + +in vec4 color; +in vec2 uv; +out vec4 frag_color; + +//********************************************************* +// RAY TRACE +// original code from: https://www.shadertoy.com/view/ldS3DW +//********************************************************* +float sphere(vec3 ray, vec3 dir, vec3 center, float radius) +{ + vec3 rc = ray-center; + float c = dot(rc, rc) - (radius*radius); + float b = dot(dir, rc); + float d = b*b - c; + float t = -b - sqrt(abs(d)); + float st = step(0.0, min(t,d)); + return mix(-1.0, t, st); +} + +vec3 background(float t, vec3 rd) +{ + vec3 light = normalize(vec3(sin(t), 0.6, cos(t))); + float sun = max(0.0, dot(rd, light)); + float sky = max(0.0, dot(rd, vec3(0.0, 1.0, 0.0))); + float ground = max(0.0, -dot(rd, vec3(0.0, 1.0, 0.0))); + return (pow(sun, 256.0)+0.2*pow(sun, 2.0))*vec3(2.0, 1.6, 1.0) + + pow(ground, 0.5)*vec3(0.4, 0.3, 0.2) + + pow(sky, 1.0)*vec3(0.5, 0.6, 0.7); +} + +vec4 mainImage(vec2 fragCoord) +{ + vec2 uv = (fragCoord-vec2(0.4,0.4))*2.0; + + //vec2 uv = (-1.0 + 2.0*fc.xy / text_res.xy) * vec2(text_res.x/text_res.y, 1.0); + vec3 ro = vec3(0.0, 0.0, -3.0); + vec3 rd = normalize(vec3(uv, 1.0)); + vec3 p = vec3(0.0, 0.0, 0.0); + float t = sphere(ro, rd, p, 1.0); + vec3 nml = normalize(p - (ro+rd*t)); + vec3 bgCol = background(iTime, rd); + rd = reflect(rd, nml); + vec3 col = background(iTime, rd) * vec3(0.9, 0.8, 1.0); + vec4 fragColor = vec4( mix(bgCol, col, step(0.0, t)), 1.0 ); + return fragColor; +} +//********************************************************* +//********************************************************* + +void main() { + vec4 c = color; + vec4 txt = texture(tex, uv/4.0); + c = txt * c; + vec4 col_ray = mainImage(uv); + float txt_mix = mod(iTime,5); + frag_color = c*txt_mix*0.1 + col_ray ; +} + +#pragma sokol @end + +#pragma sokol @program cube vs fs diff --git a/v_windows/v/examples/sokol/02_cubes_glsl/cube_glsl.v b/v_windows/v/examples/sokol/02_cubes_glsl/cube_glsl.v new file mode 100644 index 0000000..0be09a4 --- /dev/null +++ b/v_windows/v/examples/sokol/02_cubes_glsl/cube_glsl.v @@ -0,0 +1,627 @@ +/********************************************************************** +* +* Sokol 3d cube demo +* +* Copyright (c) 2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* HOW TO COMPILE SHADERS: +* - download the sokol shader convertor tool from https://github.com/floooh/sokol-tools-bin +* +* - compile the .glsl shader with: +* linux : sokol-shdc --input cube_glsl.glsl --output cube_glsl.h --slang glsl330 +* windows: sokol-shdc.exe --input cube_glsl.glsl --output cube_glsl.h --slang glsl330 +* +* --slang parameter can be: +* - glsl330: desktop GL +* - glsl100: GLES2 / WebGL +* - glsl300es: GLES3 / WebGL2 +* - hlsl4: D3D11 +* - hlsl5: D3D11 +* - metal_macos: Metal on macOS +* - metal_ios: Metal on iOS device +* - metal_sim: Metal on iOS simulator +* - wgpu: WebGPU +* +* you can have multiple platforms at the same time passing prameter like this: --slang glsl330:hlsl5:metal_macos +* for further infos have a look at the sokol shader tool docs. +* +* TODO: +* - add instancing +**********************************************************************/ +import gg +import gx +// import math +import sokol.sapp +import sokol.gfx +import sokol.sgl +import time +import gg.m4 + +// GLSL Include and functions +#flag -I @VMODROOT/. +#include "cube_glsl.h" #Please use sokol-shdc to generate the necessary cube_glsl.h file from cube_glsl.glsl (see the instructions at the top of this file) + +fn C.cube_shader_desc(gfx.Backend) &C.sg_shader_desc + +const ( + win_width = 800 + win_height = 800 + bg_color = gx.white +) + +struct App { +mut: + gg &gg.Context + pip_3d C.sgl_pipeline + texture C.sg_image + init_flag bool + frame_count int + mouse_x int = -1 + mouse_y int = -1 + // glsl + cube_pip_glsl C.sg_pipeline + cube_bind C.sg_bindings + // time + ticks i64 +} + +/****************************************************************************** +* +* Texture functions +* +******************************************************************************/ +fn create_texture(w int, h int, buf &byte) C.sg_image { + sz := w * h * 4 + mut img_desc := C.sg_image_desc{ + width: w + height: h + num_mipmaps: 0 + min_filter: .linear + mag_filter: .linear + // usage: .dynamic + wrap_u: .clamp_to_edge + wrap_v: .clamp_to_edge + label: &byte(0) + d3d11_texture: 0 + } + // comment if .dynamic is enabled + img_desc.data.subimage[0][0] = C.sg_range{ + ptr: buf + size: size_t(sz) + } + + sg_img := C.sg_make_image(&img_desc) + return sg_img +} + +fn destroy_texture(sg_img C.sg_image) { + C.sg_destroy_image(sg_img) +} + +// Use only if usage: .dynamic is enabled +fn update_text_texture(sg_img C.sg_image, w int, h int, buf &byte) { + sz := w * h * 4 + mut tmp_sbc := C.sg_image_data{} + tmp_sbc.subimage[0][0] = C.sg_range{ + ptr: buf + size: size_t(sz) + } + C.sg_update_image(sg_img, &tmp_sbc) +} + +/****************************************************************************** +* +* Draw functions +* +******************************************************************************/ +fn draw_triangle() { + sgl.defaults() + sgl.begin_triangles() + sgl.v2f_c3b( 0.0, 0.5, 255, 0 , 0 ) + sgl.v2f_c3b(-0.5, -0.5, 0, 0 , 255) + sgl.v2f_c3b( 0.5, -0.5, 0, 255, 0 ) + sgl.end() +} + +// vertex specification for a cube with colored sides and texture coords +fn cube() { + sgl.begin_quads() + // edge color + sgl.c3f(1.0, 0.0, 0.0) + // edge coord + // x,y,z, texture cord: u,v + sgl.v3f_t2f(-1.0, 1.0, -1.0, -1.0, 1.0) + sgl.v3f_t2f( 1.0, 1.0, -1.0, 1.0, 1.0) + sgl.v3f_t2f( 1.0, -1.0, -1.0, 1.0, -1.0) + sgl.v3f_t2f(-1.0, -1.0, -1.0, -1.0, -1.0) + sgl.c3f(0.0, 1.0, 0.0) + sgl.v3f_t2f(-1.0, -1.0, 1.0, -1.0, 1.0) + sgl.v3f_t2f( 1.0, -1.0, 1.0, 1.0, 1.0) + sgl.v3f_t2f( 1.0, 1.0, 1.0, 1.0, -1.0) + sgl.v3f_t2f(-1.0, 1.0, 1.0, -1.0, -1.0) + sgl.c3f(0.0, 0.0, 1.0) + sgl.v3f_t2f(-1.0, -1.0, 1.0, -1.0, 1.0) + sgl.v3f_t2f(-1.0, 1.0, 1.0, 1.0, 1.0) + sgl.v3f_t2f(-1.0, 1.0, -1.0, 1.0, -1.0) + sgl.v3f_t2f(-1.0, -1.0, -1.0, -1.0, -1.0) + sgl.c3f(1.0, 0.5, 0.0) + sgl.v3f_t2f(1.0, -1.0, 1.0, -1.0, 1.0) + sgl.v3f_t2f(1.0, -1.0, -1.0, 1.0, 1.0) + sgl.v3f_t2f(1.0, 1.0, -1.0, 1.0, -1.0) + sgl.v3f_t2f(1.0, 1.0, 1.0, -1.0, -1.0) + sgl.c3f(0.0, 0.5, 1.0) + sgl.v3f_t2f( 1.0, -1.0, -1.0, -1.0, 1.0) + sgl.v3f_t2f( 1.0, -1.0, 1.0, 1.0, 1.0) + sgl.v3f_t2f(-1.0, -1.0, 1.0, 1.0, -1.0) + sgl.v3f_t2f(-1.0, -1.0, -1.0, -1.0, -1.0) + sgl.c3f(1.0, 0.0, 0.5) + sgl.v3f_t2f(-1.0, 1.0, -1.0, -1.0, 1.0) + sgl.v3f_t2f(-1.0, 1.0, 1.0, 1.0, 1.0) + sgl.v3f_t2f( 1.0, 1.0, 1.0, 1.0, -1.0) + sgl.v3f_t2f( 1.0, 1.0, -1.0, -1.0, -1.0) + sgl.end() +} + +fn draw_cubes(app App) { + rot := [f32(1.0) * (app.frame_count % 360), 0.5 * f32(app.frame_count % 360)] + // rot := [f32(app.mouse_x), f32(app.mouse_y)] + + sgl.defaults() + sgl.load_pipeline(app.pip_3d) + + sgl.matrix_mode_projection() + sgl.perspective(sgl.rad(45.0), 1.0, 0.1, 100.0) + + sgl.matrix_mode_modelview() + sgl.translate(0.0, 0.0, -12.0) + sgl.rotate(sgl.rad(rot[0]), 1.0, 0.0, 0.0) + sgl.rotate(sgl.rad(rot[1]), 0.0, 1.0, 0.0) + cube() + sgl.push_matrix() + sgl.translate(0.0, 0.0, 3.0) + sgl.scale(0.5, 0.5, 0.5) + sgl.rotate(-2.0 * sgl.rad(rot[0]), 1.0, 0.0, 0.0) + sgl.rotate(-2.0 * sgl.rad(rot[1]), 0.0, 1.0, 0.0) + cube() + sgl.push_matrix() + sgl.translate(0.0, 0.0, 3.0) + sgl.scale(0.5, 0.5, 0.5) + sgl.rotate(-3.0 * sgl.rad(2 * rot[0]), 1.0, 0.0, 0.0) + sgl.rotate( 3.0 * sgl.rad(2 * rot[1]), 0.0, 0.0, 1.0) + cube() + sgl.pop_matrix() + sgl.pop_matrix() +} + +fn cube_texture(r f32, g f32, b f32) { + sgl.begin_quads() + // edge color + sgl.c3f(r, g, b) + // edge coord + // x,y,z, texture cord: u,v + sgl.v3f_t2f(-1.0, 1.0, -1.0, 0.0 , 0.25) + sgl.v3f_t2f( 1.0, 1.0, -1.0, 0.25, 0.25) + sgl.v3f_t2f( 1.0, -1.0, -1.0, 0.25, 0.0 ) + sgl.v3f_t2f(-1.0, -1.0, -1.0, 0.0 , 0.0 ) + sgl.c3f(r, g, b) + sgl.v3f_t2f(-1.0, -1.0, 1.0, 0.0 , 0.25) + sgl.v3f_t2f( 1.0, -1.0, 1.0, 0.25, 0.25) + sgl.v3f_t2f( 1.0, 1.0, 1.0, 0.25, 0.0 ) + sgl.v3f_t2f(-1.0, 1.0, 1.0, 0.0 , 0.0 ) + sgl.c3f(r, g, b) + sgl.v3f_t2f(-1.0, -1.0, 1.0, 0.0 , 0.25) + sgl.v3f_t2f(-1.0, 1.0, 1.0, 0.25, 0.25) + sgl.v3f_t2f(-1.0, 1.0, -1.0, 0.25, 0.0 ) + sgl.v3f_t2f(-1.0, -1.0, -1.0, 0.0 , 0.0 ) + sgl.c3f(r, g, b) + sgl.v3f_t2f(1.0, -1.0, 1.0, 0.0 , 0.25) + sgl.v3f_t2f(1.0, -1.0, -1.0, 0.25, 0.25) + sgl.v3f_t2f(1.0, 1.0, -1.0, 0.25, 0.0 ) + sgl.v3f_t2f(1.0, 1.0, 1.0, 0.0 , 0.0 ) + sgl.c3f(r, g, b) + sgl.v3f_t2f( 1.0, -1.0, -1.0, 0.0 , 0.25) + sgl.v3f_t2f( 1.0, -1.0, 1.0, 0.25, 0.25) + sgl.v3f_t2f(-1.0, -1.0, 1.0, 0.25, 0.0 ) + sgl.v3f_t2f(-1.0, -1.0, -1.0, 0.0 , 0.0 ) + sgl.c3f(r, g, b) + sgl.v3f_t2f(-1.0, 1.0, -1.0, 0.0 , 0.25) + sgl.v3f_t2f(-1.0, 1.0, 1.0, 0.25, 0.25) + sgl.v3f_t2f( 1.0, 1.0, 1.0, 0.25, 0.0 ) + sgl.v3f_t2f( 1.0, 1.0, -1.0, 0.0 , 0.0 ) + sgl.end() +} + +/* +Cube vertex buffer with packed vertex formats for color and texture coords. + Note that a vertex format which must be portable across all + backends must only use the normalized integer formats + (BYTE4N, UBYTE4N, SHORT2N, SHORT4N), which can be converted + to floating point formats in the vertex shader inputs. + The reason is that D3D11 cannot convert from non-normalized + formats to floating point inputs (only to integer inputs), + and WebGL2 / GLES2 don't support integer vertex shader inputs. +*/ + +struct Vertex_t { + x f32 + y f32 + z f32 + color u32 + // u u16 + // v u16 + u f32 + v f32 +} + +fn init_cube_glsl(mut app App) { + // cube vertex buffer + // d := u16(32767/8) // for compatibility with D3D11, 32767 stand for 1 + d := f32(1.0) // 0.05) + c := u32(0xFFFFFF_FF) // color RGBA8 + vertices := [ + // Face 0 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{ 1.0, -1.0, -1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, -1.0, c, d, d}, + Vertex_t{-1.0, 1.0, -1.0, c, 0, d}, + // Face 1 + Vertex_t{-1.0, -1.0, 1.0, c, 0, 0}, + Vertex_t{ 1.0, -1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{-1.0, 1.0, 1.0, c, 0, d}, + // Face 2 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, 1.0, -1.0, c, d, 0}, + Vertex_t{-1.0, 1.0, 1.0, c, d, d}, + Vertex_t{-1.0, -1.0, 1.0, c, 0, d}, + // Face 3 + Vertex_t{ 1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{ 1.0, 1.0, -1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, -1.0, 1.0, c, 0, d}, + // Face 4 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, -1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, -1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, -1.0, -1.0, c, 0, d}, + // Face 5 + Vertex_t{-1.0, 1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, 1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, 1.0, -1.0, c, 0, d}, + ] + + mut vert_buffer_desc := C.sg_buffer_desc{label: c'cube-vertices'} + unsafe { C.memset(&vert_buffer_desc, 0, sizeof(vert_buffer_desc)) } + + vert_buffer_desc.size = size_t(vertices.len * int(sizeof(Vertex_t))) + vert_buffer_desc.data = C.sg_range{ + ptr: vertices.data + size: size_t(vertices.len * int(sizeof(Vertex_t))) + } + + vert_buffer_desc.@type = .vertexbuffer + // vert_buffer_desc.usage = .immutable + vbuf := gfx.make_buffer(&vert_buffer_desc) + + /* create an index buffer for the cube */ + indices := [ + u16(0), 1, 2, 0, 2, 3, + 6, 5, 4, 7, 6, 4, + 8, 9, 10, 8, 10, 11, + 14, 13, 12, 15, 14, 12, + 16, 17, 18, 16, 18, 19, + 22, 21, 20, 23, 22, 20 + ] + + mut index_buffer_desc := C.sg_buffer_desc{label: c'cube-indices'} + unsafe { C.memset(&index_buffer_desc, 0, sizeof(index_buffer_desc)) } + + index_buffer_desc.size = size_t(indices.len * int(sizeof(u16))) + index_buffer_desc.data = C.sg_range{ + ptr: indices.data + size: size_t(indices.len * int(sizeof(u16))) + } + + index_buffer_desc.@type = .indexbuffer + ibuf := gfx.make_buffer(&index_buffer_desc) + + // create shader + shader := gfx.make_shader(C.cube_shader_desc(C.sg_query_backend())) + + mut pipdesc := C.sg_pipeline_desc{} + unsafe { C.memset(&pipdesc, 0, sizeof(pipdesc)) } + + pipdesc.layout.buffers[0].stride = int(sizeof(Vertex_t)) + // the constants [C.ATTR_vs_pos, C.ATTR_vs_color0, C.ATTR_vs_texcoord0] are generated bysokol-shdc + pipdesc.layout.attrs[C.ATTR_vs_pos ].format = .float3 // x,y,z as f32 + pipdesc.layout.attrs[C.ATTR_vs_color0 ].format = .ubyte4n // color as u32 + pipdesc.layout.attrs[C.ATTR_vs_texcoord0].format = .float2 // u,v as f32 + // pipdesc.layout.attrs[C.ATTR_vs_texcoord0].format = .short2n // u,v as u16 + + pipdesc.shader = shader + pipdesc.index_type = .uint16 + + pipdesc.depth = C.sg_depth_state{ + write_enabled: true + compare: gfx.CompareFunc(C.SG_COMPAREFUNC_LESS_EQUAL) + } + pipdesc.cull_mode = .back + + pipdesc.label = 'glsl_shader pipeline'.str + + app.cube_bind.vertex_buffers[0] = vbuf + app.cube_bind.index_buffer = ibuf + app.cube_bind.fs_images[C.SLOT_tex] = app.texture + app.cube_pip_glsl = gfx.make_pipeline(&pipdesc) + println('GLSL init DONE!') +} + +fn draw_cube_glsl(app App) { + if app.init_flag == false { + return + } + + rot := [f32(app.mouse_y), f32(app.mouse_x)] + + ws := gg.window_size_real_pixels() + // ratio := f32(ws.width)/ws.height + dw := f32(ws.width / 2) + dh := f32(ws.height / 2) + + tr_matrix := m4.calc_tr_matrices(dw, dh, rot[0], rot[1], 2.0) + gfx.apply_viewport(ws.width / 2, 0, ws.width / 2, ws.height / 2, true) + + // apply the pipline and bindings + gfx.apply_pipeline(app.cube_pip_glsl) + gfx.apply_bindings(app.cube_bind) + + //*************** + // Uniforms + //*************** + // passing the view matrix as uniform + // res is a 4x4 matrix of f32 thus: 4*16 byte of size + vs_uniforms_range := C.sg_range{ + ptr: &tr_matrix + size: size_t(4 * 16) + } + gfx.apply_uniforms(C.SG_SHADERSTAGE_VS, C.SLOT_vs_params, &vs_uniforms_range) + + // fs uniforms + time_ticks := f32(time.ticks() - app.ticks) / 1000 + mut text_res := [ + f32(512), + 512, /* x,y resolution to pass to FS */ + time_ticks, /* time as f32 */ + 0 /* padding 4 Bytes == 1 f32 */, + ]! + fs_uniforms_range := C.sg_range{ + ptr: unsafe { &text_res } + size: size_t(4 * 4) + } + gfx.apply_uniforms(C.SG_SHADERSTAGE_FS, C.SLOT_fs_params, &fs_uniforms_range) + + gfx.draw(0, (3 * 2) * 6, 1) + gfx.end_pass() + gfx.commit() +} + +fn draw_texture_cubes(app App) { + rot := [f32(app.mouse_x), f32(app.mouse_y)] + sgl.defaults() + sgl.load_pipeline(app.pip_3d) + + sgl.enable_texture() + sgl.texture(app.texture) + + sgl.matrix_mode_projection() + sgl.perspective(sgl.rad(45.0), 1.0, 0.1, 100.0) + + sgl.matrix_mode_modelview() + sgl.translate(0.0, 0.0, -12.0) + sgl.rotate(sgl.rad(rot[0]), 1.0, 0.0, 0.0) + sgl.rotate(sgl.rad(rot[1]), 0.0, 1.0, 0.0) + cube_texture(1, 1, 1) + sgl.push_matrix() + sgl.translate(0.0, 0.0, 3.0) + sgl.scale(0.5, 0.5, 0.5) + sgl.rotate(-2.0 * sgl.rad(rot[0]), 1.0, 0.0, 0.0) + sgl.rotate(-2.0 * sgl.rad(rot[1]), 0.0, 1.0, 0.0) + cube_texture(1,1,1) + sgl.push_matrix() + sgl.translate(0.0, 0.0, 3.0) + sgl.scale(0.5, 0.5, 0.5) + sgl.rotate(-3.0 * sgl.rad(2*rot[0]), 1.0, 0.0, 0.0) + sgl.rotate(3.0 * sgl.rad(2*rot[1]), 0.0, 0.0, 1.0) + cube_texture(1,1,1) + sgl.pop_matrix() + sgl.pop_matrix() + + sgl.disable_texture() +} + +fn frame(mut app App) { + ws := gg.window_size_real_pixels() + ratio := f32(ws.width) / ws.height + dw := ws.width + dh := ws.height + ww := int(dh / 3) // not a bug + hh := int(dh / 3) + x0 := int(f32(dw) * 0.05) + // x1 := dw/2 + y0 := 0 + y1 := int(f32(dh) * 0.5) + + // app.gg.begin() + + app.gg.begin() + sgl.defaults() + + // 2d triangle + sgl.viewport(x0, y0, ww, hh, true) + draw_triangle() + + // colored cubes with viewport + sgl.viewport(x0, y1, ww, hh, true) + draw_cubes(app) + + // textured cubed with viewport + sgl.viewport(0, int(dh / 5), dw, int(dh * ratio), true) + draw_texture_cubes(app) + + app.gg.end() + + // clear + mut color_action := C.sg_color_attachment_action{ + action: gfx.Action(C.SG_ACTION_DONTCARE) // C.SG_ACTION_CLEAR) + value: C.sg_color{ + r: 1.0 + g: 1.0 + b: 1.0 + a: 1.0 + } + } + mut pass_action := C.sg_pass_action{} + pass_action.colors[0] = color_action + gfx.begin_default_pass(&pass_action, ws.width, ws.height) + + // glsl cube + draw_cube_glsl(app) + + app.frame_count++ +} + +/****************************************************************************** +* +* Init / Cleanup +* +******************************************************************************/ +fn my_init(mut app App) { + // set max vertices, + // for a large number of the same type of object it is better use the instances!! + desc := sapp.create_desc() + gfx.setup(&desc) + sgl_desc := C.sgl_desc_t{ + max_vertices: 50 * 65536 + } + sgl.setup(&sgl_desc) + + // 3d pipeline + mut pipdesc := C.sg_pipeline_desc{} + unsafe { C.memset(&pipdesc, 0, sizeof(pipdesc)) } + + color_state := C.sg_color_state{ + blend: C.sg_blend_state{ + enabled: true + src_factor_rgb: gfx.BlendFactor(C.SG_BLENDFACTOR_SRC_ALPHA) + dst_factor_rgb: gfx.BlendFactor(C.SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA) + } + } + pipdesc.colors[0] = color_state + + pipdesc.depth = C.sg_depth_state{ + write_enabled: true + compare: gfx.CompareFunc(C.SG_COMPAREFUNC_LESS_EQUAL) + } + pipdesc.cull_mode = .back + + app.pip_3d = sgl.make_pipeline(&pipdesc) + + // create chessboard texture 256*256 RGBA + w := 256 + h := 256 + sz := w * h * 4 + tmp_txt := unsafe { malloc(sz) } + mut i := 0 + for i < sz { + unsafe { + y := (i >> 0x8) >> 5 // 8 cell + x := (i & 0xFF) >> 5 // 8 cell + // upper left corner + if x == 0 && y == 0 { + tmp_txt[i] = byte(0xFF) + tmp_txt[i + 1] = byte(0) + tmp_txt[i + 2] = byte(0) + tmp_txt[i + 3] = byte(0xFF) + } + // low right corner + else if x == 7 && y == 7 { + tmp_txt[i + 0] = byte(0) + tmp_txt[i + 1] = byte(0xFF) + tmp_txt[i + 2] = byte(0) + tmp_txt[i + 3] = byte(0xFF) + } else { + col := if ((x + y) & 1) == 1 { 0xFF } else { 128 } + tmp_txt[i + 0] = byte(col) // red + tmp_txt[i + 1] = byte(col) // green + tmp_txt[i + 2] = byte(col) // blue + tmp_txt[i + 3] = byte(0xFF) // alpha + } + i += 4 + } + } + app.texture = create_texture(w, h, tmp_txt) + unsafe { free(tmp_txt) } + + // glsl + init_cube_glsl(mut app) + app.init_flag = true +} + +fn cleanup(mut app App) { + gfx.shutdown() +} + +/****************************************************************************** +* +* event +* +******************************************************************************/ +fn my_event_manager(mut ev gg.Event, mut app App) { + if ev.typ == .mouse_move { + app.mouse_x = int(ev.mouse_x) + app.mouse_y = int(ev.mouse_y) + } + if ev.typ == .touches_began || ev.typ == .touches_moved { + if ev.num_touches > 0 { + touch_point := ev.touches[0] + app.mouse_x = int(touch_point.pos_x) + app.mouse_y = int(touch_point.pos_y) + } + } +} + +/****************************************************************************** +* +* Main +* +******************************************************************************/ +[console] // is needed for easier diagnostics on windows +fn main() { + // App init + mut app := &App{ + gg: 0 + } + + mut a := [5]int{} + a[0] = 2 + println(a) + + app.gg = gg.new_context( + width: win_width + height: win_height + create_window: true + window_title: '3D Cube Demo' + user_data: app + bg_color: bg_color + frame_fn: frame + init_fn: my_init + cleanup_fn: cleanup + event_fn: my_event_manager + ) + + app.ticks = time.ticks() + app.gg.run() +} diff --git a/v_windows/v/examples/sokol/02_cubes_glsl/v.mod b/v_windows/v/examples/sokol/02_cubes_glsl/v.mod new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/v_windows/v/examples/sokol/02_cubes_glsl/v.mod diff --git a/v_windows/v/examples/sokol/03_march_tracing_glsl/rt_glsl.glsl b/v_windows/v/examples/sokol/03_march_tracing_glsl/rt_glsl.glsl new file mode 100644 index 0000000..3490873 --- /dev/null +++ b/v_windows/v/examples/sokol/03_march_tracing_glsl/rt_glsl.glsl @@ -0,0 +1,695 @@ +//------------------------------------------------------------------------------ +// Shader code for texcube-sapp sample. +// +// NOTE: This source file also uses the '#pragma sokol' form of the +// custom tags. +//------------------------------------------------------------------------------ +//#pragma sokol @ctype mat4 hmm_mat4 + +#pragma sokol @vs vs +uniform vs_params { + mat4 mvp; +}; + +in vec4 pos; +in vec4 color0; +in vec2 texcoord0; + +out vec4 color; +out vec2 uv; + +void main() { + gl_Position = mvp * pos; + color = color0; + uv = texcoord0; +} +#pragma sokol @end + +#pragma sokol @fs fs +uniform sampler2D tex; +uniform fs_params { + vec2 iResolution; + vec2 iMouse; + float iTime; + float iFrame; +}; + +in vec4 color; +in vec2 uv; +out vec4 frag_color; + +// change to 0 to 4 to increment the AntiAliasing, +// increase AA will SLOW the rendering!! +#define AA 1 + +//********************************************************* +// Ray Marching +// original code from: https://www.shadertoy.com/view/Xds3zN +//********************************************************* +// The MIT License +// Copyright © 2013 Inigo Quilez +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// A list of useful distance function to simple primitives. All +// these functions (except for ellipsoid) return an exact +// euclidean distance, meaning they produce a better SDF than +// what you'd get if you were constructing them from boolean +// operations. +// +// More info here: +// +// https://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm + +//------------------------------------------------------------------ +float dot2( in vec2 v ) { return dot(v,v); } +float dot2( in vec3 v ) { return dot(v,v); } +float ndot( in vec2 a, in vec2 b ) { return a.x*b.x - a.y*b.y; } + +float sdPlane( vec3 p ) +{ + return p.y; +} + +float sdSphere( vec3 p, float s ) +{ + return length(p)-s; +} + +float sdBox( vec3 p, vec3 b ) +{ + vec3 d = abs(p) - b; + return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0)); +} + +float sdBoundingBox( vec3 p, vec3 b, float e ) +{ + p = abs(p )-b; + vec3 q = abs(p+e)-e; + + return min(min( + length(max(vec3(p.x,q.y,q.z),0.0))+min(max(p.x,max(q.y,q.z)),0.0), + length(max(vec3(q.x,p.y,q.z),0.0))+min(max(q.x,max(p.y,q.z)),0.0)), + length(max(vec3(q.x,q.y,p.z),0.0))+min(max(q.x,max(q.y,p.z)),0.0)); +} +float sdEllipsoid( in vec3 p, in vec3 r ) // approximated +{ + float k0 = length(p/r); + float k1 = length(p/(r*r)); + return k0*(k0-1.0)/k1; +} + +float sdTorus( vec3 p, vec2 t ) +{ + return length( vec2(length(p.xz)-t.x,p.y) )-t.y; +} + +float sdCappedTorus(in vec3 p, in vec2 sc, in float ra, in float rb) +{ + p.x = abs(p.x); + float k = (sc.y*p.x>sc.x*p.y) ? dot(p.xy,sc) : length(p.xy); + return sqrt( dot(p,p) + ra*ra - 2.0*ra*k ) - rb; +} + +float sdHexPrism( vec3 p, vec2 h ) +{ + vec3 q = abs(p); + + const vec3 k = vec3(-0.8660254, 0.5, 0.57735); + p = abs(p); + p.xy -= 2.0*min(dot(k.xy, p.xy), 0.0)*k.xy; + vec2 d = vec2( + length(p.xy - vec2(clamp(p.x, -k.z*h.x, k.z*h.x), h.x))*sign(p.y - h.x), + p.z-h.y ); + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +float sdOctogonPrism( in vec3 p, in float r, float h ) +{ + const vec3 k = vec3(-0.9238795325, // sqrt(2+sqrt(2))/2 + 0.3826834323, // sqrt(2-sqrt(2))/2 + 0.4142135623 ); // sqrt(2)-1 + // reflections + p = abs(p); + p.xy -= 2.0*min(dot(vec2( k.x,k.y),p.xy),0.0)*vec2( k.x,k.y); + p.xy -= 2.0*min(dot(vec2(-k.x,k.y),p.xy),0.0)*vec2(-k.x,k.y); + // polygon side + p.xy -= vec2(clamp(p.x, -k.z*r, k.z*r), r); + vec2 d = vec2( length(p.xy)*sign(p.y), p.z-h ); + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +float sdCapsule( vec3 p, vec3 a, vec3 b, float r ) +{ + vec3 pa = p-a, ba = b-a; + float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); + return length( pa - ba*h ) - r; +} + +float sdRoundCone( in vec3 p, in float r1, float r2, float h ) +{ + vec2 q = vec2( length(p.xz), p.y ); + + float b = (r1-r2)/h; + float a = sqrt(1.0-b*b); + float k = dot(q,vec2(-b,a)); + + if( k < 0.0 ) return length(q) - r1; + if( k > a*h ) return length(q-vec2(0.0,h)) - r2; + + return dot(q, vec2(a,b) ) - r1; +} + +float sdRoundCone(vec3 p, vec3 a, vec3 b, float r1, float r2) +{ + // sampling independent computations (only depend on shape) + vec3 ba = b - a; + float l2 = dot(ba,ba); + float rr = r1 - r2; + float a2 = l2 - rr*rr; + float il2 = 1.0/l2; + + // sampling dependant computations + vec3 pa = p - a; + float y = dot(pa,ba); + float z = y - l2; + float x2 = dot2( pa*l2 - ba*y ); + float y2 = y*y*l2; + float z2 = z*z*l2; + + // single square root! + float k = sign(rr)*rr*rr*x2; + if( sign(z)*a2*z2 > k ) return sqrt(x2 + z2) *il2 - r2; + if( sign(y)*a2*y2 < k ) return sqrt(x2 + y2) *il2 - r1; + return (sqrt(x2*a2*il2)+y*rr)*il2 - r1; +} + +float sdTriPrism( vec3 p, vec2 h ) +{ + const float k = sqrt(3.0); + h.x *= 0.5*k; + p.xy /= h.x; + p.x = abs(p.x) - 1.0; + p.y = p.y + 1.0/k; + if( p.x+k*p.y>0.0 ) p.xy=vec2(p.x-k*p.y,-k*p.x-p.y)/2.0; + p.x -= clamp( p.x, -2.0, 0.0 ); + float d1 = length(p.xy)*sign(-p.y)*h.x; + float d2 = abs(p.z)-h.y; + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +} + +// vertical +float sdCylinder( vec3 p, vec2 h ) +{ + vec2 d = abs(vec2(length(p.xz),p.y)) - h; + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +// arbitrary orientation +float sdCylinder(vec3 p, vec3 a, vec3 b, float r) +{ + vec3 pa = p - a; + vec3 ba = b - a; + float baba = dot(ba,ba); + float paba = dot(pa,ba); + + float x = length(pa*baba-ba*paba) - r*baba; + float y = abs(paba-baba*0.5)-baba*0.5; + float x2 = x*x; + float y2 = y*y*baba; + float d = (max(x,y)<0.0)?-min(x2,y2):(((x>0.0)?x2:0.0)+((y>0.0)?y2:0.0)); + return sign(d)*sqrt(abs(d))/baba; +} + +// vertical +float sdCone( in vec3 p, in vec2 c, float h ) +{ + vec2 q = h*vec2(c.x,-c.y)/c.y; + vec2 w = vec2( length(p.xz), p.y ); + + vec2 a = w - q*clamp( dot(w,q)/dot(q,q), 0.0, 1.0 ); + vec2 b = w - q*vec2( clamp( w.x/q.x, 0.0, 1.0 ), 1.0 ); + float k = sign( q.y ); + float d = min(dot( a, a ),dot(b, b)); + float s = max( k*(w.x*q.y-w.y*q.x),k*(w.y-q.y) ); + return sqrt(d)*sign(s); +} + +float sdCappedCone( in vec3 p, in float h, in float r1, in float r2 ) +{ + vec2 q = vec2( length(p.xz), p.y ); + + vec2 k1 = vec2(r2,h); + vec2 k2 = vec2(r2-r1,2.0*h); + vec2 ca = vec2(q.x-min(q.x,(q.y < 0.0)?r1:r2), abs(q.y)-h); + vec2 cb = q - k1 + k2*clamp( dot(k1-q,k2)/dot2(k2), 0.0, 1.0 ); + float s = (cb.x < 0.0 && ca.y < 0.0) ? -1.0 : 1.0; + return s*sqrt( min(dot2(ca),dot2(cb)) ); +} + +float sdCappedCone(vec3 p, vec3 a, vec3 b, float ra, float rb) +{ + float rba = rb-ra; + float baba = dot(b-a,b-a); + float papa = dot(p-a,p-a); + float paba = dot(p-a,b-a)/baba; + + float x = sqrt( papa - paba*paba*baba ); + + float cax = max(0.0,x-((paba<0.5)?ra:rb)); + float cay = abs(paba-0.5)-0.5; + + float k = rba*rba + baba; + float f = clamp( (rba*(x-ra)+paba*baba)/k, 0.0, 1.0 ); + + float cbx = x-ra - f*rba; + float cby = paba - f; + + float s = (cbx < 0.0 && cay < 0.0) ? -1.0 : 1.0; + + return s*sqrt( min(cax*cax + cay*cay*baba, + cbx*cbx + cby*cby*baba) ); +} + +// c is the sin/cos of the desired cone angle +float sdSolidAngle(vec3 pos, vec2 c, float ra) +{ + vec2 p = vec2( length(pos.xz), pos.y ); + float l = length(p) - ra; + float m = length(p - c*clamp(dot(p,c),0.0,ra) ); + return max(l,m*sign(c.y*p.x-c.x*p.y)); +} + +float sdOctahedron(vec3 p, float s) +{ + p = abs(p); + float m = p.x + p.y + p.z - s; + +// exact distance +#if 0 + vec3 o = min(3.0*p - m, 0.0); + o = max(6.0*p - m*2.0 - o*3.0 + (o.x+o.y+o.z), 0.0); + return length(p - s*o/(o.x+o.y+o.z)); +#endif + +// exact distance +#if 1 + vec3 q; + if( 3.0*p.x < m ) q = p.xyz; + else if( 3.0*p.y < m ) q = p.yzx; + else if( 3.0*p.z < m ) q = p.zxy; + else return m*0.57735027; + float k = clamp(0.5*(q.z-q.y+s),0.0,s); + return length(vec3(q.x,q.y-s+k,q.z-k)); +#endif + +// bound, not exact +#if 0 + return m*0.57735027; +#endif +} + +float sdPyramid( in vec3 p, in float h ) +{ + float m2 = h*h + 0.25; + + // symmetry + p.xz = abs(p.xz); + p.xz = (p.z>p.x) ? p.zx : p.xz; + p.xz -= 0.5; + + // project into face plane (2D) + vec3 q = vec3( p.z, h*p.y - 0.5*p.x, h*p.x + 0.5*p.y); + + float s = max(-q.x,0.0); + float t = clamp( (q.y-0.5*p.z)/(m2+0.25), 0.0, 1.0 ); + + float a = m2*(q.x+s)*(q.x+s) + q.y*q.y; + float b = m2*(q.x+0.5*t)*(q.x+0.5*t) + (q.y-m2*t)*(q.y-m2*t); + + float d2 = min(q.y,-q.x*m2-q.y*0.5) > 0.0 ? 0.0 : min(a,b); + + // recover 3D and scale, and add sign + return sqrt( (d2+q.z*q.z)/m2 ) * sign(max(q.z,-p.y)); +} + +// la,lb=semi axis, h=height, ra=corner +float sdRhombus(vec3 p, float la, float lb, float h, float ra) +{ + p = abs(p); + vec2 b = vec2(la,lb); + float f = clamp( (ndot(b,b-2.0*p.xz))/dot(b,b), -1.0, 1.0 ); + vec2 q = vec2(length(p.xz-0.5*b*vec2(1.0-f,1.0+f))*sign(p.x*b.y+p.z*b.x-b.x*b.y)-ra, p.y-h); + return min(max(q.x,q.y),0.0) + length(max(q,0.0)); +} + +//------------------------------------------------------------------ + +vec2 opU( vec2 d1, vec2 d2 ) +{ + return (d1.x<d2.x) ? d1 : d2; +} + +//------------------------------------------------------------------ + +#define ZERO (min(int(iFrame),0)) + +//------------------------------------------------------------------ + +vec2 map( in vec3 pos ) +{ + vec2 res = vec2( 1e10, 0.0 ); + + { + res = opU( res, vec2( sdSphere( pos-vec3(-2.0,0.25, 0.0), 0.25 ), 26.9 ) ); + } + + // bounding box + if( sdBox( pos-vec3(0.0,0.3,-1.0),vec3(0.35,0.3,2.5) )<res.x ) + { + // more primitives + res = opU( res, vec2( sdBoundingBox( pos-vec3( 0.0,0.25, 0.0), vec3(0.3,0.25,0.2), 0.025 ), 16.9 ) ); + res = opU( res, vec2( sdTorus( (pos-vec3( 0.0,0.30, 1.0)).xzy, vec2(0.25,0.05) ), 25.0 ) ); + res = opU( res, vec2( sdCone( pos-vec3( 0.0,0.45,-1.0), vec2(0.6,0.8),0.45 ), 55.0 ) ); + res = opU( res, vec2( sdCappedCone( pos-vec3( 0.0,0.25,-2.0), 0.25, 0.25, 0.1 ), 13.67 ) ); + res = opU( res, vec2( sdSolidAngle( pos-vec3( 0.0,0.00,-3.0), vec2(3,4)/5.0, 0.4 ), 49.13 ) ); + } + + // bounding box + if( sdBox( pos-vec3(1.0,0.3,-1.0),vec3(0.35,0.3,2.5) )<res.x ) + { + // more primitives + res = opU( res, vec2( sdCappedTorus((pos-vec3( 1.0,0.30, 1.0))*vec3(1,-1,1), vec2(0.866025,-0.5), 0.25, 0.05), 8.5) ); + res = opU( res, vec2( sdBox( pos-vec3( 1.0,0.25, 0.0), vec3(0.3,0.25,0.1) ), 3.0 ) ); + res = opU( res, vec2( sdCapsule( pos-vec3( 1.0,0.00,-1.0),vec3(-0.1,0.1,-0.1), vec3(0.2,0.4,0.2), 0.1 ), 31.9 ) ); + res = opU( res, vec2( sdCylinder( pos-vec3( 1.0,0.25,-2.0), vec2(0.15,0.25) ), 8.0 ) ); + res = opU( res, vec2( sdHexPrism( pos-vec3( 1.0,0.2,-3.0), vec2(0.2,0.05) ), 18.4 ) ); + } + + // bounding box + if( sdBox( pos-vec3(-1.0,0.35,-1.0),vec3(0.35,0.35,2.5))<res.x ) + { + // more primitives + res = opU( res, vec2( sdPyramid( pos-vec3(-1.0,-0.6,-3.0), 1.0 ), 13.56 ) ); + res = opU( res, vec2( sdOctahedron( pos-vec3(-1.0,0.15,-2.0), 0.35 ), 23.56 ) ); + res = opU( res, vec2( sdTriPrism( pos-vec3(-1.0,0.15,-1.0), vec2(0.3,0.05) ),43.5 ) ); + res = opU( res, vec2( sdEllipsoid( pos-vec3(-1.0,0.25, 0.0), vec3(0.2, 0.25, 0.05) ), 43.17 ) ); + res = opU( res, vec2( sdRhombus( (pos-vec3(-1.0,0.34, 1.0)).xzy, 0.15, 0.25, 0.04, 0.08 ),17.0 ) ); + } + + // bounding box + if( sdBox( pos-vec3(2.0,0.3,-1.0),vec3(0.35,0.3,2.5) )<res.x ) + { + // more primitives + res = opU( res, vec2( sdOctogonPrism(pos-vec3( 2.0,0.2,-3.0), 0.2, 0.05), 51.8 ) ); + res = opU( res, vec2( sdCylinder( pos-vec3( 2.0,0.15,-2.0), vec3(0.1,-0.1,0.0), vec3(-0.2,0.35,0.1), 0.08), 31.2 ) ); + res = opU( res, vec2( sdCappedCone( pos-vec3( 2.0,0.10,-1.0), vec3(0.1,0.0,0.0), vec3(-0.2,0.40,0.1), 0.15, 0.05), 46.1 ) ); + res = opU( res, vec2( sdRoundCone( pos-vec3( 2.0,0.15, 0.0), vec3(0.1,0.0,0.0), vec3(-0.1,0.35,0.1), 0.15, 0.05), 51.7 ) ); + res = opU( res, vec2( sdRoundCone( pos-vec3( 2.0,0.20, 1.0), 0.2, 0.1, 0.3 ), 37.0 ) ); + } + + return res; +} + +// http://iquilezles.org/www/articles/boxfunctions/boxfunctions.htm +vec2 iBox( in vec3 ro, in vec3 rd, in vec3 rad ) +{ + vec3 m = 1.0/rd; + vec3 n = m*ro; + vec3 k = abs(m)*rad; + vec3 t1 = -n - k; + vec3 t2 = -n + k; + return vec2( max( max( t1.x, t1.y ), t1.z ), + min( min( t2.x, t2.y ), t2.z ) ); +} + +vec2 raycast( in vec3 ro, in vec3 rd ) +{ + vec2 res = vec2(-1.0,-1.0); + + float tmin = 1.0; + float tmax = 20.0; + + // raytrace floor plane + float tp1 = (0.0-ro.y)/rd.y; + if( tp1>0.0 ) + { + tmax = min( tmax, tp1 ); + res = vec2( tp1, 1.0 ); + } + //else return res; + + // raymarch primitives + vec2 tb = iBox( ro-vec3(0.0,0.4,-0.5), rd, vec3(2.5,0.41,3.0) ); + if( tb.x<tb.y && tb.y>0.0 && tb.x<tmax) + { + //return vec2(tb.x,2.0); + tmin = max(tb.x,tmin); + tmax = min(tb.y,tmax); + + float t = tmin; + for( int i=0; i<70 && t<tmax; i++ ) + { + vec2 h = map( ro+rd*t ); + if( abs(h.x)<(0.0001*t) ) + { + res = vec2(t,h.y); + break; + } + t += h.x; + } + } + + return res; +} + +// http://iquilezles.org/www/articles/rmshadows/rmshadows.htm +float calcSoftshadow( in vec3 ro, in vec3 rd, in float mint, in float tmax ) +{ + // bounding volume + float tp = (0.8-ro.y)/rd.y; if( tp>0.0 ) tmax = min( tmax, tp ); + + float res = 1.0; + float t = mint; + for( int i=ZERO; i<24; i++ ) + { + float h = map( ro + rd*t ).x; + float s = clamp(8.0*h/t,0.0,1.0); + res = min( res, s*s*(3.0-2.0*s) ); + t += clamp( h, 0.02, 0.2 ); + if( res<0.004 || t>tmax ) break; + } + return clamp( res, 0.0, 1.0 ); +} + +// http://iquilezles.org/www/articles/normalsSDF/normalsSDF.htm +vec3 calcNormal( in vec3 pos ) +{ +#if 0 + vec2 e = vec2(1.0,-1.0)*0.5773*0.0005; + return normalize( e.xyy*map( pos + e.xyy ).x + + e.yyx*map( pos + e.yyx ).x + + e.yxy*map( pos + e.yxy ).x + + e.xxx*map( pos + e.xxx ).x ); +#else + // inspired by tdhooper and klems - a way to prevent the compiler from inlining map() 4 times + vec3 n = vec3(0.0); + for( int i=ZERO; i<4; i++ ) + { + vec3 e = 0.5773*(2.0*vec3((((i+3)>>1)&1),((i>>1)&1),(i&1))-1.0); + n += e*map(pos+0.0005*e).x; + //if( n.x+n.y+n.z>100.0 ) break; + } + return normalize(n); +#endif +} + +float calcAO( in vec3 pos, in vec3 nor ) +{ + float occ = 0.0; + float sca = 1.0; + for( int i=ZERO; i<5; i++ ) + { + float h = 0.01 + 0.12*float(i)/4.0; + float d = map( pos + h*nor ).x; + occ += (h-d)*sca; + sca *= 0.95; + if( occ>0.35 ) break; + } + return clamp( 1.0 - 3.0*occ, 0.0, 1.0 ) * (0.5+0.5*nor.y); +} + +// http://iquilezles.org/www/articles/checkerfiltering/checkerfiltering.htm +float checkersGradBox( in vec2 p, in vec2 dpdx, in vec2 dpdy ) +{ + // filter kernel + vec2 w = abs(dpdx)+abs(dpdy) + 0.001; + // analytical integral (box filter) + vec2 i = 2.0*(abs(fract((p-0.5*w)*0.5)-0.5)-abs(fract((p+0.5*w)*0.5)-0.5))/w; + // xor pattern + return 0.5 - 0.5*i.x*i.y; +} + +vec3 render( in vec3 ro, in vec3 rd, in vec3 rdx, in vec3 rdy ) +{ + // background + vec3 col = vec3(0.7, 0.7, 0.9) - max(rd.y,0.0)*0.3; + + // raycast scene + vec2 res = raycast(ro,rd); + float t = res.x; + float m = res.y; + if( m>-0.5 ) + { + vec3 pos = ro + t*rd; + vec3 nor = (m<1.5) ? vec3(0.0,1.0,0.0) : calcNormal( pos ); + vec3 ref = reflect( rd, nor ); + + // material + col = 0.2 + 0.2*sin( m*2.0 + vec3(0.0,1.0,2.0) ); + float ks = 1.0; + + if( m<1.5 ) + { + // project pixel footprint into the plane + vec3 dpdx = ro.y*(rd/rd.y-rdx/rdx.y); + vec3 dpdy = ro.y*(rd/rd.y-rdy/rdy.y); + + float f = checkersGradBox( 3.0*pos.xz, 3.0*dpdx.xz, 3.0*dpdy.xz ); + col = 0.15 + f*vec3(0.05); + ks = 0.4; + } + + // lighting + float occ = calcAO( pos, nor ); + + vec3 lin = vec3(0.0); + + // sun + { + vec3 lig = normalize( vec3(-0.5, 0.4, -0.6) ); + vec3 hal = normalize( lig-rd ); + float dif = clamp( dot( nor, lig ), 0.0, 1.0 ); + //if( dif>0.0001 ) + dif *= calcSoftshadow( pos, lig, 0.02, 2.5 ); + float spe = pow( clamp( dot( nor, hal ), 0.0, 1.0 ),16.0); + spe *= dif; + spe *= 0.04+0.96*pow(clamp(1.0-dot(hal,lig),0.0,1.0),5.0); + lin += col*2.20*dif*vec3(1.30,1.00,0.70); + lin += 5.00*spe*vec3(1.30,1.00,0.70)*ks; + } + // sky + { + float dif = sqrt(clamp( 0.5+0.5*nor.y, 0.0, 1.0 )); + dif *= occ; + float spe = smoothstep( -0.2, 0.2, ref.y ); + spe *= dif; + spe *= 0.04+0.96*pow(clamp(1.0+dot(nor,rd),0.0,1.0), 5.0 ); + //if( spe>0.001 ) + spe *= calcSoftshadow( pos, ref, 0.02, 2.5 ); + lin += col*0.60*dif*vec3(0.40,0.60,1.15); + lin += 2.00*spe*vec3(0.40,0.60,1.30)*ks; + } + // back + { + float dif = clamp( dot( nor, normalize(vec3(0.5,0.0,0.6))), 0.0, 1.0 )*clamp( 1.0-pos.y,0.0,1.0); + dif *= occ; + lin += col*0.55*dif*vec3(0.25,0.25,0.25); + } + // sss + { + float dif = pow(clamp(1.0+dot(nor,rd),0.0,1.0),2.0); + dif *= occ; + lin += col*0.25*dif*vec3(1.00,1.00,1.00); + } + + col = lin; + + col = mix( col, vec3(0.7,0.7,0.9), 1.0-exp( -0.0001*t*t*t ) ); + } + + return vec3( clamp(col,0.0,1.0) ); +} + +mat3 setCamera( in vec3 ro, in vec3 ta, float cr ) +{ + vec3 cw = normalize(ta-ro); + vec3 cp = vec3(sin(cr), cos(cr),0.0); + vec3 cu = normalize( cross(cw,cp) ); + vec3 cv = ( cross(cu,cw) ); + return mat3( cu, cv, cw ); +} + +vec4 mainImage( vec2 fragCoord ) +{ + vec2 mo = iMouse.xy/iResolution.xy; + float time = 32.0 + iTime*1.5; + + // camera + vec3 ta = vec3( 0.5, -0.5, -0.6 ); + vec3 ro = ta + vec3( 4.5*cos(0.1*time + 7.0*mo.x), 1.3 + 2.0*mo.y, 4.5*sin(0.1*time + 7.0*mo.x) ); + // camera-to-world transformation + mat3 ca = setCamera( ro, ta, 0.0 ); + + vec3 tot = vec3(0.0); +#if AA>1 + for( int m=ZERO; m<AA; m++ ) + for( int n=ZERO; n<AA; n++ ) + { + // pixel coordinates + vec2 o = vec2(float(m),float(n)) / float(AA) - 0.5; + vec2 p = (2.0*(fragCoord+o)-iResolution.xy)/iResolution.y; +#else + vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y; +#endif + + // focal length + const float fl = 2.5; + + // ray direction + vec3 rd = ca * normalize( vec3(p,fl) ); + + // ray differentials + vec2 px = (2.0*(fragCoord+vec2(1.0,0.0))-iResolution.xy)/iResolution.y; + vec2 py = (2.0*(fragCoord+vec2(0.0,1.0))-iResolution.xy)/iResolution.y; + vec3 rdx = ca * normalize( vec3(px,fl) ); + vec3 rdy = ca * normalize( vec3(py,fl) ); + + // render + vec3 col = render( ro, rd, rdx, rdy ); + + // gain + // col = col*3.0/(2.5+col); + + // gamma + col = pow( col, vec3(0.4545) ); + + tot += col; +#if AA>1 + } + tot /= float(AA*AA); +#endif + + //fragColor = vec4( tot, 1.0 ); + return vec4( tot, 1.0 ); +} + +//********************************************************* +// END Ray Marching +//********************************************************* + +void main() { + vec4 c = color; + vec4 txt = texture(tex, uv); + c = txt * c; + vec2 uv1 = uv * iResolution; + vec4 col_ray = mainImage(uv1); + + // use this to mix the chessboart texture with the ray marching + //frag_color = clamp(c*iMouse.y/512.0,0.0,1.0) * col_ray ; + + frag_color = c*0.00001 + col_ray ; +} + +#pragma sokol @end + +#pragma sokol @program rt vs fs diff --git a/v_windows/v/examples/sokol/03_march_tracing_glsl/rt_glsl.v b/v_windows/v/examples/sokol/03_march_tracing_glsl/rt_glsl.v new file mode 100644 index 0000000..73a85a3 --- /dev/null +++ b/v_windows/v/examples/sokol/03_march_tracing_glsl/rt_glsl.v @@ -0,0 +1,438 @@ +/********************************************************************** +* +* Sokol 3d cube demo +* +* Copyright (c) 2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* HOW TO COMPILE SHADERS: +* - download the sokol shader convertor tool from https://github.com/floooh/sokol-tools-bin +* +* - compile the .glsl shader with: +* linux : sokol-shdc --input rt_glsl.glsl --output rt_glsl.h --slang glsl330 +* windows: sokol-shdc.exe --input rt_glsl.glsl --output rt_glsl.h --slang glsl330 +* +* --slang parameter can be: +* - glsl330: desktop GL +* - glsl100: GLES2 / WebGL +* - glsl300es: GLES3 / WebGL2 +* - hlsl4: D3D11 +* - hlsl5: D3D11 +* - metal_macos: Metal on macOS +* - metal_ios: Metal on iOS device +* - metal_sim: Metal on iOS simulator +* - wgpu: WebGPU +* +* you can have multiple platforms at the same time passing parameters like this: --slang glsl330:hlsl5:metal_macos +* for further infos have a look at the sokol shader tool docs. +* +* TODO: +* - frame counter +**********************************************************************/ +import gg +import gg.m4 +import gx +// import math +import sokol.sapp +import sokol.gfx +import sokol.sgl +import time + +// GLSL Include and functions + +#flag -I @VMODROOT/. +#include "rt_glsl.h" #Please use sokol-shdc to generate the necessary rt_glsl.h file from rt_glsl.glsl (see the instructions at the top of this file) + +fn C.rt_shader_desc(gfx.Backend) &C.sg_shader_desc + +const ( + win_width = 800 + win_height = 800 + bg_color = gx.white +) + +struct App { +mut: + gg &gg.Context + texture C.sg_image + init_flag bool + frame_count int + + mouse_x int = -1 + mouse_y int = -1 + // glsl + cube_pip_glsl C.sg_pipeline + cube_bind C.sg_bindings + // time + ticks i64 +} + +/****************************************************************************** +* Texture functions +******************************************************************************/ +fn create_texture(w int, h int, buf &byte) C.sg_image { + sz := w * h * 4 + mut img_desc := C.sg_image_desc{ + width: w + height: h + num_mipmaps: 0 + min_filter: .linear + mag_filter: .linear + // usage: .dynamic + wrap_u: .clamp_to_edge + wrap_v: .clamp_to_edge + label: &byte(0) + d3d11_texture: 0 + } + // comment if .dynamic is enabled + img_desc.data.subimage[0][0] = C.sg_range{ + ptr: buf + size: size_t(sz) + } + + sg_img := C.sg_make_image(&img_desc) + return sg_img +} + +fn destroy_texture(sg_img C.sg_image) { + C.sg_destroy_image(sg_img) +} + +// Use only if usage: .dynamic is enabled +fn update_text_texture(sg_img C.sg_image, w int, h int, buf &byte) { + sz := w * h * 4 + mut tmp_sbc := C.sg_image_data{} + tmp_sbc.subimage[0][0] = C.sg_range{ + ptr: buf + size: size_t(sz) + } + C.sg_update_image(sg_img, &tmp_sbc) +} + +/****************************************************************************** +* Draw functions +****************************************************************************** +Cube vertex buffer with packed vertex formats for color and texture coords. + Note that a vertex format which must be portable across all + backends must only use the normalized integer formats + (BYTE4N, UBYTE4N, SHORT2N, SHORT4N), which can be converted + to floating point formats in the vertex shader inputs. + The reason is that D3D11 cannot convert from non-normalized + formats to floating point inputs (only to integer inputs), + and WebGL2 / GLES2 don't support integer vertex shader inputs. +*/ + +struct Vertex_t { + x f32 + y f32 + z f32 + color u32 + u f32 + v f32 + // u u16 // for compatibility with D3D11 + // v u16 // for compatibility with D3D11 +} + +fn init_cube_glsl(mut app App) { + // cube vertex buffer + // d := u16(32767) // for compatibility with D3D11, 32767 stand for 1 + d := f32(1.0) + c := u32(0xFFFFFF_FF) // color RGBA8 + vertices := [ + // Face 0 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{ 1.0, -1.0, -1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, -1.0, c, d, d}, + Vertex_t{-1.0, 1.0, -1.0, c, 0, d}, + // Face 1 + Vertex_t{-1.0, -1.0, 1.0, c, 0, 0}, + Vertex_t{ 1.0, -1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{-1.0, 1.0, 1.0, c, 0, d}, + // Face 2 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, 1.0, -1.0, c, d, 0}, + Vertex_t{-1.0, 1.0, 1.0, c, d, d}, + Vertex_t{-1.0, -1.0, 1.0, c, 0, d}, + // Face 3 + Vertex_t{ 1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{ 1.0, 1.0, -1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, -1.0, 1.0, c, 0, d}, + // Face 4 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, -1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, -1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, -1.0, -1.0, c, 0, d}, + // Face 5 + Vertex_t{-1.0, 1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, 1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, 1.0, -1.0, c, 0, d}, + ] + + mut vert_buffer_desc := C.sg_buffer_desc{label: c'cube-vertices'} + unsafe { C.memset(&vert_buffer_desc, 0, sizeof(vert_buffer_desc)) } + + vert_buffer_desc.size = size_t(vertices.len * int(sizeof(Vertex_t))) + vert_buffer_desc.data = C.sg_range{ + ptr: vertices.data + size: size_t(vertices.len * int(sizeof(Vertex_t))) + } + + vert_buffer_desc.@type = .vertexbuffer + vbuf := gfx.make_buffer(&vert_buffer_desc) + + // create an index buffer for the cube + indices := [ + u16(0), 1, 2, 0, 2, 3, + 6, 5, 4, 7, 6, 4, + 8, 9, 10, 8, 10, 11, + 14, 13, 12, 15, 14, 12, + 16, 17, 18, 16, 18, 19, + 22, 21, 20, 23, 22, 20, + ] + + mut index_buffer_desc := C.sg_buffer_desc{label: c'cube-indices'} + unsafe {C.memset(&index_buffer_desc, 0, sizeof(index_buffer_desc))} + + index_buffer_desc.size = size_t(indices.len * int(sizeof(u16))) + index_buffer_desc.data = C.sg_range{ + ptr: indices.data + size: size_t(indices.len * int(sizeof(u16))) + } + + index_buffer_desc.@type = .indexbuffer + ibuf := gfx.make_buffer(&index_buffer_desc) + + // create shader + shader := gfx.make_shader(C.rt_shader_desc(C.sg_query_backend())) + + mut pipdesc := C.sg_pipeline_desc{} + unsafe { C.memset(&pipdesc, 0, sizeof(pipdesc)) } + pipdesc.layout.buffers[0].stride = int(sizeof(Vertex_t)) + + // the constants [C.ATTR_vs_pos, C.ATTR_vs_color0, C.ATTR_vs_texcoord0] are generated by sokol-shdc + pipdesc.layout.attrs[C.ATTR_vs_pos ].format = .float3 // x,y,z as f32 + pipdesc.layout.attrs[C.ATTR_vs_color0 ].format = .ubyte4n // color as u32 + pipdesc.layout.attrs[C.ATTR_vs_texcoord0].format = .float2 // u,v as f32 + // pipdesc.layout.attrs[C.ATTR_vs_texcoord0].format = .short2n // u,v as u16 + + pipdesc.shader = shader + pipdesc.index_type = .uint16 + + pipdesc.depth = C.sg_depth_state{ + write_enabled: true + compare: gfx.CompareFunc(C.SG_COMPAREFUNC_LESS_EQUAL) + } + pipdesc.cull_mode = .back + + pipdesc.label = 'glsl_shader pipeline'.str + + app.cube_bind.vertex_buffers[0] = vbuf + app.cube_bind.index_buffer = ibuf + app.cube_bind.fs_images[C.SLOT_tex] = app.texture + app.cube_pip_glsl = gfx.make_pipeline(&pipdesc) + println('GLSL init DONE!') +} + +[inline] +fn vec4(x f32, y f32, z f32, w f32) m4.Vec4 { + return m4.Vec4{e:[x, y, z, w]!} +} + +fn calc_tr_matrices(w f32, h f32, rx f32, ry f32, in_scale f32) m4.Mat4 { + proj := m4.perspective(60, w/h, 0.01, 10.0) + view := m4.look_at(vec4(f32(0.0) ,0 , 6, 0), vec4(f32(0), 0, 0, 0), vec4(f32(0), 1, 0, 0)) + view_proj := view * proj + + rxm := m4.rotate(m4.rad(rx), vec4(f32(1), 0, 0, 0)) + rym := m4.rotate(m4.rad(ry), vec4(f32(0), 1, 0, 0)) + + model := rym * rxm + scale_m := m4.scale(vec4(in_scale, in_scale, in_scale, 1)) + + res := (scale_m * model) * view_proj + return res +} + +fn draw_cube_glsl(app App) { + if app.init_flag == false { + return + } + + ws := gg.window_size_real_pixels() + ratio := f32(ws.width) / ws.height + dw := f32(ws.width / 2) + dh := f32(ws.height / 2) + + // use the following commented lines to rotate the 3d glsl cube + // rot := [f32(app.mouse_y), f32(app.mouse_x)] + // calc_tr_matrices(dw, dh, rot[0], rot[1] ,2.3) + tr_matrix := calc_tr_matrices(dw, dh, 0, 0, 2.3) + gfx.apply_viewport(0, 0, ws.width, ws.height, true) + + // apply the pipline and bindings + gfx.apply_pipeline(app.cube_pip_glsl) + gfx.apply_bindings(app.cube_bind) + + // Uniforms + // *** vertex shadeer uniforms *** + // passing the view matrix as uniform + // res is a 4x4 matrix of f32 thus: 4*16 byte of size + vs_uniforms_range := C.sg_range{ + ptr: &tr_matrix + size: size_t(4 * 16) + } + gfx.apply_uniforms(C.SG_SHADERSTAGE_VS, C.SLOT_vs_params, &vs_uniforms_range) + + // *** fragment shader uniforms *** + time_ticks := f32(time.ticks() - app.ticks) / 1000 + mut tmp_fs_params := [ + f32(ws.width), + ws.height * ratio, // x,y resolution to pass to FS + app.mouse_x, // mouse x + ws.height - app.mouse_y * 2, // mouse y scaled + time_ticks, // time as f32 + app.frame_count, // frame count + 0, + 0 // padding bytes , see "fs_params" struct paddings in rt_glsl.h + ]! + fs_uniforms_range := C.sg_range{ + ptr: unsafe { &tmp_fs_params } + size: size_t(sizeof(tmp_fs_params)) + } + gfx.apply_uniforms(C.SG_SHADERSTAGE_FS, C.SLOT_fs_params, &fs_uniforms_range) + + // 3 vertices for triangle * 2 triangles per face * 6 faces = 36 vertices to draw + gfx.draw(0, (3 * 2) * 6, 1) + gfx.end_pass() + gfx.commit() +} + +fn frame(mut app App) { + ws := gg.window_size_real_pixels() + + // clear + mut color_action := C.sg_color_attachment_action{ + action: gfx.Action(C.SG_ACTION_CLEAR) + value: C.sg_color{ + r: 0.0 + g: 0.0 + b: 0.0 + a: 1.0 + } + } + + mut pass_action := C.sg_pass_action{} + pass_action.colors[0] = color_action + gfx.begin_default_pass(&pass_action, ws.width, ws.height) + + // glsl cube + draw_cube_glsl(app) + app.frame_count++ +} + +/****************************************************************************** +* Init / Cleanup +******************************************************************************/ +fn my_init(mut app App) { + // set max vertices, + // for a large number of the same type of object it is better use the instances!! + desc := sapp.create_desc() + gfx.setup(&desc) + sgl_desc := C.sgl_desc_t{ + max_vertices: 50 * 65536 + } + sgl.setup(&sgl_desc) + + // create chessboard texture 256*256 RGBA + w := 256 + h := 256 + sz := w * h * 4 + tmp_txt := unsafe { malloc(sz) } + mut i := 0 + for i < sz { + unsafe { + y := (i >> 0x8) >> 5 // 8 cell + x := (i & 0xFF) >> 5 // 8 cell + // upper left corner + if x == 0 && y == 0 { + tmp_txt[i + 0] = byte(0xFF) + tmp_txt[i + 1] = byte(0) + tmp_txt[i + 2] = byte(0) + tmp_txt[i + 3] = byte(0xFF) + } + // low right corner + else if x == 7 && y == 7 { + tmp_txt[i + 0] = byte(0) + tmp_txt[i + 1] = byte(0xFF) + tmp_txt[i + 2] = byte(0) + tmp_txt[i + 3] = byte(0xFF) + } else { + col := if ((x + y) & 1) == 1 { 0xFF } else { 128 } + tmp_txt[i + 0] = byte(col) // red + tmp_txt[i + 1] = byte(col) // green + tmp_txt[i + 2] = byte(col) // blue + tmp_txt[i + 3] = byte(0xFF) // alpha + } + i += 4 + } + } + unsafe { + app.texture = create_texture(w, h, tmp_txt) + free(tmp_txt) + } + // glsl + init_cube_glsl(mut app) + app.init_flag = true +} + +fn cleanup(mut app App) { + gfx.shutdown() +} + +/****************************************************************************** +* events handling +******************************************************************************/ +fn my_event_manager(mut ev gg.Event, mut app App) { + if ev.typ == .mouse_move { + app.mouse_x = int(ev.mouse_x) + app.mouse_y = int(ev.mouse_y) + } + if ev.typ == .touches_began || ev.typ == .touches_moved { + if ev.num_touches > 0 { + touch_point := ev.touches[0] + app.mouse_x = int(touch_point.pos_x) + app.mouse_y = int(touch_point.pos_y) + } + } +} + +/****************************************************************************** +* Main +******************************************************************************/ +[console] // is needed for easier diagnostics on windows +fn main() { + // App init + mut app := &App{ + gg: 0 + } + + app.gg = gg.new_context( + width: win_width + height: win_height + create_window: true + window_title: '3D Ray Marching Cube' + user_data: app + bg_color: bg_color + frame_fn: frame + init_fn: my_init + cleanup_fn: cleanup + event_fn: my_event_manager + ) + + app.ticks = time.ticks() + app.gg.run() +} diff --git a/v_windows/v/examples/sokol/03_march_tracing_glsl/v.mod b/v_windows/v/examples/sokol/03_march_tracing_glsl/v.mod new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/v_windows/v/examples/sokol/03_march_tracing_glsl/v.mod diff --git a/v_windows/v/examples/sokol/04_multi_shader_glsl/rt_glsl.v b/v_windows/v/examples/sokol/04_multi_shader_glsl/rt_glsl.v new file mode 100644 index 0000000..d19d199 --- /dev/null +++ b/v_windows/v/examples/sokol/04_multi_shader_glsl/rt_glsl.v @@ -0,0 +1,634 @@ +/********************************************************************** +* +* Sokol 3d cube multishader demo +* +* Copyright (c) 2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* HOW TO COMPILE SHADERS: +* - download the sokol shader convertor tool from https://github.com/floooh/sokol-tools-bin +* +* - compile the .glsl shared file with: +* linux : sokol-shdc --input rt_glsl_puppy.glsl --output rt_glsl_puppy.h --slang glsl330 + sokol-shdc --input rt_glsl_march.glsl --output rt_glsl_march.h --slang glsl330 +* windows: sokol-shdc.exe --input rt_glsl_puppy.glsl --output rt_glsl_puppy.h --slang glsl330 +* sokol-shdc.exe --input rt_glsl_march.glsl --output rt_glsl_march.h --slang glsl330 +* +* --slang parameter can be: +* - glsl330: desktop GL +* - glsl100: GLES2 / WebGL +* - glsl300es: GLES3 / WebGL2 +* - hlsl4: D3D11 +* - hlsl5: D3D11 +* - metal_macos: Metal on macOS +* - metal_ios: Metal on iOS device +* - metal_sim: Metal on iOS simulator +* - wgpu: WebGPU +* +* you can have multiple platforms at the same time passing parameters like this: --slang glsl330:hlsl5:metal_macos +* for further infos have a look at the sokol shader tool docs. +* +* TODO: +* - frame counter +**********************************************************************/ +import gg +import gg.m4 +import gx +// import math +import sokol.sapp +import sokol.gfx +import sokol.sgl +import time + +// GLSL Include and functions +#flag -I @VMODROOT/. +#include "rt_glsl_march.h" #Please use sokol-shdc to generate the necessary rt_glsl_march.h file from rt_glsl_march.glsl (see the instructions at the top of this file) +#include "rt_glsl_puppy.h" #Please use sokol-shdc to generate the necessary rt_glsl_puppy.h file from rt_glsl_puppy.glsl (see the instructions at the top of this file) +fn C.rt_march_shader_desc(gfx.Backend) &C.sg_shader_desc +fn C.rt_puppy_shader_desc(gfx.Backend) &C.sg_shader_desc + +const ( + win_width = 800 + win_height = 800 + bg_color = gx.white +) + +struct App { +mut: + gg &gg.Context + texture C.sg_image + init_flag bool + frame_count int + mouse_x int = -1 + mouse_y int = -1 + mouse_down bool + // glsl + cube_pip_glsl C.sg_pipeline + cube_bind C.sg_bindings + pipe map[string]C.sg_pipeline + bind map[string]C.sg_bindings + // time + ticks i64 +} + +/****************************************************************************** +* Texture functions +******************************************************************************/ +fn create_texture(w int, h int, buf byteptr) C.sg_image { + sz := w * h * 4 + mut img_desc := C.sg_image_desc{ + width: w + height: h + num_mipmaps: 0 + min_filter: .linear + mag_filter: .linear + // usage: .dynamic + wrap_u: .clamp_to_edge + wrap_v: .clamp_to_edge + label: &byte(0) + d3d11_texture: 0 + } + // comment if .dynamic is enabled + img_desc.data.subimage[0][0] = C.sg_range{ + ptr: buf + size: size_t(sz) + } + + sg_img := C.sg_make_image(&img_desc) + return sg_img +} + +fn destroy_texture(sg_img C.sg_image) { + C.sg_destroy_image(sg_img) +} + +// Use only if usage: .dynamic is enabled +fn update_text_texture(sg_img C.sg_image, w int, h int, buf byteptr) { + sz := w * h * 4 + mut tmp_sbc := C.sg_image_data{} + tmp_sbc.subimage[0][0] = C.sg_range{ + ptr: buf + size: size_t(sz) + } + C.sg_update_image(sg_img, &tmp_sbc) +} + +/****************************************************************************** +* Draw functions +****************************************************************************** +Cube vertex buffer with packed vertex formats for color and texture coords. + Note that a vertex format which must be portable across all + backends must only use the normalized integer formats + (BYTE4N, UBYTE4N, SHORT2N, SHORT4N), which can be converted + to floating point formats in the vertex shader inputs. + The reason is that D3D11 cannot convert from non-normalized + formats to floating point inputs (only to integer inputs), + and WebGL2 / GLES2 don't support integer vertex shader inputs. +*/ +struct Vertex_t { + x f32 + y f32 + z f32 + color u32 + // u u16 // for compatibility with D3D11 + // v u16 // for compatibility with D3D11 + u f32 + v f32 +} + +// march shader init +fn init_cube_glsl_m(mut app App) { + // cube vertex buffer + // d := u16(32767) // for compatibility with D3D11, 32767 stand for 1 + d := f32(1.0) + c := u32(0xFFFFFF_FF) // color RGBA8 + vertices := [ + // Face 0 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{ 1.0, -1.0, -1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, -1.0, c, d, d}, + Vertex_t{-1.0, 1.0, -1.0, c, 0, d}, + // Face 1 + Vertex_t{-1.0, -1.0, 1.0, c, 0, 0}, + Vertex_t{ 1.0, -1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{-1.0, 1.0, 1.0, c, 0, d}, + // Face 2 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, 1.0, -1.0, c, d, 0}, + Vertex_t{-1.0, 1.0, 1.0, c, d, d}, + Vertex_t{-1.0, -1.0, 1.0, c, 0, d}, + // Face 3 + Vertex_t{ 1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{ 1.0, 1.0, -1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, -1.0, 1.0, c, 0, d}, + // Face 4 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, -1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, -1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, -1.0, -1.0, c, 0, d}, + // Face 5 + Vertex_t{-1.0, 1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, 1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, 1.0, -1.0, c, 0, d}, + ] + + mut vert_buffer_desc := C.sg_buffer_desc{label: c'cube-vertices'} + unsafe { C.memset(&vert_buffer_desc, 0, sizeof(vert_buffer_desc)) } + vert_buffer_desc.size = size_t(vertices.len * int(sizeof(Vertex_t))) + vert_buffer_desc.data = C.sg_range{ + ptr: vertices.data + size: size_t(vertices.len * int(sizeof(Vertex_t))) + } + vert_buffer_desc.@type = .vertexbuffer + vbuf := gfx.make_buffer(&vert_buffer_desc) + + /* create an index buffer for the cube */ + indices := [ + u16(0), 1, 2, 0, 2, 3, + 6, 5, 4, 7, 6, 4, + 8, 9, 10, 8, 10, 11, +/* + u16(14), 13, 12, 15, 14, 12, + 16, 17, 18, 16, 18, 19, + 22, 21, 20, 23, 22, 20 +*/ + ] + + mut index_buffer_desc := C.sg_buffer_desc{label: c'cube-indices'} + unsafe { C.memset(&index_buffer_desc, 0, sizeof(index_buffer_desc)) } + index_buffer_desc.size = size_t(indices.len * int(sizeof(u16))) + index_buffer_desc.data = C.sg_range{ + ptr: indices.data + size: size_t(indices.len * int(sizeof(u16))) + } + index_buffer_desc.@type = .indexbuffer + ibuf := gfx.make_buffer(&index_buffer_desc) + + // create shader + shader := gfx.make_shader(C.rt_march_shader_desc(C.sg_query_backend())) + + mut pipdesc := C.sg_pipeline_desc{} + unsafe { C.memset(&pipdesc, 0, sizeof(pipdesc)) } + pipdesc.layout.buffers[0].stride = int(sizeof(Vertex_t)) + + // the constants [C.ATTR_vs_m_pos, C.ATTR_vs_m_color0, C.ATTR_vs_m_texcoord0] are generated by sokol-shdc + pipdesc.layout.attrs[C.ATTR_vs_m_pos ].format = .float3 // x,y,z as f32 + pipdesc.layout.attrs[C.ATTR_vs_m_color0 ].format = .ubyte4n // color as u32 + pipdesc.layout.attrs[C.ATTR_vs_m_texcoord0].format = .float2 // u,v as f32 + // pipdesc.layout.attrs[C.ATTR_vs_m_texcoord0].format = .short2n // u,v as u16 + + pipdesc.shader = shader + pipdesc.index_type = .uint16 + + pipdesc.depth = C.sg_depth_state{ + write_enabled: true + compare: gfx.CompareFunc(C.SG_COMPAREFUNC_LESS_EQUAL) + } + pipdesc.cull_mode = .back + pipdesc.label = 'glsl_shader pipeline'.str + + mut bind := C.sg_bindings{} + unsafe { C.memset(&bind, 0, sizeof(bind)) } + bind.vertex_buffers[0] = vbuf + bind.index_buffer = ibuf + bind.fs_images[C.SLOT_tex] = app.texture + app.bind['march'] = bind + + app.pipe['march'] = gfx.make_pipeline(&pipdesc) + + println('GLSL March init DONE!') +} + +// putty shader init +fn init_cube_glsl_p(mut app App) { + // cube vertex buffer + // d := u16(32767) // for compatibility with D3D11, 32767 stand for 1 + d := f32(1.0) + c := u32(0xFFFFFF_FF) // color RGBA8 + vertices := [ + // Face 0 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{ 1.0, -1.0, -1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, -1.0, c, d, d}, + Vertex_t{-1.0, 1.0, -1.0, c, 0, d}, + // Face 1 + Vertex_t{-1.0, -1.0, 1.0, c, 0, 0}, + Vertex_t{ 1.0, -1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{-1.0, 1.0, 1.0, c, 0, d}, + // Face 2 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, 1.0, -1.0, c, d, 0}, + Vertex_t{-1.0, 1.0, 1.0, c, d, d}, + Vertex_t{-1.0, -1.0, 1.0, c, 0, d}, + // Face 3 + Vertex_t{ 1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{ 1.0, 1.0, -1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, -1.0, 1.0, c, 0, d}, + // Face 4 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, -1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, -1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, -1.0, -1.0, c, 0, d}, + // Face 5 + Vertex_t{-1.0, 1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, 1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, 1.0, -1.0, c, 0, d}, + ] + + mut vert_buffer_desc := C.sg_buffer_desc{label: c'cube-vertices'} + unsafe { C.memset(&vert_buffer_desc, 0, sizeof(vert_buffer_desc)) } + vert_buffer_desc.size = size_t(vertices.len * int(sizeof(Vertex_t))) + vert_buffer_desc.data = C.sg_range{ + ptr: vertices.data + size: size_t(vertices.len * int(sizeof(Vertex_t))) + } + vert_buffer_desc.@type = .vertexbuffer + vbuf := gfx.make_buffer(&vert_buffer_desc) + + /* create an index buffer for the cube */ + indices := [ +/* + u16(0), 1, 2, 0, 2, 3, + 6, 5, 4, 7, 6, 4, + 8, 9, 10, 8, 10, 11, +*/ + u16(14), 13, 12, 15, 14, 12, + 16, 17, 18, 16, 18, 19, + 22, 21, 20, 23, 22, 20 + + ] + + mut index_buffer_desc := C.sg_buffer_desc{label: c'cube-indices'} + unsafe { C.memset(&index_buffer_desc, 0, sizeof(index_buffer_desc)) } + index_buffer_desc.size = size_t(indices.len * int(sizeof(u16))) + index_buffer_desc.data = C.sg_range{ + ptr: indices.data + size: size_t(indices.len * int(sizeof(u16))) + } + index_buffer_desc.@type = .indexbuffer + ibuf := gfx.make_buffer(&index_buffer_desc) + + // create shader + shader := gfx.make_shader(C.rt_puppy_shader_desc(C.sg_query_backend())) + + mut pipdesc := C.sg_pipeline_desc{} + unsafe { C.memset(&pipdesc, 0, sizeof(pipdesc)) } + pipdesc.layout.buffers[0].stride = int(sizeof(Vertex_t)) + + // the constants [C.ATTR_vs_p_pos, C.ATTR_vs_p_color0, C.ATTR_vs_p_texcoord0] are generated by sokol-shdc + pipdesc.layout.attrs[C.ATTR_vs_p_pos ].format = .float3 // x,y,z as f32 + pipdesc.layout.attrs[C.ATTR_vs_p_color0 ].format = .ubyte4n // color as u32 + pipdesc.layout.attrs[C.ATTR_vs_p_texcoord0].format = .float2 // u,v as f32 + // pipdesc.layout.attrs[C.ATTR_vs_p_texcoord0].format = .short2n // u,v as u16 + + pipdesc.shader = shader + pipdesc.index_type = .uint16 + + pipdesc.depth = C.sg_depth_state{ + write_enabled: true + compare: gfx.CompareFunc(C.SG_COMPAREFUNC_LESS_EQUAL) + } + pipdesc.cull_mode = .back + + pipdesc.label = 'glsl_shader pipeline'.str + + mut bind := C.sg_bindings{} + unsafe { C.memset(&bind, 0, sizeof(bind)) } + bind.vertex_buffers[0] = vbuf + bind.index_buffer = ibuf + bind.fs_images[C.SLOT_tex] = app.texture + app.bind['puppy'] = bind + + app.pipe['puppy'] = gfx.make_pipeline(&pipdesc) + + println('GLSL Puppy init DONE!') +} + +[inline] +fn vec4(x f32, y f32, z f32, w f32) m4.Vec4 { + return m4.Vec4{e:[x, y, z, w]!} +} + +fn calc_tr_matrices(w f32, h f32, rx f32, ry f32, in_scale f32) m4.Mat4 { + proj := m4.perspective(60, w/h, 0.01, 10.0) + view := m4.look_at(vec4(f32(0.0) ,0 , 6, 0), vec4(f32(0), 0, 0, 0), vec4(f32(0), 1, 0, 0)) + view_proj := view * proj + + rxm := m4.rotate(m4.rad(rx), vec4(f32(1), 0, 0, 0)) + rym := m4.rotate(m4.rad(ry), vec4(f32(0), 1, 0, 0)) + + model := rym * rxm + scale_m := m4.scale(vec4(in_scale, in_scale, in_scale, 1)) + + res := (scale_m * model) * view_proj + return res +} + +// march triangles draw +fn draw_cube_glsl_m(app App) { + if app.init_flag == false { + return + } + + ws := gg.window_size_real_pixels() + ratio := f32(ws.width) / ws.height + dw := f32(ws.width / 2) + dh := f32(ws.height / 2) + + rot := [f32(app.mouse_y), f32(app.mouse_x)] + tr_matrix := calc_tr_matrices(dw, dh, rot[0], rot[1], 2.3) + + gfx.apply_pipeline(app.pipe['march']) + gfx.apply_bindings(app.bind['march']) + + // Uniforms + // *** vertex shadeer uniforms *** + // passing the view matrix as uniform + // res is a 4x4 matrix of f32 thus: 4*16 byte of size + vs_uniforms_range := C.sg_range{ + ptr: &tr_matrix + size: size_t(4 * 16) + } + gfx.apply_uniforms(C.SG_SHADERSTAGE_VS, C.SLOT_vs_params_m, &vs_uniforms_range) + + // *** fragment shader uniforms *** + time_ticks := f32(time.ticks() - app.ticks) / 1000 + mut tmp_fs_params := [ + f32(ws.width), + ws.height * ratio, // x,y resolution to pass to FS + 0, + 0, // dont send mouse position + /* app.mouse_x, // mouse x */ + /* ws.height - app.mouse_y*2, // mouse y scaled */ + time_ticks, // time as f32 + app.frame_count, // frame count + 0, + 0 // padding bytes , see "fs_params" struct paddings in rt_glsl.h + ]! + fs_uniforms_range := C.sg_range{ + ptr: unsafe { &tmp_fs_params } + size: size_t(sizeof(tmp_fs_params)) + } + gfx.apply_uniforms(C.SG_SHADERSTAGE_FS, C.SLOT_fs_params_p, &fs_uniforms_range) + + // 3 vertices for triangle * 2 triangles per face * 6 faces = 36 vertices to draw + gfx.draw(0, (3 * 2) * 3, 1) +} + +// puppy triangles draw +fn draw_cube_glsl_p(app App) { + if app.init_flag == false { + return + } + + ws := gg.window_size_real_pixels() + ratio := f32(ws.width) / ws.height + dw := f32(ws.width / 2) + dh := f32(ws.height / 2) + + rot := [f32(app.mouse_y), f32(app.mouse_x)] + tr_matrix := calc_tr_matrices(dw, dh, rot[0], rot[1], 2.3) + + // apply the pipline and bindings + gfx.apply_pipeline(app.pipe['puppy']) + gfx.apply_bindings(app.bind['puppy']) + + // Uniforms + // *** vertex shadeer uniforms *** + // passing the view matrix as uniform + // res is a 4x4 matrix of f32 thus: 4*16 byte of size + vs_uniforms_range := C.sg_range{ + ptr: &tr_matrix + size: size_t(4 * 16) + } + gfx.apply_uniforms(C.SG_SHADERSTAGE_VS, C.SLOT_vs_params_p, &vs_uniforms_range) + + // *** fragment shader uniforms *** + time_ticks := f32(time.ticks() - app.ticks) / 1000 + mut tmp_fs_params := [ + f32(ws.width), + ws.height * ratio, // x,y resolution to pass to FS + 0, + 0, // dont send mouse position + /* app.mouse_x, // mouse x */ + /* ws.height - app.mouse_y*2, // mouse y scaled */ + time_ticks, // time as f32 + app.frame_count, // frame count + 0, + 0 // padding bytes , see "fs_params" struct paddings in rt_glsl.h + ]! + fs_uniforms_range := C.sg_range{ + ptr: unsafe { &tmp_fs_params } + size: size_t(sizeof(tmp_fs_params)) + } + gfx.apply_uniforms(C.SG_SHADERSTAGE_FS, C.SLOT_fs_params_p, &fs_uniforms_range) + + // 3 vertices for triangle * 2 triangles per face * 6 faces = 36 vertices to draw + gfx.draw(0, (3 * 2) * 3, 1) +} + +fn draw_start_glsl(app App) { + if app.init_flag == false { + return + } + + ws := gg.window_size_real_pixels() + // ratio := f32(ws.width) / ws.height + // dw := f32(ws.width / 2) + // dh := f32(ws.height / 2) + + gfx.apply_viewport(0, 0, ws.width, ws.height, true) +} + +fn draw_end_glsl(app App) { + gfx.end_pass() + gfx.commit() +} + +fn frame(mut app App) { + ws := gg.window_size_real_pixels() + + // clear + mut color_action := C.sg_color_attachment_action{ + action: gfx.Action(C.SG_ACTION_CLEAR) + value: C.sg_color{ + r: 0.0 + g: 0.0 + b: 0.0 + a: 1.0 + } + } + mut pass_action := C.sg_pass_action{} + pass_action.colors[0] = color_action + gfx.begin_default_pass(&pass_action, ws.width, ws.height) + + /* + // glsl cube + if app.frame_count % 1 == 1{ + draw_cube_glsl_m(app) + } else { + draw_cube_glsl_p(app) + } + */ + draw_start_glsl(app) + draw_cube_glsl_m(app) + draw_cube_glsl_p(app) + draw_end_glsl(app) + app.frame_count++ +} + +/****************************************************************************** +* Init / Cleanup +******************************************************************************/ +fn my_init(mut app App) { + // set max vertices, + // for a large number of the same type of object it is better use the instances!! + desc := sapp.create_desc() + gfx.setup(&desc) + sgl_desc := C.sgl_desc_t{ + max_vertices: 50 * 65536 + } + sgl.setup(&sgl_desc) + + // create chessboard texture 256*256 RGBA + w := 256 + h := 256 + sz := w * h * 4 + tmp_txt := unsafe { malloc(sz) } + mut i := 0 + for i < sz { + unsafe { + y := (i >> 0x8) >> 5 // 8 cell + x := (i & 0xFF) >> 5 // 8 cell + // upper left corner + if x == 0 && y == 0 { + tmp_txt[i + 0] = byte(0xFF) + tmp_txt[i + 1] = byte(0) + tmp_txt[i + 2] = byte(0) + tmp_txt[i + 3] = byte(0xFF) + } + // low right corner + else if x == 7 && y == 7 { + tmp_txt[i + 0] = byte(0) + tmp_txt[i + 1] = byte(0xFF) + tmp_txt[i + 2] = byte(0) + tmp_txt[i + 3] = byte(0xFF) + } else { + col := if ((x + y) & 1) == 1 { 0xFF } else { 128 } + tmp_txt[i + 0] = byte(col) // red + tmp_txt[i + 1] = byte(col) // green + tmp_txt[i + 2] = byte(col) // blue + tmp_txt[i + 3] = byte(0xFF) // alpha + } + i += 4 + } + } + app.texture = create_texture(w, h, tmp_txt) + unsafe { free(tmp_txt) } + + // glsl + init_cube_glsl_m(mut app) + init_cube_glsl_p(mut app) + app.init_flag = true +} + +fn cleanup(mut app App) { + gfx.shutdown() +} + +/****************************************************************************** +* events handling +******************************************************************************/ +fn my_event_manager(mut ev gg.Event, mut app App) { + if ev.typ == .mouse_down { + app.mouse_down = true + } + if ev.typ == .mouse_up { + app.mouse_down = false + } + if app.mouse_down == true && ev.typ == .mouse_move { + app.mouse_x = int(ev.mouse_x) + app.mouse_y = int(ev.mouse_y) + } + if ev.typ == .touches_began || ev.typ == .touches_moved { + if ev.num_touches > 0 { + touch_point := ev.touches[0] + app.mouse_x = int(touch_point.pos_x) + app.mouse_y = int(touch_point.pos_y) + } + } +} + +/****************************************************************************** +* Main +******************************************************************************/ +[console] // is needed for easier diagnostics on windows +fn main() { + // App init + mut app := &App{ + gg: 0 + } + + app.gg = gg.new_context( + width: win_width + height: win_height + create_window: true + window_title: '3D Dual shader Cube - click and rotate with the mouse' + user_data: app + bg_color: bg_color + frame_fn: frame + init_fn: my_init + cleanup_fn: cleanup + event_fn: my_event_manager + ) + + app.ticks = time.ticks() + app.gg.run() +} diff --git a/v_windows/v/examples/sokol/04_multi_shader_glsl/rt_glsl_march.glsl b/v_windows/v/examples/sokol/04_multi_shader_glsl/rt_glsl_march.glsl new file mode 100644 index 0000000..ed76e25 --- /dev/null +++ b/v_windows/v/examples/sokol/04_multi_shader_glsl/rt_glsl_march.glsl @@ -0,0 +1,695 @@ +//------------------------------------------------------------------------------ +// Shader code for texcube-sapp sample. +// +// NOTE: This source file also uses the '#pragma sokol' form of the +// custom tags. +//------------------------------------------------------------------------------ +//#pragma sokol @ctype mat4 hmm_mat4 + +#pragma sokol @vs vs_m +uniform vs_params_m { + mat4 mvp; +}; + +in vec4 pos; +in vec4 color0; +in vec2 texcoord0; + +out vec4 color; +out vec2 uv; + +void main() { + gl_Position = mvp * pos; + color = color0; + uv = texcoord0; +} +#pragma sokol @end + +#pragma sokol @fs fs_m +uniform sampler2D tex; +uniform fs_params_m { + vec2 iResolution; + vec2 iMouse; + float iTime; + float iFrame; +}; + +in vec4 color; +in vec2 uv; +out vec4 frag_color; + +// change to 0 to 4 to increment the AntiAliasing, +// increase AA will SLOW the rendering!! +#define AA 1 + +//********************************************************* +// Ray Marching +// original code from: https://www.shadertoy.com/view/Xds3zN +//********************************************************* +// The MIT License +// Copyright © 2013 Inigo Quilez +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// A list of useful distance function to simple primitives. All +// these functions (except for ellipsoid) return an exact +// euclidean distance, meaning they produce a better SDF than +// what you'd get if you were constructing them from boolean +// operations. +// +// More info here: +// +// https://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm + +//------------------------------------------------------------------ +float dot2( in vec2 v ) { return dot(v,v); } +float dot2( in vec3 v ) { return dot(v,v); } +float ndot( in vec2 a, in vec2 b ) { return a.x*b.x - a.y*b.y; } + +float sdPlane( vec3 p ) +{ + return p.y; +} + +float sdSphere( vec3 p, float s ) +{ + return length(p)-s; +} + +float sdBox( vec3 p, vec3 b ) +{ + vec3 d = abs(p) - b; + return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0)); +} + +float sdBoundingBox( vec3 p, vec3 b, float e ) +{ + p = abs(p )-b; + vec3 q = abs(p+e)-e; + + return min(min( + length(max(vec3(p.x,q.y,q.z),0.0))+min(max(p.x,max(q.y,q.z)),0.0), + length(max(vec3(q.x,p.y,q.z),0.0))+min(max(q.x,max(p.y,q.z)),0.0)), + length(max(vec3(q.x,q.y,p.z),0.0))+min(max(q.x,max(q.y,p.z)),0.0)); +} +float sdEllipsoid( in vec3 p, in vec3 r ) // approximated +{ + float k0 = length(p/r); + float k1 = length(p/(r*r)); + return k0*(k0-1.0)/k1; +} + +float sdTorus( vec3 p, vec2 t ) +{ + return length( vec2(length(p.xz)-t.x,p.y) )-t.y; +} + +float sdCappedTorus(in vec3 p, in vec2 sc, in float ra, in float rb) +{ + p.x = abs(p.x); + float k = (sc.y*p.x>sc.x*p.y) ? dot(p.xy,sc) : length(p.xy); + return sqrt( dot(p,p) + ra*ra - 2.0*ra*k ) - rb; +} + +float sdHexPrism( vec3 p, vec2 h ) +{ + vec3 q = abs(p); + + const vec3 k = vec3(-0.8660254, 0.5, 0.57735); + p = abs(p); + p.xy -= 2.0*min(dot(k.xy, p.xy), 0.0)*k.xy; + vec2 d = vec2( + length(p.xy - vec2(clamp(p.x, -k.z*h.x, k.z*h.x), h.x))*sign(p.y - h.x), + p.z-h.y ); + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +float sdOctogonPrism( in vec3 p, in float r, float h ) +{ + const vec3 k = vec3(-0.9238795325, // sqrt(2+sqrt(2))/2 + 0.3826834323, // sqrt(2-sqrt(2))/2 + 0.4142135623 ); // sqrt(2)-1 + // reflections + p = abs(p); + p.xy -= 2.0*min(dot(vec2( k.x,k.y),p.xy),0.0)*vec2( k.x,k.y); + p.xy -= 2.0*min(dot(vec2(-k.x,k.y),p.xy),0.0)*vec2(-k.x,k.y); + // polygon side + p.xy -= vec2(clamp(p.x, -k.z*r, k.z*r), r); + vec2 d = vec2( length(p.xy)*sign(p.y), p.z-h ); + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +float sdCapsule( vec3 p, vec3 a, vec3 b, float r ) +{ + vec3 pa = p-a, ba = b-a; + float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); + return length( pa - ba*h ) - r; +} + +float sdRoundCone( in vec3 p, in float r1, float r2, float h ) +{ + vec2 q = vec2( length(p.xz), p.y ); + + float b = (r1-r2)/h; + float a = sqrt(1.0-b*b); + float k = dot(q,vec2(-b,a)); + + if( k < 0.0 ) return length(q) - r1; + if( k > a*h ) return length(q-vec2(0.0,h)) - r2; + + return dot(q, vec2(a,b) ) - r1; +} + +float sdRoundCone(vec3 p, vec3 a, vec3 b, float r1, float r2) +{ + // sampling independent computations (only depend on shape) + vec3 ba = b - a; + float l2 = dot(ba,ba); + float rr = r1 - r2; + float a2 = l2 - rr*rr; + float il2 = 1.0/l2; + + // sampling dependant computations + vec3 pa = p - a; + float y = dot(pa,ba); + float z = y - l2; + float x2 = dot2( pa*l2 - ba*y ); + float y2 = y*y*l2; + float z2 = z*z*l2; + + // single square root! + float k = sign(rr)*rr*rr*x2; + if( sign(z)*a2*z2 > k ) return sqrt(x2 + z2) *il2 - r2; + if( sign(y)*a2*y2 < k ) return sqrt(x2 + y2) *il2 - r1; + return (sqrt(x2*a2*il2)+y*rr)*il2 - r1; +} + +float sdTriPrism( vec3 p, vec2 h ) +{ + const float k = sqrt(3.0); + h.x *= 0.5*k; + p.xy /= h.x; + p.x = abs(p.x) - 1.0; + p.y = p.y + 1.0/k; + if( p.x+k*p.y>0.0 ) p.xy=vec2(p.x-k*p.y,-k*p.x-p.y)/2.0; + p.x -= clamp( p.x, -2.0, 0.0 ); + float d1 = length(p.xy)*sign(-p.y)*h.x; + float d2 = abs(p.z)-h.y; + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +} + +// vertical +float sdCylinder( vec3 p, vec2 h ) +{ + vec2 d = abs(vec2(length(p.xz),p.y)) - h; + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +// arbitrary orientation +float sdCylinder(vec3 p, vec3 a, vec3 b, float r) +{ + vec3 pa = p - a; + vec3 ba = b - a; + float baba = dot(ba,ba); + float paba = dot(pa,ba); + + float x = length(pa*baba-ba*paba) - r*baba; + float y = abs(paba-baba*0.5)-baba*0.5; + float x2 = x*x; + float y2 = y*y*baba; + float d = (max(x,y)<0.0)?-min(x2,y2):(((x>0.0)?x2:0.0)+((y>0.0)?y2:0.0)); + return sign(d)*sqrt(abs(d))/baba; +} + +// vertical +float sdCone( in vec3 p, in vec2 c, float h ) +{ + vec2 q = h*vec2(c.x,-c.y)/c.y; + vec2 w = vec2( length(p.xz), p.y ); + + vec2 a = w - q*clamp( dot(w,q)/dot(q,q), 0.0, 1.0 ); + vec2 b = w - q*vec2( clamp( w.x/q.x, 0.0, 1.0 ), 1.0 ); + float k = sign( q.y ); + float d = min(dot( a, a ),dot(b, b)); + float s = max( k*(w.x*q.y-w.y*q.x),k*(w.y-q.y) ); + return sqrt(d)*sign(s); +} + +float sdCappedCone( in vec3 p, in float h, in float r1, in float r2 ) +{ + vec2 q = vec2( length(p.xz), p.y ); + + vec2 k1 = vec2(r2,h); + vec2 k2 = vec2(r2-r1,2.0*h); + vec2 ca = vec2(q.x-min(q.x,(q.y < 0.0)?r1:r2), abs(q.y)-h); + vec2 cb = q - k1 + k2*clamp( dot(k1-q,k2)/dot2(k2), 0.0, 1.0 ); + float s = (cb.x < 0.0 && ca.y < 0.0) ? -1.0 : 1.0; + return s*sqrt( min(dot2(ca),dot2(cb)) ); +} + +float sdCappedCone(vec3 p, vec3 a, vec3 b, float ra, float rb) +{ + float rba = rb-ra; + float baba = dot(b-a,b-a); + float papa = dot(p-a,p-a); + float paba = dot(p-a,b-a)/baba; + + float x = sqrt( papa - paba*paba*baba ); + + float cax = max(0.0,x-((paba<0.5)?ra:rb)); + float cay = abs(paba-0.5)-0.5; + + float k = rba*rba + baba; + float f = clamp( (rba*(x-ra)+paba*baba)/k, 0.0, 1.0 ); + + float cbx = x-ra - f*rba; + float cby = paba - f; + + float s = (cbx < 0.0 && cay < 0.0) ? -1.0 : 1.0; + + return s*sqrt( min(cax*cax + cay*cay*baba, + cbx*cbx + cby*cby*baba) ); +} + +// c is the sin/cos of the desired cone angle +float sdSolidAngle(vec3 pos, vec2 c, float ra) +{ + vec2 p = vec2( length(pos.xz), pos.y ); + float l = length(p) - ra; + float m = length(p - c*clamp(dot(p,c),0.0,ra) ); + return max(l,m*sign(c.y*p.x-c.x*p.y)); +} + +float sdOctahedron(vec3 p, float s) +{ + p = abs(p); + float m = p.x + p.y + p.z - s; + +// exact distance +#if 0 + vec3 o = min(3.0*p - m, 0.0); + o = max(6.0*p - m*2.0 - o*3.0 + (o.x+o.y+o.z), 0.0); + return length(p - s*o/(o.x+o.y+o.z)); +#endif + +// exact distance +#if 1 + vec3 q; + if( 3.0*p.x < m ) q = p.xyz; + else if( 3.0*p.y < m ) q = p.yzx; + else if( 3.0*p.z < m ) q = p.zxy; + else return m*0.57735027; + float k = clamp(0.5*(q.z-q.y+s),0.0,s); + return length(vec3(q.x,q.y-s+k,q.z-k)); +#endif + +// bound, not exact +#if 0 + return m*0.57735027; +#endif +} + +float sdPyramid( in vec3 p, in float h ) +{ + float m2 = h*h + 0.25; + + // symmetry + p.xz = abs(p.xz); + p.xz = (p.z>p.x) ? p.zx : p.xz; + p.xz -= 0.5; + + // project into face plane (2D) + vec3 q = vec3( p.z, h*p.y - 0.5*p.x, h*p.x + 0.5*p.y); + + float s = max(-q.x,0.0); + float t = clamp( (q.y-0.5*p.z)/(m2+0.25), 0.0, 1.0 ); + + float a = m2*(q.x+s)*(q.x+s) + q.y*q.y; + float b = m2*(q.x+0.5*t)*(q.x+0.5*t) + (q.y-m2*t)*(q.y-m2*t); + + float d2 = min(q.y,-q.x*m2-q.y*0.5) > 0.0 ? 0.0 : min(a,b); + + // recover 3D and scale, and add sign + return sqrt( (d2+q.z*q.z)/m2 ) * sign(max(q.z,-p.y)); +} + +// la,lb=semi axis, h=height, ra=corner +float sdRhombus(vec3 p, float la, float lb, float h, float ra) +{ + p = abs(p); + vec2 b = vec2(la,lb); + float f = clamp( (ndot(b,b-2.0*p.xz))/dot(b,b), -1.0, 1.0 ); + vec2 q = vec2(length(p.xz-0.5*b*vec2(1.0-f,1.0+f))*sign(p.x*b.y+p.z*b.x-b.x*b.y)-ra, p.y-h); + return min(max(q.x,q.y),0.0) + length(max(q,0.0)); +} + +//------------------------------------------------------------------ + +vec2 opU( vec2 d1, vec2 d2 ) +{ + return (d1.x<d2.x) ? d1 : d2; +} + +//------------------------------------------------------------------ + +#define ZERO (min(int(iFrame),0)) + +//------------------------------------------------------------------ + +vec2 map( in vec3 pos ) +{ + vec2 res = vec2( 1e10, 0.0 ); + + { + res = opU( res, vec2( sdSphere( pos-vec3(-2.0,0.25, 0.0), 0.25 ), 26.9 ) ); + } + + // bounding box + if( sdBox( pos-vec3(0.0,0.3,-1.0),vec3(0.35,0.3,2.5) )<res.x ) + { + // more primitives + res = opU( res, vec2( sdBoundingBox( pos-vec3( 0.0,0.25, 0.0), vec3(0.3,0.25,0.2), 0.025 ), 16.9 ) ); + res = opU( res, vec2( sdTorus( (pos-vec3( 0.0,0.30, 1.0)).xzy, vec2(0.25,0.05) ), 25.0 ) ); + res = opU( res, vec2( sdCone( pos-vec3( 0.0,0.45,-1.0), vec2(0.6,0.8),0.45 ), 55.0 ) ); + res = opU( res, vec2( sdCappedCone( pos-vec3( 0.0,0.25,-2.0), 0.25, 0.25, 0.1 ), 13.67 ) ); + res = opU( res, vec2( sdSolidAngle( pos-vec3( 0.0,0.00,-3.0), vec2(3,4)/5.0, 0.4 ), 49.13 ) ); + } + + // bounding box + if( sdBox( pos-vec3(1.0,0.3,-1.0),vec3(0.35,0.3,2.5) )<res.x ) + { + // more primitives + res = opU( res, vec2( sdCappedTorus((pos-vec3( 1.0,0.30, 1.0))*vec3(1,-1,1), vec2(0.866025,-0.5), 0.25, 0.05), 8.5) ); + res = opU( res, vec2( sdBox( pos-vec3( 1.0,0.25, 0.0), vec3(0.3,0.25,0.1) ), 3.0 ) ); + res = opU( res, vec2( sdCapsule( pos-vec3( 1.0,0.00,-1.0),vec3(-0.1,0.1,-0.1), vec3(0.2,0.4,0.2), 0.1 ), 31.9 ) ); + res = opU( res, vec2( sdCylinder( pos-vec3( 1.0,0.25,-2.0), vec2(0.15,0.25) ), 8.0 ) ); + res = opU( res, vec2( sdHexPrism( pos-vec3( 1.0,0.2,-3.0), vec2(0.2,0.05) ), 18.4 ) ); + } + + // bounding box + if( sdBox( pos-vec3(-1.0,0.35,-1.0),vec3(0.35,0.35,2.5))<res.x ) + { + // more primitives + res = opU( res, vec2( sdPyramid( pos-vec3(-1.0,-0.6,-3.0), 1.0 ), 13.56 ) ); + res = opU( res, vec2( sdOctahedron( pos-vec3(-1.0,0.15,-2.0), 0.35 ), 23.56 ) ); + res = opU( res, vec2( sdTriPrism( pos-vec3(-1.0,0.15,-1.0), vec2(0.3,0.05) ),43.5 ) ); + res = opU( res, vec2( sdEllipsoid( pos-vec3(-1.0,0.25, 0.0), vec3(0.2, 0.25, 0.05) ), 43.17 ) ); + res = opU( res, vec2( sdRhombus( (pos-vec3(-1.0,0.34, 1.0)).xzy, 0.15, 0.25, 0.04, 0.08 ),17.0 ) ); + } + + // bounding box + if( sdBox( pos-vec3(2.0,0.3,-1.0),vec3(0.35,0.3,2.5) )<res.x ) + { + // more primitives + res = opU( res, vec2( sdOctogonPrism(pos-vec3( 2.0,0.2,-3.0), 0.2, 0.05), 51.8 ) ); + res = opU( res, vec2( sdCylinder( pos-vec3( 2.0,0.15,-2.0), vec3(0.1,-0.1,0.0), vec3(-0.2,0.35,0.1), 0.08), 31.2 ) ); + res = opU( res, vec2( sdCappedCone( pos-vec3( 2.0,0.10,-1.0), vec3(0.1,0.0,0.0), vec3(-0.2,0.40,0.1), 0.15, 0.05), 46.1 ) ); + res = opU( res, vec2( sdRoundCone( pos-vec3( 2.0,0.15, 0.0), vec3(0.1,0.0,0.0), vec3(-0.1,0.35,0.1), 0.15, 0.05), 51.7 ) ); + res = opU( res, vec2( sdRoundCone( pos-vec3( 2.0,0.20, 1.0), 0.2, 0.1, 0.3 ), 37.0 ) ); + } + + return res; +} + +// http://iquilezles.org/www/articles/boxfunctions/boxfunctions.htm +vec2 iBox( in vec3 ro, in vec3 rd, in vec3 rad ) +{ + vec3 m = 1.0/rd; + vec3 n = m*ro; + vec3 k = abs(m)*rad; + vec3 t1 = -n - k; + vec3 t2 = -n + k; + return vec2( max( max( t1.x, t1.y ), t1.z ), + min( min( t2.x, t2.y ), t2.z ) ); +} + +vec2 raycast( in vec3 ro, in vec3 rd ) +{ + vec2 res = vec2(-1.0,-1.0); + + float tmin = 1.0; + float tmax = 20.0; + + // raytrace floor plane + float tp1 = (0.0-ro.y)/rd.y; + if( tp1>0.0 ) + { + tmax = min( tmax, tp1 ); + res = vec2( tp1, 1.0 ); + } + //else return res; + + // raymarch primitives + vec2 tb = iBox( ro-vec3(0.0,0.4,-0.5), rd, vec3(2.5,0.41,3.0) ); + if( tb.x<tb.y && tb.y>0.0 && tb.x<tmax) + { + //return vec2(tb.x,2.0); + tmin = max(tb.x,tmin); + tmax = min(tb.y,tmax); + + float t = tmin; + for( int i=0; i<70 && t<tmax; i++ ) + { + vec2 h = map( ro+rd*t ); + if( abs(h.x)<(0.0001*t) ) + { + res = vec2(t,h.y); + break; + } + t += h.x; + } + } + + return res; +} + +// http://iquilezles.org/www/articles/rmshadows/rmshadows.htm +float calcSoftshadow( in vec3 ro, in vec3 rd, in float mint, in float tmax ) +{ + // bounding volume + float tp = (0.8-ro.y)/rd.y; if( tp>0.0 ) tmax = min( tmax, tp ); + + float res = 1.0; + float t = mint; + for( int i=ZERO; i<24; i++ ) + { + float h = map( ro + rd*t ).x; + float s = clamp(8.0*h/t,0.0,1.0); + res = min( res, s*s*(3.0-2.0*s) ); + t += clamp( h, 0.02, 0.2 ); + if( res<0.004 || t>tmax ) break; + } + return clamp( res, 0.0, 1.0 ); +} + +// http://iquilezles.org/www/articles/normalsSDF/normalsSDF.htm +vec3 calcNormal( in vec3 pos ) +{ +#if 0 + vec2 e = vec2(1.0,-1.0)*0.5773*0.0005; + return normalize( e.xyy*map( pos + e.xyy ).x + + e.yyx*map( pos + e.yyx ).x + + e.yxy*map( pos + e.yxy ).x + + e.xxx*map( pos + e.xxx ).x ); +#else + // inspired by tdhooper and klems - a way to prevent the compiler from inlining map() 4 times + vec3 n = vec3(0.0); + for( int i=ZERO; i<4; i++ ) + { + vec3 e = 0.5773*(2.0*vec3((((i+3)>>1)&1),((i>>1)&1),(i&1))-1.0); + n += e*map(pos+0.0005*e).x; + //if( n.x+n.y+n.z>100.0 ) break; + } + return normalize(n); +#endif +} + +float calcAO( in vec3 pos, in vec3 nor ) +{ + float occ = 0.0; + float sca = 1.0; + for( int i=ZERO; i<5; i++ ) + { + float h = 0.01 + 0.12*float(i)/4.0; + float d = map( pos + h*nor ).x; + occ += (h-d)*sca; + sca *= 0.95; + if( occ>0.35 ) break; + } + return clamp( 1.0 - 3.0*occ, 0.0, 1.0 ) * (0.5+0.5*nor.y); +} + +// http://iquilezles.org/www/articles/checkerfiltering/checkerfiltering.htm +float checkersGradBox( in vec2 p, in vec2 dpdx, in vec2 dpdy ) +{ + // filter kernel + vec2 w = abs(dpdx)+abs(dpdy) + 0.001; + // analytical integral (box filter) + vec2 i = 2.0*(abs(fract((p-0.5*w)*0.5)-0.5)-abs(fract((p+0.5*w)*0.5)-0.5))/w; + // xor pattern + return 0.5 - 0.5*i.x*i.y; +} + +vec3 render( in vec3 ro, in vec3 rd, in vec3 rdx, in vec3 rdy ) +{ + // background + vec3 col = vec3(0.7, 0.7, 0.9) - max(rd.y,0.0)*0.3; + + // raycast scene + vec2 res = raycast(ro,rd); + float t = res.x; + float m = res.y; + if( m>-0.5 ) + { + vec3 pos = ro + t*rd; + vec3 nor = (m<1.5) ? vec3(0.0,1.0,0.0) : calcNormal( pos ); + vec3 ref = reflect( rd, nor ); + + // material + col = 0.2 + 0.2*sin( m*2.0 + vec3(0.0,1.0,2.0) ); + float ks = 1.0; + + if( m<1.5 ) + { + // project pixel footprint into the plane + vec3 dpdx = ro.y*(rd/rd.y-rdx/rdx.y); + vec3 dpdy = ro.y*(rd/rd.y-rdy/rdy.y); + + float f = checkersGradBox( 3.0*pos.xz, 3.0*dpdx.xz, 3.0*dpdy.xz ); + col = 0.15 + f*vec3(0.05); + ks = 0.4; + } + + // lighting + float occ = calcAO( pos, nor ); + + vec3 lin = vec3(0.0); + + // sun + { + vec3 lig = normalize( vec3(-0.5, 0.4, -0.6) ); + vec3 hal = normalize( lig-rd ); + float dif = clamp( dot( nor, lig ), 0.0, 1.0 ); + //if( dif>0.0001 ) + dif *= calcSoftshadow( pos, lig, 0.02, 2.5 ); + float spe = pow( clamp( dot( nor, hal ), 0.0, 1.0 ),16.0); + spe *= dif; + spe *= 0.04+0.96*pow(clamp(1.0-dot(hal,lig),0.0,1.0),5.0); + lin += col*2.20*dif*vec3(1.30,1.00,0.70); + lin += 5.00*spe*vec3(1.30,1.00,0.70)*ks; + } + // sky + { + float dif = sqrt(clamp( 0.5+0.5*nor.y, 0.0, 1.0 )); + dif *= occ; + float spe = smoothstep( -0.2, 0.2, ref.y ); + spe *= dif; + spe *= 0.04+0.96*pow(clamp(1.0+dot(nor,rd),0.0,1.0), 5.0 ); + //if( spe>0.001 ) + spe *= calcSoftshadow( pos, ref, 0.02, 2.5 ); + lin += col*0.60*dif*vec3(0.40,0.60,1.15); + lin += 2.00*spe*vec3(0.40,0.60,1.30)*ks; + } + // back + { + float dif = clamp( dot( nor, normalize(vec3(0.5,0.0,0.6))), 0.0, 1.0 )*clamp( 1.0-pos.y,0.0,1.0); + dif *= occ; + lin += col*0.55*dif*vec3(0.25,0.25,0.25); + } + // sss + { + float dif = pow(clamp(1.0+dot(nor,rd),0.0,1.0),2.0); + dif *= occ; + lin += col*0.25*dif*vec3(1.00,1.00,1.00); + } + + col = lin; + + col = mix( col, vec3(0.7,0.7,0.9), 1.0-exp( -0.0001*t*t*t ) ); + } + + return vec3( clamp(col,0.0,1.0) ); +} + +mat3 setCamera( in vec3 ro, in vec3 ta, float cr ) +{ + vec3 cw = normalize(ta-ro); + vec3 cp = vec3(sin(cr), cos(cr),0.0); + vec3 cu = normalize( cross(cw,cp) ); + vec3 cv = ( cross(cu,cw) ); + return mat3( cu, cv, cw ); +} + +vec4 mainImage( vec2 fragCoord ) +{ + vec2 mo = iMouse.xy/iResolution.xy; + float time = 32.0 + iTime*1.5; + + // camera + vec3 ta = vec3( 0.5, -0.5, -0.6 ); + vec3 ro = ta + vec3( 4.5*cos(0.1*time + 7.0*mo.x), 1.3 + 2.0*mo.y, 4.5*sin(0.1*time + 7.0*mo.x) ); + // camera-to-world transformation + mat3 ca = setCamera( ro, ta, 0.0 ); + + vec3 tot = vec3(0.0); +#if AA>1 + for( int m=ZERO; m<AA; m++ ) + for( int n=ZERO; n<AA; n++ ) + { + // pixel coordinates + vec2 o = vec2(float(m),float(n)) / float(AA) - 0.5; + vec2 p = (2.0*(fragCoord+o)-iResolution.xy)/iResolution.y; +#else + vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y; +#endif + + // focal length + const float fl = 2.5; + + // ray direction + vec3 rd = ca * normalize( vec3(p,fl) ); + + // ray differentials + vec2 px = (2.0*(fragCoord+vec2(1.0,0.0))-iResolution.xy)/iResolution.y; + vec2 py = (2.0*(fragCoord+vec2(0.0,1.0))-iResolution.xy)/iResolution.y; + vec3 rdx = ca * normalize( vec3(px,fl) ); + vec3 rdy = ca * normalize( vec3(py,fl) ); + + // render + vec3 col = render( ro, rd, rdx, rdy ); + + // gain + // col = col*3.0/(2.5+col); + + // gamma + col = pow( col, vec3(0.4545) ); + + tot += col; +#if AA>1 + } + tot /= float(AA*AA); +#endif + + //fragColor = vec4( tot, 1.0 ); + return vec4( tot, 1.0 ); +} + +//********************************************************* +// END Ray Marching +//********************************************************* + +void main() { + vec4 c = color; + vec4 txt = texture(tex, uv); + c = txt * c; + vec2 uv1 = uv * iResolution; + vec4 col_ray = mainImage(uv1); + + // use this to mix the chessboart texture with the ray marching + //frag_color = clamp(c*iMouse.y/512.0,0.0,1.0) * col_ray ; + + frag_color = c*0.00001 + col_ray ; +} + +#pragma sokol @end + +#pragma sokol @program rt_march vs_m fs_m diff --git a/v_windows/v/examples/sokol/04_multi_shader_glsl/rt_glsl_puppy.glsl b/v_windows/v/examples/sokol/04_multi_shader_glsl/rt_glsl_puppy.glsl new file mode 100644 index 0000000..bea60d5 --- /dev/null +++ b/v_windows/v/examples/sokol/04_multi_shader_glsl/rt_glsl_puppy.glsl @@ -0,0 +1,568 @@ +//------------------------------------------------------------------------------ +// Shader code for texcube-sapp sample. +// +// NOTE: This source file also uses the '#pragma sokol' form of the +// custom tags. +//------------------------------------------------------------------------------ +//#pragma sokol @ctype mat4 hmm_mat4 + +#pragma sokol @vs vs_p +uniform vs_params_p { + mat4 mvp; +}; + +in vec4 pos; +in vec4 color0; +in vec2 texcoord0; + +out vec4 color; +out vec2 uv; + +void main() { + gl_Position = mvp * pos; + color = color0; + uv = texcoord0; +} +#pragma sokol @end + +#pragma sokol @fs fs_p +uniform sampler2D tex; +uniform fs_params_p { + vec2 iResolution; + vec2 iMouse; + float iTime; + float iFrame; +}; + +in vec4 color; +in vec2 uv; +out vec4 frag_color; + +// change to 0 to 4 to increment the AntiAliasing, +// increase AA will SLOW the rendering!! +#define AA 1 + +//********************************************************* +// Ray Marching +// original code from: https://www.shadertoy.com/view/Xds3zN +//********************************************************* +// Created by inigo quilez - iq/2019 +// I share this piece (art and code) here in Shadertoy and through its Public API, only for educational purposes. +// You cannot use, share or host this piece or modifications of it as part of your own commercial or non-commercial product, website or project. +// You can share a link to it or an unmodified screenshot of it provided you attribute "by Inigo Quilez, @iquilezles and iquilezles.org". +// If you are a teacher, lecturer, educator or similar and these conditions are too restrictive for your needs, please contact me and we'll work it out. + + +// An animation test - a happy and blobby creature +// jumping and looking around. It gets off-model very +// often, but it looks good enough I think. +// +// Making-of with math/shader/art explanations (6 hours +// long): https://www.youtube.com/watch?v=Cfe5UQ-1L9Q +// +// Video capture: https://www.youtube.com/watch?v=s_UOFo2IULQ +// +// Buy a metal print here: https://www.redbubble.com/i/metal-print/Happy-Jumping-by-InigoQuilez/43594745.0JXQP + +//------------------------------------------------------------------ + + +// http://iquilezles.org/www/articles/smin/smin.htm +float smin( float a, float b, float k ) +{ + float h = max(k-abs(a-b),0.0); + return min(a, b) - h*h*0.25/k; +} + +// http://iquilezles.org/www/articles/smin/smin.htm +vec2 smin( vec2 a, vec2 b, float k ) +{ + float h = clamp( 0.5+0.5*(b.x-a.x)/k, 0.0, 1.0 ); + return mix( b, a, h ) - k*h*(1.0-h); +} + +// http://iquilezles.org/www/articles/smin/smin.htm +float smax( float a, float b, float k ) +{ + float h = max(k-abs(a-b),0.0); + return max(a, b) + h*h*0.25/k; +} + +// http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm +float sdSphere( vec3 p, float s ) +{ + return length(p)-s; +} + +// http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm +float sdEllipsoid( in vec3 p, in vec3 r ) // approximated +{ + float k0 = length(p/r); + float k1 = length(p/(r*r)); + return k0*(k0-1.0)/k1; +} + +vec2 sdStick(vec3 p, vec3 a, vec3 b, float r1, float r2) // approximated +{ + vec3 pa = p-a, ba = b-a; + float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); + return vec2( length( pa - ba*h ) - mix(r1,r2,h*h*(3.0-2.0*h)), h ); +} + +// http://iquilezles.org/www/articles/smin/smin.htm +vec4 opU( vec4 d1, vec4 d2 ) +{ + return (d1.x<d2.x) ? d1 : d2; +} + +//------------------------------------------------------------------ + +#define ZERO (min(int(iFrame),0)) + +//------------------------------------------------------------------ + +float href; +float hsha; + +vec4 map( in vec3 pos, float atime ) +{ + hsha = 1.0; + + float t1 = fract(atime); + float t4 = abs(fract(atime*0.5)-0.5)/0.5; + + float p = 4.0*t1*(1.0-t1); + float pp = 4.0*(1.0-2.0*t1); // derivative of p + + vec3 cen = vec3( 0.5*(-1.0 + 2.0*t4), + pow(p,2.0-p) + 0.1, + floor(atime) + pow(t1,0.7) -1.0 ); + + // body + vec2 uu = normalize(vec2( 1.0, -pp )); + vec2 vv = vec2(-uu.y, uu.x); + + float sy = 0.5 + 0.5*p; + float compress = 1.0-smoothstep(0.0,0.4,p); + sy = sy*(1.0-compress) + compress; + float sz = 1.0/sy; + + vec3 q = pos - cen; + float rot = -0.25*(-1.0 + 2.0*t4); + float rc = cos(rot); + float rs = sin(rot); + q.xy = mat2x2(rc,rs,-rs,rc)*q.xy; + vec3 r = q; + href = q.y; + q.yz = vec2( dot(uu,q.yz), dot(vv,q.yz) ); + + vec4 res = vec4( sdEllipsoid( q, vec3(0.25, 0.25*sy, 0.25*sz) ), 2.0, 0.0, 1.0 ); + + if( res.x-1.0 < pos.y ) // bounding volume + { + float t2 = fract(atime+0.8); + float p2 = 0.5-0.5*cos(6.2831*t2); + r.z += 0.05-0.2*p2; + r.y += 0.2*sy-0.2; + vec3 sq = vec3( abs(r.x), r.yz ); + + // head + vec3 h = r; + float hr = sin(0.791*atime); + hr = 0.7*sign(hr)*smoothstep(0.5,0.7,abs(hr)); + h.xz = mat2x2(cos(hr),sin(hr),-sin(hr),cos(hr))*h.xz; + vec3 hq = vec3( abs(h.x), h.yz ); + float d = sdEllipsoid( h-vec3(0.0,0.20,0.02), vec3(0.08,0.2,0.15) ); + float d2 = sdEllipsoid( h-vec3(0.0,0.21,-0.1), vec3(0.20,0.2,0.20) ); + d = smin( d, d2, 0.1 ); + res.x = smin( res.x, d, 0.1 ); + + // belly wrinkles + { + float yy = r.y-0.02-2.5*r.x*r.x; + res.x += 0.001*sin(yy*120.0)*(1.0-smoothstep(0.0,0.1,abs(yy))); + } + + // arms + { + vec2 arms = sdStick( sq, vec3(0.18-0.06*hr*sign(r.x),0.2,-0.05), vec3(0.3+0.1*p2,-0.2+0.3*p2,-0.15), 0.03, 0.06 ); + res.xz = smin( res.xz, arms, 0.01+0.04*(1.0-arms.y)*(1.0-arms.y)*(1.0-arms.y) ); + } + + // ears + { + float t3 = fract(atime+0.9); + float p3 = 4.0*t3*(1.0-t3); + vec2 ear = sdStick( hq, vec3(0.15,0.32,-0.05), vec3(0.2+0.05*p3,0.2+0.2*p3,-0.07), 0.01, 0.04 ); + res.xz = smin( res.xz, ear, 0.01 ); + } + + // mouth + { + d = sdEllipsoid( h-vec3(0.0,0.15+4.0*hq.x*hq.x,0.15), vec3(0.1,0.04,0.2) ); + res.w = 0.3+0.7*clamp( d*150.0,0.0,1.0); + res.x = smax( res.x, -d, 0.03 ); + } + + // legs + { + float t6 = cos(6.2831*(atime*0.5+0.25)); + float ccc = cos(1.57*t6*sign(r.x)); + float sss = sin(1.57*t6*sign(r.x)); + vec3 base = vec3(0.12,-0.07,-0.1); base.y -= 0.1/sy; + vec2 legs = sdStick( sq, base, base + vec3(0.2,-ccc,sss)*0.2, 0.04, 0.07 ); + res.xz = smin( res.xz, legs, 0.07 ); + } + + // eye + { + float blink = pow(0.5+0.5*sin(2.1*iTime),20.0); + float eyeball = sdSphere(hq-vec3(0.08,0.27,0.06),0.065+0.02*blink); + res.x = smin( res.x, eyeball, 0.03 ); + + vec3 cq = hq-vec3(0.1,0.34,0.08); + cq.xy = mat2x2(0.8,0.6,-0.6,0.8)*cq.xy; + d = sdEllipsoid( cq, vec3(0.06,0.03,0.03) ); + res.x = smin( res.x, d, 0.03 ); + + float eo = 1.0-0.5*smoothstep(0.01,0.04,length((hq.xy-vec2(0.095,0.285))*vec2(1.0,1.1))); + res = opU( res, vec4(sdSphere(hq-vec3(0.08,0.28,0.08),0.060),3.0,0.0,eo)); + res = opU( res, vec4(sdSphere(hq-vec3(0.075,0.28,0.102),0.0395),4.0,0.0,1.0)); + } + } + + // ground + float fh = -0.1 - 0.05*(sin(pos.x*2.0)+sin(pos.z*2.0)); + float t5f = fract(atime+0.05); + float t5i = floor(atime+0.05); + float bt4 = abs(fract(t5i*0.5)-0.5)/0.5; + vec2 bcen = vec2( 0.5*(-1.0+2.0*bt4),t5i+pow(t5f,0.7)-1.0 ); + + float k = length(pos.xz-bcen); + float tt = t5f*15.0-6.2831 - k*3.0; + fh -= 0.1*exp(-k*k)*sin(tt)*exp(-max(tt,0.0)/2.0)*smoothstep(0.0,0.01,t5f); + float d = pos.y - fh; + + // bubbles + { + vec3 vp = vec3( mod(abs(pos.x),3.0)-1.5,pos.y,mod(pos.z+1.5,3.0)-1.5); + vec2 id = vec2( floor(pos.x/3.0), floor((pos.z+1.5)/3.0) ); + float fid = id.x*11.1 + id.y*31.7; + float fy = fract(fid*1.312+atime*0.1); + float y = -1.0+4.0*fy; + vec3 rad = vec3(0.7,1.0+0.5*sin(fid),0.7); + rad -= 0.1*(sin(pos.x*3.0)+sin(pos.y*4.0)+sin(pos.z*5.0)); + float siz = 4.0*fy*(1.0-fy); + float d2 = sdEllipsoid( vp-vec3(0.5,y,0.0), siz*rad ); + + d2 -= 0.03*smoothstep(-1.0,1.0,sin(18.0*pos.x)+sin(18.0*pos.y)+sin(18.0*pos.z)); + d2 *= 0.6; + d2 = min(d2,2.0); + d = smin( d, d2, 0.32 ); + if( d<res.x ) { res = vec4(d,1.0,0.0,1.0); hsha=sqrt(siz); } + } + + // candy + { + float fs = 5.0; + vec3 qos = fs*vec3(pos.x, pos.y-fh, pos.z ); + vec2 id = vec2( floor(qos.x+0.5), floor(qos.z+0.5) ); + vec3 vp = vec3( fract(qos.x+0.5)-0.5,qos.y,fract(qos.z+0.5)-0.5); + vp.xz += 0.1*cos( id.x*130.143 + id.y*120.372 + vec2(0.0,2.0) ); + float den = sin(id.x*0.1+sin(id.y*0.091))+sin(id.y*0.1); + float fid = id.x*0.143 + id.y*0.372; + float ra = smoothstep(0.0,0.1,den*0.1+fract(fid)-0.95); + d = sdSphere( vp, 0.35*ra )/fs; + if( d<res.x ) res = vec4(d,5.0,qos.y,1.0); + } + + return res; +} + +vec4 raycast( in vec3 ro, in vec3 rd, float time ) +{ + vec4 res = vec4(-1.0,-1.0,0.0,1.0); + + float tmin = 0.5; + float tmax = 20.0; + + #if 1 + // raytrace bounding plane + float tp = (3.5-ro.y)/rd.y; + if( tp>0.0 ) tmax = min( tmax, tp ); + #endif + + // raymarch scene + float t = tmin; + for( int i=0; i<256 && t<tmax; i++ ) + { + vec4 h = map( ro+rd*t, time ); + if( abs(h.x)<(0.0005*t) ) + { + res = vec4(t,h.yzw); + break; + } + t += h.x; + } + + return res; +} + +// http://iquilezles.org/www/articles/rmshadows/rmshadows.htm +float calcSoftshadow( in vec3 ro, in vec3 rd, float time ) +{ + float res = 1.0; + + float tmax = 12.0; + #if 1 + float tp = (3.5-ro.y)/rd.y; // raytrace bounding plane + if( tp>0.0 ) tmax = min( tmax, tp ); + #endif + + float t = 0.02; + for( int i=0; i<50; i++ ) + { + float h = map( ro + rd*t, time ).x; + res = min( res, mix(1.0,16.0*h/t, hsha) ); + t += clamp( h, 0.05, 0.40 ); + if( res<0.005 || t>tmax ) break; + } + return clamp( res, 0.0, 1.0 ); +} + +// http://iquilezles.org/www/articles/normalsSDF/normalsSDF.htm +vec3 calcNormal( in vec3 pos, float time ) +{ + +#if 0 + vec2 e = vec2(1.0,-1.0)*0.5773*0.001; + return normalize( e.xyy*map( pos + e.xyy, time ).x + + e.yyx*map( pos + e.yyx, time ).x + + e.yxy*map( pos + e.yxy, time ).x + + e.xxx*map( pos + e.xxx, time ).x ); +#else + // inspired by tdhooper and klems - a way to prevent the compiler from inlining map() 4 times + vec3 n = vec3(0.0); + for( int i=ZERO; i<4; i++ ) + { + vec3 e = 0.5773*(2.0*vec3((((i+3)>>1)&1),((i>>1)&1),(i&1))-1.0); + n += e*map(pos+0.001*e,time).x; + } + return normalize(n); +#endif +} + +float calcOcclusion( in vec3 pos, in vec3 nor, float time ) +{ + float occ = 0.0; + float sca = 1.0; + for( int i=ZERO; i<5; i++ ) + { + float h = 0.01 + 0.11*float(i)/4.0; + vec3 opos = pos + h*nor; + float d = map( opos, time ).x; + occ += (h-d)*sca; + sca *= 0.95; + } + return clamp( 1.0 - 2.0*occ, 0.0, 1.0 ); +} + +vec3 render( in vec3 ro, in vec3 rd, float time ) +{ + // sky dome + vec3 col = vec3(0.5, 0.8, 0.9) - max(rd.y,0.0)*0.5; + // sky clouds + vec2 uv = 1.5*rd.xz/rd.y; + float cl = 1.0*(sin(uv.x)+sin(uv.y)); uv *= mat2(0.8,0.6,-0.6,0.8)*2.1; + cl += 0.5*(sin(uv.x)+sin(uv.y)); + col += 0.1*(-1.0+2.0*smoothstep(-0.1,0.1,cl-0.4)); + // sky horizon + col = mix( col, vec3(0.5, 0.7, .9), exp(-10.0*max(rd.y,0.0)) ); + + + // scene geometry + vec4 res = raycast(ro,rd, time); + if( res.y>-0.5 ) + { + float t = res.x; + vec3 pos = ro + t*rd; + vec3 nor = calcNormal( pos, time ); + vec3 ref = reflect( rd, nor ); + float focc = res.w; + + // material + col = vec3(0.2); + float ks = 1.0; + + if( res.y>4.5 ) // candy + { + col = vec3(0.14,0.048,0.0); + vec2 id = floor(5.0*pos.xz+0.5); + col += 0.036*cos((id.x*11.1+id.y*37.341) + vec3(0.0,1.0,2.0) ); + col = max(col,0.0); + focc = clamp(4.0*res.z,0.0,1.0); + } + else if( res.y>3.5 ) // eyeball + { + col = vec3(0.0); + } + else if( res.y>2.5 ) // iris + { + col = vec3(0.4); + } + else if( res.y>1.5 ) // body + { + col = mix(vec3(0.144,0.09,0.0036),vec3(0.36,0.1,0.04),res.z*res.z); + col = mix(col,vec3(0.14,0.09,0.06)*2.0, (1.0-res.z)*smoothstep(-0.15, 0.15, -href)); + } + else // terrain + { + // base green + col = vec3(0.05,0.09,0.02); + float f = 0.2*(-1.0+2.0*smoothstep(-0.2,0.2,sin(18.0*pos.x)+sin(18.0*pos.y)+sin(18.0*pos.z))); + col += f*vec3(0.06,0.06,0.02); + ks = 0.5 + pos.y*0.15; + + // footprints + vec2 mp = vec2(pos.x-0.5*(mod(floor(pos.z+0.5),2.0)*2.0-1.0), fract(pos.z+0.5)-0.5 ); + float mark = 1.0-smoothstep(0.1, 0.5, length(mp)); + mark *= smoothstep(0.0, 0.1, floor(time) - floor(pos.z+0.5) ); + col *= mix( vec3(1.0), vec3(0.5,0.5,0.4), mark ); + ks *= 1.0-0.5*mark; + } + + // lighting (sun, sky, bounce, back, sss) + float occ = calcOcclusion( pos, nor, time )*focc; + float fre = clamp(1.0+dot(nor,rd),0.0,1.0); + + vec3 sun_lig = normalize( vec3(0.6, 0.35, 0.5) ); + float sun_dif = clamp(dot( nor, sun_lig ), 0.0, 1.0 ); + vec3 sun_hal = normalize( sun_lig-rd ); + float sun_sha = calcSoftshadow( pos, sun_lig, time ); + float sun_spe = ks*pow(clamp(dot(nor,sun_hal),0.0,1.0),8.0)*sun_dif*(0.04+0.96*pow(clamp(1.0+dot(sun_hal,rd),0.0,1.0),5.0)); + float sky_dif = sqrt(clamp( 0.5+0.5*nor.y, 0.0, 1.0 )); + float sky_spe = ks*smoothstep( 0.0, 0.5, ref.y )*(0.04+0.96*pow(fre,4.0)); + float bou_dif = sqrt(clamp( 0.1-0.9*nor.y, 0.0, 1.0 ))*clamp(1.0-0.1*pos.y,0.0,1.0); + float bac_dif = clamp(0.1+0.9*dot( nor, normalize(vec3(-sun_lig.x,0.0,-sun_lig.z))), 0.0, 1.0 ); + float sss_dif = fre*sky_dif*(0.25+0.75*sun_dif*sun_sha); + + vec3 lin = vec3(0.0); + lin += sun_dif*vec3(8.10,6.00,4.20)*vec3(sun_sha,sun_sha*sun_sha*0.5+0.5*sun_sha,sun_sha*sun_sha); + lin += sky_dif*vec3(0.50,0.70,1.00)*occ; + lin += bou_dif*vec3(0.20,0.70,0.10)*occ; + lin += bac_dif*vec3(0.45,0.35,0.25)*occ; + lin += sss_dif*vec3(3.25,2.75,2.50)*occ; + col = col*lin; + col += sun_spe*vec3(9.90,8.10,6.30)*sun_sha; + col += sky_spe*vec3(0.20,0.30,0.65)*occ*occ; + + col = pow(col,vec3(0.8,0.9,1.0) ); + + // fog + col = mix( col, vec3(0.5,0.7,0.9), 1.0-exp( -0.0001*t*t*t ) ); + } + + return col; +} + +mat3 setCamera( in vec3 ro, in vec3 ta, float cr ) +{ + vec3 cw = normalize(ta-ro); + vec3 cp = vec3(sin(cr), cos(cr),0.0); + vec3 cu = normalize( cross(cw,cp) ); + vec3 cv = ( cross(cu,cw) ); + return mat3( cu, cv, cw ); +} + +//void mainImage( out vec4 fragColor, in vec2 fragCoord ) +vec4 mainImage( vec2 fragCoord ) +{ + vec3 tot = vec3(0.0); +#if AA>1 + for( int m=ZERO; m<AA; m++ ) + for( int n=ZERO; n<AA; n++ ) + { + // pixel coordinates + vec2 o = vec2(float(m),float(n)) / float(AA) - 0.5; + vec2 p = (-iResolution.xy + 2.0*(fragCoord+o))/iResolution.y; + // time coordinate (motion blurred, shutter=0.5) + float d = 0.5+0.5*sin(fragCoord.x*147.0)*sin(fragCoord.y*131.0); + float time = iTime - 0.5*(1.0/24.0)*(float(m*AA+n)+d)/float(AA*AA); +#else + vec2 p = (-iResolution.xy + 2.0*fragCoord)/iResolution.y; + float time = iTime; +#endif + time += -2.6; + time *= 0.9; + + // camera + float cl = sin(0.5*time); + float an = 1.57 + 0.7*sin(0.15*time); + vec3 ta = vec3( 0.0, 0.65, -0.6+time*1.0 - 0.4*cl); + vec3 ro = ta + vec3( 1.3*cos(an), -0.250, 1.3*sin(an) ); + float ti = fract(time-0.15); + ti = 4.0*ti*(1.0-ti); + ta.y += 0.15*ti*ti*(3.0-2.0*ti)*smoothstep(0.4,0.9,cl); + + // camera bounce + float t4 = abs(fract(time*0.5)-0.5)/0.5; + float bou = -1.0 + 2.0*t4; + ro += 0.06*sin(time*12.0+vec3(0.0,2.0,4.0))*smoothstep( 0.85, 1.0, abs(bou) ); + + // camera-to-world rotation + mat3 ca = setCamera( ro, ta, 0.0 ); + + // ray direction + vec3 rd = ca * normalize( vec3(p,1.8) ); + + // render + vec3 col = render( ro, rd, time ); + + // color grading + col = col*vec3(1.11,0.89,0.79); + + // compress + col = 1.35*col/(1.0+col); + + // gamma + col = pow( col, vec3(0.4545) ); + + tot += col; +#if AA>1 + } + tot /= float(AA*AA); +#endif + + // s-surve + tot = clamp(tot,0.0,1.0); + tot = tot*tot*(3.0-2.0*tot); + + // vignetting + vec2 q = fragCoord/iResolution.xy; + tot *= 0.5 + 0.5*pow(16.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.25); + + // output + //fragColor = vec4( tot, 1.0 ); + return vec4( tot, 1.0 ); +} + +//********************************************************* +// END Ray Marching +//********************************************************* + +void main() { + vec4 c = color; + vec4 txt = texture(tex, uv); + c = txt * c; + vec2 uv1 = uv * iResolution; + vec4 col_ray = mainImage(uv1); + + // use this to mix the chessboart texture with the ray marching + //frag_color = clamp(c*iMouse.y/512.0,0.0,1.0) * col_ray ; + + frag_color = c*0.00001 + col_ray ; +} + +#pragma sokol @end + +#pragma sokol @program rt_puppy vs_p fs_p diff --git a/v_windows/v/examples/sokol/04_multi_shader_glsl/v.mod b/v_windows/v/examples/sokol/04_multi_shader_glsl/v.mod new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/v_windows/v/examples/sokol/04_multi_shader_glsl/v.mod diff --git a/v_windows/v/examples/sokol/05_instancing_glsl/rt_glsl.v b/v_windows/v/examples/sokol/05_instancing_glsl/rt_glsl.v new file mode 100644 index 0000000..6c32fd5 --- /dev/null +++ b/v_windows/v/examples/sokol/05_instancing_glsl/rt_glsl.v @@ -0,0 +1,521 @@ +/********************************************************************** +* +* Sokol 3d cube multishader demo +* +* Copyright (c) 2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* HOW TO COMPILE SHADERS: +* - download the sokol shader convertor tool from https://github.com/floooh/sokol-tools-bin +* +* - compile the .glsl shared file with: +* linux : sokol-shdc --input rt_glsl_instancing.glsl --output rt_glsl_instancing.h --slang glsl330 +* windows: sokol-shdc.exe --input rt_glsl_instancing.glsl --output rt_glsl_instancing.h --slang glsl330 +* +* --slang parameter can be: +* - glsl330: desktop GL +* - glsl100: GLES2 / WebGL +* - glsl300es: GLES3 / WebGL2 +* - hlsl4: D3D11 +* - hlsl5: D3D11 +* - metal_macos: Metal on macOS +* - metal_ios: Metal on iOS device +* - metal_sim: Metal on iOS simulator +* - wgpu: WebGPU +* +* you can have multiple platforms at the same time passing parameters like this: --slang glsl330:hlsl5:metal_macos +* for further infos have a look at the sokol shader tool docs. +* +* TODO: +* - frame counter +**********************************************************************/ +import gg +import gg.m4 +import gx +import math + +import sokol.gfx +//import sokol.sgl + +import time + +const ( + win_width = 800 + win_height = 800 + bg_color = gx.white + num_inst = 16384 +) + +struct App { +mut: + gg &gg.Context + texture C.sg_image + init_flag bool + frame_count int + + mouse_x int = -1 + mouse_y int = -1 + mouse_down bool + + // glsl + cube_pip_glsl C.sg_pipeline + cube_bind C.sg_bindings + + pipe map[string]C.sg_pipeline + bind map[string]C.sg_bindings + + // time + ticks i64 + + // instances + inst_pos [num_inst]m4.Vec4 + + // camera + camera_x f32 + camera_z f32 +} + +/****************************************************************************** +* GLSL Include and functions +******************************************************************************/ +#flag -I @VMODROOT/. +#include "rt_glsl_instancing.h" #Please use sokol-shdc to generate the necessary rt_glsl_march.h file from rt_glsl_march.glsl (see the instructions at the top of this file) +fn C.instancing_shader_desc(gfx.Backend) &C.sg_shader_desc + +/****************************************************************************** +* Texture functions +******************************************************************************/ +fn create_texture(w int, h int, buf byteptr) C.sg_image{ + sz := w * h * 4 + mut img_desc := C.sg_image_desc{ + width: w + height: h + num_mipmaps: 0 + min_filter: .linear + mag_filter: .linear + //usage: .dynamic + wrap_u: .clamp_to_edge + wrap_v: .clamp_to_edge + label: &byte(0) + d3d11_texture: 0 + } + // comment if .dynamic is enabled + img_desc.data.subimage[0][0] = C.sg_range{ + ptr: buf + size: size_t(sz) + } + + sg_img := C.sg_make_image(&img_desc) + return sg_img +} + +fn destroy_texture(sg_img C.sg_image){ + C.sg_destroy_image(sg_img) +} + +// Use only if usage: .dynamic is enabled +fn update_text_texture(sg_img C.sg_image, w int, h int, buf byteptr){ + sz := w * h * 4 + mut tmp_sbc := C.sg_image_data{} + tmp_sbc.subimage[0][0] = C.sg_range{ + ptr: buf + size: size_t(sz) + } + C.sg_update_image(sg_img, &tmp_sbc) +} + +/****************************************************************************** +* Draw functions +****************************************************************************** + Cube vertex buffer with packed vertex formats for color and texture coords. + Note that a vertex format which must be portable across all + backends must only use the normalized integer formats + (BYTE4N, UBYTE4N, SHORT2N, SHORT4N), which can be converted + to floating point formats in the vertex shader inputs. + The reason is that D3D11 cannot convert from non-normalized + formats to floating point inputs (only to integer inputs), + and WebGL2 / GLES2 don't support integer vertex shader inputs. +*/ + +struct Vertex_t { + x f32 + y f32 + z f32 + color u32 + + //u u16 // for compatibility with D3D11 + //v u16 // for compatibility with D3D11 + u f32 + v f32 +} + +// march shader init +fn init_cube_glsl_i(mut app App) { + /* cube vertex buffer */ + //d := u16(32767) // for compatibility with D3D11, 32767 stand for 1 + d := f32(1.0) + c := u32(0xFFFFFF_FF) // color RGBA8 + vertices := [ + // Face 0 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{ 1.0, -1.0, -1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, -1.0, c, d, d}, + Vertex_t{-1.0, 1.0, -1.0, c, 0, d}, + // Face 1 + Vertex_t{-1.0, -1.0, 1.0, c, 0, 0}, + Vertex_t{ 1.0, -1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{-1.0, 1.0, 1.0, c, 0, d}, + // Face 2 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, 1.0, -1.0, c, d, 0}, + Vertex_t{-1.0, 1.0, 1.0, c, d, d}, + Vertex_t{-1.0, -1.0, 1.0, c, 0, d}, + // Face 3 + Vertex_t{ 1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{ 1.0, 1.0, -1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, -1.0, 1.0, c, 0, d}, + // Face 4 + Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, -1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, -1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, -1.0, -1.0, c, 0, d}, + // Face 5 + Vertex_t{-1.0, 1.0, -1.0, c, 0, 0}, + Vertex_t{-1.0, 1.0, 1.0, c, d, 0}, + Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, + Vertex_t{ 1.0, 1.0, -1.0, c, 0, d}, + ] + + mut vert_buffer_desc := C.sg_buffer_desc{label: c'cube-vertices'} + unsafe {C.memset(&vert_buffer_desc, 0, sizeof(vert_buffer_desc))} + vert_buffer_desc.size = size_t(vertices.len * int(sizeof(Vertex_t))) + vert_buffer_desc.data = C.sg_range{ + ptr: vertices.data + size: size_t(vertices.len * int(sizeof(Vertex_t))) + } + vert_buffer_desc.@type = .vertexbuffer + vbuf := gfx.make_buffer(&vert_buffer_desc) + + /* create an instance buffer for the cube */ + mut inst_buffer_desc := C.sg_buffer_desc{label: c'instance-data'} + unsafe {C.memset(&inst_buffer_desc, 0, sizeof(inst_buffer_desc))} + + inst_buffer_desc.size = size_t(num_inst * int(sizeof(m4.Vec4))) + inst_buffer_desc.@type = .vertexbuffer + inst_buffer_desc.usage = .stream + inst_buf := gfx.make_buffer(&inst_buffer_desc) + + + /* create an index buffer for the cube */ + indices := [ + u16(0), 1, 2, 0, 2, 3, + 6, 5, 4, 7, 6, 4, + 8, 9, 10, 8, 10, 11, + 14, 13, 12, 15, 14, 12, + 16, 17, 18, 16, 18, 19, + 22, 21, 20, 23, 22, 20 + ] + + mut index_buffer_desc := C.sg_buffer_desc{label: c'cube-indices'} + unsafe {C.memset(&index_buffer_desc, 0, sizeof(index_buffer_desc))} + index_buffer_desc.size = size_t(indices.len * int(sizeof(u16))) + index_buffer_desc.data = C.sg_range{ + ptr: indices.data + size: size_t(indices.len * int(sizeof(u16))) + } + index_buffer_desc.@type = .indexbuffer + ibuf := gfx.make_buffer(&index_buffer_desc) + + /* create shader */ + shader := gfx.make_shader(C.instancing_shader_desc(C.sg_query_backend())) + + mut pipdesc := C.sg_pipeline_desc{} + unsafe {C.memset(&pipdesc, 0, sizeof(pipdesc))} + pipdesc.layout.buffers[0].stride = int(sizeof(Vertex_t)) + + // the constants [C.ATTR_vs_m_pos, C.ATTR_vs_m_color0, C.ATTR_vs_m_texcoord0] are generated by sokol-shdc + pipdesc.layout.attrs[C.ATTR_vs_i_pos ].format = .float3 // x,y,z as f32 + pipdesc.layout.attrs[C.ATTR_vs_i_pos ].buffer_index = 0 + pipdesc.layout.attrs[C.ATTR_vs_i_color0 ].format = .ubyte4n // color as u32 + pipdesc.layout.attrs[C.ATTR_vs_i_pos ].buffer_index = 0 + pipdesc.layout.attrs[C.ATTR_vs_i_texcoord0].format = .float2 // u,v as f32 + pipdesc.layout.attrs[C.ATTR_vs_i_pos ].buffer_index = 0 + + // instancing + // the constant ATTR_vs_i_inst_pos is generated by sokol-shdc + pipdesc.layout.buffers[1].stride = int(sizeof(m4.Vec4)) + pipdesc.layout.buffers[1].step_func = .per_instance // we will pass a single parameter for each instance!! + pipdesc.layout.attrs[C.ATTR_vs_i_inst_pos ].format = .float4 + pipdesc.layout.attrs[C.ATTR_vs_i_inst_pos ].buffer_index = 1 + + pipdesc.shader = shader + pipdesc.index_type = .uint16 + + pipdesc.depth = C.sg_depth_state{ + write_enabled: true + compare: gfx.CompareFunc(C.SG_COMPAREFUNC_LESS_EQUAL) + } + pipdesc.cull_mode = .back + + pipdesc.label = "glsl_shader pipeline".str + + mut bind := C.sg_bindings{} + unsafe {C.memset(&bind, 0, sizeof(bind))} + bind.vertex_buffers[0] = vbuf // vertex buffer + bind.vertex_buffers[1] = inst_buf // instance buffer + bind.index_buffer = ibuf + bind.fs_images[C.SLOT_tex] = app.texture + app.bind['inst'] = bind + app.pipe['inst'] = gfx.make_pipeline(&pipdesc) + + println("GLSL March init DONE!") +} + +fn calc_tr_matrices(w f32, h f32, rx f32, ry f32, in_scale f32) m4.Mat4{ + proj := m4.perspective(60, w/h, 0.01, 4000.0) + view := m4.look_at(m4.Vec4{e:[f32(0.0),100,6,0]!}, m4.Vec4{e:[f32(0),0,0,0]!}, m4.Vec4{e:[f32(0),1.0,0,0]!}) + view_proj := view * proj + + rxm := m4.rotate(m4.rad(rx), m4.Vec4{e:[f32(1),0,0,0]!}) + rym := m4.rotate(m4.rad(ry), m4.Vec4{e:[f32(0),1,0,0]!}) + + model := rym * rxm + scale_m := m4.scale(m4.Vec4{e:[in_scale, in_scale, in_scale, 1]!}) + + res := (scale_m * model)* view_proj + return res +} + +// triangles draw +fn draw_cube_glsl_i(mut app App){ + if app.init_flag == false { + return + } + + ws := gg.window_size_real_pixels() + //ratio := f32(ws.width) / ws.height + dw := f32(ws.width / 2) + dh := f32(ws.height / 2) + + rot := [f32(app.mouse_y), f32(app.mouse_x)] + tr_matrix := calc_tr_matrices(dw, dh, rot[0], rot[1], 2.3) + + gfx.apply_pipeline(app.pipe['inst']) + gfx.apply_bindings(app.bind['inst']) + + //*************** + // Instancing + //*************** + // passing the instancing to the vs + time_ticks := f32(time.ticks() - app.ticks) / 1000 + cube_size := 2 + sz := 128 // field size dimension + cx := 64 // x center for the cubes + cz := 64 // z center for the cubes + //frame := (app.frame_count/4) % 100 + for index in 0..num_inst { + x := f32(index % sz) + z := f32(index / sz) + // simply waves + y := f32(math.cos((x+time_ticks)/2.0)*math.sin(z/2.0))*2 + // sombrero function + //r := ((x-cx)*(x-cx)+(z-cz)*(z-cz))/(sz/2) + //y := f32(math.sin(r+time_ticks)*4.0) + spare_param := f32(index % 10) + app.inst_pos[index] = m4.Vec4{e:[f32((x - cx - app.camera_x) * cube_size),y ,f32( (z - cz - app.camera_z) * cube_size),spare_param]!} + } + range := C.sg_range{ + ptr: unsafe { &app.inst_pos } + size: size_t(num_inst * int(sizeof(m4.Vec4))) + } + gfx.update_buffer(app.bind['inst'].vertex_buffers[1], &range ) + + // Uniforms + // *** vertex shadeer uniforms *** + // passing the view matrix as uniform + // res is a 4x4 matrix of f32 thus: 4*16 byte of size + vs_uniforms_range := C.sg_range{ + ptr: unsafe { &tr_matrix } + size: size_t(4 * 16) + } + gfx.apply_uniforms(C.SG_SHADERSTAGE_VS, C.SLOT_vs_params_i, &vs_uniforms_range) + +/* + // *** fragment shader uniforms *** + time_ticks := f32(time.ticks() - app.ticks) / 1000 + mut tmp_fs_params := [ + f32(ws.width), ws.height * ratio, // x,y resolution to pass to FS + 0,0, // dont send mouse position + //app.mouse_x, // mouse x + //ws.height - app.mouse_y*2, // mouse y scaled + time_ticks, // time as f32 + app.frame_count, // frame count + 0,0 // padding bytes , see "fs_params" struct paddings in rt_glsl.h + ]! + fs_uniforms_range := C.sg_range{ + ptr: unsafe { &tmp_fs_params } + size: size_t(sizeof(tmp_fs_params)) + } + gfx.apply_uniforms(C.SG_SHADERSTAGE_FS, C.SLOT_fs_params, &fs_uniforms_range) +*/ + // 3 vertices for triangle * 2 triangles per face * 6 faces = 36 vertices to draw for num_inst times + gfx.draw(0, (3 * 2) * 6, num_inst) +} + + +fn draw_start_glsl(app App){ + if app.init_flag == false { + return + } + + ws := gg.window_size_real_pixels() + //ratio := f32(ws.width) / ws.height + //dw := f32(ws.width / 2) + //dh := f32(ws.height / 2) + + gfx.apply_viewport(0, 0, ws.width, ws.height, true) +} + +fn draw_end_glsl(app App){ + gfx.end_pass() + gfx.commit() +} + +fn frame(mut app App) { + ws := gg.window_size_real_pixels() + + // clear + mut color_action := C.sg_color_attachment_action{ + action: gfx.Action(C.SG_ACTION_CLEAR) + value: C.sg_color{ + r: 0.0 + g: 0.0 + b: 0.0 + a: 1.0 + } + } + mut pass_action := C.sg_pass_action{} + pass_action.colors[0] = color_action + gfx.begin_default_pass(&pass_action, ws.width, ws.height) + + draw_start_glsl(app) + draw_cube_glsl_i(mut app) + draw_end_glsl(app) + app.frame_count++ +} + +/****************************************************************************** +* Init / Cleanup +******************************************************************************/ +fn my_init(mut app App) { + // create chessboard texture 256*256 RGBA + w := 256 + h := 256 + sz := w * h * 4 + tmp_txt := unsafe { malloc(sz) } + mut i := 0 + for i < sz { + unsafe { + y := (i >> 0x8) >> 5 // 8 cell + x := (i & 0xFF) >> 5 // 8 cell + // upper left corner + if x == 0 && y == 0 { + tmp_txt[i + 0] = byte(0xFF) + tmp_txt[i + 1] = byte(0) + tmp_txt[i + 2] = byte(0) + tmp_txt[i + 3] = byte(0xFF) + } + // low right corner + else if x == 7 && y == 7 { + tmp_txt[i + 0] = byte(0) + tmp_txt[i + 1] = byte(0xFF) + tmp_txt[i + 2] = byte(0) + tmp_txt[i + 3] = byte(0xFF) + } else { + col := if ((x + y) & 1) == 1 { 0xFF } else { 128 } + tmp_txt[i + 0] = byte(col) // red + tmp_txt[i + 1] = byte(col) // green + tmp_txt[i + 2] = byte(col) // blue + tmp_txt[i + 3] = byte(0xFF) // alpha + } + i += 4 + } + } + unsafe { + app.texture = create_texture(w, h, tmp_txt) + free(tmp_txt) + } + + // glsl + init_cube_glsl_i(mut app) + app.init_flag = true +} + +fn cleanup(mut app App) { + gfx.shutdown() +} + +/****************************************************************************** +* events handling +******************************************************************************/ +fn my_event_manager(mut ev gg.Event, mut app App) { + if ev.typ == .mouse_down{ + app.mouse_down = true + } + if ev.typ == .mouse_up{ + app.mouse_down = false + } + if app.mouse_down == true && ev.typ == .mouse_move { + app.mouse_x = int(ev.mouse_x) + app.mouse_y = int(ev.mouse_y) + } + if ev.typ == .touches_began || ev.typ == .touches_moved { + if ev.num_touches > 0 { + touch_point := ev.touches[0] + app.mouse_x = int(touch_point.pos_x) + app.mouse_y = int(touch_point.pos_y) + } + } + + // keyboard + if ev.typ == .key_down { + step := f32(1.0) + match ev.key_code { + .w { app.camera_z += step } + .s { app.camera_z -= step } + .a { app.camera_x -= step } + .d { app.camera_x += step } + else{} + } + } +} + +/****************************************************************************** +* Main +******************************************************************************/ +[console] // is needed for easier diagnostics on windows +fn main(){ + // App init + mut app := &App{ + gg: 0 + } + + app.gg = gg.new_context( + width: win_width + height: win_height + create_window: true + window_title: 'Instancing Cube' + user_data: app + bg_color: bg_color + frame_fn: frame + init_fn: my_init + cleanup_fn: cleanup + event_fn: my_event_manager + ) + + app.ticks = time.ticks() + app.gg.run() +} diff --git a/v_windows/v/examples/sokol/05_instancing_glsl/rt_glsl_instancing.glsl b/v_windows/v/examples/sokol/05_instancing_glsl/rt_glsl_instancing.glsl new file mode 100644 index 0000000..0e780b3 --- /dev/null +++ b/v_windows/v/examples/sokol/05_instancing_glsl/rt_glsl_instancing.glsl @@ -0,0 +1,64 @@ +//------------------------------------------------------------------------------ +// Shader code for texcube-sapp sample. +// +// NOTE: This source file also uses the '#pragma sokol' form of the +// custom tags. +//------------------------------------------------------------------------------ +//#pragma sokol @ctype mat4 hmm_mat4 + +#pragma sokol @vs vs_i +uniform vs_params_i { + mat4 mvp; +}; + +in vec4 pos; +in vec4 color0; +in vec2 texcoord0; + +in vec4 inst_pos; + +out vec4 color; +out vec4 color_inst; +out vec2 uv; + +const vec4 palette[10] = vec4[10]( + vec4(1,0,0,1), + vec4(0,1,0,1), + vec4(0,0,1,1), + vec4(1,1,0,1), + vec4(0,1,1,1), + vec4(1,1,1,1), + vec4(0,0,0,1), + vec4(0.2,0.2,0.2,1), + vec4(0.3,0.3,0.3,1), + vec4(0.9,0.9,0.9,1) +); + +void main() { + vec4 delta_pos = vec4(inst_pos.xyz,0); + float w = inst_pos.w; + color_inst = palette[int(w)]; + gl_Position = mvp * (pos + delta_pos); + color = color0; + uv = texcoord0/4; +} +#pragma sokol @end + +#pragma sokol @fs fs_i +uniform sampler2D tex; + +in vec4 color; +in vec4 color_inst; +in vec2 uv; +out vec4 frag_color; + +void main() { + vec4 c = color; + vec4 txt = texture(tex, uv); + c = txt * c * color_inst; + frag_color = c ; +} + +#pragma sokol @end + +#pragma sokol @program instancing vs_i fs_i diff --git a/v_windows/v/examples/sokol/05_instancing_glsl/v.mod b/v_windows/v/examples/sokol/05_instancing_glsl/v.mod new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/v_windows/v/examples/sokol/05_instancing_glsl/v.mod diff --git a/v_windows/v/examples/sokol/06_obj_viewer/assets/models/v.mtl b/v_windows/v/examples/sokol/06_obj_viewer/assets/models/v.mtl new file mode 100644 index 0000000..47d0381 --- /dev/null +++ b/v_windows/v/examples/sokol/06_obj_viewer/assets/models/v.mtl @@ -0,0 +1,20 @@ +# Blender MTL File: 'v-logo.blend' +# Material Count: 2 +newmtl Material.001 +Ns 96.078431 +Ka 0.2 0.2 0.2 +Kd 0.365 0.529 0.749 +Ks 0.85 0.85 0.85 +Ke 0 0 0 +Ni 1 +d 1 +illum 1 +newmtl Material.002 +Ns 96.078431 +Ka 0.1 0.1 0.1 +Kd 0.325 0.42 0.541 +Ks 0.85 0.85 0.85 +Ke 0 0 0 +Ni 1 +d 1 +illum 1
\ No newline at end of file diff --git a/v_windows/v/examples/sokol/06_obj_viewer/assets/models/v.obj_ b/v_windows/v/examples/sokol/06_obj_viewer/assets/models/v.obj_ new file mode 100644 index 0000000..d25cc0a --- /dev/null +++ b/v_windows/v/examples/sokol/06_obj_viewer/assets/models/v.obj_ @@ -0,0 +1,194 @@ +# Blender v2.92.0 V Logo +mtllib v.mtl +o Vleft.020_Plane.030 +v -1.379 6.295 0.625 +v -1.389 6.019 -3.177 +v -1.442 6.063 -3.177 +v -1.433 6.339 0.625 +v -4.038 6.499 0.662 +v -4.037 6.537 0.661 +v -4.047 6.261 -3.141 +v -4.048 6.222 -3.140 +v -3.900 6.637 0.658 +v -3.910 6.360 -3.144 +v -3.974 6.342 -3.142 +v -3.964 6.618 0.660 +v -4.012 6.582 0.661 +v -4.022 6.306 -3.142 +v -1.273 6.135 0.624 +v -1.283 5.859 -3.178 +v -1.321 5.933 -3.178 +v -1.311 6.209 0.624 +v 1.288 -1.652 0.018 +v 1.282 -1.628 -3.759 +v -1.510 6.387 0.625 +v -1.520 6.111 -3.177 +v -1.559 6.128 -3.176 +v -1.550 6.404 0.626 +v -1.103 -1.603 0.043 +v -1.113 -1.579 -3.759 +v -1.082 -1.597 -3.759 +v -1.072 -1.620 0.043 +v -1.127 -1.583 0.043 +v -1.137 -1.560 -3.759 +v -1.042 -1.631 0.043 +v -1.052 -1.608 -3.759 +v -1.490 6.095 -3.177 +v -1.480 6.372 0.625 +v -1.148 -1.563 0.043 +v -1.158 -1.540 -3.759 +v -1.164 -1.541 0.043 +v -1.174 -1.518 -3.759 +v -1.171 -1.528 0.043 +v -1.181 -1.504 -3.759 +v -1.347 5.974 -3.178 +v -1.337 6.250 0.624 +v -4.034 6.459 0.662 +v -4.044 6.183 -3.140 +vn 0.634 0.771 -0.057 +vn -0.999 0.034 0.000 +vn -0.278 0.958 -0.068 +vn -0.868 0.494 -0.033 +vn 0.889 0.455 -0.035 +vn 0.947 0.319 -0.012 +vn 0.399 0.914 -0.067 +vn -0.502 -0.864 -0.004 +vn 0.097 0.992 -0.072 +vn -0.630 -0.776 -0.003 +vn -0.009 -0.999 -0.006 +vn 0.564 0.822 -0.061 +vn -0.693 -0.720 -0.002 +vn -0.805 -0.592 -0.001 +vn -0.888 -0.458 -0.000 +vn 0.843 0.535 -0.041 +vn -0.993 -0.116 0.011 +vn 0.731 0.680 -0.051 +vn -0.938 -0.344 0.013 +vn 0.450 0.890 -0.065 +vn -0.604 0.794 -0.056 +vn 0.005 -0.072 0.997 +vn -0.000 0.075 -0.997 +vn -0.329 -0.944 -0.005 +usemtl Material.001 +s 1 +f 1//1 2//1 3//1 4//1 +f 5//2 6//2 7//2 8//2 +f 9//3 10//3 11//3 12//3 +f 6//4 13//4 14//4 7//4 +f 15//5 16//5 17//5 18//5 +f 19//6 20//6 16//6 15//6 +f 21//7 22//7 23//7 24//7 +f 25//8 26//8 27//8 28//8 +f 24//9 23//9 10//9 9//9 +f 29//10 30//10 26//10 25//10 +f 19//11 31//11 32//11 20//11 +f 4//12 3//12 33//12 34//12 +f 35//13 36//13 30//13 29//13 +f 37//14 38//14 36//14 35//14 +f 39//15 40//15 38//15 37//15 +f 18//16 17//16 41//16 42//16 +f 43//17 5//17 8//17 44//17 +f 42//18 41//18 2//18 1//18 +f 39//19 43//19 44//19 40//19 +f 34//20 33//20 22//20 21//20 +f 13//21 12//21 11//21 14//21 +f 15//22 18//22 42//22 1//22 4//22 34//22 21//22 24//22 9//22 12//22 13//22 6//22 5//22 43//22 39//22 37//22 35//22 29//22 25//22 28//22 31//22 19//22 +f 22//23 33//23 3//23 2//23 41//23 17//23 16//23 20//23 32//23 27//23 26//23 30//23 36//23 38//23 40//23 44//23 8//23 7//23 14//23 11//23 10//23 23//23 +f 28//24 27//24 32//24 31//24 +o Vleft.019_Plane.029 +v 1.617 6.547 -4.385 +v 1.627 6.270 -0.582 +v 1.681 6.314 -0.582 +v 1.671 6.591 -4.385 +v 4.277 6.750 -4.422 +v 4.276 6.789 -4.421 +v 4.286 6.513 -0.618 +v 4.287 6.474 -0.618 +v 1.280 -1.685 -3.803 +v 1.290 -1.656 -0.000 +v -1.044 -1.625 -0.000 +v -1.059 -1.615 -3.788 +v 4.250 6.834 -4.420 +v 4.260 6.557 -0.617 +v 1.512 6.387 -4.384 +v 1.522 6.110 -0.581 +v 1.559 6.184 -0.581 +v 1.550 6.461 -4.384 +v 1.410 -1.581 -3.803 +v 4.273 6.711 -4.422 +v 4.282 6.434 -0.619 +v 1.419 -1.552 -0.000 +v 1.749 6.639 -4.385 +v 1.758 6.362 -0.582 +v 1.798 6.380 -0.582 +v 1.788 6.656 -4.386 +v 1.342 -1.657 -3.803 +v 1.351 -1.628 -0.000 +v 1.321 -1.645 -0.000 +v 1.311 -1.674 -3.803 +v 4.148 6.612 -0.615 +v 4.139 6.888 -4.418 +v 4.213 6.593 -0.617 +v 4.203 6.870 -4.420 +v 1.366 -1.637 -3.803 +v 1.375 -1.608 -0.000 +v 1.728 6.347 -0.582 +v 1.719 6.623 -4.385 +v 1.386 -1.617 -3.803 +v 1.396 -1.588 -0.000 +v 1.402 -1.595 -3.803 +v 1.412 -1.566 -0.000 +v 1.585 6.225 -0.581 +v 1.575 6.501 -4.384 +vn -0.634 0.771 0.057 +vn 0.999 0.034 -0.000 +vn -0.021 -0.999 0.002 +vn 0.868 0.494 0.033 +vn -0.889 0.455 0.035 +vn 0.943 -0.332 -0.013 +vn -0.399 0.914 0.067 +vn 0.502 -0.864 0.005 +vn -0.097 0.992 0.072 +vn 0.278 0.958 0.068 +vn 0.630 -0.776 0.004 +vn -0.564 0.822 0.061 +vn 0.693 -0.720 0.003 +vn -0.950 0.311 0.014 +vn 0.805 -0.592 0.002 +vn 0.888 -0.458 0.001 +vn -0.843 0.535 0.041 +vn 0.993 -0.116 -0.011 +vn -0.731 0.680 0.051 +vn -0.450 0.890 0.065 +vn 0.604 0.794 0.056 +vn -0.005 -0.070 -0.997 +vn 0.000 -0.000 1.000 +vn 0.001 0.072 0.997 +vn 0.329 -0.944 0.006 +usemtl Material.002 +s 1 +f 45//25 46//25 47//25 48//25 +f 49//26 50//26 51//26 52//26 +f 53//27 54//27 55//27 56//27 +f 50//28 57//28 58//28 51//28 +f 59//29 60//29 61//29 62//29 +f 63//30 64//30 65//30 66//30 +f 67//31 68//31 69//31 70//31 +f 71//32 72//32 73//32 74//32 +f 70//33 69//33 75//33 76//33 +f 76//34 75//34 77//34 78//34 +f 79//35 80//35 72//35 71//35 +f 48//36 47//36 81//36 82//36 +f 83//37 84//37 80//37 79//37 +f 56//38 55//38 60//38 59//38 +f 85//39 86//39 84//39 83//39 +f 63//40 66//40 86//40 85//40 +f 62//41 61//41 87//41 88//41 +f 64//42 49//42 52//42 65//42 +f 88//43 87//43 46//43 45//43 +f 82//44 81//44 68//44 67//44 +f 57//45 78//45 77//45 58//45 +f 63//46 85//46 83//46 79//46 71//46 74//46 53//46 56//46 59//46 62//46 88//46 45//46 48//46 82//46 67//46 70//46 76//46 78//46 57//46 50//46 49//46 64//46 +f 66//47 84//47 86//47 +f 66//48 65//48 52//48 51//48 58//48 77//48 75//48 69//48 68//48 81//48 47//48 46//48 87//48 61//48 60//48 55//48 54//48 73//48 72//48 80//48 84//48 +f 74//49 73//49 54//49 53//49 diff --git a/v_windows/v/examples/sokol/06_obj_viewer/gouraud.glsl b/v_windows/v/examples/sokol/06_obj_viewer/gouraud.glsl new file mode 100644 index 0000000..7f36499 --- /dev/null +++ b/v_windows/v/examples/sokol/06_obj_viewer/gouraud.glsl @@ -0,0 +1,108 @@ +//#pragma sokol @ctype mat4 hmm_mat4 + +#pragma sokol @vs vs + +uniform vs_params { + mat4 u_MVMatrix; // A constant representing the combined model/view matrix. + mat4 u_MVPMatrix; // A constant representing the combined model/view/projection matrix. + mat4 u_NMatrix; // A constant representing the Normal Matrix +}; + +in vec4 a_Position; // Per-vertex position information we will pass in. +in vec3 a_Normal; // Per-vertex normal information we will pass in. +in vec4 a_Color; // Per-vertex color information we will pass in. +in vec2 a_Texcoord0; + +out vec3 v_Position; // This will be passed into the fragment shader. +out vec4 v_Color; // This will be passed into the fragment shader. +out vec3 v_Normal; // This will be passed into the fragment shader. +out vec3 v_Normal1; +out vec2 uv; // This will be passed into the fragment shader. + +// The entry point for our vertex shader. +void main() +{ + // Transform the vertex into eye space. + v_Position = vec3(u_MVMatrix * a_Position); + // Pass through the color. + v_Color = a_Color; + // calc eye space normal + v_Normal = vec3(u_NMatrix * vec4(a_Normal, 1.0)); + // texture coord + uv = a_Texcoord0; + + v_Normal1 = normalize(vec3(u_MVMatrix * vec4(a_Normal, 1.0))); + + // gl_Position is a special variable used to store the final position. + // Multiply the vertex by the matrix to get the final point in normalized screen coordinates. + gl_Position = u_MVPMatrix * a_Position; +} + +#pragma sokol @end + +#pragma sokol @fs fs +//precision mediump float; // Set the default precision to medium. We don't need as high of a precision in the fragment shader +uniform sampler2D tex; +uniform fs_params { + vec4 u_LightPos; // The position of the light in eye space. + vec4 ambientColor; + vec4 diffuseColor; + vec4 specularColor; + +}; +in vec3 v_Position; // Interpolated position for this fragment. +in vec4 v_Color; // This is the color from the vertex shader interpolated across the triangle per fragment. +in vec3 v_Normal; // Interpolated normal for this fragment. +in vec3 v_Normal1; +in vec2 uv; +out vec4 frag_color; + +vec3 lightDirection = -u_LightPos.xyz;// vec3(0.0, -0.5, 0.5); +//const vec4 ambientColor = vec4(0.094, 0.0, 0.0, 1.0); +//const vec4 diffuseColor = vec4(0.5, 0.0, 0.0, 1.0); +//const vec4 specularColor = vec4(1.0, 1.0, 1.0, 1.0); +//const float shininess = 10.0; +const vec4 lightColor = vec4(1.0, 1.0, 1.0, 1.0); + +vec3 phongBRDF(vec3 lightDir, vec3 viewDir, vec3 normal, vec3 phongDiffuseCol, vec3 phongSpecularCol, float phongShininess) { + vec3 color = phongDiffuseCol; + vec3 reflectDir = reflect(-lightDir, normal); + float specDot = max(dot(reflectDir, viewDir), 0.0); + color += pow(specDot, phongShininess) * phongSpecularCol; + return color; +} + +vec4 getPhong(in vec4 diffuseColor) { + vec3 lightDir = normalize(-lightDirection); + vec3 viewDir = normalize(-v_Position); + vec3 n = normalize(v_Normal); + + vec3 luminance = ambientColor.rgb * 0.5; + + float illuminance = dot(lightDir, n); + if(illuminance > 0.0) { + // we save specular shiness in specularColor.a + vec3 brdf = phongBRDF(lightDir, viewDir, n, diffuseColor.rgb, specularColor.rgb, specularColor.a * 1000); + luminance += brdf * illuminance * lightColor.rgb; + } + + vec4 outColor = vec4(luminance,1.0); + return outColor; +} + +// The entry point for our fragment shader. +void main() +{ + vec4 txt = texture(tex, uv); + + // Directional light + float directional = dot(normalize(v_Normal1), normalize(vec3(0,0.5,1))) ; + directional = directional * 0.15; + + // Multiply the color by the diffuse illumination level to get final output color. + frag_color = vec4(clamp(directional + txt.rgb * getPhong(diffuseColor).rgb,0,1), txt.a * diffuseColor.a); + +} +#pragma sokol @end + +#pragma sokol @program gouraud vs fs
\ No newline at end of file diff --git a/v_windows/v/examples/sokol/06_obj_viewer/modules/obj/obj.v b/v_windows/v/examples/sokol/06_obj_viewer/modules/obj/obj.v new file mode 100644 index 0000000..1d239a0 --- /dev/null +++ b/v_windows/v/examples/sokol/06_obj_viewer/modules/obj/obj.v @@ -0,0 +1,595 @@ +module obj + +/********************************************************************** +* +* .obj loader +* +* Copyright (c) 2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* TODO: +**********************************************************************/ +import gg.m4 +import strconv + +enum F_state { + start + first + ints + decimals + exp_start + exp_sign + exp_int +} + +// read a int from a string +fn get_int(s string, start_index int) (int, int) { + mut i := start_index + mut res := 0 + mut sgn := 1 + + mut state := F_state.start + for true { + if i >= s.len { + break + } + c := s[i] + if state == .start { + match c { + `+` { + i++ + state = .ints + continue + } + `-` { + sgn = -1 + i++ + state = .ints + continue + } + `0`...`9` { + state = .ints + } + ` `, `\t` { + i++ + continue + } + else { // no number found + break + } + } + } + + if state == .ints { + match c { + `0`...`9` { + // println("$res => ${(int(c) - 48)}") + res = res * 10 + (int(c) - 48) + i++ + continue + } + else { + break + } + } + } + } + // println("---") + return res * sgn, i +} + +// reas a float number from a string +fn get_float(s string, start_index int) (f64, int) { + mut i1 := start_index //+ 1 + for i1 < s.len && s[i1] in [` `, `\t`] { + i1++ + } + mut i := i1 + for i < s.len { + if s[i] in [` `, `\t`] { + break + } + i++ + } + // println(" get_float: ($start_index,$i) [${s[start_index..i]}]") + // f_res := strconv.atof_quick(s[start_index..i]) + f_res := strconv.atof_quick(s[i1..i]) + return f_res, i +} + +// read 3 f32 in sequence from a string +fn parse_3f(row string, start_index int) m4.Vec4 { + // println(row) + mut i := start_index //+ 1 + mut f1 := f64(0) + mut f2 := f64(0) + f0, mut p := get_float(row, i) + // print("Here f0: $f0 $p ") + f1, p = get_float(row, p + 1) + // print("Here f1: $f1 $p ") + f2, p = get_float(row, p + 1) + // print("Here f2: $f2 $p ") + return m4.Vec4{ + e: [f32(f0), f32(f1), f32(f2), 1]! + } +} + +// reas a sequence of f32 from a string +fn (mut m ObjPart) parse_floats(row string, start_index int) m4.Vec4 { + mut i := start_index //+ 1 + mut res_f := f64(0) + mut res := m4.Vec4{ + e: [f32(0), 0, 0, 1]! + } + mut c := 0 + for true { + res_f, i = get_float(row, i) + unsafe { + res.e[c] = f32(res_f) + } + c++ + i++ + if i >= row.len { + break + } + } + return res +} + +// read and manage all the faes from an .obj file data +fn (mut p Part) parse_faces(row string, start_index int, obj ObjPart) { + mut i := start_index + 1 + mut res := [][3]int{} + mut v := 0 + mut t := 0 + mut n := 0 + // println("row: ${row[i..]}") + for true { + t = 0 + n = 0 + if i >= row.len { + break + } + mut c := row[i] + if (c > `9` || c < `0`) && c != `-` { + i++ + continue + } + v, i = get_int(row, i) + if i < row.len && row[i] == `/` { + if row[i + 1] != `/` { + t, i = get_int(row, i + 1) + if i < row.len && row[i] == `/` { + n, i = get_int(row, i + 1) + } + } else { + i++ + n, i = get_int(row, i + 1) + } + } + // manage negative indexes + // NOTE: not well suporeted now + if v < 0 { + // println("${obj.v.len} ${obj.v.len-c}") + v = obj.v.len - v + 1 + // exit(0) + } + if n < 0 { + n = obj.vn.len - n + 1 + } + if t < 0 { + t = obj.vt.len - t + 1 + } + res << [v - 1, n - 1, t - 1]! + } + // println("ok res: ${res}") + // println(p.faces.len) + p.faces << res +} + +// parse the obj file, if single_material is true it use only one default material +pub fn (mut obj_part ObjPart) parse_obj_buffer(rows []string, single_material bool) { + mut mat_count := 0 + mut row_count := 0 + default_part := Part{ + name: 'default part' + } + obj_part.part << default_part + // println("OBJ file has ${rows.len} rows") + for c, row in rows { + // println("$c $row") + mut i := 0 + row_count++ + for true { + if i >= row.len { + break + } + match row[i] { + `s` { + break + } + `m` { + if row[i..i + 6] == 'mtllib' { + obj_part.material_file = row[i + 7..].trim_space() + obj_part.load_materials() + } + break + } + `o`, `g` { + mut part := Part{} + part.name = row[i + 1..].trim_space() + obj_part.part << part + mat_count = 0 + break + } + `u` { + if single_material == false && row[i..i + 6] == 'usemtl' { + material := row[i + 7..].trim_space() + // println("material: $material") + // manage multiple materials in an part + if obj_part.part[obj_part.part.len - 1].material.len > 0 { + mat_count++ + mut part := Part{} + if mat_count > 1 { + li := obj_part.part[obj_part.part.len - 1].name.last_index('_m') or { + obj_part.part[obj_part.part.len - 1].name.len - 1 + } + part.name = obj_part.part[obj_part.part.len - 1].name[..li] + + '_m${mat_count:02}' + } else { + part.name = obj_part.part[obj_part.part.len - 1].name + '_m01' + } + obj_part.part << part + } + obj_part.part[obj_part.part.len - 1].material = material + } + break + } + `v` { + i++ + match row[i] { + // normals + `n` { + obj_part.vn << parse_3f(row, i + 2) + // println("Vertex line: $c") + break + } + // parameteres uvw + `p` { + obj_part.vp << parse_3f(row, i + 2) + // println("Vertex line: ${obj_part.vp.len}") + break + } + // texture uvw + `t` { + obj_part.vt << obj_part.parse_floats(row, i + 2) + // println("Vertex line: $c") + break + } + else { + obj_part.v << parse_3f(row, i + 1) + // println("$row => ${obj_part.v[obj_part.v.len-1]}") + break + } + } + } + `f` { + // println("$c $row") + obj_part.part[obj_part.part.len - 1].parse_faces(row, i, obj_part) + // println(obj_part.part[obj_part.part.len - 1].faces.len) + // println("Faces line: $c") + break + } + // end of the line, comments + `\n`, `#` { + break + } + else {} + } + i++ + } + // if c == 2 { break } + if c % 100000 == 0 && c > 0 { + println('$c rows parsed') + } + } + println('$row_count .obj Rows parsed') + // remove default part if empty + if obj_part.part.len > 1 && obj_part.part[0].faces.len == 0 { + obj_part.part = obj_part.part[1..] + } +} + +// load the materials if found the .mtl file +fn (mut obj_part ObjPart) load_materials() { + rows := read_lines_from_file(obj_part.material_file) + println('Material file [$obj_part.material_file] $rows.len Rows.') + for row in rows { + // println("$row") + mut i := 0 + for true { + if i >= row.len { + break + } + match row[i] { + `n` { + if row[i..i + 6] == 'newmtl' { + name := row[i + 6..].trim_space() + mut mat := Material{ + name: name + } + obj_part.mat << mat + break + } + } + `K` { + if row[i + 1] !in [`a`, `d`, `e`, `s`] { + break + } + k_name := row[i..i + 2] + i += 3 + value := parse_3f(row, i) + obj_part.mat[obj_part.mat.len - 1].ks[k_name] = value + break + } + `N` { + n_name := row[i..i + 2] + i += 3 + value, _ := get_float(row, i) + obj_part.mat[obj_part.mat.len - 1].ns[n_name] = f32(value) + break + } + `m` { + if row[i..i + 4] == 'map_' { + name := row[i..i + 6] + if (i + 7) < row.len { + file_name := row[i + 7..].trim_space() + obj_part.mat[obj_part.mat.len - 1].maps[name] = file_name + } + break + } + } + // trasparency + `d` { + if row[i + 1] == ` ` { + value, _ := get_float(row, i + 2) + obj_part.mat[obj_part.mat.len - 1].ns['Tr'] = f32(value) + } + } + `T` { + if row[i + 1] == `r` { + value, _ := get_float(row, i + 3) + obj_part.mat[obj_part.mat.len - 1].ns['Tr'] = f32(1.0 - value) + } + } + // end of the line, comments + `\n`, `#` { + break + } + ` `, `\t` { + i++ + continue + } + else { + break + } + } + i++ + } + } + + // create map material name => material index + for i, m in obj_part.mat { + if m.name !in obj_part.mat_map { + obj_part.mat_map[m.name] = i + } + } + + println('Material Loading Done!') +} + +//============================================================================== +// Sokol data +//============================================================================== + +// vertex data struct +pub struct Vertex_pnct { +pub mut: + x f32 // poistion + y f32 + z f32 + nx f32 // normal + ny f32 + nz f32 + color u32 = 0xFFFFFFFF // color + u f32 // uv + v f32 + // u u16 // for compatibility with D3D11 + // v u16 // for compatibility with D3D11 +} + +// struct used to pass the data to the sokol calls +pub struct Skl_buffer { +pub mut: + vbuf []Vertex_pnct + ibuf []u32 + n_vertex u32 +} + +// transforms data from .obj format to buffer ready to be used in the render +pub fn (mut obj_part ObjPart) get_buffer(in_part_list []int) Skl_buffer { + // in_part := 0 + mut v_count_index := 0 + mut out_buf := Skl_buffer{} + + mut cache := map[string]int{} + mut cache_hit := 0 + + // has_normals := obj_part.vn.len > 0 + // has_uvs := obj_part.vt.len > 0 + + for in_part in in_part_list { + part := obj_part.part[in_part] + for fc, face in part.faces { + // println("$fc $face") + // default 3 faces + mut v_seq := [0, 1, 2] + if face.len == 4 { + v_seq = [0, 1, 2, 0, 2, 3] + } + + // if big faces => use the fan of triangles as solution + // Note: this trick doesn't work with concave faces + if face.len > 4 { + v_seq = [] + mut i := 1 + for i < (face.len - 1) { + v_seq << 0 + v_seq << i + v_seq << (i + 1) + i++ + } + // println("BIG FACES! ${fc} ${face.len} v_seq:${v_seq.len}") + } + + // no vertex index, generate normals + if face[0][1] == -1 && face.len >= 3 { + mut v_count := 0 + v0 := face[v_count + 0][0] + v1 := face[v_count + 1][0] + v2 := face[v_count + 2][0] + + vec0 := obj_part.v[v2] - obj_part.v[v1] + vec1 := obj_part.v[v0] - obj_part.v[v1] + tmp_normal := vec0 % vec1 + + for v_count < face.len { + obj_part.vn << tmp_normal + obj_part.part[in_part].faces[fc][v_count][1] = obj_part.vn.len - 1 + v_count++ + } + } + + for vertex_index in v_seq { + // position + if vertex_index >= face.len { + continue + } + v_index := face[vertex_index][0] // vertex index + n_index := face[vertex_index][1] // normal index + t_index := face[vertex_index][2] // uv texture index + key := '${v_index}_${n_index}_$t_index' + if key !in cache { + cache[key] = v_count_index + mut pnct := Vertex_pnct{ + x: obj_part.v[v_index].e[0] + y: obj_part.v[v_index].e[1] + z: obj_part.v[v_index].e[2] + } + // normal + if n_index >= 0 { + pnct.nx = obj_part.vn[n_index].e[0] + pnct.ny = obj_part.vn[n_index].e[1] + pnct.nz = obj_part.vn[n_index].e[2] + } + // texture uv + if t_index >= 0 { + pnct.u = obj_part.vt[t_index].e[0] + pnct.v = obj_part.vt[t_index].e[1] + } + + out_buf.vbuf << pnct + out_buf.ibuf << u32(v_count_index) + v_count_index++ + } else { + // println("Cache used! $key") + out_buf.ibuf << u32(cache[key]) + cache_hit++ + } + } + } + } + + /* + println("------------") + for c1, x1 in out_buf.vbuf[..10] { + println("$c1 $x1") + } + println(out_buf.ibuf[..10]) + */ + // println("vbuf size: ${out_buf.vbuf.len} ibuf size: ${out_buf.ibuf.len} Cache hit: $cache_hit") + out_buf.n_vertex = u32(out_buf.ibuf.len) + return out_buf +} + +//============================================================================== +// Utility +//============================================================================== +// print on the console the summary of the .obj model loaded +pub fn (obj_part ObjPart) summary() { + println('---- Stats ----') + println('vertices: $obj_part.v.len') + println('normals : $obj_part.vn.len') + println('uv : $obj_part.vt.len') + println('parts : $obj_part.part.len') + // Parts + println('---- Parts ----') + for c, x in obj_part.part { + println('${c:3} [${x.name:-16}] mat:[${x.material:-10}] ${x.faces.len:7} faces') + } + // Materials + println('---- Materials ----') + println('Material dict: $obj_part.mat_map.keys()') + for c, mat in obj_part.mat { + println('${c:3} [${mat.name:-16}]') + for k, v in mat.ks { + print('$k = $v') + } + for k, v in mat.ns { + println('$k = $v') + } + for k, v in mat.maps { + println('$k = $v') + } + } +} + +// debug test function, do not remove. +pub fn tst() { + /* + //fname := "capsule.obj" + //fname := "Forklift.obj" + fname := "cube.obj" + //fname := "Orange Robot 3D ObjPart.obj" + + mut obj := ObjPart{} + buf := os.read_lines(fname) or { panic(err.msg) } + obj.parse_obj_buffer(buf) + obj.summary() + */ + /* + a :="f 7048 7070 7071 7072 7073 7074 7075 7076 7077 7078 7079 7080 7003" + mut f1 := 0 + mut f2 := 0 + f0,mut p := get_int(a,1) + f1, p = get_int(a,p) + f2, p = get_int(a,p) + println("res: ${f0} ${f1} ${f2}") + */ + /* + a :="v -0 0.107769 -0.755914" + println("${parse_3f(a,1)}") + */ + /* + ort := m4.ortho(0,300,0,200,0,0) + println(ort) + a := m4.vec3(0,0,0) + println("a: $a") + res := m4.mul_vec(ort, a) + println("res:\n${res}") + */ + s := 'K 1 1 1' + r := strconv.atof_quick(s[1..s.len - 1]) + println(r) +} diff --git a/v_windows/v/examples/sokol/06_obj_viewer/modules/obj/rend.v b/v_windows/v/examples/sokol/06_obj_viewer/modules/obj/rend.v new file mode 100644 index 0000000..d4ea3bc --- /dev/null +++ b/v_windows/v/examples/sokol/06_obj_viewer/modules/obj/rend.v @@ -0,0 +1,297 @@ +/********************************************************************** +* +* .obj loader +* +* Copyright (c) 2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* TODO: +**********************************************************************/ +module obj + +import sokol.gfx +import gg.m4 +import math +import stbi + +/****************************************************************************** +* Texture functions +******************************************************************************/ +pub fn create_texture(w int, h int, buf &byte) C.sg_image { + sz := w * h * 4 + mut img_desc := C.sg_image_desc{ + width: w + height: h + num_mipmaps: 0 + min_filter: .linear + mag_filter: .linear + // usage: .dynamic + wrap_u: .clamp_to_edge + wrap_v: .clamp_to_edge + label: &byte(0) + d3d11_texture: 0 + } + // comment if .dynamic is enabled + img_desc.data.subimage[0][0] = C.sg_range{ + ptr: buf + size: size_t(sz) + } + + sg_img := C.sg_make_image(&img_desc) + return sg_img +} + +pub fn destroy_texture(sg_img C.sg_image) { + C.sg_destroy_image(sg_img) +} + +pub fn load_texture(file_name string) C.sg_image { + buffer := read_bytes_from_file(file_name) + stbi.set_flip_vertically_on_load(true) + img := stbi.load_from_memory(buffer.data, buffer.len) or { + eprintln('Texure file: [$file_name] ERROR!') + exit(0) + } + res := create_texture(int(img.width), int(img.height), img.data) + img.free() + return res +} + +/****************************************************************************** +* Pipeline +******************************************************************************/ +pub fn (mut obj_part ObjPart) create_pipeline(in_part []int, shader C.sg_shader, texture C.sg_image) Render_data { + mut res := Render_data{} + obj_buf := obj_part.get_buffer(in_part) + res.n_vert = obj_buf.n_vertex + res.material = obj_part.part[in_part[0]].material + + // vertex buffer + mut vert_buffer_desc := C.sg_buffer_desc{ + label: 0 + } + unsafe { C.memset(&vert_buffer_desc, 0, sizeof(vert_buffer_desc)) } + + vert_buffer_desc.size = size_t(obj_buf.vbuf.len * int(sizeof(Vertex_pnct))) + vert_buffer_desc.data = C.sg_range{ + ptr: obj_buf.vbuf.data + size: size_t(obj_buf.vbuf.len * int(sizeof(Vertex_pnct))) + } + + vert_buffer_desc.@type = .vertexbuffer + vert_buffer_desc.label = 'vertbuf_part_${in_part:03}'.str + vbuf := gfx.make_buffer(&vert_buffer_desc) + + // index buffer + mut index_buffer_desc := C.sg_buffer_desc{ + label: 0 + } + unsafe { C.memset(&index_buffer_desc, 0, sizeof(index_buffer_desc)) } + + index_buffer_desc.size = size_t(obj_buf.ibuf.len * int(sizeof(u32))) + index_buffer_desc.data = C.sg_range{ + ptr: obj_buf.ibuf.data + size: size_t(obj_buf.ibuf.len * int(sizeof(u32))) + } + + index_buffer_desc.@type = .indexbuffer + index_buffer_desc.label = 'indbuf_part_${in_part:03}'.str + ibuf := gfx.make_buffer(&index_buffer_desc) + + mut pipdesc := C.sg_pipeline_desc{} + unsafe { C.memset(&pipdesc, 0, sizeof(pipdesc)) } + pipdesc.layout.buffers[0].stride = int(sizeof(Vertex_pnct)) + + // the constants [C.ATTR_vs_a_Position, C.ATTR_vs_a_Color, C.ATTR_vs_a_Texcoord0] are generated by sokol-shdc + pipdesc.layout.attrs[C.ATTR_vs_a_Position].format = .float3 // x,y,z as f32 + pipdesc.layout.attrs[C.ATTR_vs_a_Normal].format = .float3 // x,y,z as f32 + pipdesc.layout.attrs[C.ATTR_vs_a_Color].format = .ubyte4n // color as u32 + pipdesc.layout.attrs[C.ATTR_vs_a_Texcoord0].format = .float2 // u,v as f32 + // pipdesc.layout.attrs[C.ATTR_vs_a_Texcoord0].format = .short2n // u,v as u16 + pipdesc.index_type = .uint32 + + color_state := C.sg_color_state{ + blend: C.sg_blend_state{ + enabled: true + src_factor_rgb: gfx.BlendFactor(C.SG_BLENDFACTOR_SRC_ALPHA) + dst_factor_rgb: gfx.BlendFactor(C.SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA) + } + } + pipdesc.colors[0] = color_state + + pipdesc.depth = C.sg_depth_state{ + write_enabled: true + compare: gfx.CompareFunc(C.SG_COMPAREFUNC_LESS_EQUAL) + } + pipdesc.cull_mode = .front + + pipdesc.label = 'pip_part_${in_part:03}'.str + + // shader + pipdesc.shader = shader + + res.bind.vertex_buffers[0] = vbuf + res.bind.index_buffer = ibuf + res.bind.fs_images[C.SLOT_tex] = texture + res.pipeline = gfx.make_pipeline(&pipdesc) + // println('Buffers part [$in_part] init done!') + + return res +} + +/****************************************************************************** +* Render functions +******************************************************************************/ +// agregate all the part by materials +pub fn (mut obj_part ObjPart) init_render_data(texture C.sg_image) { + // create shader + // One shader for all the model + shader := gfx.make_shader(C.gouraud_shader_desc(gfx.query_backend())) + + mut part_dict := map[string][]int{} + for i, p in obj_part.part { + if p.faces.len > 0 { + part_dict[p.material] << i + } + } + obj_part.rend_data.clear() + // println("Material dict: ${obj_part.mat_map.keys()}") + + for k, v in part_dict { + // println("$k => Parts $v") + + mut txt := texture + + if k in obj_part.mat_map { + mat_map := obj_part.mat[obj_part.mat_map[k]] + if 'map_Kd' in mat_map.maps { + file_name := mat_map.maps['map_Kd'] + if file_name in obj_part.texture { + txt = obj_part.texture[file_name] + // println("Texture [${file_name}] => from CACHE") + } else { + txt = load_texture(file_name) + obj_part.texture[file_name] = txt + // println("Texture [${file_name}] => LOADED") + } + } + } + // key := obj_part.texture.keys()[0] + // obj_part.rend_data << obj_part.create_pipeline(v, shader, obj_part.texture[key]) + obj_part.rend_data << obj_part.create_pipeline(v, shader, txt) + } + // println("Texture array len: ${obj_part.texture.len}") + // println("Calc bounding box.") + obj_part.calc_bbox() + println('init_render_data DONE!') +} + +pub fn (obj_part ObjPart) bind_and_draw(rend_data_index int, in_data Shader_data) u32 { + // apply the pipline and bindings + mut part_render_data := obj_part.rend_data[rend_data_index] + + // pass light position + mut tmp_fs_params := Tmp_fs_param{} + tmp_fs_params.ligth = in_data.fs_data.ligth + + if part_render_data.material in obj_part.mat_map { + mat_index := obj_part.mat_map[part_render_data.material] + mat := obj_part.mat[mat_index] + + // ambient + tmp_fs_params.ka = in_data.fs_data.ka + if 'Ka' in mat.ks { + tmp_fs_params.ka = mat.ks['Ka'] + } + + // specular + tmp_fs_params.ks = in_data.fs_data.ks + if 'Ks' in mat.ks { + tmp_fs_params.ks = mat.ks['Ks'] + } + + // specular exponent Ns + if 'Ns' in mat.ns { + tmp_fs_params.ks.e[3] = mat.ns['Ns'] / 1000.0 + } else { + // defautl value is 10 + tmp_fs_params.ks.e[3] = f32(10) / 1000.0 + } + + // diffuse + tmp_fs_params.kd = in_data.fs_data.kd + if 'Kd' in mat.ks { + tmp_fs_params.kd = mat.ks['Kd'] + } + + // alpha/transparency + if 'Tr' in mat.ns { + tmp_fs_params.kd.e[3] = mat.ns['Tr'] + } + } + + gfx.apply_pipeline(part_render_data.pipeline) + gfx.apply_bindings(part_render_data.bind) + + vs_uniforms_range := C.sg_range{ + ptr: in_data.vs_data + size: size_t(in_data.vs_len) + } + fs_uniforms_range := C.sg_range{ + ptr: unsafe { &tmp_fs_params } + size: size_t(in_data.fs_len) + } + + gfx.apply_uniforms(C.SG_SHADERSTAGE_VS, C.SLOT_vs_params, &vs_uniforms_range) + gfx.apply_uniforms(C.SG_SHADERSTAGE_FS, C.SLOT_fs_params, &fs_uniforms_range) + gfx.draw(0, int(part_render_data.n_vert), 1) + return part_render_data.n_vert +} + +pub fn (obj_part ObjPart) bind_and_draw_all(in_data Shader_data) u32 { + mut n_vert := u32(0) + // println("Parts: ${obj_part.rend_data.len}") + for i, _ in obj_part.rend_data { + n_vert += obj_part.bind_and_draw(i, in_data) + } + return n_vert +} + +pub fn (mut obj_part ObjPart) calc_bbox() { + obj_part.max = m4.Vec4{ + e: [f32(-math.max_f32), -math.max_f32, -math.max_f32, 0]! + } + obj_part.min = m4.Vec4{ + e: [f32(math.max_f32), math.max_f32, math.max_f32, 0]! + } + for v in obj_part.v { + if v.e[0] > obj_part.max.e[0] { + obj_part.max.e[0] = v.e[0] + } + if v.e[1] > obj_part.max.e[1] { + obj_part.max.e[1] = v.e[1] + } + if v.e[2] > obj_part.max.e[2] { + obj_part.max.e[2] = v.e[2] + } + + if v.e[0] < obj_part.min.e[0] { + obj_part.min.e[0] = v.e[0] + } + if v.e[1] < obj_part.min.e[1] { + obj_part.min.e[1] = v.e[1] + } + if v.e[2] < obj_part.min.e[2] { + obj_part.min.e[2] = v.e[2] + } + } + val1 := obj_part.max.mod3() + val2 := obj_part.min.mod3() + if val1 > val2 { + obj_part.radius = f32(val1) + } else { + obj_part.radius = f32(val2) + } + // println("BBox: ${obj_part.min} <=> ${obj_part.max}\nRadius: ${obj_part.radius}") +} diff --git a/v_windows/v/examples/sokol/06_obj_viewer/modules/obj/struct.v b/v_windows/v/examples/sokol/06_obj_viewer/modules/obj/struct.v new file mode 100644 index 0000000..55a528b --- /dev/null +++ b/v_windows/v/examples/sokol/06_obj_viewer/modules/obj/struct.v @@ -0,0 +1,104 @@ +/********************************************************************** +* +* .obj loader +* +* Copyright (c) 2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* TODO: +**********************************************************************/ +module obj + +import gg.m4 + +// part struct mantain the fae indexes list +pub struct Part { +pub mut: + faces [][][3]int // v n t index order, if -1 not available + name string + material string +} + +// materias struct, all Ks and Ns are stored as maps of string +pub struct Material { +pub mut: + name string + ks map[string]m4.Vec4 + ns map[string]f32 + maps map[string]string +} + +// render data used for the rendering +pub struct Render_data { +pub mut: + pipeline C.sg_pipeline + bind C.sg_bindings + n_vert u32 + material string +} + +// base object parts struct +pub struct ObjPart { +pub mut: + v []m4.Vec4 // position + vn []m4.Vec4 // normals + vp []m4.Vec4 // vertex params + vt []m4.Vec4 // textures + + name string + part []Part // parts of the ObjPart + mat []Material // list of the materials of the ObjPart + mat_map map[string]int // maping material name to its material index + texture map[string]C.sg_image // GPU loaded texture map + material_file string // .mtl file name for the .obj + + rend_data []Render_data // render data used for the rendering + + t_m m4.Mat4 = m4.unit_m4() // transform matrix for this ObjPart + // child []ObjPart // childs + // stats + min m4.Vec4 // min 3d position in the ObjPart + max m4.Vec4 // max 3d position in the ObjPart + radius f32 // bounding circle radius of the ObjPart +} + +// used in to pass the matrices to the shader +pub struct Mats { +pub mut: + mv m4.Mat4 + mvp m4.Mat4 + nm m4.Mat4 +} + +// data passed to the vertex shader +pub struct Tmp_vs_param { +pub mut: + mv m4.Mat4 + mvp m4.Mat4 + nm m4.Mat4 +} + +// data passed to the pixel shader +pub struct Tmp_fs_param { +pub mut: + ligth m4.Vec4 + ka m4.Vec4 = m4.Vec4{ + e: [f32(0.1), 0.0, 0.0, 1.0]! + } + kd m4.Vec4 = m4.Vec4{ + e: [f32(0.5), 0.5, 0.5, 1.0]! + } + ks m4.Vec4 = m4.Vec4{ + e: [f32(1.0), 1.0, 1.0, 1.0]! + } +} + +// shader data for the rendering +pub struct Shader_data { +pub mut: + vs_data &Tmp_vs_param + vs_len int + fs_data &Tmp_fs_param + fs_len int +} diff --git a/v_windows/v/examples/sokol/06_obj_viewer/modules/obj/util.v b/v_windows/v/examples/sokol/06_obj_viewer/modules/obj/util.v new file mode 100644 index 0000000..a1e596a --- /dev/null +++ b/v_windows/v/examples/sokol/06_obj_viewer/modules/obj/util.v @@ -0,0 +1,44 @@ +module obj + +import os + +// read a file as single lines +pub fn read_lines_from_file(file_path string) []string { + mut path := '' + mut rows := []string{} + $if android { + path = 'models/' + file_path + bts := os.read_apk_asset(path) or { + eprintln('File [$path] NOT FOUND!') + return rows + } + rows = bts.bytestr().split_into_lines() + } $else { + path = os.resource_abs_path('assets/models/' + file_path) + rows = os.read_lines(path) or { + eprintln('File [$path] NOT FOUND! file_path: $file_path') + return rows + } + } + return rows +} + +// read a file as []byte +pub fn read_bytes_from_file(file_path string) []byte { + mut path := '' + mut buffer := []byte{} + $if android { + path = 'models/' + file_path + buffer = os.read_apk_asset(path) or { + eprintln('Texure file: [$path] NOT FOUND!') + exit(0) + } + } $else { + path = os.resource_abs_path('assets/models/' + file_path) + buffer = os.read_bytes(path) or { + eprintln('Texure file: [$path] NOT FOUND!') + exit(0) + } + } + return buffer +} diff --git a/v_windows/v/examples/sokol/06_obj_viewer/show_obj.v b/v_windows/v/examples/sokol/06_obj_viewer/show_obj.v new file mode 100644 index 0000000..e2e0be3 --- /dev/null +++ b/v_windows/v/examples/sokol/06_obj_viewer/show_obj.v @@ -0,0 +1,338 @@ +/********************************************************************** +* +* .obj viewer +* +* Copyright (c) 2021 Dario Deledda. All rights reserved. +* Use of this source code is governed by an MIT license +* that can be found in the LICENSE file. +* +* Example .obj model of V from SurmanPP +* +* HOW TO COMPILE SHADERS: +* - download the sokol shader convertor tool from https://github.com/floooh/sokol-tools-bin +* +* - compile the .glsl shader with: +* linux : sokol-shdc --input gouraud.glsl --output gouraud.h --slang glsl330 +* windows: sokol-shdc.exe --input gouraud.glsl --output gouraud.h --slang glsl330 +* +* --slang parameter can be: +* - glsl330: desktop GL +* - glsl100: GLES2 / WebGL +* - glsl300es: GLES3 / WebGL2 +* - hlsl4: D3D11 +* - hlsl5: D3D11 +* - metal_macos: Metal on macOS +* - metal_ios: Metal on iOS device +* - metal_sim: Metal on iOS simulator +* - wgpu: WebGPU +* +* you can have multiple platforms at the same time passing parameters like this: --slang glsl330:hlsl5:metal_macos +* for further infos have a look at the sokol shader tool docs. +* +* ALTERNATIVE .OBJ MODELS: +* you can load alternative models putting them in the "assets/model" folder with or without their .mtl file. +* use the program help for further instructions. +* +* TODO: +* - frame counter +**********************************************************************/ +import gg +import gg.m4 +import gx +import math +import sokol.sapp +import sokol.gfx +import sokol.sgl +import time +import os +import obj + +// GLSL Include and functions + +#flag -I @VMODROOT/. +#include "gouraud.h" #Please use sokol-shdc to generate the necessary rt_glsl.h file from rt_glsl.glsl (see the instructions at the top of this file) + +fn C.gouraud_shader_desc(gfx.Backend) &C.sg_shader_desc + +const ( + win_width = 600 + win_height = 600 + bg_color = gx.white +) + +struct App { +mut: + gg &gg.Context + texture C.sg_image + init_flag bool + frame_count int + + mouse_x int = -1 + mouse_y int = -1 + scroll_y int // mouse wheel value + // time + ticks i64 + // model + obj_part &obj.ObjPart + n_vertex u32 + // init parameters + file_name string + single_material_flag bool +} + +/****************************************************************************** +* Draw functions +******************************************************************************/ +[inline] +fn vec4(x f32, y f32, z f32, w f32) m4.Vec4 { + return m4.Vec4{ + e: [x, y, z, w]! + } +} + +fn calc_matrices(w f32, h f32, rx f32, ry f32, in_scale f32, pos m4.Vec4) obj.Mats { + proj := m4.perspective(60, w / h, 0.01, 100.0) // set far plane to 100 fro the zoom function + view := m4.look_at(vec4(f32(0.0), 0, 6, 0), vec4(f32(0), 0, 0, 0), vec4(f32(0), 1, + 0, 0)) + view_proj := view * proj + + rxm := m4.rotate(m4.rad(rx), vec4(f32(1), 0, 0, 0)) + rym := m4.rotate(m4.rad(ry), vec4(f32(0), 1, 0, 0)) + + model_pos := m4.unit_m4().translate(pos) + + model_m := (rym * rxm) * model_pos + scale_m := m4.scale(vec4(in_scale, in_scale, in_scale, 1)) + + mv := scale_m * model_m // model view + nm := mv.inverse().transpose() // normal matrix + mvp := mv * view_proj // model view projection + + return obj.Mats{ + mv: mv + mvp: mvp + nm: nm + } +} + +fn draw_model(app App, model_pos m4.Vec4) u32 { + if app.init_flag == false { + return 0 + } + + ws := gg.window_size_real_pixels() + dw := ws.width / 2 + dh := ws.height / 2 + + mut scale := f32(1) + if app.obj_part.radius > 1 { + scale = 1 / (app.obj_part.radius) + } else { + scale = app.obj_part.radius + } + scale *= 3 + + // *** vertex shader uniforms *** + rot := [f32(app.mouse_y), f32(app.mouse_x)] + mut zoom_scale := scale + f32(app.scroll_y) / (app.obj_part.radius * 4) + mats := calc_matrices(dw, dh, rot[0], rot[1], zoom_scale, model_pos) + + mut tmp_vs_param := obj.Tmp_vs_param{ + mv: mats.mv + mvp: mats.mvp + nm: mats.nm + } + + // *** fragment shader uniforms *** + time_ticks := f32(time.ticks() - app.ticks) / 1000 + radius_light := f32(app.obj_part.radius) + x_light := f32(math.cos(time_ticks) * radius_light) + z_light := f32(math.sin(time_ticks) * radius_light) + + mut tmp_fs_params := obj.Tmp_fs_param{} + tmp_fs_params.ligth = m4.vec3(x_light, radius_light, z_light) + + sd := obj.Shader_data{ + vs_data: unsafe { &tmp_vs_param } + vs_len: int(sizeof(tmp_vs_param)) + fs_data: unsafe { &tmp_fs_params } + fs_len: int(sizeof(tmp_fs_params)) + } + + return app.obj_part.bind_and_draw_all(sd) +} + +fn frame(mut app App) { + ws := gg.window_size_real_pixels() + + // clear + mut color_action := C.sg_color_attachment_action{ + action: gfx.Action(C.SG_ACTION_CLEAR) + value: C.sg_color{ + r: 0.0 + g: 0.0 + b: 0.0 + a: 1.0 + } + } + + mut pass_action := C.sg_pass_action{} + pass_action.colors[0] = color_action + gfx.begin_default_pass(&pass_action, ws.width, ws.height) + + // render the data + draw_start_glsl(app) + draw_model(app, m4.Vec4{}) + // uncoment if you want a raw benchmark mode + /* + mut n_vertex_drawn := u32(0) + n_x_obj := 20 + + for x in 0..n_x_obj { + for z in 0..30 { + for y in 0..4 { + n_vertex_drawn += draw_model(app, m4.Vec4{e:[f32((x-(n_x_obj>>1))*3),-3 + y*3,f32(-6*z),1]!}) + } + } + } + */ + draw_end_glsl(app) + + // println("v:$n_vertex_drawn") + app.frame_count++ +} + +fn draw_start_glsl(app App) { + if app.init_flag == false { + return + } + ws := gg.window_size_real_pixels() + gfx.apply_viewport(0, 0, ws.width, ws.height, true) +} + +fn draw_end_glsl(app App) { + gfx.end_pass() + gfx.commit() +} + +/****************************************************************************** +* Init / Cleanup +******************************************************************************/ +fn my_init(mut app App) { + mut object := &obj.ObjPart{} + obj_file_lines := obj.read_lines_from_file(app.file_name) + object.parse_obj_buffer(obj_file_lines, app.single_material_flag) + object.summary() + app.obj_part = object + + // set max vertices, + // for a large number of the same type of object it is better use the instances!! + desc := sapp.create_desc() + gfx.setup(&desc) + sgl_desc := C.sgl_desc_t{ + max_vertices: 128 * 65536 + } + sgl.setup(&sgl_desc) + + // 1x1 pixel white, default texture + unsafe { + tmp_txt := malloc(4) + tmp_txt[0] = byte(0xFF) + tmp_txt[1] = byte(0xFF) + tmp_txt[2] = byte(0xFF) + tmp_txt[3] = byte(0xFF) + app.texture = obj.create_texture(1, 1, tmp_txt) + free(tmp_txt) + } + // glsl + app.obj_part.init_render_data(app.texture) + app.init_flag = true +} + +fn cleanup(mut app App) { + gfx.shutdown() + /* + for _, mat in app.obj_part.texture { + obj.destroy_texture(mat) + } + */ +} + +/****************************************************************************** +* events handling +******************************************************************************/ +fn my_event_manager(mut ev gg.Event, mut app App) { + if ev.typ == .mouse_move { + app.mouse_x = int(ev.mouse_x) + app.mouse_y = int(ev.mouse_y) + } + + if ev.scroll_y != 0 { + app.scroll_y += int(ev.scroll_y) + } + + if ev.typ == .touches_began || ev.typ == .touches_moved { + if ev.num_touches > 0 { + touch_point := ev.touches[0] + app.mouse_x = int(touch_point.pos_x) + app.mouse_y = int(touch_point.pos_y) + } + } +} + +/****************************************************************************** +* Main +******************************************************************************/ +// is needed for easier diagnostics on windows +[console] +fn main() { + /* + obj.tst() + exit(0) + */ + + // App init + mut app := &App{ + gg: 0 + obj_part: 0 + } + + app.file_name = 'v.obj_' // default object is the v logo + + app.single_material_flag = false + $if !android { + if os.args.len > 3 || (os.args.len >= 2 && os.args[1] in ['-h', '--help', '\\?', '-?']) { + eprintln('Usage:\nshow_obj [file_name:string] [single_material_flag:(true|false)]\n') + eprintln('file_name : name of the .obj file, it must be in the folder "assets/models"') + eprintln(' if no file name is passed the default V logo will be showed.') + eprintln(' if you want custom models you can put them in the folder "assets/models".') + eprintln("single_material_flag: if true the viewer use for all the model's parts the default material\n") + exit(0) + } + + if os.args.len >= 2 { + app.file_name = os.args[1] + } + if os.args.len >= 3 { + app.single_material_flag = os.args[2].bool() + } + println('Loading model: $app.file_name') + println('Using single material: $app.single_material_flag') + } + + app.gg = gg.new_context( + width: win_width + height: win_height + create_window: true + window_title: 'V Wavefront OBJ viewer - Use the mouse wheel to zoom' + user_data: app + bg_color: bg_color + frame_fn: frame + init_fn: my_init + cleanup_fn: cleanup + event_fn: my_event_manager + ) + + app.ticks = time.ticks() + app.gg.run() +} diff --git a/v_windows/v/examples/sokol/06_obj_viewer/v.mod b/v_windows/v/examples/sokol/06_obj_viewer/v.mod new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/v_windows/v/examples/sokol/06_obj_viewer/v.mod diff --git a/v_windows/v/examples/sokol/drawing.v b/v_windows/v/examples/sokol/drawing.v new file mode 100644 index 0000000..e1b779b --- /dev/null +++ b/v_windows/v/examples/sokol/drawing.v @@ -0,0 +1,75 @@ +import sokol +import sokol.sapp +import sokol.gfx +import sokol.sgl + +struct AppState { + pass_action C.sg_pass_action +} + +const ( + used_import = sokol.used_import +) + +fn main() { + state := &AppState{ + pass_action: gfx.create_clear_pass(0.1, 0.1, 0.1, 1.0) + } + title := 'Sokol Drawing Template' + desc := C.sapp_desc{ + user_data: state + init_userdata_cb: init + frame_userdata_cb: frame + window_title: title.str + html5_canvas_name: title.str + } + sapp.run(&desc) +} + +fn init(user_data voidptr) { + desc := sapp.create_desc() // C.sg_desc{ + gfx.setup(&desc) + sgl_desc := C.sgl_desc_t{} + sgl.setup(&sgl_desc) +} + +fn frame(user_data voidptr) { + // println('frame') + state := &AppState(user_data) + draw() + gfx.begin_default_pass(&state.pass_action, sapp.width(), sapp.height()) + sgl.draw() + gfx.end_pass() + gfx.commit() +} + +fn draw() { + // first, reset and setup ortho projection + sgl.defaults() + sgl.matrix_mode_projection() + sgl.ortho(0.0, f32(sapp.width()), f32(sapp.height()), 0.0, -1.0, 1.0) + sgl.c4b(255, 0, 0, 128) + draw_hollow_rect(10, 10, 100, 30) + sgl.c4b(25, 150, 0, 128) + draw_filled_rect(10, 150, 80, 40) + // line(0, 0, 500, 500) +} + +fn draw_hollow_rect(x f32, y f32, w f32, h f32) { + sgl.begin_line_strip() + sgl.v2f(x, y) + sgl.v2f(x + w, y) + sgl.v2f(x + w, y + h) + sgl.v2f(x, y + h) + sgl.v2f(x, y) + sgl.end() +} + +fn draw_filled_rect(x f32, y f32, w f32, h f32) { + sgl.begin_quads() + sgl.v2f(x, y) + sgl.v2f(x + w, y) + sgl.v2f(x + w, y + h) + sgl.v2f(x, y + h) + sgl.end() +} diff --git a/v_windows/v/examples/sokol/fonts.v b/v_windows/v/examples/sokol/fonts.v new file mode 100644 index 0000000..1cf2170 --- /dev/null +++ b/v_windows/v/examples/sokol/fonts.v @@ -0,0 +1,164 @@ +import sokol +import sokol.sapp +import sokol.gfx +import sokol.sgl +import sokol.sfons +import os + +struct AppState { +mut: + pass_action C.sg_pass_action + fons &C.FONScontext + font_normal int +} + +fn main() { + mut color_action := C.sg_color_attachment_action{ + action: gfx.Action(C.SG_ACTION_CLEAR) + value: C.sg_color{ + r: 0.3 + g: 0.3 + b: 0.32 + a: 1.0 + } + } + mut pass_action := C.sg_pass_action{} + pass_action.colors[0] = color_action + state := &AppState{ + pass_action: pass_action + fons: &C.FONScontext(0) + } + title := 'V Metal/GL Text Rendering' + desc := C.sapp_desc{ + user_data: state + init_userdata_cb: init + frame_userdata_cb: frame + window_title: title.str + html5_canvas_name: title.str + } + sapp.run(&desc) +} + +fn init(mut state AppState) { + desc := sapp.create_desc() + gfx.setup(&desc) + s := &C.sgl_desc_t{} + C.sgl_setup(s) + state.fons = sfons.create(512, 512, 1) + // or use DroidSerif-Regular.ttf + if bytes := os.read_bytes(os.resource_abs_path('../assets/fonts/RobotoMono-Regular.ttf')) { + println('loaded font: $bytes.len') + state.font_normal = C.fonsAddFontMem(state.fons, c'sans', bytes.data, bytes.len, + false) + } +} + +fn frame(user_data voidptr) { + mut state := &AppState(user_data) + state.render_font() + gfx.begin_default_pass(&state.pass_action, sapp.width(), sapp.height()) + sgl.draw() + gfx.end_pass() + gfx.commit() +} + +fn (state &AppState) render_font() { + mut sx := 0.0 + mut sy := 0.0 + mut dx := 0.0 + mut dy := 0.0 + lh := f32(0.0) + white := C.sfons_rgba(255, 255, 255, 255) + black := C.sfons_rgba(0, 0, 0, 255) + brown := C.sfons_rgba(192, 128, 0, 128) + blue := C.sfons_rgba(0, 192, 255, 255) + state.fons.clear_state() + sgl.defaults() + sgl.matrix_mode_projection() + sgl.ortho(0.0, f32(C.sapp_width()), f32(C.sapp_height()), 0.0, -1.0, 1.0) + sx = 0 + sy = 50 + dx = sx + dy = sy + state.fons.set_font(state.font_normal) + state.fons.set_size(100.0) + ascender := f32(0.0) + descender := f32(0.0) + state.fons.vert_metrics(&ascender, &descender, &lh) + dx = sx + dy += lh + C.fonsSetColor(state.fons, white) + dx = C.fonsDrawText(state.fons, dx, dy, c'The quick ', C.NULL) + C.fonsSetFont(state.fons, state.font_normal) + C.fonsSetSize(state.fons, 48.0) + C.fonsSetColor(state.fons, brown) + dx = C.fonsDrawText(state.fons, dx, dy, c'brown ', C.NULL) + C.fonsSetFont(state.fons, state.font_normal) + C.fonsSetSize(state.fons, 24.0) + C.fonsSetColor(state.fons, white) + dx = C.fonsDrawText(state.fons, dx, dy, c'fox ', C.NULL) + dx = sx + dy += lh * 1.2 + C.fonsSetSize(state.fons, 20.0) + C.fonsSetFont(state.fons, state.font_normal) + C.fonsSetColor(state.fons, blue) + C.fonsDrawText(state.fons, dx, dy, c'Now is the time for all good men to come to the aid of the party.', + C.NULL) + dx = 300 + dy = 350 + C.fonsSetAlign(state.fons, C.FONS_ALIGN_LEFT | C.FONS_ALIGN_BASELINE) + C.fonsSetSize(state.fons, 60.0) + C.fonsSetFont(state.fons, state.font_normal) + C.fonsSetColor(state.fons, white) + C.fonsSetSpacing(state.fons, 5.0) + C.fonsSetBlur(state.fons, 6.0) + C.fonsDrawText(state.fons, dx, dy, c'Blurry...', C.NULL) + dx = 300 + dy += 50.0 + C.fonsSetSize(state.fons, 28.0) + C.fonsSetFont(state.fons, state.font_normal) + C.fonsSetColor(state.fons, white) + C.fonsSetSpacing(state.fons, 0.0) + C.fonsSetBlur(state.fons, 3.0) + C.fonsDrawText(state.fons, dx, dy + 2, c'DROP SHADOW', C.NULL) + C.fonsSetColor(state.fons, black) + C.fonsSetBlur(state.fons, 0) + C.fonsDrawText(state.fons, dx, dy, c'DROP SHADOW', C.NULL) + C.fonsSetSize(state.fons, 18.0) + C.fonsSetFont(state.fons, state.font_normal) + C.fonsSetColor(state.fons, white) + dx = 50 + dy = 350 + line(f32(dx - 10), f32(dy), f32(dx + 250), f32(dy)) + C.fonsSetAlign(state.fons, C.FONS_ALIGN_LEFT | C.FONS_ALIGN_TOP) + dx = C.fonsDrawText(state.fons, dx, dy, c'Top', C.NULL) + dx += 10 + C.fonsSetAlign(state.fons, C.FONS_ALIGN_LEFT | C.FONS_ALIGN_MIDDLE) + dx = C.fonsDrawText(state.fons, dx, dy, c'Middle', C.NULL) + dx += 10 + C.fonsSetAlign(state.fons, C.FONS_ALIGN_LEFT | C.FONS_ALIGN_BASELINE) + dx = C.fonsDrawText(state.fons, dx, dy, c'Baseline', C.NULL) + dx += 10 + C.fonsSetAlign(state.fons, C.FONS_ALIGN_LEFT | C.FONS_ALIGN_BOTTOM) + C.fonsDrawText(state.fons, dx, dy, c'Bottom', C.NULL) + dx = 150 + dy = 400 + line(f32(dx), f32(dy - 30), f32(dx), f32(dy + 80.0)) + C.fonsSetAlign(state.fons, C.FONS_ALIGN_LEFT | C.FONS_ALIGN_BASELINE) + C.fonsDrawText(state.fons, dx, dy, c'Left', C.NULL) + dy += 30 + C.fonsSetAlign(state.fons, C.FONS_ALIGN_CENTER | C.FONS_ALIGN_BASELINE) + C.fonsDrawText(state.fons, dx, dy, c'Center', C.NULL) + dy += 30 + C.fonsSetAlign(state.fons, C.FONS_ALIGN_RIGHT | C.FONS_ALIGN_BASELINE) + C.fonsDrawText(state.fons, dx, dy, c'Right', C.NULL) + C.sfons_flush(state.fons) +} + +fn line(sx f32, sy f32, ex f32, ey f32) { + sgl.begin_lines() + sgl.c4b(255, 255, 0, 128) + sgl.v2f(sx, sy) + sgl.v2f(ex, ey) + sgl.end() +} diff --git a/v_windows/v/examples/sokol/freetype_raven.v b/v_windows/v/examples/sokol/freetype_raven.v new file mode 100644 index 0000000..d65b01f --- /dev/null +++ b/v_windows/v/examples/sokol/freetype_raven.v @@ -0,0 +1,153 @@ +import sokol +import sokol.sapp +import sokol.gfx +import sokol.sgl +import sokol.sfons +import os +import time + +const ( + text = ' +Once upon a midnight dreary, while I pondered, weak and weary, +Over many a quaint and curious volume of forgotten lore— + While I nodded, nearly napping, suddenly there came a tapping, +As of some one gently rapping, rapping at my chamber door. +“’Tis some visitor,” I muttered, “tapping at my chamber door— + Only this and nothing more.” + + Ah, distinctly I remember it was in the bleak December; +And each separate dying ember wrought its ghost upon the floor. + Eagerly I wished the morrow;—vainly I had sought to borrow + From my books surcease of sorrow—sorrow for the lost Lenore— +For the rare and radiant maiden whom the angels name Lenore— + Nameless here for evermore. + + And the silken, sad, uncertain rustling of each purple curtain +Thrilled me—filled me with fantastic terrors never felt before; + So that now, to still the beating of my heart, I stood repeating + “’Tis some visitor entreating entrance at my chamber door— +Some late visitor entreating entrance at my chamber door;— + This it is and nothing more.” + + Presently my soul grew stronger; hesitating then no longer, +“Sir,” said I, “or Madam, truly your forgiveness I implore; + But the fact is I was napping, and so gently you came rapping, + And so faintly you came tapping, tapping at my chamber door, +That I scarce was sure I heard you”—here I opened wide the door;— + Darkness there and nothing more. + +Deep into that darkness peering, long I stood there wondering, fearing, +Doubting, dreaming dreams no mortal ever dared to dream before; + But the silence was unbroken, and the stillness gave no token, + And the only word there spoken was the whispered word, “Lenore?” +This I whispered, and an echo murmured back the word, “Lenore!”— + Merely this and nothing more. + + Back into the chamber turning, all my soul within me burning, +Soon again I heard a tapping somewhat louder than before. + “Surely,” said I, “surely that is something at my window lattice; + Let me see, then, what thereat is, and this mystery explore— +Let my heart be still a moment and this mystery explore;— + ’Tis the wind and nothing more!” +' + lines = text.split('\n') +) + +struct AppState { +mut: + pass_action C.sg_pass_action + fons &C.FONScontext + font_normal int + inited bool +} + +fn main() { + mut color_action := C.sg_color_attachment_action{ + action: gfx.Action(C.SG_ACTION_CLEAR) + value: C.sg_color{ + r: 1.0 + g: 1.0 + b: 1.0 + a: 1.0 + } + } + mut pass_action := C.sg_pass_action{} + pass_action.colors[0] = color_action + state := &AppState{ + pass_action: pass_action + fons: &C.FONScontext(0) + } + title := 'V Metal/GL Text Rendering' + desc := C.sapp_desc{ + user_data: state + init_userdata_cb: init + frame_userdata_cb: frame + window_title: title.str + html5_canvas_name: title.str + width: 600 + height: 700 + high_dpi: true + } + sapp.run(&desc) +} + +fn init(user_data voidptr) { + mut state := &AppState(user_data) + desc := sapp.create_desc() + gfx.setup(&desc) + s := &C.sgl_desc_t{} + C.sgl_setup(s) + state.fons = sfons.create(512, 512, 1) + // or use DroidSerif-Regular.ttf + if bytes := os.read_bytes(os.resource_abs_path('../assets/fonts/RobotoMono-Regular.ttf')) { + println('loaded font: $bytes.len') + state.font_normal = C.fonsAddFontMem(state.fons, c'sans', bytes.data, bytes.len, + false) + } +} + +fn frame(user_data voidptr) { + t := time.ticks() + mut state := &AppState(user_data) + state.render_font() + gfx.begin_default_pass(&state.pass_action, sapp.width(), sapp.height()) + sgl.draw() + gfx.end_pass() + gfx.commit() + println(time.ticks() - t) +} + +const ( + black = C.sfons_rgba(0, 0, 0, 255) +) + +fn (mut state AppState) render_font() { + lh := 30 + mut dy := lh + if !state.inited { + state.fons.clear_state() + sgl.defaults() + sgl.matrix_mode_projection() + sgl.ortho(0.0, f32(C.sapp_width()), f32(C.sapp_height()), 0.0, -1.0, 1.0) + state.fons.set_font(state.font_normal) + state.fons.set_size(100.0) + C.fonsSetColor(state.fons, black) + C.fonsSetFont(state.fons, state.font_normal) + C.fonsSetSize(state.fons, 35.0) + state.inited = true + } + + for line in lines { + C.fonsDrawText(state.fons, 40, dy, line.str, C.NULL) + dy += lh + } + C.sfons_flush(state.fons) +} + +fn line(sx f32, sy f32, ex f32, ey f32) { + sgl.begin_lines() + sgl.c4b(255, 255, 0, 128) + sgl.v2f(sx, sy) + sgl.v2f(ex, ey) + sgl.end() +} diff --git a/v_windows/v/examples/sokol/particles/modules/particle/LICENSE b/v_windows/v/examples/sokol/particles/modules/particle/LICENSE new file mode 100644 index 0000000..1595a57 --- /dev/null +++ b/v_windows/v/examples/sokol/particles/modules/particle/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Lars Pontoppidan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/v_windows/v/examples/sokol/particles/modules/particle/color.v b/v_windows/v/examples/sokol/particles/modules/particle/color.v new file mode 100644 index 0000000..535e48a --- /dev/null +++ b/v_windows/v/examples/sokol/particles/modules/particle/color.v @@ -0,0 +1,12 @@ +// Copyright(C) 2019 Lars Pontoppidan. All rights reserved. +// Use of this source code is governed by an MIT license file distributed with this software package +module particle + +// * Color +pub struct Color { +mut: + r byte + g byte + b byte + a byte +} diff --git a/v_windows/v/examples/sokol/particles/modules/particle/particle.v b/v_windows/v/examples/sokol/particles/modules/particle/particle.v new file mode 100644 index 0000000..ac172b9 --- /dev/null +++ b/v_windows/v/examples/sokol/particles/modules/particle/particle.v @@ -0,0 +1,81 @@ +// Copyright(C) 2019 Lars Pontoppidan. All rights reserved. +// Use of this source code is governed by an MIT license file distributed with this software package +module particle + +import particle.vec2 +import sokol.sgl + +const ( + default_life_time = 1000 + default_v_color = Color{93, 136, 193, 255} +) + +// * Module public +pub fn new(location vec2.Vec2) &Particle { + p := &Particle{ + location: location + velocity: vec2.Vec2{0, 0} + acceleration: vec2.Vec2{0, 0} + color: particle.default_v_color + life_time: particle.default_life_time + life_time_init: particle.default_life_time + } + return p +} + +fn remap(v f64, min f64, max f64, new_min f64, new_max f64) f64 { + return (((v - min) * (new_max - new_min)) / (max - min)) + new_min +} + +// Particle +pub struct Particle { +mut: + location vec2.Vec2 + velocity vec2.Vec2 + acceleration vec2.Vec2 + color Color + life_time f64 + life_time_init f64 +} + +pub fn (mut p Particle) update(dt f64) { + mut acc := p.acceleration + acc.multiply_f64(dt) + p.velocity = p.velocity.add(acc) + p.location = p.location.add(p.velocity) + lt := p.life_time - (1000 * dt) + if lt > 0 { + p.life_time = lt + p.color.r = p.color.r - 1 // byte(remap(p.life_time,0.0,p.life_time_init,0,p.color.r)) + p.color.g = p.color.g - 1 // byte(remap(p.life_time,0.0,p.life_time_init,0,p.color.g)) + p.color.b = p.color.b - 1 // byte(remap(p.life_time,0.0,p.life_time_init,0,p.color.b)) + p.color.a = byte(int(remap(p.life_time, 0.0, p.life_time_init, 0, 255))) - 10 + } else { + p.life_time = 0 + } +} + +pub fn (p Particle) is_dead() bool { + return p.life_time <= 0.0 +} + +pub fn (p Particle) draw() { + l := p.location + sgl.c4b(p.color.r, p.color.g, p.color.b, p.color.a) + lx := f32(l.x) + ly := f32(l.y) + sgl.v2f(lx, ly) + sgl.v2f(lx + 2, ly) + sgl.v2f(lx + 2, ly + 2) + sgl.v2f(lx, ly + 2) +} + +pub fn (mut p Particle) reset() { + p.location.zero() + p.acceleration.zero() + p.velocity.zero() + // p.color = Color{93, 136, 193, 255} + p.color = particle.default_v_color + p.life_time = particle.default_life_time + p.life_time_init = p.life_time +} diff --git a/v_windows/v/examples/sokol/particles/modules/particle/system.v b/v_windows/v/examples/sokol/particles/modules/particle/system.v new file mode 100644 index 0000000..4f0382b --- /dev/null +++ b/v_windows/v/examples/sokol/particles/modules/particle/system.v @@ -0,0 +1,99 @@ +// Copyright(C) 2019 Lars Pontoppidan. All rights reserved. +// Use of this source code is governed by an MIT license file distributed with this software package +module particle + +import particle.vec2 +import rand +import sokol.sgl + +pub struct SystemConfig { + pool int +} + +pub struct System { + width int + height int +mut: + pool []&Particle + bin []&Particle +} + +pub fn (mut s System) init(sc SystemConfig) { + for i := 0; i < sc.pool; i++ { + p := new(vec2.Vec2{f32(s.width) * 0.5, f32(s.height) * 0.5}) + s.bin << p + } +} + +pub fn (mut s System) update(dt f64) { + mut p := &Particle(0) + for i := 0; i < s.pool.len; i++ { + p = s.pool[i] + p.update(dt) + if p.is_dead() { + s.bin << p + s.pool.delete(i) + } + } +} + +pub fn (s System) draw() { + sgl.begin_quads() + for p in s.pool { + p.draw() + } + sgl.end() +} + +pub fn (mut s System) reset() { + for i in 0 .. s.pool.len { + mut p := s.pool[i] + p.reset() + p.life_time = 0 + } + for i in 0 .. s.bin.len { + mut p := s.pool[i] + p.reset() + p.life_time = 0 + } +} + +pub fn (mut s System) explode(x f32, y f32) { + mut reserve := 500 + center := vec2.Vec2{x, y} + mut p := &Particle(0) + for i := 0; i < s.bin.len && reserve > 0; i++ { + p = s.bin[i] + p.reset() + p.location.from(center) + p.acceleration = vec2.Vec2{rand.f32_in_range(-0.5, 0.5), rand.f32_in_range(-0.5, + 0.5)} + p.velocity = vec2.Vec2{rand.f32_in_range(-0.5, 0.5), rand.f32_in_range(-0.5, 0.5)} + p.life_time = rand.f64_in_range(500, 2000) + s.pool << p + s.bin.delete(i) + reserve-- + } +} + +pub fn (mut s System) free() { + for p in s.pool { + if p == 0 { + print(ptr_str(p) + ' ouch') + continue + } + unsafe { free(p) } + } + s.pool.clear() + for p in s.bin { + if p == 0 { + print(ptr_str(p) + ' ouch') + continue + } + unsafe { + // println('Freeing from bin') + free(p) + } + } + s.bin.clear() +} diff --git a/v_windows/v/examples/sokol/particles/modules/particle/v.mod b/v_windows/v/examples/sokol/particles/modules/particle/v.mod new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/v_windows/v/examples/sokol/particles/modules/particle/v.mod diff --git a/v_windows/v/examples/sokol/particles/modules/particle/vec2/v.mod b/v_windows/v/examples/sokol/particles/modules/particle/vec2/v.mod new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/v_windows/v/examples/sokol/particles/modules/particle/vec2/v.mod diff --git a/v_windows/v/examples/sokol/particles/modules/particle/vec2/vec2.v b/v_windows/v/examples/sokol/particles/modules/particle/vec2/vec2.v new file mode 100644 index 0000000..ec41485 --- /dev/null +++ b/v_windows/v/examples/sokol/particles/modules/particle/vec2/vec2.v @@ -0,0 +1,89 @@ +// Copyright(C) 2019 Lars Pontoppidan. All rights reserved. +// Use of this source code is governed by an MIT license file distributed with this software package +module vec2 + +pub struct Vec2 { +pub mut: + x f64 + y f64 +} + +pub fn (mut v Vec2) zero() { + v.x = 0.0 + v.y = 0.0 +} + +pub fn (mut v Vec2) from(src Vec2) { + v.x = src.x + v.y = src.y +} + +// * Addition +// + operator overload. Adds two vectors +pub fn (v1 Vec2) + (v2 Vec2) Vec2 { + return Vec2{v1.x + v2.x, v1.y + v2.y} +} + +pub fn (v Vec2) add(vector Vec2) Vec2 { + return Vec2{v.x + vector.x, v.y + vector.y} +} + +pub fn (v Vec2) add_f64(scalar f64) Vec2 { + return Vec2{v.x + scalar, v.y + scalar} +} + +pub fn (mut v Vec2) plus(vector Vec2) { + v.x += vector.x + v.y += vector.y +} + +pub fn (mut v Vec2) plus_f64(scalar f64) { + v.x += scalar + v.y += scalar +} + +// * Subtraction +pub fn (v1 Vec2) - (v2 Vec2) Vec2 { + return Vec2{v1.x - v2.x, v1.y - v2.y} +} + +pub fn (v Vec2) sub(vector Vec2) Vec2 { + return Vec2{v.x - vector.x, v.y - vector.y} +} + +pub fn (v Vec2) sub_f64(scalar f64) Vec2 { + return Vec2{v.x - scalar, v.y - scalar} +} + +pub fn (mut v Vec2) subtract(vector Vec2) { + v.x -= vector.x + v.y -= vector.y +} + +pub fn (mut v Vec2) subtract_f64(scalar f64) { + v.x -= scalar + v.y -= scalar +} + +// * Multiplication +pub fn (v1 Vec2) * (v2 Vec2) Vec2 { + return Vec2{v1.x * v2.x, v1.y * v2.y} +} + +pub fn (v Vec2) mul(vector Vec2) Vec2 { + return Vec2{v.x * vector.x, v.y * vector.y} +} + +pub fn (v Vec2) mul_f64(scalar f64) Vec2 { + return Vec2{v.x * scalar, v.y * scalar} +} + +pub fn (mut v Vec2) multiply(vector Vec2) { + v.x *= vector.x + v.y *= vector.y +} + +pub fn (mut v Vec2) multiply_f64(scalar f64) { + v.x *= scalar + v.y *= scalar +} diff --git a/v_windows/v/examples/sokol/particles/particles.v b/v_windows/v/examples/sokol/particles/particles.v new file mode 100644 index 0000000..05b0cb3 --- /dev/null +++ b/v_windows/v/examples/sokol/particles/particles.v @@ -0,0 +1,155 @@ +// Copyright(C) 2019 Lars Pontoppidan. All rights reserved. +// Use of this source code is governed by an MIT license file distributed with this software package +module main + +import time +import sokol +import sokol.sapp +import sokol.gfx +import sokol.sgl +import particle + +const ( + used_import = sokol.used_import +) + +fn main() { + mut app := &App{ + width: 800 + height: 400 + pass_action: gfx.create_clear_pass(0.1, 0.1, 0.1, 1.0) + } + app.init() + app.run() +} + +struct App { + pass_action C.sg_pass_action +mut: + width int + height int + frame i64 + last i64 + ps particle.System + alpha_pip C.sgl_pipeline +} + +fn (mut a App) init() { + a.frame = 0 + a.last = time.ticks() + a.ps = particle.System{ + width: a.width + height: a.height + } + a.ps.init(particle.SystemConfig{ + pool: 20000 + }) +} + +fn (mut a App) cleanup() { + a.ps.free() +} + +fn (mut a App) run() { + title := 'V Particle Example' + desc := C.sapp_desc{ + width: a.width + height: a.height + user_data: a + init_userdata_cb: init + frame_userdata_cb: frame + event_userdata_cb: event + window_title: title.str + html5_canvas_name: title.str + cleanup_userdata_cb: cleanup + } + sapp.run(&desc) +} + +fn (a App) draw() { + sgl.load_pipeline(a.alpha_pip) + a.ps.draw() +} + +fn init(user_data voidptr) { + mut app := &App(user_data) + desc := sapp.create_desc() + gfx.setup(&desc) + sgl_desc := C.sgl_desc_t{ + max_vertices: 50 * 65536 + } + sgl.setup(&sgl_desc) + mut pipdesc := C.sg_pipeline_desc{} + unsafe { C.memset(&pipdesc, 0, sizeof(pipdesc)) } + + color_state := C.sg_color_state{ + blend: C.sg_blend_state{ + enabled: true + src_factor_rgb: gfx.BlendFactor(C.SG_BLENDFACTOR_SRC_ALPHA) + dst_factor_rgb: gfx.BlendFactor(C.SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA) + } + } + pipdesc.colors[0] = color_state + + app.alpha_pip = sgl.make_pipeline(&pipdesc) +} + +fn cleanup(user_data voidptr) { + mut app := &App(user_data) + app.cleanup() + gfx.shutdown() +} + +fn frame(user_data voidptr) { + mut app := &App(user_data) + app.width = sapp.width() + app.height = sapp.height() + t := time.ticks() + dt := f64(t - app.last) / 1000.0 + app.ps.update(dt) + draw(app) + gfx.begin_default_pass(&app.pass_action, app.width, app.height) + sgl.default_pipeline() + sgl.draw() + gfx.end_pass() + gfx.commit() + app.frame++ + app.last = t +} + +fn event(ev &C.sapp_event, mut app App) { + if ev.@type == .mouse_move { + app.ps.explode(ev.mouse_x, ev.mouse_y) + } + if ev.@type == .mouse_up || ev.@type == .mouse_down { + if ev.mouse_button == .left { + is_pressed := ev.@type == .mouse_down + if is_pressed { + app.ps.explode(ev.mouse_x, ev.mouse_y) + } + } + } + if ev.@type == .key_up || ev.@type == .key_down { + if ev.key_code == .r { + is_pressed := ev.@type == .key_down + if is_pressed { + app.ps.reset() + } + } + } + if ev.@type == .touches_began || ev.@type == .touches_moved { + if ev.num_touches > 0 { + touch_point := ev.touches[0] + app.ps.explode(touch_point.pos_x, touch_point.pos_y) + } + } +} + +fn draw(a &App) { + sgl.defaults() + sgl.matrix_mode_projection() + sgl.ortho(0, f32(sapp.width()), f32(sapp.height()), 0.0, -1.0, 1.0) + sgl.push_matrix() + a.draw() + sgl.pop_matrix() +} diff --git a/v_windows/v/examples/sokol/sounds/melody.v b/v_windows/v/examples/sokol/sounds/melody.v new file mode 100644 index 0000000..aa5ebc4 --- /dev/null +++ b/v_windows/v/examples/sokol/sounds/melody.v @@ -0,0 +1,75 @@ +import gg +import gx +import sokol.audio + +const credits = 'Based on the ByteBeat formula from: https://www.youtube.com/watch?v=V4GfkFbDojc \n "Techno" by Gabriel Miceli' + +struct AppState { +mut: + gframe int // the current graphical frame + frame_0 int // offset of the current audio frames, relative to the start of the music + frames [2048]f32 // a copy of the last rendered audio frames + gg &gg.Context // used for drawing +} + +fn my_audio_stream_callback(buffer &f32, num_frames int, num_channels int, mut acontext AppState) { + mut soundbuffer := unsafe { buffer } + for frame := 0; frame < num_frames; frame++ { + t := int(f32(acontext.frame_0 + frame) * 0.245) + // "Techno" by Gabriel Miceli + y := (t * (((t / 10 | 0) ^ ((t / 10 | 0) - 1280)) % 11) / 2 & 127) + + (t * (((t / 640 | 0) ^ ((t / 640 | 0) - 2)) % 13) / 2 & 127) + for ch := 0; ch < num_channels; ch++ { + idx := frame * num_channels + ch + unsafe { + a := f32(byte(y) - 127) / 255.0 + soundbuffer[idx] = a + acontext.frames[idx & 2047] = a + } + } + } + acontext.frame_0 += num_frames +} + +fn main() { + println(credits) + mut state := &AppState{ + gg: 0 + } + audio.setup( + stream_userdata_cb: my_audio_stream_callback + user_data: state + ) + state.gg = gg.new_context( + bg_color: gx.rgb(50, 50, 50) + width: 1024 + height: 400 + create_window: true + window_title: 'ByteBeat Music' + frame_fn: graphics_frame + user_data: state + ) + state.gg.run() + audio.shutdown() +} + +fn graphics_frame(mut state AppState) { + state.gframe++ + state.gg.begin() + state.draw() + state.gg.end() +} + +[inline] +fn (mut state AppState) bsample(idx int) byte { + return byte(127 + state.frames[(state.gframe + idx) & 2047] * 128) +} + +fn (mut state AppState) draw() { + // first, reset and setup ortho projection + for x in 0 .. 1024 { + mut y := 100 * (state.frames[2 * x] + state.frames[2 * x + 1]) + state.gg.draw_line(x, 200, x, 200 + y, gx.rgba(state.bsample(x), state.bsample(x + 300), + state.bsample(x + 700), 255)) + } +} diff --git a/v_windows/v/examples/sokol/sounds/simple_sin_tones.v b/v_windows/v/examples/sokol/sounds/simple_sin_tones.v new file mode 100644 index 0000000..c327117 --- /dev/null +++ b/v_windows/v/examples/sokol/sounds/simple_sin_tones.v @@ -0,0 +1,42 @@ +import time +import math +import sokol.audio + +const ( + sw = time.new_stopwatch() + sw_start_ms = sw.elapsed().milliseconds() +) + +[inline] +fn sintone(periods int, frame int, num_frames int) f32 { + return math.sinf(f32(periods) * (2 * math.pi) * f32(frame) / f32(num_frames)) +} + +fn my_audio_stream_callback(buffer &f32, num_frames int, num_channels int) { + ms := sw.elapsed().milliseconds() - sw_start_ms + unsafe { + mut soundbuffer := buffer + for frame := 0; frame < num_frames; frame++ { + for ch := 0; ch < num_channels; ch++ { + idx := frame * num_channels + ch + if ms < 250 { + soundbuffer[idx] = 0.5 * sintone(20, frame, num_frames) + } else if ms < 300 { + soundbuffer[idx] = 0.5 * sintone(25, frame, num_frames) + } else if ms < 1500 { + soundbuffer[idx] *= sintone(22, frame, num_frames) + } else { + soundbuffer[idx] = 0.5 * sintone(25, frame, num_frames) + } + } + } + } +} + +fn main() { + audio.setup( + stream_cb: my_audio_stream_callback + ) + time.sleep(2000 * time.millisecond) + audio.shutdown() +} diff --git a/v_windows/v/examples/sokol/sounds/uhoh.wav b/v_windows/v/examples/sokol/sounds/uhoh.wav Binary files differnew file mode 100644 index 0000000..cff9e3c --- /dev/null +++ b/v_windows/v/examples/sokol/sounds/uhoh.wav diff --git a/v_windows/v/examples/sokol/sounds/wav_player.v b/v_windows/v/examples/sokol/sounds/wav_player.v new file mode 100644 index 0000000..3c9419b --- /dev/null +++ b/v_windows/v/examples/sokol/sounds/wav_player.v @@ -0,0 +1,209 @@ +import os +import time +import sokol.audio + +struct Player { +mut: + samples []f32 + pos int + finished bool +} + +fn main() { + if os.args.len < 2 { + eprintln('Usage: play_wav file1.wav file2.wav ...') + play_sounds([os.resource_abs_path('uhoh.wav')]) ? + exit(1) + } + play_sounds(os.args[1..]) ? +} + +fn play_sounds(files []string) ? { + mut player := Player{} + player.init() + for f in files { + if !os.exists(f) || os.is_dir(f) { + eprintln('skipping "$f" (does not exist)') + continue + } + fext := os.file_ext(f).to_lower() + if fext != '.wav' { + eprintln('skipping "$f" (not a .wav file)') + continue + } + player.play_wav_file(f) ? + } + player.stop() +} + +// +fn audio_player_callback(buffer &f32, num_frames int, num_channels int, mut p Player) { + if p.finished { + return + } + ntotal := num_channels * num_frames + nremaining := p.samples.len - p.pos + nsamples := if nremaining < ntotal { nremaining } else { ntotal } + if nsamples <= 0 { + p.finished = true + return + } + unsafe { C.memcpy(buffer, &p.samples[p.pos], nsamples * int(sizeof(f32))) } + p.pos += nsamples +} + +fn (mut p Player) init() { + audio.setup( + num_channels: 2 + stream_userdata_cb: audio_player_callback + user_data: p + ) +} + +fn (mut p Player) stop() { + audio.shutdown() + p.free() +} + +fn (mut p Player) play_wav_file(fpath string) ? { + println('> play_wav_file: $fpath') + samples := read_wav_file_samples(fpath) ? + p.finished = true + p.samples << samples + p.finished = false + for !p.finished { + time.sleep(16 * time.millisecond) + } + p.free() +} + +fn (mut p Player) free() { + p.finished = false + p.samples = []f32{} + p.pos = 0 +} + +// The read_wav_file_samples function below is based on the following sources: +// http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html +// http://www.lightlink.com/tjweber/StripWav/WAVE.html +// http://www.lightlink.com/tjweber/StripWav/Canon.html +// https://tools.ietf.org/html/draft-ema-vpim-wav-00 +// NB: > The chunks MAY appear in any order except that the Format chunk +// > MUST be placed before the Sound data chunk (but not necessarily +// > contiguous to the Sound data chunk). +struct RIFFHeader { + riff [4]byte + file_size u32 + form_type [4]byte +} + +struct RIFFChunkHeader { + chunk_type [4]byte + chunk_size u32 + chunk_data voidptr +} + +struct RIFFFormat { + format_tag u16 // PCM = 1; Values other than 1 indicate some form of compression. + nchannels u16 // Nc ; 1 = mono ; 2 = stereo + sample_rate u32 // F + avg_bytes_per_second u32 // F * M*Nc + nblock_align u16 // M*Nc + bits_per_sample u16 // 8 * M + cbsize u16 // Size of the extension: 22 + valid_bits_per_sample u16 // at most 8*M + channel_mask u32 // Speaker position mask + sub_format [16]byte // GUID +} + +fn read_wav_file_samples(fpath string) ?[]f32 { + mut res := []f32{} + // eprintln('> read_wav_file_samples: $fpath -------------------------------------------------') + mut bytes := os.read_bytes(fpath) ? + mut pbytes := &byte(bytes.data) + mut offset := u32(0) + rh := unsafe { &RIFFHeader(pbytes) } + // eprintln('rh: $rh') + if rh.riff != [byte(`R`), `I`, `F`, `F`]! { + return error('WAV should start with `RIFF`') + } + if rh.form_type != [byte(`W`), `A`, `V`, `E`]! { + return error('WAV should have `WAVE` form type') + } + if rh.file_size + 8 != bytes.len { + return error('WAV should have valid lenght') + } + offset += sizeof(RIFFHeader) + mut rf := &RIFFFormat(0) + for { + if offset >= bytes.len { + break + } + // + ch := unsafe { &RIFFChunkHeader(pbytes + offset) } + offset += 8 + ch.chunk_size + // eprintln('ch: $ch') + // eprintln('p: $pbytes | offset: $offset | bytes.len: $bytes.len') + // //////// + if ch.chunk_type == [byte(`L`), `I`, `S`, `T`]! { + continue + } + // + if ch.chunk_type == [byte(`i`), `d`, `3`, ` `]! { + continue + } + // + if ch.chunk_type == [byte(`f`), `m`, `t`, ` `]! { + // eprintln('`fmt ` chunk') + rf = unsafe { &RIFFFormat(&ch.chunk_data) } + // eprintln('fmt riff format: $rf') + if rf.format_tag != 1 { + return error('only PCM encoded WAVs are supported') + } + if rf.nchannels < 1 || rf.nchannels > 2 { + return error('only mono or stereo WAVs are supported') + } + if rf.bits_per_sample !in [u16(8), 16] { + return error('only 8 or 16 bits per sample WAVs are supported') + } + continue + } + // + if ch.chunk_type == [byte(`d`), `a`, `t`, `a`]! { + if rf == 0 { + return error('`data` chunk should be after `fmt ` chunk') + } + // eprintln('`fmt ` chunk: $rf\n`data` chunk: $ch') + mut doffset := 0 + mut dp := unsafe { &byte(&ch.chunk_data) } + for doffset < ch.chunk_size { + for c := 0; c < rf.nchannels; c++ { + mut x := f32(0.0) + mut step := 0 + ppos := unsafe { dp + doffset } + if rf.bits_per_sample == 8 { + d8 := unsafe { &byte(ppos) } + x = (f32(*d8) - 128) / 128.0 + step = 1 + doffset++ + } + if rf.bits_per_sample == 16 { + d16 := unsafe { &i16(ppos) } + x = f32(*d16) / 32768.0 + step = 2 + } + doffset += step + if doffset < ch.chunk_size { + res << x + if rf.nchannels == 1 { + // Duplicating single channel mono sounds, + // produces a stereo sound, simplifying further processing: + res << x + } + } + } + } + } + } + return res +} diff --git a/v_windows/v/examples/spectral.v b/v_windows/v/examples/spectral.v new file mode 100644 index 0000000..88451af --- /dev/null +++ b/v_windows/v/examples/spectral.v @@ -0,0 +1,68 @@ +/* +https://benchmarksgame-team.pages.debian.net/benchmarksgame/performance/spectralnorm.html +Added: Pradeep Verghese +Benchmarks: +Used v -prod spectral.v +Command: time ./spectral 5500 +Output: 1.274224153 + +Time: 11.67s user 0.02s system 99% cpu 11.721 total +*/ +module main + +import math +import os +import strconv + +fn evala(i int, j int) int { + return ((i + j) * (i + j + 1) / 2 + i + 1) +} + +fn times(mut v []f64, u []f64) { + for i in 0 .. v.len { + mut a := f64(0) + for j in 0 .. u.len { + a += u[j] / f64(evala(i, j)) + } + v[i] = a + } +} + +fn times_trans(mut v []f64, u []f64) { + for i in 0 .. v.len { + mut a := f64(0) + for j in 0 .. u.len { + a += u[j] / f64(evala(j, i)) + } + v[i] = a + } +} + +fn a_times_transp(mut v []f64, u []f64) { + mut x := []f64{len: u.len, init: 0} + times(mut x, u) + times_trans(mut v, x) +} + +fn main() { + mut n := 0 + if os.args.len == 2 { + n = strconv.atoi(os.args[1]) or { 0 } + } else { + n = 0 + } + mut u := []f64{len: n, init: 1} + mut v := []f64{len: n, init: 1} + for _ in 0 .. 10 { + a_times_transp(mut v, u) + a_times_transp(mut u, v) + } + mut vbv := f64(0) + mut vv := f64(0) + for i in 0 .. n { + vbv += u[i] * v[i] + vv += v[i] * v[i] + } + ans := math.sqrt(vbv / vv) + println('${ans:0.9f}') +} diff --git a/v_windows/v/examples/submodule/main.v b/v_windows/v/examples/submodule/main.v new file mode 100644 index 0000000..e1ae1db --- /dev/null +++ b/v_windows/v/examples/submodule/main.v @@ -0,0 +1,7 @@ +import mymodules { add_xy } +import mymodules.submodule { sub_xy } + +fn main() { + println(add_xy(2, 3)) // expected: 5 + println(sub_xy(10, 7)) // expected: 3 +} diff --git a/v_windows/v/examples/submodule/mymodules/main_functions.v b/v_windows/v/examples/submodule/mymodules/main_functions.v new file mode 100644 index 0000000..0697d36 --- /dev/null +++ b/v_windows/v/examples/submodule/mymodules/main_functions.v @@ -0,0 +1,5 @@ +module mymodule + +pub fn add_xy(x int, y int) int { + return x + y +} diff --git a/v_windows/v/examples/submodule/mymodules/submodule/sub_functions.v b/v_windows/v/examples/submodule/mymodules/submodule/sub_functions.v new file mode 100644 index 0000000..15448ec --- /dev/null +++ b/v_windows/v/examples/submodule/mymodules/submodule/sub_functions.v @@ -0,0 +1,5 @@ +module submodule + +pub fn sub_xy(x int, y int) int { + return x - y +} diff --git a/v_windows/v/examples/tcp_echo_server.v b/v_windows/v/examples/tcp_echo_server.v new file mode 100644 index 0000000..e5a0973 --- /dev/null +++ b/v_windows/v/examples/tcp_echo_server.v @@ -0,0 +1,40 @@ +import io +import net + +// This file shows how a basic TCP echo server can be implemented using +// the net module. You can connect to the server by using netcat or telnet, +// in separate shells, for example: +// nc 127.0.0.1 12345 +// or +// telnet 127.0.0.1 12345 + +fn main() { + mut server := net.listen_tcp(.ip6, ':12345') ? + laddr := server.addr() ? + eprintln('Listen on $laddr ...') + for { + mut socket := server.accept() ? + go handle_client(mut socket) + } +} + +fn handle_client(mut socket net.TcpConn) { + defer { + socket.close() or { panic(err) } + } + client_addr := socket.peer_addr() or { return } + eprintln('> new client: $client_addr') + mut reader := io.new_buffered_reader(reader: socket) + defer { + reader.free() + } + socket.write_string('server: hello\n') or { return } + for { + received_line := reader.read_line() or { return } + if received_line == '' { + return + } + println('client $client_addr: $received_line') + socket.write_string('server: $received_line\n') or { return } + } +} diff --git a/v_windows/v/examples/tcp_notify_echo_server.v b/v_windows/v/examples/tcp_notify_echo_server.v new file mode 100644 index 0000000..0e2d0bf --- /dev/null +++ b/v_windows/v/examples/tcp_notify_echo_server.v @@ -0,0 +1,73 @@ +import os +import os.notify +import net +import time + +// This example demonstrates a single threaded TCP server using os.notify to be +// notified of events on file descriptors. You can connect to the server using +// netcat in separate shells, for example: `nc localhost 9001` + +fn main() { + $if !linux { + eprintln('This example only works on Linux') + exit(1) + } + + // create TCP listener + mut listener := net.listen_tcp(.ip, 'localhost:9001') ? + defer { + listener.close() or {} + } + addr := listener.addr() ? + eprintln('Listening on $addr') + eprintln('Type `stop` to stop the server') + + // create file descriptor notifier + mut notifier := notify.new() ? + defer { + notifier.close() or {} + } + notifier.add(os.stdin().fd, .read) ? + notifier.add(listener.sock.handle, .read) ? + + for { + for event in notifier.wait(time.infinite) { + match event.fd { + listener.sock.handle { + // someone is trying to connect + eprint('trying to connect.. ') + if conn := listener.accept() { + if _ := notifier.add(conn.sock.handle, .read | .peer_hangup) { + eprintln('connected') + } else { + eprintln('error adding to notifier: $err') + } + } else { + eprintln('unable to accept: $err') + } + } + 0 { + // stdin + s, _ := os.fd_read(event.fd, 10) + if s == 'stop\n' { + eprintln('stopping') + return + } + } + else { + // remote connection + if event.kind.has(.peer_hangup) { + if _ := notifier.remove(event.fd) { + eprintln('remote disconnected') + } else { + eprintln('error removing from notifier: $err') + } + } else { + s, _ := os.fd_read(event.fd, 10) + os.fd_write(event.fd, s) + } + } + } + } + } +} diff --git a/v_windows/v/examples/templates/data.json b/v_windows/v/examples/templates/data.json new file mode 100644 index 0000000..a6765cf --- /dev/null +++ b/v_windows/v/examples/templates/data.json @@ -0,0 +1,197 @@ +[ + { + "name": "www_threefold_io", + "url": "https://github.com/threefoldfoundation/www_threefold_io", + "branch": "default", + "pull": false, + "cat": 2, + "alias": "tf", + "path_code": "/Users/despiegk/codewww/github/threefoldfoundation/www_threefold_io", + "domains": [ + "www.threefold.io", + "www.threefold.me" + ], + "descr": "is our entry point for everyone, redirect to the detailed websites underneith." + }, + { + "name": "www_threefold_cloud", + "url": "https://github.com/threefoldfoundation/www_threefold_cloud", + "branch": "default", + "pull": false, + "cat": 2, + "alias": "cloud", + "path_code": "/Users/despiegk/codewww/github/threefoldfoundation/www_threefold_cloud", + "domains": [ + "cloud.threefold.io", + "cloud.threefold.me" + ], + "descr": "for people looking to deploy solutions on top of a cloud, alternative to e.g. digital ocean" + }, + { + "name": "www_threefold_farming", + "url": "https://github.com/threefoldfoundation/www_threefold_farming", + "branch": "default", + "pull": false, + "cat": 2, + "alias": "farming", + "path_code": "/Users/despiegk/codewww/github/threefoldfoundation/www_threefold_farming", + "domains": [ + "farming.threefold.io", + "farming.threefold.me" + ], + "descr": "crypto & minining enthusiasts, be the internet, know about farming & tokens." + }, + { + "name": "www_threefold_twin", + "url": "https://github.com/threefoldfoundation/www_threefold_twin", + "branch": "default", + "pull": false, + "cat": 2, + "alias": "twin", + "path_code": "/Users/despiegk/codewww/github/threefoldfoundation/www_threefold_twin", + "domains": [ + "twin.threefold.io", + "twin.threefold.me" + ], + "descr": "you digital life" + }, + { + "name": "www_threefold_marketplace", + "url": "https://github.com/threefoldfoundation/www_threefold_marketplace", + "branch": "default", + "pull": false, + "cat": 2, + "alias": "marketplace", + "path_code": "/Users/despiegk/codewww/github/threefoldfoundation/www_threefold_marketplace", + "domains": [ + "now.threefold.io", + "marketplace.threefold.io", + "now.threefold.me", + "marketplace.threefold.me" + ], + "descr": "apps for community builders, runs on top of evdc" + }, + { + "name": "www_conscious_internet", + "url": "https://github.com/threefoldfoundation/www_conscious_internet", + "branch": "default", + "pull": false, + "cat": 2, + "alias": "conscious_internet", + "path_code": "/Users/despiegk/codewww/github/threefoldfoundation/www_conscious_internet", + "domains": [ + "www.consciousinternet.org", + "eco.threefold.io", + "community.threefold.io", + "eco.threefold.me", + "community.threefold.me" + ], + "descr": "community around threefold, partners, friends, ..." + }, + { + "name": "www_threefold_tech", + "url": "https://github.com/threefoldtech/www_threefold_tech", + "branch": "default", + "pull": false, + "cat": 2, + "alias": "tech", + "path_code": "/Users/despiegk/codewww/github/threefoldtech/www_threefold_tech", + "domains": [ + "www.threefold.tech" + ], + "descr": "cyberpandemic, use the tech to build your own solutions with, certification for TFGrid" + }, + { + "name": "www_examplesite", + "url": "https://github.com/threefoldfoundation/www_examplesite", + "branch": "default", + "pull": false, + "cat": 2, + "alias": "example", + "path_code": "/Users/despiegk/codewww/github/threefoldfoundation/www_examplesite", + "domains": [ + "example.threefold.io" + ], + "descr": "" + }, + { + "name": "info_threefold", + "url": "https://github.com/threefoldfoundation/info_foundation_archive", + "branch": "default", + "pull": false, + "cat": 0, + "alias": "threefold", + "path_code": "/Users/despiegk/codewww/github/threefoldfoundation/info_foundation_archive", + "domains": [ + "info.threefold.io" + ], + "descr": "wiki for foundation, collaborate, what if farmings, tokens" + }, + { + "name": "info_sdk", + "url": "https://github.com/threefoldfoundation/info_sdk", + "branch": "default", + "pull": false, + "cat": 0, + "alias": "sdk", + "path_code": "/Users/despiegk/codewww/github/threefoldfoundation/info_sdk", + "domains": [ + "sdk.threefold.io", + "sdk_info.threefold.io" + ], + "descr": "for IAC, devops, how to do Infrastruture As Code, 3bot, Ansible, tfgrid-sdk, ..." + }, + { + "name": "info_legal", + "url": "https://github.com/threefoldfoundation/info_legal", + "branch": "default", + "pull": false, + "cat": 0, + "alias": "legal", + "path_code": "/Users/despiegk/codewww/github/threefoldfoundation/info_legal", + "domains": [ + "legal.threefold.io", + "legal_info.threefold.io" + ], + "descr": "" + }, + { + "name": "info_cloud", + "url": "https://github.com/threefoldfoundation/info_cloud", + "branch": "default", + "pull": false, + "cat": 0, + "alias": "cloud", + "path_code": "/Users/despiegk/codewww/github/threefoldfoundation/info_cloud", + "domains": [ + "cloud_info.threefold.io" + ], + "descr": "how to use the cloud for deploying apps: evdc, kubernetes, planetary fs, ... + marketplace solutions " + }, + { + "name": "info_tftech", + "url": "https://github.com/threefoldtech/info_tftech", + "branch": "default", + "pull": false, + "cat": 0, + "alias": "tftech", + "path_code": "/Users/despiegk/codewww/github/threefoldtech/info_tftech", + "domains": [ + "info.threefold.tech" + ], + "descr": "" + }, + { + "name": "info_digitaltwin", + "url": "https://github.com/threefoldfoundation/info_digitaltwin.git", + "branch": "default", + "pull": false, + "cat": 0, + "alias": "twin", + "path_code": "/Users/despiegk/codewww/github/threefoldfoundation/info_digitaltwin", + "domains": [ + "twin_info.threefold.io" + ], + "descr": "" + } +]
\ No newline at end of file diff --git a/v_windows/v/examples/templates/readme.md b/v_windows/v/examples/templates/readme.md new file mode 100644 index 0000000..3129068 --- /dev/null +++ b/v_windows/v/examples/templates/readme.md @@ -0,0 +1,14 @@ +# example how to work with templates + +- templates support + - if + - for loops + - even nesting of for loops + - syntax checking + - embed the template into the binary + +Its a very cool way how to do also do e.g. system administration, fill in +config files, etc... + +The example there is also a good demonstration of how to use json on a more +complex object and read/write to file. diff --git a/v_windows/v/examples/templates/template.md b/v_windows/v/examples/templates/template.md new file mode 100644 index 0000000..7bbdc0d --- /dev/null +++ b/v_windows/v/examples/templates/template.md @@ -0,0 +1,16 @@ +# WIKIs + +@for site in sites +@if site.cat == .wiki + +## @{site.domains[0]}@port_str + +- [errors]("//@{site.domains[0]}@port_str/errors") + +### domains + +@for dom in site.domains +- @dom +@end +@end +@end diff --git a/v_windows/v/examples/templates/templates.v b/v_windows/v/examples/templates/templates.v new file mode 100644 index 0000000..65ec459 --- /dev/null +++ b/v_windows/v/examples/templates/templates.v @@ -0,0 +1,196 @@ +module main + +import os +import json + +pub struct SiteConfig { +pub mut: + name string + url string + branch string = 'default' // means is the default branch + pull bool + cat SiteCat + alias string + path_code string + domains []string + descr string +} + +pub enum SiteCat { + wiki + data + web +} + +fn data_get() []SiteConfig { + data := [SiteConfig{ + name: 'www_threefold_io' + url: 'https://github.com/threefoldfoundation/www_threefold_io' + branch: 'default' + pull: false + cat: .web + alias: 'tf' + path_code: '/Users/despiegk/codewww/github/threefoldfoundation/www_threefold_io' + domains: ['www.threefold.io', 'www.threefold.me'] + descr: 'is our entry point for everyone, redirect to the detailed websites underneith.' + }, SiteConfig{ + name: 'www_threefold_cloud' + url: 'https://github.com/threefoldfoundation/www_threefold_cloud' + branch: 'default' + pull: false + cat: .web + alias: 'cloud' + path_code: '/Users/despiegk/codewww/github/threefoldfoundation/www_threefold_cloud' + domains: ['cloud.threefold.io', 'cloud.threefold.me'] + descr: 'for people looking to deploy solutions on top of a cloud, alternative to e.g. digital ocean' + }, SiteConfig{ + name: 'www_threefold_farming' + url: 'https://github.com/threefoldfoundation/www_threefold_farming' + branch: 'default' + pull: false + cat: .web + alias: 'farming' + path_code: '/Users/despiegk/codewww/github/threefoldfoundation/www_threefold_farming' + domains: ['farming.threefold.io', 'farming.threefold.me'] + descr: 'crypto & minining enthusiasts, be the internet, know about farming & tokens.' + }, SiteConfig{ + name: 'www_threefold_twin' + url: 'https://github.com/threefoldfoundation/www_threefold_twin' + branch: 'default' + pull: false + cat: .web + alias: 'twin' + path_code: '/Users/despiegk/codewww/github/threefoldfoundation/www_threefold_twin' + domains: ['twin.threefold.io', 'twin.threefold.me'] + descr: 'you digital life' + }, SiteConfig{ + name: 'www_threefold_marketplace' + url: 'https://github.com/threefoldfoundation/www_threefold_marketplace' + branch: 'default' + pull: false + cat: .web + alias: 'marketplace' + path_code: '/Users/despiegk/codewww/github/threefoldfoundation/www_threefold_marketplace' + domains: ['now.threefold.io', 'marketplace.threefold.io', 'now.threefold.me', + 'marketplace.threefold.me', + ] + descr: 'apps for community builders, runs on top of evdc' + }, SiteConfig{ + name: 'www_conscious_internet' + url: 'https://github.com/threefoldfoundation/www_conscious_internet' + branch: 'default' + pull: false + cat: .web + alias: 'conscious_internet' + path_code: '/Users/despiegk/codewww/github/threefoldfoundation/www_conscious_internet' + domains: ['www.consciousinternet.org', 'eco.threefold.io', 'community.threefold.io', + 'eco.threefold.me', 'community.threefold.me'] + descr: 'community around threefold, partners, friends, ...' + }, SiteConfig{ + name: 'www_threefold_tech' + url: 'https://github.com/threefoldtech/www_threefold_tech' + branch: 'default' + pull: false + cat: .web + alias: 'tech' + path_code: '/Users/despiegk/codewww/github/threefoldtech/www_threefold_tech' + domains: ['www.threefold.tech'] + descr: 'cyberpandemic, use the tech to build your own solutions with, certification for TFGrid' + }, SiteConfig{ + name: 'www_examplesite' + url: 'https://github.com/threefoldfoundation/www_examplesite' + branch: 'default' + pull: false + cat: .web + alias: 'example' + path_code: '/Users/despiegk/codewww/github/threefoldfoundation/www_examplesite' + domains: ['example.threefold.io'] + descr: '' + }, SiteConfig{ + name: 'info_threefold' + url: 'https://github.com/threefoldfoundation/info_foundation_archive' + branch: 'default' + pull: false + cat: .wiki + alias: 'threefold' + path_code: '/Users/despiegk/codewww/github/threefoldfoundation/info_foundation_archive' + domains: ['info.threefold.io'] + descr: 'wiki for foundation, collaborate, what if farmings, tokens' + }, SiteConfig{ + name: 'info_sdk' + url: 'https://github.com/threefoldfoundation/info_sdk' + branch: 'default' + pull: false + cat: .wiki + alias: 'sdk' + path_code: '/Users/despiegk/codewww/github/threefoldfoundation/info_sdk' + domains: ['sdk.threefold.io', 'sdk_info.threefold.io'] + descr: 'for IAC, devops, how to do Infrastruture As Code, 3bot, Ansible, tfgrid-sdk, ...' + }, SiteConfig{ + name: 'info_legal' + url: 'https://github.com/threefoldfoundation/info_legal' + branch: 'default' + pull: false + cat: .wiki + alias: 'legal' + path_code: '/Users/despiegk/codewww/github/threefoldfoundation/info_legal' + domains: ['legal.threefold.io', 'legal_info.threefold.io'] + descr: '' + }, SiteConfig{ + name: 'info_cloud' + url: 'https://github.com/threefoldfoundation/info_cloud' + branch: 'default' + pull: false + cat: .wiki + alias: 'cloud' + path_code: '/Users/despiegk/codewww/github/threefoldfoundation/info_cloud' + domains: ['cloud_info.threefold.io'] + descr: 'how to use the cloud for deploying apps: evdc, kubernetes, planetary fs, ... + marketplace solutions ' + }, SiteConfig{ + name: 'info_tftech' + url: 'https://github.com/threefoldtech/info_tftech' + branch: 'default' + pull: false + cat: .wiki + alias: 'tftech' + path_code: '/Users/despiegk/codewww/github/threefoldtech/info_tftech' + domains: ['info.threefold.tech'] + descr: '' + }, SiteConfig{ + name: 'info_digitaltwin' + url: 'https://github.com/threefoldfoundation/info_digitaltwin.git' + branch: 'default' + pull: false + cat: .wiki + alias: 'twin' + path_code: '/Users/despiegk/codewww/github/threefoldfoundation/info_digitaltwin' + domains: ['twin_info.threefold.io'] + descr: '' + }] + return data +} + +fn data_dump(data []SiteConfig) { + a := json.encode_pretty(data) + os.write_file(os.resource_abs_path('data.json'), a) or { panic(err) } +} + +fn data_load() []SiteConfig { + data := os.read_file(os.resource_abs_path('data.json')) or { panic(err) } + a := json.decode([]SiteConfig, data) or { panic(err) } + return a +} + +fn filled_in_template() string { + port_str := '80' + sites := data_load() + return $tmpl('template.md') +} + +fn main() { + // A NICE test how to work with the json module + // data := data_get() + // data_dump(data) + b := filled_in_template() + println(b) +} diff --git a/v_windows/v/examples/term.ui/README.md b/v_windows/v/examples/term.ui/README.md new file mode 100644 index 0000000..409a55e --- /dev/null +++ b/v_windows/v/examples/term.ui/README.md @@ -0,0 +1 @@ +<img src='https://raw.githubusercontent.com/vlang/v/master/examples/term.ui/screenshot_pong.png' width=300> diff --git a/v_windows/v/examples/term.ui/cursor_chaser.v b/v_windows/v/examples/term.ui/cursor_chaser.v new file mode 100644 index 0000000..dd987e6 --- /dev/null +++ b/v_windows/v/examples/term.ui/cursor_chaser.v @@ -0,0 +1,100 @@ +import term.ui as tui + +const ( + colors = [ + tui.Color{33, 150, 243}, + tui.Color{0, 150, 136}, + tui.Color{205, 220, 57}, + tui.Color{255, 152, 0}, + tui.Color{244, 67, 54}, + tui.Color{156, 39, 176}, + ] +) + +struct Point { + x int + y int +} + +struct App { +mut: + tui &tui.Context = 0 + points []Point + color tui.Color = colors[0] + color_idx int + cut_rate f64 = 5 +} + +fn frame(x voidptr) { + mut app := &App(x) + + app.tui.clear() + + if app.points.len > 0 { + app.tui.set_bg_color(app.color) + mut last := app.points[0] + for point in app.points { + // if the cursor moves quickly enough, different events are not + // necessarily touching, so we need to draw a line between them + app.tui.draw_line(last.x, last.y, point.x, point.y) + last = point + } + app.tui.reset() + + l := int(app.points.len / app.cut_rate) + 1 + app.points = app.points[l..].clone() + } + + ww := app.tui.window_width + + app.tui.bold() + app.tui.draw_text(ww / 6, 2, 'V term.input: cursor chaser demo') + app.tui.draw_text((ww - ww / 6) - 14, 2, 'cut rate: ${(100 / app.cut_rate):3.0f}%') + app.tui.horizontal_separator(3) + app.tui.reset() + app.tui.flush() +} + +fn event(e &tui.Event, x voidptr) { + mut app := &App(x) + + match e.typ { + .key_down { + match e.code { + .escape { + exit(0) + } + .space, .enter { + app.color_idx++ + if app.color_idx == colors.len { + app.color_idx = 0 + } + app.color = colors[app.color_idx] + } + else {} + } + } + .mouse_move, .mouse_drag, .mouse_down { + app.points << Point{e.x, e.y} + } + .mouse_scroll { + d := if e.direction == .up { 0.1 } else { -0.1 } + app.cut_rate += d + if app.cut_rate < 1 { + app.cut_rate = 1 + } + } + else {} + } +} + +fn main() { + mut app := &App{} + app.tui = tui.init( + user_data: app + frame_fn: frame + event_fn: event + hide_cursor: true + ) + app.tui.run() ? +} diff --git a/v_windows/v/examples/term.ui/event_viewer.v b/v_windows/v/examples/term.ui/event_viewer.v new file mode 100644 index 0000000..f9c443f --- /dev/null +++ b/v_windows/v/examples/term.ui/event_viewer.v @@ -0,0 +1,47 @@ +import term.ui as tui + +struct App { +mut: + tui &tui.Context = 0 +} + +fn event(e &tui.Event, x voidptr) { + mut app := &App(x) + app.tui.clear() + app.tui.set_cursor_position(0, 0) + app.tui.write('V term.input event viewer (press `esc` to exit)\n\n') + app.tui.write('$e') + app.tui.write('\n\nRaw event bytes: "$e.utf8.bytes().hex()" = $e.utf8.bytes()') + if !e.modifiers.is_empty() { + app.tui.write('\nModifiers: $e.modifiers = ') + if e.modifiers.has(.ctrl) { + app.tui.write('ctrl. ') + } + if e.modifiers.has(.shift) { + app.tui.write('shift ') + } + if e.modifiers.has(.alt) { + app.tui.write('alt. ') + } + } + app.tui.flush() + + if e.typ == .key_down && e.code == .escape { + exit(0) + } +} + +fn main() { + mut app := &App{} + app.tui = tui.init( + user_data: app + event_fn: event + window_title: 'V term.ui event viewer' + hide_cursor: true + capture_events: true + frame_rate: 60 + use_alternate_buffer: false + ) + println('V term.ui event viewer (press `esc` to exit)\n\n') + app.tui.run() ? +} diff --git a/v_windows/v/examples/term.ui/pong.v b/v_windows/v/examples/term.ui/pong.v new file mode 100644 index 0000000..375261f --- /dev/null +++ b/v_windows/v/examples/term.ui/pong.v @@ -0,0 +1,499 @@ +// Copyright (c) 2020 Lars Pontoppidan. All rights reserved. +// Use of this source code is governed by the MIT license distributed with this software. +import term +import term.ui +import time + +enum Mode { + menu + game +} + +const ( + player_one = 1 // Human control this racket + player_two = 0 // Take over this AI controller + white = ui.Color{255, 255, 255} + orange = ui.Color{255, 140, 0} +) + +[heap] +struct App { +mut: + tui &ui.Context = 0 + mode Mode = Mode.menu + width int + height int + game &Game = 0 + dt f32 + ticks i64 +} + +fn (mut a App) init() { + a.game = &Game{ + app: a + } + w, h := a.tui.window_width, a.tui.window_height + a.width = w + a.height = h + term.erase_del_clear() + term.set_cursor_position( + x: 0 + y: 0 + ) +} + +fn (mut a App) start_game() { + if a.mode != .game { + a.mode = .game + } + a.game.init() +} + +fn (mut a App) frame() { + ticks := time.ticks() + a.dt = f32(ticks - a.ticks) / 1000.0 + a.width, a.height = a.tui.window_width, a.tui.window_height + if a.mode == .game { + a.game.update() + } + a.tui.clear() + a.render() + a.tui.flush() + a.ticks = ticks +} + +fn (mut a App) quit() { + if a.mode != .menu { + a.game.quit() + return + } + term.set_cursor_position( + x: 0 + y: 0 + ) + exit(0) +} + +fn (mut a App) event(e &ui.Event) { + match e.typ { + .mouse_move { + if a.mode != .game { + return + } + // TODO mouse movement for real Pong sharks + // a.game.move_player(player_one, 0, -1) + } + .key_down { + match e.code { + .escape, .q { + a.quit() + } + .w { + if a.mode != .game { + return + } + a.game.move_player(player_one, 0, -1) + } + .a { + if a.mode != .game { + return + } + a.game.move_player(player_one, 0, -1) + } + .s { + if a.mode != .game { + return + } + a.game.move_player(player_one, 0, 1) + } + .d { + if a.mode != .game { + return + } + a.game.move_player(player_one, 0, 1) + } + .left { + if a.mode != .game { + return + } + a.game.move_player(player_two, 0, -1) + } + .right { + if a.mode != .game { + return + } + a.game.move_player(player_two, 0, 1) + } + .up { + if a.mode != .game { + return + } + a.game.move_player(player_two, 0, -1) + } + .down { + if a.mode != .game { + return + } + a.game.move_player(player_two, 0, 1) + } + .enter, .space { + if a.mode == .menu { + a.start_game() + } + } + else {} + } + } + else {} + } +} + +fn (mut a App) free() { + unsafe { + a.game.free() + free(a.game) + } +} + +fn (mut a App) render() { + match a.mode { + .menu { a.draw_menu() } + else { a.draw_game() } + } +} + +fn (mut a App) draw_menu() { + cx := int(f32(a.width) * 0.5) + y025 := int(f32(a.height) * 0.25) + y075 := int(f32(a.height) * 0.75) + cy := int(f32(a.height) * 0.5) + // + a.tui.set_color(white) + a.tui.bold() + a.tui.draw_text(cx - 2, y025, 'VONG') + a.tui.reset() + a.tui.draw_text(cx - 13, y025 + 1, '(A game of Pong written in V)') + // + a.tui.set_color(white) + a.tui.bold() + a.tui.draw_text(cx - 3, cy + 1, 'START') + a.tui.reset() + // + a.tui.draw_text(cx - 9, y075 + 1, 'Press SPACE to start') + a.tui.reset() + a.tui.draw_text(cx - 5, y075 + 3, 'ESC to Quit') + a.tui.reset() +} + +fn (mut a App) draw_game() { + a.game.draw() +} + +struct Player { +mut: + game &Game + pos Vec + racket_size int = 4 + score int + ai bool +} + +fn (mut p Player) move(x f32, y f32) { + p.pos.x += x + p.pos.y += y +} + +fn (mut p Player) update() { + if !p.ai { + return + } + if isnil(p.game) { + return + } + // dt := p.game.app.dt + ball := unsafe { &p.game.ball } + // Evil AI that eventually will take over the world + p.pos.y = ball.pos.y - int(f32(p.racket_size) * 0.5) +} + +struct Vec { +mut: + x f32 + y f32 +} + +fn (mut v Vec) set(x f32, y f32) { + v.x = x + v.y = y +} + +struct Ball { +mut: + pos Vec + vel Vec + acc Vec +} + +fn (mut b Ball) update(dt f32) { + b.pos.x += b.vel.x * b.acc.x * dt + b.pos.y += b.vel.y * b.acc.y * dt +} + +[heap] +struct Game { +mut: + app &App = 0 + players []Player + ball Ball +} + +fn (mut g Game) move_player(id int, x int, y int) { + mut p := unsafe { &g.players[id] } + if p.ai { // disable AI when moved + p.ai = false + } + p.move(x, y) +} + +fn (mut g Game) init() { + if g.players.len == 0 { + g.players = []Player{len: 2, init: Player{ // <- BUG omitting the init will result in smaller racket sizes??? + game: g + }} + } + g.reset() +} + +fn (mut g Game) reset() { + mut i := 0 + for mut p in g.players { + p.score = 0 + if i != player_one { + p.ai = true + } + i++ + } + g.new_round() +} + +fn (mut g Game) new_round() { + mut i := 0 + for mut p in g.players { + p.pos.x = if i == 0 { 3 } else { g.app.width - 2 } + p.pos.y = f32(g.app.height) * 0.5 - f32(p.racket_size) * 0.5 + i++ + } + g.ball.pos.set(f32(g.app.width) * 0.5, f32(g.app.height) * 0.5) + g.ball.vel.set(-8, -15) + g.ball.acc.set(2.0, 1.0) +} + +fn (mut g Game) update() { + dt := g.app.dt + mut b := unsafe { &g.ball } + for mut p in g.players { + p.update() + // Keep rackets within the game area + if p.pos.y <= 0 { + p.pos.y = 1 + } + if p.pos.y + p.racket_size >= g.app.height { + p.pos.y = g.app.height - p.racket_size - 1 + } + // Check ball collision + // Player left side + if p.pos.x < f32(g.app.width) * 0.5 { + // Racket collision + if b.pos.x <= p.pos.x + 1 { + if b.pos.y >= p.pos.y && b.pos.y <= p.pos.y + p.racket_size { + b.vel.x *= -1 + } + } + // Behind racket + if b.pos.x < p.pos.x { + g.players[1].score++ + g.new_round() + } + } else { + // Player right side + if b.pos.x >= p.pos.x - 1 { + if b.pos.y >= p.pos.y && b.pos.y <= p.pos.y + p.racket_size { + b.vel.x *= -1 + } + } + if b.pos.x > p.pos.x { + g.players[0].score++ + g.new_round() + } + } + } + if b.pos.x <= 1 || b.pos.x >= g.app.width { + b.vel.x *= -1 + } + if b.pos.y <= 2 || b.pos.y >= g.app.height { + b.vel.y *= -1 + } + b.update(dt) +} + +fn (mut g Game) quit() { + if g.app.mode != .game { + return + } + g.app.mode = .menu +} + +fn (mut g Game) draw_big_digit(px f32, py f32, digit int) { + // TODO use draw_line or draw_point to fix tearing with non-monospaced terminal fonts + mut gfx := g.app.tui + x, y := int(px), int(py) + match digit { + 0 { + gfx.draw_text(x, y + 0, '█████') + gfx.draw_text(x, y + 1, '█ █') + gfx.draw_text(x, y + 2, '█ █') + gfx.draw_text(x, y + 3, '█ █') + gfx.draw_text(x, y + 4, '█████') + } + 1 { + gfx.draw_text(x + 3, y + 0, '█') + gfx.draw_text(x + 3, y + 1, '█') + gfx.draw_text(x + 3, y + 2, '█') + gfx.draw_text(x + 3, y + 3, '█') + gfx.draw_text(x + 3, y + 4, '█') + } + 2 { + gfx.draw_text(x, y + 0, '█████') + gfx.draw_text(x, y + 1, ' █') + gfx.draw_text(x, y + 2, '█████') + gfx.draw_text(x, y + 3, '█') + gfx.draw_text(x, y + 4, '█████') + } + 3 { + gfx.draw_text(x, y + 0, '█████') + gfx.draw_text(x, y + 1, ' ██') + gfx.draw_text(x, y + 2, ' ████') + gfx.draw_text(x, y + 3, ' ██') + gfx.draw_text(x, y + 4, '█████') + } + 4 { + gfx.draw_text(x, y + 0, '█ █') + gfx.draw_text(x, y + 1, '█ █') + gfx.draw_text(x, y + 2, '█████') + gfx.draw_text(x, y + 3, ' █') + gfx.draw_text(x, y + 4, ' █') + } + 5 { + gfx.draw_text(x, y + 0, '█████') + gfx.draw_text(x, y + 1, '█') + gfx.draw_text(x, y + 2, '█████') + gfx.draw_text(x, y + 3, ' █') + gfx.draw_text(x, y + 4, '█████') + } + 6 { + gfx.draw_text(x, y + 0, '█████') + gfx.draw_text(x, y + 1, '█') + gfx.draw_text(x, y + 2, '█████') + gfx.draw_text(x, y + 3, '█ █') + gfx.draw_text(x, y + 4, '█████') + } + 7 { + gfx.draw_text(x, y + 0, '█████') + gfx.draw_text(x, y + 1, ' █') + gfx.draw_text(x, y + 2, ' █') + gfx.draw_text(x, y + 3, ' █') + gfx.draw_text(x, y + 4, ' █') + } + 8 { + gfx.draw_text(x, y + 0, '█████') + gfx.draw_text(x, y + 1, '█ █') + gfx.draw_text(x, y + 2, '█████') + gfx.draw_text(x, y + 3, '█ █') + gfx.draw_text(x, y + 4, '█████') + } + 9 { + gfx.draw_text(x, y + 0, '█████') + gfx.draw_text(x, y + 1, '█ █') + gfx.draw_text(x, y + 2, '█████') + gfx.draw_text(x, y + 3, ' █') + gfx.draw_text(x, y + 4, '█████') + } + else {} + } +} + +fn (mut g Game) draw() { + mut gfx := g.app.tui + gfx.set_bg_color(white) + // Border + gfx.draw_empty_rect(1, 1, g.app.width, g.app.height) + // Center line + gfx.draw_dashed_line(int(f32(g.app.width) * 0.5), 0, int(f32(g.app.width) * 0.5), + int(g.app.height)) + border := 1 + mut y, mut x := 0, 0 + for p in g.players { + x = int(p.pos.x) + y = int(p.pos.y) + gfx.reset_bg_color() + gfx.set_color(white) + if x < f32(g.app.width) * 0.5 { + g.draw_big_digit(f32(g.app.width) * 0.25, 3, p.score) + } else { + g.draw_big_digit(f32(g.app.width) * 0.75, 3, p.score) + } + gfx.reset_color() + gfx.set_bg_color(white) + // Racket + gfx.draw_line(x, y + border, x, y + p.racket_size) + } + // Ball + gfx.draw_point(int(g.ball.pos.x), int(g.ball.pos.y)) + // gfx.draw_text(22,2,'$g.ball.pos') + gfx.reset_bg_color() +} + +fn (mut g Game) free() { + g.players.clear() +} + +// TODO Remove these wrapper functions when we can assign methods as callbacks +fn init(x voidptr) { + mut app := &App(x) + app.init() +} + +fn frame(x voidptr) { + mut app := &App(x) + app.frame() +} + +fn cleanup(x voidptr) { + mut app := &App(x) + app.free() +} + +fn fail(error string) { + eprintln(error) +} + +fn event(e &ui.Event, x voidptr) { + mut app := &App(x) + app.event(e) +} + +fn main() { + mut app := &App{} + app.tui = ui.init( + user_data: app + init_fn: init + frame_fn: frame + cleanup_fn: cleanup + event_fn: event + fail_fn: fail + capture_events: true + hide_cursor: true + frame_rate: 60 + ) + app.tui.run() ? +} diff --git a/v_windows/v/examples/term.ui/rectangles.v b/v_windows/v/examples/term.ui/rectangles.v new file mode 100644 index 0000000..09e9d6a --- /dev/null +++ b/v_windows/v/examples/term.ui/rectangles.v @@ -0,0 +1,97 @@ +import term.ui as tui +import rand + +struct Rect { +mut: + c tui.Color + x int + y int + x2 int + y2 int +} + +struct App { +mut: + tui &tui.Context = 0 + rects []Rect + cur_rect Rect + is_drag bool + redraw bool +} + +fn random_color() tui.Color { + return tui.Color{ + r: byte(rand.intn(256)) + g: byte(rand.intn(256)) + b: byte(rand.intn(256)) + } +} + +fn event(e &tui.Event, x voidptr) { + mut app := &App(x) + match e.typ { + .mouse_down { + app.is_drag = true + app.cur_rect = Rect{ + c: random_color() + x: e.x + y: e.y + x2: e.x + y2: e.y + } + } + .mouse_drag { + app.cur_rect.x2 = e.x + app.cur_rect.y2 = e.y + } + .mouse_up { + app.rects << app.cur_rect + app.is_drag = false + } + .key_down { + if e.code == .c { + app.rects.clear() + } else if e.code == .escape { + exit(0) + } + } + else {} + } + app.redraw = true +} + +fn frame(x voidptr) { + mut app := &App(x) + if !app.redraw { + return + } + + app.tui.clear() + + for rect in app.rects { + app.tui.set_bg_color(rect.c) + app.tui.draw_rect(rect.x, rect.y, rect.x2, rect.y2) + } + + if app.is_drag { + r := app.cur_rect + app.tui.set_bg_color(r.c) + app.tui.draw_empty_rect(r.x, r.y, r.x2, r.y2) + } + + app.tui.reset_bg_color() + app.tui.flush() + app.redraw = false +} + +fn main() { + mut app := &App{} + app.tui = tui.init( + user_data: app + event_fn: event + frame_fn: frame + hide_cursor: true + frame_rate: 60 + ) + app.tui.run() ? +} diff --git a/v_windows/v/examples/term.ui/screenshot_pong.png b/v_windows/v/examples/term.ui/screenshot_pong.png Binary files differnew file mode 100644 index 0000000..9127d7b --- /dev/null +++ b/v_windows/v/examples/term.ui/screenshot_pong.png diff --git a/v_windows/v/examples/term.ui/term_drawing.v b/v_windows/v/examples/term.ui/term_drawing.v new file mode 100644 index 0000000..df08cb7 --- /dev/null +++ b/v_windows/v/examples/term.ui/term_drawing.v @@ -0,0 +1,510 @@ +// Copyright (c) 2020 Raúl Hernández. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module main + +import term.ui + +// The color palette, taken from Google's Material design +const ( + colors = [ + [ + ui.Color{239, 154, 154}, + ui.Color{244, 143, 177}, + ui.Color{206, 147, 216}, + ui.Color{179, 157, 219}, + ui.Color{159, 168, 218}, + ui.Color{144, 202, 249}, + ui.Color{129, 212, 250}, + ui.Color{128, 222, 234}, + ui.Color{128, 203, 196}, + ui.Color{165, 214, 167}, + ui.Color{197, 225, 165}, + ui.Color{230, 238, 156}, + ui.Color{255, 245, 157}, + ui.Color{255, 224, 130}, + ui.Color{255, 204, 128}, + ui.Color{255, 171, 145}, + ui.Color{188, 170, 164}, + ui.Color{238, 238, 238}, + ui.Color{176, 190, 197}, + ], + [ + ui.Color{244, 67, 54}, + ui.Color{233, 30, 99}, + ui.Color{156, 39, 176}, + ui.Color{103, 58, 183}, + ui.Color{63, 81, 181}, + ui.Color{33, 150, 243}, + ui.Color{3, 169, 244}, + ui.Color{0, 188, 212}, + ui.Color{0, 150, 136}, + ui.Color{76, 175, 80}, + ui.Color{139, 195, 74}, + ui.Color{205, 220, 57}, + ui.Color{255, 235, 59}, + ui.Color{255, 193, 7}, + ui.Color{255, 152, 0}, + ui.Color{255, 87, 34}, + ui.Color{121, 85, 72}, + ui.Color{120, 120, 120}, + ui.Color{96, 125, 139}, + ], + [ + ui.Color{198, 40, 40}, + ui.Color{173, 20, 87}, + ui.Color{106, 27, 154}, + ui.Color{69, 39, 160}, + ui.Color{40, 53, 147}, + ui.Color{21, 101, 192}, + ui.Color{2, 119, 189}, + ui.Color{0, 131, 143}, + ui.Color{0, 105, 92}, + ui.Color{46, 125, 50}, + ui.Color{85, 139, 47}, + ui.Color{158, 157, 36}, + ui.Color{249, 168, 37}, + ui.Color{255, 143, 0}, + ui.Color{239, 108, 0}, + ui.Color{216, 67, 21}, + ui.Color{78, 52, 46}, + ui.Color{33, 33, 33}, + ui.Color{55, 71, 79}, + ], + ] +) + +const ( + frame_rate = 30 // fps + msg_display_time = 5 * frame_rate + w = 200 + h = 100 + space = ' ' + spaces = ' ' + select_color = 'Select color: ' + select_size = 'Size: + -' + help_1 = '╭────────╮' + help_2 = '│ HELP │' + help_3 = '╰────────╯' +) + +struct App { +mut: + ui &ui.Context = 0 + header_text []string + mouse_pos Point + msg string + msg_hide_tick int + primary_color ui.Color = colors[1][6] + secondary_color ui.Color = colors[1][9] + primary_color_idx int = 25 + secondary_color_idx int = 28 + bg_color ui.Color = ui.Color{0, 0, 0} + drawing [][]ui.Color = [][]ui.Color{len: h, init: []ui.Color{len: w}} + size int = 1 + should_redraw bool = true + is_dragging bool +} + +struct Point { +mut: + x int + y int +} + +fn main() { + mut app := &App{} + app.ui = ui.init( + user_data: app + frame_fn: frame + event_fn: event + frame_rate: frame_rate + hide_cursor: true + window_title: 'V terminal pixelart drawing app' + ) + app.mouse_pos.x = 40 + app.mouse_pos.y = 15 + app.ui.clear() + app.ui.run() ? +} + +fn frame(x voidptr) { + mut app := &App(x) + mut redraw := app.should_redraw + if app.msg != '' && app.ui.frame_count >= app.msg_hide_tick { + app.msg = '' + redraw = true + } + if redraw { + app.render(false) + app.should_redraw = false + } +} + +fn event(event &ui.Event, x voidptr) { + mut app := &App(x) + match event.typ { + .mouse_down { + app.is_dragging = true + if app.ui.window_height - event.y < 5 { + app.footer_click(event) + } else { + app.paint(event) + } + } + .mouse_up { + app.is_dragging = false + } + .mouse_drag { + app.mouse_pos = Point{ + x: event.x + y: event.y + } + app.paint(event) + } + .mouse_move { + app.mouse_pos = Point{ + x: event.x + y: event.y + } + } + .mouse_scroll { + app.mouse_pos = Point{ + x: event.x + y: event.y + } + d := event.direction == .down + if event.modifiers.has(.ctrl) { + p := !event.modifiers.has(.shift) + c := if d { + if p { app.primary_color_idx - 1 } else { app.secondary_color_idx - 1 } + } else { + if p { app.primary_color_idx + 1 } else { app.secondary_color_idx + 1 } + } + app.select_color(p, c) + } else { + if d { + app.inc_size() + } else { + app.dec_size() + } + } + } + .key_down { + match event.code { + .f1, ._1 { + oevent := *event + nevent := ui.Event{ + ...oevent + button: ui.MouseButton.left + x: app.mouse_pos.x + y: app.mouse_pos.y + } + app.paint(nevent) + } + .f2, ._2 { + oevent := *event + nevent := ui.Event{ + ...oevent + button: ui.MouseButton.right + x: app.mouse_pos.x + y: app.mouse_pos.y + } + app.paint(nevent) + } + .space, .enter { + oevent := *event + nevent := ui.Event{ + ...oevent + button: .left + x: app.mouse_pos.x + y: app.mouse_pos.y + } + app.paint(nevent) + } + .delete, .backspace { + oevent := *event + nevent := ui.Event{ + ...oevent + button: .middle + x: app.mouse_pos.x + y: app.mouse_pos.y + } + app.paint(nevent) + } + .j, .down { + if event.modifiers.has(.shift) { + app.set_pixel((1 + app.mouse_pos.x) / 2, app.mouse_pos.y, app.primary_color) + } + app.mouse_pos.y++ + } + .k, .up { + if event.modifiers.has(.shift) { + app.set_pixel((1 + app.mouse_pos.x) / 2, app.mouse_pos.y, app.primary_color) + } + app.mouse_pos.y-- + } + .h, .left { + if event.modifiers.has(.shift) { + app.set_pixel((1 + app.mouse_pos.x) / 2, app.mouse_pos.y, app.primary_color) + } + app.mouse_pos.x -= 2 + } + .l, .right { + if event.modifiers.has(.shift) { + app.set_pixel((1 + app.mouse_pos.x) / 2, app.mouse_pos.y, app.primary_color) + } + app.mouse_pos.x += 2 + } + .t { + p := !event.modifiers.has(.alt) + c := if event.modifiers.has(.shift) { + if p { app.primary_color_idx - 19 } else { app.secondary_color_idx - 19 } + } else { + if p { app.primary_color_idx + 19 } else { app.secondary_color_idx + 19 } + } + app.select_color(p, c) + } + .r { + p := !event.modifiers.has(.alt) + c := if event.modifiers.has(.shift) { + if p { app.primary_color_idx - 1 } else { app.secondary_color_idx - 1 } + } else { + if p { app.primary_color_idx + 1 } else { app.secondary_color_idx + 1 } + } + app.select_color(p, c) + } + .plus { + app.inc_size() + } + .minus { + app.dec_size() + } + .c { + app.drawing = [][]ui.Color{len: h, init: []ui.Color{len: w}} + } + .q, .escape { + app.render(true) + exit(0) + } + else {} + } + } + else {} + } + app.should_redraw = true +} + +fn (mut app App) render(paint_only bool) { + app.ui.clear() + app.draw_header() + app.draw_content() + if !paint_only { + app.draw_footer() + app.draw_cursor() + } + app.ui.flush() +} + +fn (mut app App) select_color(primary bool, idx int) { + c := (idx + 57) % 57 + cx := c % 19 + cy := (c / 19) % 3 + color := colors[cy][cx] + if primary { + app.primary_color_idx = c % (19 * 3) + app.primary_color = color + } else { + app.secondary_color_idx = c % (19 * 3) + app.secondary_color = color + } + c_str := if primary { 'primary' } else { 'secondary' } + app.show_msg('set $c_str color idx: $idx', 1) +} + +fn (mut app App) set_pixel(x_ int, y_ int, c ui.Color) { + // Term coords start at 1, and adjust for the header + x, y := x_ - 1, y_ - 4 + if y < 0 || app.ui.window_height - y < 3 { + return + } + if y >= app.drawing.len || x < 0 || x >= app.drawing[0].len { + return + } + app.drawing[y][x] = c +} + +fn (mut app App) paint(event &ui.Event) { + if event.y < 4 || app.ui.window_height - event.y < 4 { + return + } + x_start, y_start := int(f32((event.x - 1) / 2) - app.size / 2 + 1), event.y - app.size / 2 + color := match event.button { + .left { app.primary_color } + .right { app.secondary_color } + else { app.bg_color } + } + for x in x_start .. x_start + app.size { + for y in y_start .. y_start + app.size { + app.set_pixel(x, y, color) + } + } +} + +fn (mut app App) draw_content() { + w_, mut h_ := app.ui.window_width / 2, app.ui.window_height - 8 + if h_ > app.drawing.len { + h_ = app.drawing.len + } + for row_idx, row in app.drawing[..h_] { + app.ui.set_cursor_position(0, row_idx + 4) + mut last := ui.Color{0, 0, 0} + for cell in row[..w_] { + if cell.r == 0 && cell.g == 0 && cell.b == 0 { + if !(cell.r == last.r && cell.g == last.g && cell.b == last.b) { + app.ui.reset() + } + } else { + if !(cell.r == last.r && cell.g == last.g && cell.b == last.b) { + app.ui.set_bg_color(cell) + } + } + app.ui.write(spaces) + last = cell + } + app.ui.reset() + } +} + +fn (mut app App) draw_cursor() { + if app.mouse_pos.y in [3, app.ui.window_height - 5] { + // inside the horizontal separators + return + } + cursor_color := if app.is_dragging { ui.Color{220, 220, 220} } else { ui.Color{160, 160, 160} } + app.ui.set_bg_color(cursor_color) + if app.mouse_pos.y >= 3 && app.mouse_pos.y <= app.ui.window_height - 4 { + // inside the main content + mut x_start := int(f32((app.mouse_pos.x - 1) / 2) - app.size / 2 + 1) * 2 - 1 + mut y_start := app.mouse_pos.y - app.size / 2 + mut x_end := x_start + app.size * 2 - 1 + mut y_end := y_start + app.size - 1 + if x_start < 1 { + x_start = 1 + } + if y_start < 4 { + y_start = 4 + } + if x_end > app.ui.window_width { + x_end = app.ui.window_width + } + if y_end > app.ui.window_height - 5 { + y_end = app.ui.window_height - 5 + } + app.ui.draw_rect(x_start, y_start, x_end, y_end) + } else { + app.ui.draw_text(app.mouse_pos.x, app.mouse_pos.y, space) + } + app.ui.reset() +} + +fn (mut app App) draw_header() { + if app.msg != '' { + app.ui.set_color( + r: 0 + g: 0 + b: 0 + ) + app.ui.set_bg_color( + r: 220 + g: 220 + b: 220 + ) + app.ui.draw_text(0, 0, ' $app.msg ') + app.ui.reset() + } + //'tick: $app.ui.frame_count | ' + + app.ui.draw_text(3, 2, 'terminal size: ($app.ui.window_width, $app.ui.window_height) | primary color: $app.primary_color.hex() | secondary color: $app.secondary_color.hex()') + app.ui.horizontal_separator(3) +} + +fn (mut app App) draw_footer() { + _, wh := app.ui.window_width, app.ui.window_height + app.ui.horizontal_separator(wh - 4) + for i, color_row in colors { + for j, color in color_row { + x := j * 3 + 19 + y := wh - 3 + i + app.ui.set_bg_color(color) + if app.primary_color_idx == j + (i * 19) { + app.ui.set_color(r: 0, g: 0, b: 0) + app.ui.draw_text(x, y, '><') + app.ui.reset_color() + } else if app.secondary_color_idx == j + (i * 19) { + app.ui.set_color(r: 255, g: 255, b: 255) + app.ui.draw_text(x, y, '><') + app.ui.reset_color() + } else { + app.ui.draw_rect(x, y, x + 1, y) + } + } + } + app.ui.reset_bg_color() + app.ui.draw_text(3, wh - 3, select_color) + app.ui.bold() + app.ui.draw_text(3, wh - 1, '$select_size $app.size') + app.ui.reset() + + // TODO: help button + // if ww >= 90 { + // app.ui.draw_text(80, wh - 3, help_1) + // app.ui.draw_text(80, wh - 2, help_2) + // app.ui.draw_text(80, wh - 1, help_3) + // } +} + +[inline] +fn (mut app App) inc_size() { + if app.size < 30 { + app.size++ + } + app.show_msg('inc. size: $app.size', 1) +} + +[inline] +fn (mut app App) dec_size() { + if app.size > 1 { + app.size-- + } + app.show_msg('dec. size: $app.size', 1) +} + +fn (mut app App) footer_click(event &ui.Event) { + footer_y := 3 - (app.ui.window_height - event.y) + match event.x { + 8...11 { + app.inc_size() + } + 12...15 { + app.dec_size() + } + 18...75 { + if (event.x % 3) == 0 { + // Inside the gap between tiles + return + } + idx := footer_y * 19 - 6 + event.x / 3 + if idx < 0 || idx > 56 { + return + } + app.select_color(event.button == .left, idx) + } + else {} + } +} + +fn (mut app App) show_msg(text string, time int) { + frames := time * frame_rate + app.msg_hide_tick = if time > 0 { int(app.ui.frame_count) + frames } else { -1 } + app.msg = text +} diff --git a/v_windows/v/examples/term.ui/text_editor.v b/v_windows/v/examples/term.ui/text_editor.v new file mode 100644 index 0000000..78e6e40 --- /dev/null +++ b/v_windows/v/examples/term.ui/text_editor.v @@ -0,0 +1,583 @@ +// Copyright (c) 2020 Lars Pontoppidan. All rights reserved. +// Use of this source code is governed by the MIT license distributed with this software. +// Don't use this editor for any serious work. +// A lot of funtionality is missing compared to your favourite editor :) +import strings +import math.mathutil as mu +import os +import term.ui as tui + +enum Movement { + up + down + left + right + home + end + page_up + page_down +} + +struct View { +pub: + raw string + cursor Cursor +} + +struct App { +mut: + tui &tui.Context = 0 + ed &Buffer = 0 + current_file int + files []string + status string + t int + magnet_x int + footer_height int = 2 + viewport int +} + +fn (mut a App) set_status(msg string, duration_ms int) { + a.status = msg + a.t = duration_ms +} + +fn (mut a App) save() { + if a.cfile().len > 0 { + b := a.ed + os.write_file(a.cfile(), b.raw()) or { panic(err) } + a.set_status('Saved', 2000) + } else { + a.set_status('No file loaded', 4000) + } +} + +fn (mut a App) cfile() string { + if a.files.len == 0 { + return '' + } + if a.current_file >= a.files.len { + return '' + } + return a.files[a.current_file] +} + +fn (mut a App) visit_prev_file() { + if a.files.len == 0 { + a.current_file = 0 + } else { + a.current_file = (a.current_file + a.files.len - 1) % a.files.len + } + a.init_file() +} + +fn (mut a App) visit_next_file() { + if a.files.len == 0 { + a.current_file = 0 + } else { + a.current_file = (a.current_file + a.files.len + 1) % a.files.len + } + a.init_file() +} + +fn (mut a App) footer() { + w, h := a.tui.window_width, a.tui.window_height + mut b := a.ed + // flat := b.flat() + // snip := if flat.len > 19 { flat[..20] } else { flat } + finfo := if a.cfile().len > 0 { ' (' + os.file_name(a.cfile()) + ')' } else { '' } + mut status := a.status + a.tui.draw_text(0, h - 1, '─'.repeat(w)) + footer := '$finfo Line ${b.cursor.pos_y + 1:4}/${b.lines.len:-4}, Column ${b.cursor.pos_x + 1:3}/${b.cur_line().len:-3} index: ${b.cursor_index():5} (ESC = quit, Ctrl+s = save)' + if footer.len < w { + a.tui.draw_text((w - footer.len) / 2, h, footer) + } else if footer.len == w { + a.tui.draw_text(0, h, footer) + } else { + a.tui.draw_text(0, h, footer[..w]) + } + if a.t <= 0 { + status = '' + } else { + a.tui.set_bg_color( + r: 200 + g: 200 + b: 200 + ) + a.tui.set_color( + r: 0 + g: 0 + b: 0 + ) + a.tui.draw_text((w + 4 - status.len) / 2, h - 1, ' $status ') + a.tui.reset() + a.t -= 33 + } +} + +struct Buffer { + tab_width int = 4 +pub mut: + lines []string + cursor Cursor +} + +fn (b Buffer) flat() string { + return b.raw().replace_each(['\n', r'\n', '\t', r'\t']) +} + +fn (b Buffer) raw() string { + return b.lines.join('\n') +} + +fn (b Buffer) view(from int, to int) View { + l := b.cur_line() + mut x := 0 + for i := 0; i < b.cursor.pos_x && i < l.len; i++ { + if l[i] == `\t` { + x += b.tab_width + continue + } + x++ + } + mut lines := []string{} + for i, line in b.lines { + if i >= from && i <= to { + lines << line + } + } + raw := lines.join('\n') + return View{ + raw: raw.replace('\t', strings.repeat(` `, b.tab_width)) + cursor: Cursor{ + pos_x: x + pos_y: b.cursor.pos_y + } + } +} + +fn (b Buffer) line(i int) string { + if i < 0 || i >= b.lines.len { + return '' + } + return b.lines[i] +} + +fn (b Buffer) cur_line() string { + return b.line(b.cursor.pos_y) +} + +fn (b Buffer) cursor_index() int { + mut i := 0 + for y, line in b.lines { + if b.cursor.pos_y == y { + i += b.cursor.pos_x + break + } + i += line.len + 1 + } + return i +} + +fn (mut b Buffer) put(s string) { + has_line_ending := s.contains('\n') + x, y := b.cursor.xy() + if b.lines.len == 0 { + b.lines.prepend('') + } + line := b.lines[y] + l, r := line[..x], line[x..] + if has_line_ending { + mut lines := s.split('\n') + lines[0] = l + lines[0] + lines[lines.len - 1] += r + b.lines.delete(y) + b.lines.insert(y, lines) + last := lines[lines.len - 1] + b.cursor.set(last.len, y + lines.len - 1) + if s == '\n' { + b.cursor.set(0, b.cursor.pos_y) + } + } else { + b.lines[y] = l + s + r + b.cursor.set(x + s.len, y) + } + $if debug { + flat := s.replace('\n', r'\n') + eprintln(@MOD + '.' + @STRUCT + '::' + @FN + ' "$flat"') + } +} + +fn (mut b Buffer) del(amount int) string { + if amount == 0 { + return '' + } + x, y := b.cursor.xy() + if amount < 0 { // don't delete left if we're at 0,0 + if x == 0 && y == 0 { + return '' + } + } else if x >= b.cur_line().len && y >= b.lines.len - 1 { + return '' + } + mut removed := '' + if amount < 0 { // backspace (backward) + i := b.cursor_index() + removed = b.raw()[i + amount..i] + mut left := amount * -1 + for li := y; li >= 0 && left > 0; li-- { + ln := b.lines[li] + if left > ln.len { + b.lines.delete(li) + if ln.len == 0 { // line break delimiter + left-- + if y == 0 { + return '' + } + line_above := b.lines[li - 1] + b.cursor.pos_x = line_above.len + } else { + left -= ln.len + } + b.cursor.pos_y-- + } else { + if x == 0 { + if y == 0 { + return '' + } + line_above := b.lines[li - 1] + if ln.len == 0 { // at line break + b.lines.delete(li) + b.cursor.pos_y-- + b.cursor.pos_x = line_above.len + } else { + b.lines[li - 1] = line_above + ln + b.lines.delete(li) + b.cursor.pos_y-- + b.cursor.pos_x = line_above.len + } + } else if x == 1 { + b.lines[li] = b.lines[li][left..] + b.cursor.pos_x = 0 + } else { + b.lines[li] = ln[..x - left] + ln[x..] + b.cursor.pos_x -= left + } + left = 0 + break + } + } + } else { // delete (forward) + i := b.cursor_index() + 1 + removed = b.raw()[i - amount..i] + mut left := amount + for li := y; li >= 0 && left > 0; li++ { + ln := b.lines[li] + if x == ln.len { // at line end + if y + 1 <= b.lines.len { + b.lines[li] = ln + b.lines[y + 1] + b.lines.delete(y + 1) + left-- + b.del(left) + } + } else if left > ln.len { + b.lines.delete(li) + left -= ln.len + } else { + b.lines[li] = ln[..x] + ln[x + left..] + left = 0 + } + } + } + $if debug { + flat := removed.replace('\n', r'\n') + eprintln(@MOD + '.' + @STRUCT + '::' + @FN + ' "$flat"') + } + return removed +} + +fn (mut b Buffer) free() { + $if debug { + eprintln(@MOD + '.' + @STRUCT + '::' + @FN) + } + for line in b.lines { + unsafe { line.free() } + } + unsafe { b.lines.free() } +} + +fn (mut b Buffer) move_updown(amount int) { + b.cursor.move(0, amount) + // Check the move + line := b.cur_line() + if b.cursor.pos_x > line.len { + b.cursor.set(line.len, b.cursor.pos_y) + } +} + +// move_cursor will navigate the cursor within the buffer bounds +fn (mut b Buffer) move_cursor(amount int, movement Movement) { + cur_line := b.cur_line() + match movement { + .up { + if b.cursor.pos_y - amount >= 0 { + b.move_updown(-amount) + } + } + .down { + if b.cursor.pos_y + amount < b.lines.len { + b.move_updown(amount) + } + } + .page_up { + dlines := mu.min(b.cursor.pos_y, amount) + b.move_updown(-dlines) + } + .page_down { + dlines := mu.min(b.lines.len - 1, b.cursor.pos_y + amount) - b.cursor.pos_y + b.move_updown(dlines) + } + .left { + if b.cursor.pos_x - amount >= 0 { + b.cursor.move(-amount, 0) + } else if b.cursor.pos_y > 0 { + b.cursor.set(b.line(b.cursor.pos_y - 1).len, b.cursor.pos_y - 1) + } + } + .right { + if b.cursor.pos_x + amount <= cur_line.len { + b.cursor.move(amount, 0) + } else if b.cursor.pos_y + 1 < b.lines.len { + b.cursor.set(0, b.cursor.pos_y + 1) + } + } + .home { + b.cursor.set(0, b.cursor.pos_y) + } + .end { + b.cursor.set(cur_line.len, b.cursor.pos_y) + } + } +} + +fn (mut b Buffer) move_to_word(movement Movement) { + a := if movement == .left { -1 } else { 1 } + mut line := b.cur_line() + mut x, mut y := b.cursor.pos_x, b.cursor.pos_y + if x + a < 0 && y > 0 { + y-- + line = b.line(b.cursor.pos_y - 1) + x = line.len + } else if x + a >= line.len && y + 1 < b.lines.len { + y++ + line = b.line(b.cursor.pos_y + 1) + x = 0 + } + // first, move past all non-`a-zA-Z0-9_` characters + for x + a >= 0 && x + a < line.len && !(line[x + a].is_letter() + || line[x + a].is_digit() || line[x + a] == `_`) { + x += a + } + // then, move past all the letters and numbers + for x + a >= 0 && x + a < line.len && (line[x + a].is_letter() + || line[x + a].is_digit() || line[x + a] == `_`) { + x += a + } + // if the cursor is out of bounds, move it to the next/previous line + if x + a >= 0 && x + a <= line.len { + x += a + } else if a < 0 && y + 1 > b.lines.len && y - 1 >= 0 { + y += a + x = 0 + } + b.cursor.set(x, y) +} + +struct Cursor { +pub mut: + pos_x int + pos_y int +} + +fn (mut c Cursor) set(x int, y int) { + c.pos_x = x + c.pos_y = y +} + +fn (mut c Cursor) move(x int, y int) { + c.pos_x += x + c.pos_y += y +} + +fn (c Cursor) xy() (int, int) { + return c.pos_x, c.pos_y +} + +// App callbacks +fn init(x voidptr) { + mut a := &App(x) + a.init_file() +} + +fn (mut a App) init_file() { + a.ed = &Buffer{} + mut init_y := 0 + mut init_x := 0 + if a.files.len > 0 && a.current_file < a.files.len && a.files[a.current_file].len > 0 { + if !os.is_file(a.files[a.current_file]) && a.files[a.current_file].contains(':') { + // support the file:line:col: format + fparts := a.files[a.current_file].split(':') + if fparts.len > 0 { + a.files[a.current_file] = fparts[0] + } + if fparts.len > 1 { + init_y = fparts[1].int() - 1 + } + if fparts.len > 2 { + init_x = fparts[2].int() - 1 + } + } + if os.is_file(a.files[a.current_file]) { + // 'vico: ' + + a.tui.set_window_title(a.files[a.current_file]) + mut b := a.ed + content := os.read_file(a.files[a.current_file]) or { panic(err) } + b.put(content) + a.ed.cursor.pos_x = init_x + a.ed.cursor.pos_y = init_y + } + } +} + +fn (a &App) view_height() int { + return a.tui.window_height - a.footer_height - 1 +} + +// magnet_cursor_x will place the cursor as close to it's last move left or right as possible +fn (mut a App) magnet_cursor_x() { + mut buffer := a.ed + if buffer.cursor.pos_x < a.magnet_x { + if a.magnet_x < buffer.cur_line().len { + move_x := a.magnet_x - buffer.cursor.pos_x + buffer.move_cursor(move_x, .right) + } + } +} + +fn frame(x voidptr) { + mut a := &App(x) + mut ed := a.ed + a.tui.clear() + scroll_limit := a.view_height() + // scroll down + if ed.cursor.pos_y > a.viewport + scroll_limit { // scroll down + a.viewport = ed.cursor.pos_y - scroll_limit + } else if ed.cursor.pos_y < a.viewport { // scroll up + a.viewport = ed.cursor.pos_y + } + view := ed.view(a.viewport, scroll_limit + a.viewport) + a.tui.draw_text(0, 0, view.raw) + a.footer() + a.tui.set_cursor_position(view.cursor.pos_x + 1, ed.cursor.pos_y + 1 - a.viewport) + a.tui.flush() +} + +fn event(e &tui.Event, x voidptr) { + mut a := &App(x) + mut buffer := a.ed + if e.typ == .key_down { + match e.code { + .escape { + exit(0) + } + .enter { + buffer.put('\n') + } + .backspace { + buffer.del(-1) + } + .delete { + buffer.del(1) + } + .left { + if e.modifiers == .ctrl { + buffer.move_to_word(.left) + } else if e.modifiers.is_empty() { + buffer.move_cursor(1, .left) + } + a.magnet_x = buffer.cursor.pos_x + } + .right { + if e.modifiers == .ctrl { + buffer.move_to_word(.right) + } else if e.modifiers.is_empty() { + buffer.move_cursor(1, .right) + } + a.magnet_x = buffer.cursor.pos_x + } + .up { + buffer.move_cursor(1, .up) + a.magnet_cursor_x() + } + .down { + buffer.move_cursor(1, .down) + a.magnet_cursor_x() + } + .page_up { + buffer.move_cursor(a.view_height(), .page_up) + } + .page_down { + buffer.move_cursor(a.view_height(), .page_down) + } + .home { + buffer.move_cursor(1, .home) + } + .end { + buffer.move_cursor(1, .end) + } + 48...57, 97...122 { // 0-9a-zA-Z + if e.modifiers == .ctrl { + if e.code == .s { + a.save() + } + } else if !(e.modifiers.has(.ctrl | .alt) || e.code == .null) { + buffer.put(e.ascii.ascii_str()) + } + } + else { + if e.modifiers == .alt { + if e.code == .comma { + a.visit_prev_file() + return + } + if e.code == .period { + a.visit_next_file() + return + } + } + buffer.put(e.utf8.bytes().bytestr()) + } + } + } else if e.typ == .mouse_scroll { + direction := if e.direction == .up { Movement.down } else { Movement.up } + buffer.move_cursor(1, direction) + } +} + +fn main() { + mut files := []string{} + if os.args.len > 1 { + files << os.args[1..] + } + mut a := &App{ + files: files + } + a.tui = tui.init( + user_data: a + init_fn: init + frame_fn: frame + event_fn: event + capture_events: true + ) + a.tui.run() ? +} diff --git a/v_windows/v/examples/term.ui/vyper.v b/v_windows/v/examples/term.ui/vyper.v new file mode 100644 index 0000000..cf41e60 --- /dev/null +++ b/v_windows/v/examples/term.ui/vyper.v @@ -0,0 +1,475 @@ +// import modules for use in app +import term.ui as termui +import rand + +// define some global constants +const ( + block_size = 1 + buffer = 10 + green = termui.Color{0, 255, 0} + grey = termui.Color{150, 150, 150} + white = termui.Color{255, 255, 255} + blue = termui.Color{0, 0, 255} + red = termui.Color{255, 0, 0} + black = termui.Color{0, 0, 0} +) + +// what edge of the screen are you facing +enum Orientation { + top + right + bottom + left +} + +// what's the current state of the game +enum GameState { + pause + gameover + game + oob // snake out-of-bounds +} + +// simple 2d vector representation +struct Vec { +mut: + x int + y int +} + +// determine orientation from vector (hacky way to set facing from velocity) +fn (v Vec) facing() Orientation { + result := if v.x >= 0 { + Orientation.right + } else if v.x < 0 { + Orientation.left + } else if v.y >= 0 { + Orientation.bottom + } else { + Orientation.top + } + return result +} + +// generate a random vector with x in [min_x, max_x] and y in [min_y, max_y] +fn (mut v Vec) randomize(min_x int, min_y int, max_x int, max_y int) { + v.x = rand.int_in_range(min_x, max_x) + v.y = rand.int_in_range(min_y, max_y) +} + +// part of snake's body representation +struct BodyPart { +mut: + pos Vec = Vec{ + x: block_size + y: block_size + } + color termui.Color = green + facing Orientation = .top +} + +// snake representation +struct Snake { +mut: + app &App + direction Orientation + body []BodyPart + velocity Vec = Vec{ + x: 0 + y: 0 + } +} + +// length returns the snake's current length +fn (s Snake) length() int { + return s.body.len +} + +// impulse provides a impulse to change the snake's direction +fn (mut s Snake) impulse(direction Orientation) { + mut vec := Vec{} + match direction { + .top { + vec.x = 0 + vec.y = -1 * block_size + } + .right { + vec.x = 2 * block_size + vec.y = 0 + } + .bottom { + vec.x = 0 + vec.y = block_size + } + .left { + vec.x = -2 * block_size + vec.y = 0 + } + } + s.direction = direction + s.velocity = vec +} + +// move performs the calculations for the snake's movements +fn (mut s Snake) move() { + mut i := s.body.len - 1 + width := s.app.width + height := s.app.height + // move the parts of the snake as appropriate + for i = s.body.len - 1; i >= 0; i-- { + mut piece := s.body[i] + if i > 0 { // just move the body of the snake up one position + piece.pos = s.body[i - 1].pos + piece.facing = s.body[i - 1].facing + } else { // verify that the move is valid and move the head if so + piece.facing = s.direction + new_x := piece.pos.x + s.velocity.x + new_y := piece.pos.y + s.velocity.y + piece.pos.x += if new_x > block_size && new_x < width - block_size { + s.velocity.x + } else { + 0 + } + piece.pos.y += if new_y > block_size && new_y < height - block_size { + s.velocity.y + } else { + 0 + } + } + s.body[i] = piece + } +} + +// grow add another part to the snake when it catches the rat +fn (mut s Snake) grow() { + head := s.get_tail() + mut pos := Vec{} + // add the segment on the opposite side of the previous tail + match head.facing { + .bottom { + pos.x = head.pos.x + pos.y = head.pos.y - block_size + } + .left { + pos.x = head.pos.x + block_size + pos.y = head.pos.y + } + .top { + pos.x = head.pos.x + pos.y = head.pos.y + block_size + } + .right { + pos.x = head.pos.x - block_size + pos.y = head.pos.y + } + } + s.body << BodyPart{ + pos: pos + facing: head.facing + } +} + +// get_body gets the parts of the snakes body +fn (s Snake) get_body() []BodyPart { + return s.body +} + +// get_head get snake's head +fn (s Snake) get_head() BodyPart { + return s.body[0] +} + +// get_tail get snake's tail +fn (s Snake) get_tail() BodyPart { + return s.body[s.body.len - 1] +} + +// randomize randomizes position and veolcity of snake +fn (mut s Snake) randomize() { + speeds := [-2, 0, 2] + mut pos := s.get_head().pos + pos.randomize(buffer, buffer, s.app.width - buffer, s.app.height - buffer) + for pos.x % 2 != 0 || (pos.x < buffer && pos.x > s.app.width - buffer) { + pos.randomize(buffer, buffer, s.app.width - buffer, s.app.height - buffer) + } + s.velocity.y = rand.int_in_range(-1 * block_size, block_size) + s.velocity.x = speeds[rand.intn(speeds.len)] + s.direction = s.velocity.facing() + s.body[0].pos = pos +} + +// check_overlap determine if the snake's looped onto itself +fn (s Snake) check_overlap() bool { + h := s.get_head() + head_pos := h.pos + for i in 2 .. s.length() { + piece_pos := s.body[i].pos + if head_pos.x == piece_pos.x && head_pos.y == piece_pos.y { + return true + } + } + return false +} + +fn (s Snake) check_out_of_bounds() bool { + h := s.get_head() + return h.pos.x + s.velocity.x <= block_size + || h.pos.x + s.velocity.x > s.app.width - s.velocity.x + || h.pos.y + s.velocity.y <= block_size + || h.pos.y + s.velocity.y > s.app.height - block_size - s.velocity.y +} + +// draw draws the parts of the snake +fn (s Snake) draw() { + mut a := s.app + for part in s.get_body() { + a.termui.set_bg_color(part.color) + a.termui.draw_rect(part.pos.x, part.pos.y, part.pos.x + block_size, part.pos.y + block_size) + $if verbose ? { + text := match part.facing { + .top { '^' } + .bottom { 'v' } + .right { '>' } + .left { '<' } + } + a.termui.set_color(white) + a.termui.draw_text(part.pos.x, part.pos.y, text) + } + } +} + +// rat representation +struct Rat { +mut: + pos Vec = Vec{ + x: block_size + y: block_size + } + captured bool + color termui.Color = grey + app &App +} + +// randomize spawn the rat in a new spot within the playable field +fn (mut r Rat) randomize() { + r.pos.randomize(2 * block_size + buffer, 2 * block_size + buffer, r.app.width - block_size - buffer, + r.app.height - block_size - buffer) +} + +[heap] +struct App { +mut: + termui &termui.Context = 0 + snake Snake + rat Rat + width int + height int + redraw bool = true + state GameState = .game +} + +// new_game setups the rat and snake for play +fn (mut a App) new_game() { + mut snake := Snake{ + body: []BodyPart{len: 1, init: BodyPart{}} + app: a + } + snake.randomize() + mut rat := Rat{ + app: a + } + rat.randomize() + a.snake = snake + a.rat = rat + a.state = .game + a.redraw = true +} + +// initialize the app and record the width and height of the window +fn init(x voidptr) { + mut app := &App(x) + w, h := app.termui.window_width, app.termui.window_height + app.width = w + app.height = h + app.new_game() +} + +// event handles different events for the app as they occur +fn event(e &termui.Event, x voidptr) { + mut app := &App(x) + match e.typ { + .mouse_down {} + .mouse_drag {} + .mouse_up {} + .key_down { + match e.code { + .up, .w { app.move_snake(.top) } + .down, .s { app.move_snake(.bottom) } + .left, .a { app.move_snake(.left) } + .right, .d { app.move_snake(.right) } + .r { app.new_game() } + .c {} + .p { app.state = if app.state == .game { GameState.pause } else { GameState.game } } + .escape, .q { exit(0) } + else { exit(0) } + } + if e.code == .c { + } else if e.code == .escape { + exit(0) + } + } + else {} + } + app.redraw = true +} + +// frame perform actions on every tick +fn frame(x voidptr) { + mut app := &App(x) + app.update() + app.draw() +} + +// update perform any calculations that are needed before drawing +fn (mut a App) update() { + if a.state == .game { + a.snake.move() + if a.snake.check_out_of_bounds() { + $if verbose ? { + a.snake.body[0].color = red + } $else { + a.state = .oob + } + } + if a.snake.check_overlap() { + a.state = .gameover + return + } + if a.check_capture() { + a.rat.randomize() + a.snake.grow() + } + } +} + +// draw write to the screen +fn (mut a App) draw() { + // reset screen + a.termui.clear() + a.termui.set_bg_color(white) + a.termui.draw_empty_rect(1, 1, a.width, a.height) + // determine if a special screen needs to be draw + match a.state { + .gameover { + a.draw_gameover() + a.redraw = false + } + .pause { + a.draw_pause() + } + else { + a.redraw = true + } + } + a.termui.set_color(blue) + a.termui.set_bg_color(white) + a.termui.draw_text(3 * block_size, a.height - (2 * block_size), 'p - (un)pause r - reset q - quit') + // draw the snake, rat, and score if appropriate + if a.redraw { + a.termui.set_bg_color(black) + a.draw_gamescreen() + if a.state == .oob { + a.state = .gameover + } + } + // write to the screen + a.termui.reset_bg_color() + a.termui.flush() +} + +// move_snake move the snake in specified direction +fn (mut a App) move_snake(direction Orientation) { + a.snake.impulse(direction) +} + +// check_capture determine if the snake overlaps with the rat +fn (a App) check_capture() bool { + snake_pos := a.snake.get_head().pos + rat_pos := a.rat.pos + return snake_pos.x <= rat_pos.x + block_size && snake_pos.x + block_size >= rat_pos.x + && snake_pos.y <= rat_pos.y + block_size && snake_pos.y + block_size >= rat_pos.y +} + +fn (mut a App) draw_snake() { + a.snake.draw() +} + +fn (mut a App) draw_rat() { + a.termui.set_bg_color(a.rat.color) + a.termui.draw_rect(a.rat.pos.x, a.rat.pos.y, a.rat.pos.x + block_size, a.rat.pos.y + block_size) +} + +fn (mut a App) draw_gamescreen() { + $if verbose ? { + a.draw_debug() + } + a.draw_score() + a.draw_rat() + a.draw_snake() +} + +fn (mut a App) draw_score() { + a.termui.set_color(blue) + a.termui.set_bg_color(white) + score := a.snake.length() - 1 + a.termui.draw_text(a.width - (2 * block_size), block_size, '${score:03d}') +} + +fn (mut a App) draw_pause() { + a.termui.set_color(blue) + a.termui.draw_text((a.width / 2) - block_size, 3 * block_size, 'Paused!') +} + +fn (mut a App) draw_debug() { + a.termui.set_color(blue) + a.termui.set_bg_color(white) + snake := a.snake + a.termui.draw_text(block_size, 1 * block_size, 'Display_width: ${a.width:04d} Display_height: ${a.height:04d}') + a.termui.draw_text(block_size, 2 * block_size, 'Vx: ${snake.velocity.x:+02d} Vy: ${snake.velocity.y:+02d}') + a.termui.draw_text(block_size, 3 * block_size, 'F: $snake.direction') + snake_head := snake.get_head() + rat := a.rat + a.termui.draw_text(block_size, 4 * block_size, 'Sx: ${snake_head.pos.x:+03d} Sy: ${snake_head.pos.y:+03d}') + a.termui.draw_text(block_size, 5 * block_size, 'Rx: ${rat.pos.x:+03d} Ry: ${rat.pos.y:+03d}') +} + +fn (mut a App) draw_gameover() { + a.termui.set_bg_color(white) + a.termui.set_color(red) + a.rat.pos = Vec{ + x: -1 + y: -1 + } + x_offset := ' ##### '.len // take half of a line from the game over text and store the length + start_x := (a.width / 2) - x_offset + a.termui.draw_text(start_x, (a.height / 2) - 3 * block_size, ' ##### ####### ') + a.termui.draw_text(start_x, (a.height / 2) - 2 * block_size, ' # # ## # # ###### # # # # ###### ##### ') + a.termui.draw_text(start_x, (a.height / 2) - 1 * block_size, ' # # # ## ## # # # # # # # # ') + a.termui.draw_text(start_x, (a.height / 2) - 0 * block_size, ' # #### # # # ## # ##### # # # # ##### # # ') + a.termui.draw_text(start_x, (a.height / 2) + 1 * block_size, ' # # ###### # # # # # # # # ##### ') + a.termui.draw_text(start_x, (a.height / 2) + 2 * block_size, ' # # # # # # # # # # # # # # ') + a.termui.draw_text(start_x, (a.height / 2) + 3 * block_size, ' ##### # # # # ###### ####### ## ###### # # ') +} + +fn main() { + mut app := &App{} + app.termui = termui.init( + user_data: app + event_fn: event + frame_fn: frame + init_fn: init + hide_cursor: true + frame_rate: 10 + ) + app.termui.run() ? +} diff --git a/v_windows/v/examples/terminal_control.v b/v_windows/v/examples/terminal_control.v new file mode 100644 index 0000000..404f290 --- /dev/null +++ b/v_windows/v/examples/terminal_control.v @@ -0,0 +1,36 @@ +import term + +fn main() { + term.erase_clear() + sleeping_line(5, 5, 5, '*') + standing_line(5, 5, 5, '*') + sleeping_line(5, 10, 5, '*') + standing_line(9, 5, 5, '*') + term.cursor_down(5) + print('\n') + println(term.bold(term.red('It Worked!'))) +} + +fn sleeping_line(x int, y int, size int, ch string) { + mut i := 0 + for i < size { + term.set_cursor_position( + x: x + i + y: y + ) + print(term.bold(term.yellow(ch))) + i++ + } +} + +fn standing_line(x int, y int, size int, ch string) { + mut i := 0 + for i < size { + term.set_cursor_position( + x: x + y: y + i + ) + print(term.bold(term.yellow(ch))) + i++ + } +} diff --git a/v_windows/v/examples/tetris/README.md b/v_windows/v/examples/tetris/README.md new file mode 100644 index 0000000..353db1e --- /dev/null +++ b/v_windows/v/examples/tetris/README.md @@ -0,0 +1,9 @@ +<img src='https://raw.githubusercontent.com/vlang/v/master/examples/tetris/screenshot.png' width=300> + +### Dependencies (Ubuntu) +```sh +sudo apt install libx11-dev +sudo apt install libxi-dev +sudo apt install libxcursor-dev +sudo apt install libgl-dev +``` diff --git a/v_windows/v/examples/tetris/screenshot.png b/v_windows/v/examples/tetris/screenshot.png Binary files differnew file mode 100644 index 0000000..8b457a1 --- /dev/null +++ b/v_windows/v/examples/tetris/screenshot.png diff --git a/v_windows/v/examples/tetris/tetris.v b/v_windows/v/examples/tetris/tetris.v new file mode 100644 index 0000000..01589a3 --- /dev/null +++ b/v_windows/v/examples/tetris/tetris.v @@ -0,0 +1,518 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module main + +import os +import rand +import time +import gx +import gg +// import sokol.sapp + +const ( + block_size = 20 // virtual pixels + field_height = 20 // # of blocks + field_width = 10 + tetro_size = 4 + win_width = block_size * field_width + win_height = block_size * field_height + timer_period = 250 // ms + text_size = 24 + limit_thickness = 3 +) + +const ( + text_cfg = gx.TextCfg{ + align: .left + size: text_size + color: gx.rgb(0, 0, 0) + } + over_cfg = gx.TextCfg{ + align: .left + size: text_size + color: gx.white + } +) + +const ( + // Tetros' 4 possible states are encoded in binaries + // 0000 0 0000 0 0000 0 0000 0 0000 0 0000 0 + // 0000 0 0000 0 0000 0 0000 0 0011 3 0011 3 + // 0110 6 0010 2 0011 3 0110 6 0001 1 0010 2 + // 0110 6 0111 7 0110 6 0011 3 0001 1 0010 2 + // There is a special case 1111, since 15 can't be used. + b_tetros = [ + [66, 66, 66, 66], + [27, 131, 72, 232], + [36, 231, 36, 231], + [63, 132, 63, 132], + [311, 17, 223, 74], + [322, 71, 113, 47], + [1111, 9, 1111, 9], + ] + // Each tetro has its unique color + colors = [ + gx.rgb(0, 0, 0), /* unused ? */ + gx.rgb(255, 242, 0), /* yellow quad */ + gx.rgb(174, 0, 255), /* purple triple */ + gx.rgb(60, 255, 0), /* green short topright */ + gx.rgb(255, 0, 0), /* red short topleft */ + gx.rgb(255, 180, 31), /* orange long topleft */ + gx.rgb(33, 66, 255), /* blue long topright */ + gx.rgb(74, 198, 255), /* lightblue longest */ + gx.rgb(0, 170, 170), + ] + background_color = gx.white + ui_color = gx.rgba(255, 0, 0, 210) +) + +// TODO: type Tetro [tetro_size]struct{ x, y int } +struct Block { +mut: + x int + y int +} + +enum GameState { + paused + running + gameover +} + +struct Game { +mut: + // Score of the current game + score int + // Lines of the current game + lines int + // State of the current game + state GameState + // Block size in screen dimensions + block_size int = block_size + // Field margin + margin int + // Position of the current tetro + pos_x int + pos_y int + // field[y][x] contains the color of the block with (x,y) coordinates + // "-1" border is to avoid bounds checking. + // -1 -1 -1 -1 + // -1 0 0 -1 + // -1 0 0 -1 + // -1 -1 -1 -1 + field [][]int + // TODO: tetro Tetro + tetro []Block + // TODO: tetros_cache []Tetro + tetros_cache []Block + // Index of the current tetro. Refers to its color. + tetro_idx int + // Idem for the next tetro + next_tetro_idx int + // Index of the rotation (0-3) + rotation_idx int + // gg context for drawing + gg &gg.Context = voidptr(0) + font_loaded bool + show_ghost bool = true + // frame/time counters: + frame int + frame_old int + frame_sw time.StopWatch = time.new_stopwatch() + second_sw time.StopWatch = time.new_stopwatch() +} + +fn remap(v f32, min f32, max f32, new_min f32, new_max f32) f32 { + return (((v - min) * (new_max - new_min)) / (max - min)) + new_min +} + +[if showfps ?] +fn (mut game Game) showfps() { + game.frame++ + last_frame_ms := f64(game.frame_sw.elapsed().microseconds()) / 1000.0 + ticks := f64(game.second_sw.elapsed().microseconds()) / 1000.0 + if ticks > 999.0 { + fps := f64(game.frame - game.frame_old) * ticks / 1000.0 + $if debug { + eprintln('fps: ${fps:5.1f} | last frame took: ${last_frame_ms:6.3f}ms | frame: ${game.frame:6} ') + } + game.second_sw.restart() + game.frame_old = game.frame + } +} + +fn frame(mut game Game) { + ws := gg.window_size() + bs := remap(block_size, 0, win_height, 0, ws.height) + m := (f32(ws.width) - bs * field_width) * 0.5 + game.block_size = int(bs) + game.margin = int(m) + game.frame_sw.restart() + game.gg.begin() + game.draw_scene() + game.showfps() + game.gg.end() +} + +fn main() { + mut game := &Game{ + gg: 0 + } + mut fpath := os.resource_abs_path(os.join_path('..', 'assets', 'fonts', 'RobotoMono-Regular.ttf')) + $if android { + fpath = 'fonts/RobotoMono-Regular.ttf' + } + game.gg = gg.new_context( + bg_color: gx.white + width: win_width + height: win_height + create_window: true + window_title: 'V Tetris' // + user_data: game + frame_fn: frame + event_fn: on_event + font_path: fpath // wait_events: true + ) + game.init_game() + go game.run() // Run the game loop in a new thread + game.gg.run() // Run the render loop in the main thread +} + +fn (mut g Game) init_game() { + g.parse_tetros() + g.next_tetro_idx = rand.intn(b_tetros.len) // generate initial "next" + g.generate_tetro() + g.field = [] + // Generate the field, fill it with 0's, add -1's on each edge + for _ in 0 .. field_height + 2 { + mut row := [0].repeat(field_width + 2) + row[0] = -1 + row[field_width + 1] = -1 + g.field << row.clone() + } + for j in 0 .. field_width + 2 { + g.field[0][j] = -1 + g.field[field_height + 1][j] = -1 + } + g.score = 0 + g.lines = 0 + g.state = .running +} + +fn (mut g Game) parse_tetros() { + for b_tetros0 in b_tetros { + for b_tetro in b_tetros0 { + for t in parse_binary_tetro(b_tetro) { + g.tetros_cache << t + } + } + } +} + +fn (mut g Game) run() { + for { + if g.state == .running { + g.move_tetro() + g.delete_completed_lines() + } + // glfw.post_empty_event() // force window redraw + time.sleep(timer_period * time.millisecond) + } +} + +fn (g &Game) draw_ghost() { + if g.state != .gameover && g.show_ghost { + pos_y := g.move_ghost() + for i in 0 .. tetro_size { + tetro := g.tetro[i] + g.draw_block_color(pos_y + tetro.y, g.pos_x + tetro.x, gx.rgba(125, 125, 225, + 40)) + } + } +} + +fn (g Game) move_ghost() int { + mut pos_y := g.pos_y + mut end := false + for !end { + for block in g.tetro { + y := block.y + pos_y + 1 + x := block.x + g.pos_x + if g.field[y][x] != 0 { + end = true + break + } + } + pos_y++ + } + return pos_y - 1 +} + +fn (mut g Game) move_tetro() bool { + // Check each block in current tetro + for block in g.tetro { + y := block.y + g.pos_y + 1 + x := block.x + g.pos_x + // Reached the bottom of the screen or another block? + if g.field[y][x] != 0 { + // The new tetro has no space to drop => end of the game + if g.pos_y < 2 { + g.state = .gameover + return false + } + // Drop it and generate a new one + g.drop_tetro() + g.generate_tetro() + return false + } + } + g.pos_y++ + return true +} + +fn (mut g Game) move_right(dx int) bool { + // Reached left/right edge or another tetro? + for i in 0 .. tetro_size { + tetro := g.tetro[i] + y := tetro.y + g.pos_y + x := tetro.x + g.pos_x + dx + if g.field[y][x] != 0 { + // Do not move + return false + } + } + g.pos_x += dx + return true +} + +fn (mut g Game) delete_completed_lines() { + for y := field_height; y >= 1; y-- { + g.delete_completed_line(y) + } +} + +fn (mut g Game) delete_completed_line(y int) { + for x := 1; x <= field_width; x++ { + if g.field[y][x] == 0 { + return + } + } + g.score += 10 + g.lines++ + // Move everything down by 1 position + for yy := y - 1; yy >= 1; yy-- { + for x := 1; x <= field_width; x++ { + g.field[yy + 1][x] = g.field[yy][x] + } + } +} + +// Place a new tetro on top +fn (mut g Game) generate_tetro() { + g.pos_y = 0 + g.pos_x = field_width / 2 - tetro_size / 2 + g.tetro_idx = g.next_tetro_idx + g.next_tetro_idx = rand.intn(b_tetros.len) + g.rotation_idx = 0 + g.get_tetro() +} + +// Get the right tetro from cache +fn (mut g Game) get_tetro() { + idx := g.tetro_idx * tetro_size * tetro_size + g.rotation_idx * tetro_size + g.tetro = g.tetros_cache[idx..idx + tetro_size].clone() +} + +// TODO mut +fn (mut g Game) drop_tetro() { + for i in 0 .. tetro_size { + tetro := g.tetro[i] + x := tetro.x + g.pos_x + y := tetro.y + g.pos_y + // Remember the color of each block + g.field[y][x] = g.tetro_idx + 1 + } +} + +fn (g &Game) draw_tetro() { + for i in 0 .. tetro_size { + tetro := g.tetro[i] + g.draw_block(g.pos_y + tetro.y, g.pos_x + tetro.x, g.tetro_idx + 1) + } +} + +fn (g &Game) draw_next_tetro() { + if g.state != .gameover { + idx := g.next_tetro_idx * tetro_size * tetro_size + next_tetro := g.tetros_cache[idx..idx + tetro_size].clone() + pos_y := 0 + pos_x := field_width / 2 - tetro_size / 2 + for i in 0 .. tetro_size { + block := next_tetro[i] + g.draw_block_color(pos_y + block.y, pos_x + block.x, gx.rgb(220, 220, 220)) + } + } +} + +fn (g &Game) draw_block_color(i int, j int, color gx.Color) { + g.gg.draw_rect(f32((j - 1) * g.block_size) + g.margin, f32((i - 1) * g.block_size), + f32(g.block_size - 1), f32(g.block_size - 1), color) +} + +fn (g &Game) draw_block(i int, j int, color_idx int) { + color := if g.state == .gameover { gx.gray } else { colors[color_idx] } + g.draw_block_color(i, j, color) +} + +fn (g &Game) draw_field() { + for i := 1; i < field_height + 1; i++ { + for j := 1; j < field_width + 1; j++ { + if g.field[i][j] > 0 { + g.draw_block(i, j, g.field[i][j]) + } + } + } +} + +fn (mut g Game) draw_ui() { + ws := gg.window_size() + textsize := int(remap(text_size, 0, win_width, 0, ws.width)) + g.gg.draw_text(1, 3, g.score.str(), text_cfg) + lines := g.lines.str() + g.gg.draw_text(ws.width - lines.len * textsize, 3, lines, text_cfg) + if g.state == .gameover { + g.gg.draw_rect(0, ws.height / 2 - textsize, ws.width, 5 * textsize, ui_color) + g.gg.draw_text(1, ws.height / 2 + 0 * textsize, 'Game Over', over_cfg) + g.gg.draw_text(1, ws.height / 2 + 2 * textsize, 'Space to restart', over_cfg) + } else if g.state == .paused { + g.gg.draw_rect(0, ws.height / 2 - textsize, ws.width, 5 * textsize, ui_color) + g.gg.draw_text(1, ws.height / 2 + 0 * textsize, 'Game Paused', text_cfg) + g.gg.draw_text(1, ws.height / 2 + 2 * textsize, 'SPACE to resume', text_cfg) + } + // g.gg.draw_rect(0, block_size, win_width, limit_thickness, ui_color) +} + +fn (mut g Game) draw_scene() { + g.draw_ghost() + g.draw_next_tetro() + g.draw_tetro() + g.draw_field() + g.draw_ui() +} + +fn parse_binary_tetro(t_ int) []Block { + mut t := t_ + mut res := [Block{}].repeat(4) + mut cnt := 0 + horizontal := t == 9 // special case for the horizontal line + ten_powers := [1000, 100, 10, 1] + for i := 0; i <= 3; i++ { + // Get ith digit of t + p := ten_powers[i] + mut digit := t / p + t %= p + // Convert the digit to binary + for j := 3; j >= 0; j-- { + bin := digit % 2 + digit /= 2 + if bin == 1 || (horizontal && i == tetro_size - 1) { + res[cnt].x = j + res[cnt].y = i + cnt++ + } + } + } + return res +} + +fn on_event(e &gg.Event, mut game Game) { + // println('code=$e.char_code') + if e.typ == .key_down { + game.key_down(e.key_code) + } + if e.typ == .touches_began || e.typ == .touches_moved { + if e.num_touches > 0 { + touch_point := e.touches[0] + game.touch_event(touch_point) + } + } +} + +fn (mut game Game) rotate_tetro() { + old_rotation_idx := game.rotation_idx + game.rotation_idx++ + if game.rotation_idx == tetro_size { + game.rotation_idx = 0 + } + game.get_tetro() + if !game.move_right(0) { + game.rotation_idx = old_rotation_idx + game.get_tetro() + } + if game.pos_x < 0 { + // game.pos_x = 1 + } +} + +fn (mut game Game) key_down(key gg.KeyCode) { + // global keys + match key { + .escape { + exit(0) + } + .space { + if game.state == .running { + game.state = .paused + } else if game.state == .paused { + game.state = .running + } else if game.state == .gameover { + game.init_game() + game.state = .running + } + } + else {} + } + if game.state != .running { + return + } + // keys while game is running + match key { + .up { + // Rotate the tetro + game.rotate_tetro() + } + .left { + game.move_right(-1) + } + .right { + game.move_right(1) + } + .down { + game.move_tetro() // drop faster when the player presses <down> + } + .d { + for game.move_tetro() { + } + } + .g { + game.show_ghost = !game.show_ghost + } + else {} + } +} + +fn (mut game Game) touch_event(touch_point C.sapp_touchpoint) { + ws := gg.window_size() + tx := touch_point.pos_x + ty := touch_point.pos_y + if ty < f32(ws.height) * 0.5 { + game.rotate_tetro() + } else { + if tx <= f32(ws.width) * 0.5 { + game.move_right(-1) + } else { + game.move_right(1) + } + } +} diff --git a/v_windows/v/examples/tree_of_nodes.v b/v_windows/v/examples/tree_of_nodes.v new file mode 100644 index 0000000..76d69a2 --- /dev/null +++ b/v_windows/v/examples/tree_of_nodes.v @@ -0,0 +1,27 @@ +type Tree = Empty | Node + +struct Empty {} + +struct Node { + value int + left Tree + right Tree +} + +// NB: a match expression, infers the type of its result +// from the type of the return value in the first branch, +// => it needs an explicit int(0) cast here: +fn size(tree Tree) int { + return match tree { + Empty { int(0) } + Node { 1 + size(tree.left) + size(tree.right) } + } +} + +fn main() { + node1 := Node{30, Empty{}, Empty{}} + node2 := Node{20, Empty{}, Empty{}} + tree := Node{10, node1, node2} + println('tree structure:\n $tree') + println('tree size: ${size(tree)}') +} diff --git a/v_windows/v/examples/ttf_font/Graduate-Regular.ttf b/v_windows/v/examples/ttf_font/Graduate-Regular.ttf Binary files differnew file mode 100644 index 0000000..0ce68b7 --- /dev/null +++ b/v_windows/v/examples/ttf_font/Graduate-Regular.ttf diff --git a/v_windows/v/examples/ttf_font/Imprima-Regular.ttf b/v_windows/v/examples/ttf_font/Imprima-Regular.ttf Binary files differnew file mode 100644 index 0000000..9c701e1 --- /dev/null +++ b/v_windows/v/examples/ttf_font/Imprima-Regular.ttf diff --git a/v_windows/v/examples/ttf_font/OFL.txt b/v_windows/v/examples/ttf_font/OFL.txt new file mode 100644 index 0000000..3a5f6c1 --- /dev/null +++ b/v_windows/v/examples/ttf_font/OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2012, Eduardo Tunni (http://www.tipo.net.ar), with Reserved Font Name "Imprima" + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/v_windows/v/examples/ttf_font/example_ttf.v b/v_windows/v/examples/ttf_font/example_ttf.v new file mode 100644 index 0000000..02e43cd --- /dev/null +++ b/v_windows/v/examples/ttf_font/example_ttf.v @@ -0,0 +1,171 @@ +import gg +import gx +import sokol.sapp +import sokol.sgl +import x.ttf +import os + +// import math +const ( + win_width = 600 + win_height = 700 + bg_color = gx.white + font_paths = [ + os.resource_abs_path('Imprima-Regular.ttf'), + os.resource_abs_path('Graduate-Regular.ttf'), + ] +) + +// UI +struct App_data { +pub mut: + gg &gg.Context + sg_img C.sg_image + init_flag bool + frame_c int + tf []ttf.TTF_File + ttf_render []ttf.TTF_render_Sokol + text_ready_flag bool + mouse_x int = -1 + mouse_y int = -1 +} + +fn my_init(mut app App_data) { + app.init_flag = true +} + +fn draw_frame(mut app App_data) { + cframe_txt := 'Current Frame: $app.frame_c' + app.gg.begin() + sgl.defaults() + sgl.matrix_mode_projection() + sgl.ortho(0.0, f32(sapp.width()), f32(sapp.height()), 0.0, -1.0, 1.0) + sgl.c4b(0, 0, 0, 255) // black + // draw a line as background + sgl.begin_line_strip() + sgl.v2f(10, 10) + sgl.v2f(100, 100) + sgl.end() + // draw text only if the app is already initialized + if app.init_flag == true { + sgl.begin_line_strip() + sgl.v2f(410, 400) + sgl.v2f(510, 400) + sgl.end() + // update the text + mut txt1 := unsafe { &app.ttf_render[0] } + if app.frame_c % 2 == 0 { + txt1.destroy_texture() + txt1.create_text(cframe_txt, 43) + txt1.create_texture() + } + // ----- decomment if you want text rotation ---- + // txt1.bmp.angle = 3.141592 / 180 * f32(app.frame_c % 360) + // txt1.draw_text_bmp(app.gg, 300, 350) + // txt1.bmp.angle = 0 + txt1.draw_text_bmp(app.gg, 30, 60) + // block test + block_txt := "Today it is a good day! +Tommorow I'm not so sure :( +Frame: $app.frame_c +But Vwill prevail for sure, V is the way!! +òàèì@ò!£$%& +" + txt1 = unsafe { &app.ttf_render[1] } + if app.frame_c % 2 == 0 { + txt1.bmp.justify = false + if (app.frame_c >> 6) % 2 == 0 { + // txt1.align = .left + txt1.bmp.justify = true + } + txt1.bmp.align = .left + if (app.frame_c >> 6) % 3 == 0 { + txt1.bmp.align = .right + } + txt1.destroy_texture() + txt1.create_text_block(block_txt, 500, 500, 32) + txt1.create_texture() + } + // decomment if want block color change + // txt1.bmp.color = ttf.color_multiply(0xFF00FFFF, f32(app.frame_c % 255)/255.0) + // decomment if want block rotation wanted + // txt1.bmp.angle = 3.141592/180 * f32(app.frame_c % 45) + txt1.draw_text_bmp(app.gg, 30 + (app.frame_c >> 1) & 0xFF, 200) + // draw mouse position + if app.mouse_x >= 0 { + txt1 = unsafe { &app.ttf_render[2] } + txt1.destroy_texture() + txt1.create_text('$app.mouse_x,$app.mouse_y', 25) + txt1.create_texture() + r := app.mouse_x % 255 + g := app.mouse_y % 255 + color := u32(r << 24) | u32(g << 16) | 0xFF + txt1.bmp.color = color + txt1.draw_text_bmp(app.gg, app.mouse_x, app.mouse_y) + } + app.frame_c++ + } + app.gg.end() +} + +fn my_event_manager(mut ev gg.Event, mut app App_data) { + if ev.typ == .mouse_move { + app.mouse_x = int(ev.mouse_x) + app.mouse_y = int(ev.mouse_y) + } +} + +[console] +fn main() { + mut app := &App_data{ + gg: 0 + } + app.gg = gg.new_context( + width: win_width + height: win_height + create_window: true + window_title: 'Test TTF module' + user_data: app + bg_color: bg_color + frame_fn: draw_frame + event_fn: my_event_manager + init_fn: my_init + ) + // load TTF fonts + for font_path in font_paths { + mut tf := ttf.TTF_File{} + tf.buf = os.read_bytes(font_path) or { panic(err) } + println('TrueTypeFont file [$font_path] len: $tf.buf.len') + tf.init() + println('Unit per EM: $tf.units_per_em') + app.tf << tf + } + // TTF render 0 Frame counter + app.ttf_render << &ttf.TTF_render_Sokol{ + bmp: &ttf.BitMap{ + tf: &(app.tf[0]) + buf: unsafe { malloc_noscan(32000000) } + buf_size: (32000000) + color: 0xFF0000FF + // style: .raw + // use_font_metrics: true + } + } + // TTF render 1 Text Block + app.ttf_render << &ttf.TTF_render_Sokol{ + bmp: &ttf.BitMap{ + tf: &(app.tf[1]) + // color : 0xFF0000_10 + // style: .raw + // use_font_metrics: true + } + } + // TTF mouse position render + app.ttf_render << &ttf.TTF_render_Sokol{ + bmp: &ttf.BitMap{ + tf: &(app.tf[0]) + } + } + // setup sokol_gfx + app.gg.run() +} diff --git a/v_windows/v/examples/v_script.vsh b/v_windows/v/examples/v_script.vsh new file mode 100644 index 0000000..1cdf910 --- /dev/null +++ b/v_windows/v/examples/v_script.vsh @@ -0,0 +1,32 @@ +#!/usr/local/bin/v run + +// The shebang above associates the file to V on Unix-like systems, +// so it can be run just by specifying the path to the file +// once it's made executable using `chmod +x`. + +for _ in 0 .. 3 { + println('V script') +} + +println('\nMaking dir "v_script_dir".') +mkdir('v_script_dir') + +println("\nEntering into v_script_dir and listing it's files.") +chdir('v_script_dir') +files := ls('.') or { panic(err.msg) } +println(files) + +println('\nCreating foo.txt') +create('foo.txt') ? + +println('\nFiles:') +again_ls := ls('.') or { panic(err.msg) } +println(again_ls) + +println('\nRemoving foo.txt and v_script_dir') +rm('foo.txt') +chdir('../') +rmdir('v_script_dir') + +print('\nDoes v_script_dir still exist? ') +println(exists('v_script_dir')) diff --git a/v_windows/v/examples/vcasino/README.md b/v_windows/v/examples/vcasino/README.md new file mode 100644 index 0000000..a2a6a3d --- /dev/null +++ b/v_windows/v/examples/vcasino/README.md @@ -0,0 +1,17 @@ +# VCasino +VCasino is a very simple game made to learn V. + +# Compile and Run + +Use this to generate a binary and then launch the game. +```bash +v VCasino.v +./VCasino +``` + +And this to compile and launch the game directly. +```bash +v run VCasino.v +``` + +Created by Thomas Senechal : https://github.com/thomas-senechal/VCasino diff --git a/v_windows/v/examples/vcasino/vcasino.v b/v_windows/v/examples/vcasino/vcasino.v new file mode 100644 index 0000000..e6fd87c --- /dev/null +++ b/v_windows/v/examples/vcasino/vcasino.v @@ -0,0 +1,146 @@ +import rand +import os + +const ( + help_text = ' Usage:\t./VCasino\n + Description:\n VCasino is a little game only made to learn V.\n' + g_desc = " The object of Roulette is to pick the number where the spinning ball will land on the wheel. + If your number is the good one, you'll get your bet x3. + If your number is the same color as the ball one, you'll get your bet /2. + Otherwise, you will lose your bet.\n" + odd = 'red' + even = 'black' +) + +struct Options { + long_opt string + short_opt string +} + +fn display_help() { + println(help_text + g_desc) +} + +fn option_parser() bool { + help := Options{'--help', '-h'} + for i in 0 .. os.args.len { + if os.args[i] == help.long_opt || os.args[i] == help.short_opt { + display_help() + return true + } + } + return false +} + +fn str_is_nbr(s string) bool { + for i in 0 .. s.len { + if !s[i].is_digit() { + return false + } + } + return true +} + +fn get_bet_nbr() int { + mut bet_nbr := -1 + for bet_nbr < 0 || bet_nbr > 49 { + println('Reminder: odd numbers are red and even are black.') + println('Type the number you want to bet on (between 0 and 49):') + line := os.get_line().trim_space() + if line.len < 1 { + println('error: empty line.') + continue + } + if !str_is_nbr(line) { + println('error: $line is not a number.') + continue + } + bet_nbr = line.int() + if bet_nbr < 0 || bet_nbr > 49 { + println('error: $line is not between 0 and 49.') + bet_nbr = -1 + continue + } + } + return bet_nbr +} + +fn get_bet(money int) int { + mut bet := -1 + for bet <= 0 || bet > money { + println('You have $money V. Type in the amount of your bet:') + line := os.get_line().trim_space() + if line.len < 1 { + println('error: empty line.') + continue + } + if !str_is_nbr(line) { + println('error: $line is not a number.') + continue + } + bet = line.int() + if bet <= 0 { + println('error: $line is not higher than 1.') + continue + } else if bet > money { + println('error: $line is more money than you have.') + } + } + return bet +} + +fn run_wheel(bet_nbr int, _bet int) int { + mut bet := _bet + winning_nbr := rand.intn(50) + print('Roulette Wheel spinning... and stops on the number $winning_nbr which is a ') + if winning_nbr % 2 == 1 { + println(odd) + } else { + println(even) + } + if winning_nbr == bet_nbr { + bet *= 3 + println('Congratulations! You get $bet V!') + } else if winning_nbr % 2 == bet_nbr % 2 { + bet /= 2 + println('You bet the right color. You get $bet V!') + } else { + println('Sorry buddy. You lost $bet V!') + bet *= -1 + } + return bet +} + +fn is_broke(money int) bool { + if money <= 0 { + println("You're broke, the game is over..") + return false + } + quit := Options{'yes', 'y'} + println('You have $money V. Do you want to quit the casino with your winnings? (y/n)') + line := os.get_line().trim_space().to_lower() + if line == quit.long_opt || line == quit.short_opt { + return false + } + return true +} + +fn game_loop() { + mut can_play := true + mut money := 1000 + println(g_desc) + println('You start the game with $money V.\n') + for can_play { + bet_nbr := get_bet_nbr() + bet := get_bet(money) + money += run_wheel(bet_nbr, bet) + can_play = is_broke(money) + } +} + +fn main() { + if os.args.len >= 2 && option_parser() { + return + } + game_loop() +} diff --git a/v_windows/v/examples/vmod.v b/v_windows/v/examples/vmod.v new file mode 100644 index 0000000..9e34b32 --- /dev/null +++ b/v_windows/v/examples/vmod.v @@ -0,0 +1,9 @@ +module main + +import v.vmod + +fn main() { + mod := vmod.decode(@VMOD_FILE) or { panic('Error decoding v.mod') } + println('$mod.name has version $mod.version') + println('\nThe full mod struct: \n$mod') +} diff --git a/v_windows/v/examples/vpwgen.v b/v_windows/v/examples/vpwgen.v new file mode 100644 index 0000000..2f803e4 --- /dev/null +++ b/v_windows/v/examples/vpwgen.v @@ -0,0 +1,25 @@ +import os +import os.cmdline +import crypto.rand +import strings + +fn main() { + blocksize := 256 + size := cmdline.option(os.args, '-size', '80').int() + repeats := cmdline.option(os.args, '-repeats', '4').int() + for _ in 0 .. repeats { + mut sb := strings.new_builder(blocksize) + for { + x := rand.read(blocksize) ? + for c in x { + if c >= `0` && c <= `~` { + sb.write_b(c) + } + } + if sb.len > size { + println(sb.str()[0..size]) + break + } + } + } +} diff --git a/v_windows/v/examples/vweb/file_upload/index.html b/v_windows/v/examples/vweb/file_upload/index.html new file mode 100644 index 0000000..a2bffad --- /dev/null +++ b/v_windows/v/examples/vweb/file_upload/index.html @@ -0,0 +1,6 @@ +<h2>File Upload</h2> + +<form method="POST" enctype="multipart/form-data" action="/upload"> + <input type="file" name="upfile" multiple> + <input type="submit" value="Press"> +</form> diff --git a/v_windows/v/examples/vweb/file_upload/upload.html b/v_windows/v/examples/vweb/file_upload/upload.html new file mode 100644 index 0000000..e80359d --- /dev/null +++ b/v_windows/v/examples/vweb/file_upload/upload.html @@ -0,0 +1,14 @@ +<meta charset="utf-8"> + +File amount: @fdata.len + +@for i, data in fdata + +<h2>Filename: @data.filename</h2> +<h2>Type: @data.content_type</h2> + +<p>@{files[i]}</p> + +@end + +<a href="/">Back</a> diff --git a/v_windows/v/examples/vweb/file_upload/vweb_example.v b/v_windows/v/examples/vweb/file_upload/vweb_example.v new file mode 100644 index 0000000..bde55c3 --- /dev/null +++ b/v_windows/v/examples/vweb/file_upload/vweb_example.v @@ -0,0 +1,32 @@ +module main + +import vweb + +const ( + port = 8082 +) + +struct App { + vweb.Context +} + +fn main() { + vweb.run(&App{}, port) +} + +pub fn (mut app App) index() vweb.Result { + return $vweb.html() +} + +['/upload'; post] +pub fn (mut app App) upload() vweb.Result { + fdata := app.files['upfile'] + + mut files := []vweb.RawHtml{} + + for d in fdata { + files << d.data.replace_each(['\n', '<br>', '\n\r', '<br>', '\t', ' ', ' ', ' ']) + } + + return $vweb.html() +} diff --git a/v_windows/v/examples/vweb/index.html b/v_windows/v/examples/vweb/index.html new file mode 100644 index 0000000..d69ff78 --- /dev/null +++ b/v_windows/v/examples/vweb/index.html @@ -0,0 +1,15 @@ +Test <b>app</b> +<br> +<h1>@hello</h1> +<hr> +@if show + show = true +@end + +@for number in numbers + @number <br> +@end + + +<hr> +End. diff --git a/v_windows/v/examples/vweb/server_sent_events/assets/site.css b/v_windows/v/examples/vweb/server_sent_events/assets/site.css new file mode 100644 index 0000000..4ad9eb8 --- /dev/null +++ b/v_windows/v/examples/vweb/server_sent_events/assets/site.css @@ -0,0 +1,19 @@ +body { + font-family: Arial, Helvetica, sans-serif; + color: #eee; + background-color: #333; + background-image: url("v-logo.svg"); + background-repeat: no-repeat; + background-size: 10em; + margin: 0; + padding-left: 11em; +} + +h1 { + color: #6699CC; +} + +img.logo { + float: left; + width: 10em; +} diff --git a/v_windows/v/examples/vweb/server_sent_events/assets/v-logo.svg b/v_windows/v/examples/vweb/server_sent_events/assets/v-logo.svg new file mode 100644 index 0000000..9a4ec60 --- /dev/null +++ b/v_windows/v/examples/vweb/server_sent_events/assets/v-logo.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 500 500" width="500px" height="500px"><defs><clipPath id="_clipPath_8TWIgR1z3pxinjWBiigzcEIrVJKv9Gq4"><rect width="500" height="500"/></clipPath></defs><g clip-path="url(#_clipPath_8TWIgR1z3pxinjWBiigzcEIrVJKv9Gq4)"><path d=" M 318.422 453.543 L 463.705 49.541 C 466.168 42.689 462.285 37.693 455.037 38.392 L 340.786 49.398 C 333.539 50.097 325.71 56.246 323.316 63.121 L 188.843 449.216 C 186.447 456.091 190.414 461.673 197.695 461.673 L 308.901 461.673 C 312.541 461.673 316.497 458.893 317.729 455.466 L 318.422 453.543 Z " fill="rgb(83,107,138)"/><defs><filter id="Hmac7mZraFWHw0G84Yxj4QuzeTFp0E7Y" x="-200%" y="-200%" width="400%" height="400%" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB"><feGaussianBlur xmlns="http://www.w3.org/2000/svg" in="SourceGraphic" stdDeviation="6.440413594258542"/><feOffset xmlns="http://www.w3.org/2000/svg" dx="0" dy="0" result="pf_100_offsetBlur"/><feFlood xmlns="http://www.w3.org/2000/svg" flood-color="#000000" flood-opacity="0.65"/><feComposite xmlns="http://www.w3.org/2000/svg" in2="pf_100_offsetBlur" operator="in" result="pf_100_dropShadow"/><feBlend xmlns="http://www.w3.org/2000/svg" in="SourceGraphic" in2="pf_100_dropShadow" mode="normal"/></filter></defs><g filter="url(#Hmac7mZraFWHw0G84Yxj4QuzeTFp0E7Y)"><path d=" M 301.848 455.466 L 241.359 280.725 L 250 275.324 L 311.57 453.543 L 301.848 455.466 Z " fill="rgb(235,235,235)"/></g><path d=" M 44.963 38.392 L 159.214 49.398 C 166.461 50.097 174.298 56.243 176.704 63.115 L 314.022 455.448 C 315.224 458.885 313.245 461.673 309.604 461.673 L 197.695 461.673 C 190.414 461.673 182.502 456.111 180.038 449.259 L 36.295 49.541 C 33.832 42.689 37.715 37.693 44.963 38.392 Z " fill="rgb(93,135,191)"/></g></svg> diff --git a/v_windows/v/examples/vweb/server_sent_events/favicon.ico b/v_windows/v/examples/vweb/server_sent_events/favicon.ico Binary files differnew file mode 100644 index 0000000..fa834c3 --- /dev/null +++ b/v_windows/v/examples/vweb/server_sent_events/favicon.ico diff --git a/v_windows/v/examples/vweb/server_sent_events/index.html b/v_windows/v/examples/vweb/server_sent_events/index.html new file mode 100644 index 0000000..7e500c9 --- /dev/null +++ b/v_windows/v/examples/vweb/server_sent_events/index.html @@ -0,0 +1,38 @@ +<html> + <header> + <title>@title</title> + <meta charset="utf-8"/> + @css 'assets/site.css' + </header> + <body> + <h1>@title</h1> + <button>Close the connection</button> + <ul></ul> + <script> + "use strict"; + var button = document.querySelector('button'); + var eventList = document.querySelector('ul'); + const evtSource = new EventSource('/sse'); + evtSource.onerror = function() { console.log("EventSource failed."); }; + console.log(evtSource.withCredentials); + console.log(evtSource.readyState); + console.log(evtSource.url); + evtSource.onopen = function() { + console.log("Connection to server opened."); + }; + evtSource.onmessage = function(e) { + var newElement = document.createElement("li"); + newElement.textContent = "message: " + e.data; + eventList.appendChild(newElement); + }; + evtSource.addEventListener("ping", function(e) { + console.log(e) + var newElement = document.createElement("li"); + var obj = JSON.parse(e.data); + newElement.innerHTML = "ping at " + obj.time + ' server data: ' + e.data; + eventList.appendChild(newElement); + }, false); + button.onclick = function() { console.log('Connection closed'); evtSource.close(); }; + </script> + </body> +</html> diff --git a/v_windows/v/examples/vweb/server_sent_events/server.v b/v_windows/v/examples/vweb/server_sent_events/server.v new file mode 100644 index 0000000..c1139d8 --- /dev/null +++ b/v_windows/v/examples/vweb/server_sent_events/server.v @@ -0,0 +1,37 @@ +module main + +import os +import rand +import time +import vweb +import vweb.sse + +struct App { + vweb.Context +} + +fn main() { + mut app := &App{} + app.serve_static('/favicon.ico', 'favicon.ico') + app.mount_static_folder_at(os.resource_abs_path('.'), '/') + vweb.run(app, 8081) +} + +pub fn (mut app App) index() vweb.Result { + title := 'SSE Example' + return $vweb.html() +} + +fn (mut app App) sse() vweb.Result { + mut session := sse.new_connection(app.conn) + // NB: you can setup session.write_timeout and session.headers here + session.start() or { return app.server_error(501) } + session.send_message(data: 'ok') or { return app.server_error(501) } + for { + data := '{"time": "$time.now().str()", "random_id": "$rand.ulid()"}' + session.send_message(event: 'ping', data: data) or { return app.server_error(501) } + println('> sent event: $data') + time.sleep(1 * time.second) + } + return app.server_error(501) +} diff --git a/v_windows/v/examples/vweb/vweb_assets/assets/index.css b/v_windows/v/examples/vweb/vweb_assets/assets/index.css new file mode 100644 index 0000000..4ad9eb8 --- /dev/null +++ b/v_windows/v/examples/vweb/vweb_assets/assets/index.css @@ -0,0 +1,19 @@ +body { + font-family: Arial, Helvetica, sans-serif; + color: #eee; + background-color: #333; + background-image: url("v-logo.svg"); + background-repeat: no-repeat; + background-size: 10em; + margin: 0; + padding-left: 11em; +} + +h1 { + color: #6699CC; +} + +img.logo { + float: left; + width: 10em; +} diff --git a/v_windows/v/examples/vweb/vweb_assets/assets/v-logo.svg b/v_windows/v/examples/vweb/vweb_assets/assets/v-logo.svg new file mode 100644 index 0000000..9a4ec60 --- /dev/null +++ b/v_windows/v/examples/vweb/vweb_assets/assets/v-logo.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 500 500" width="500px" height="500px"><defs><clipPath id="_clipPath_8TWIgR1z3pxinjWBiigzcEIrVJKv9Gq4"><rect width="500" height="500"/></clipPath></defs><g clip-path="url(#_clipPath_8TWIgR1z3pxinjWBiigzcEIrVJKv9Gq4)"><path d=" M 318.422 453.543 L 463.705 49.541 C 466.168 42.689 462.285 37.693 455.037 38.392 L 340.786 49.398 C 333.539 50.097 325.71 56.246 323.316 63.121 L 188.843 449.216 C 186.447 456.091 190.414 461.673 197.695 461.673 L 308.901 461.673 C 312.541 461.673 316.497 458.893 317.729 455.466 L 318.422 453.543 Z " fill="rgb(83,107,138)"/><defs><filter id="Hmac7mZraFWHw0G84Yxj4QuzeTFp0E7Y" x="-200%" y="-200%" width="400%" height="400%" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB"><feGaussianBlur xmlns="http://www.w3.org/2000/svg" in="SourceGraphic" stdDeviation="6.440413594258542"/><feOffset xmlns="http://www.w3.org/2000/svg" dx="0" dy="0" result="pf_100_offsetBlur"/><feFlood xmlns="http://www.w3.org/2000/svg" flood-color="#000000" flood-opacity="0.65"/><feComposite xmlns="http://www.w3.org/2000/svg" in2="pf_100_offsetBlur" operator="in" result="pf_100_dropShadow"/><feBlend xmlns="http://www.w3.org/2000/svg" in="SourceGraphic" in2="pf_100_dropShadow" mode="normal"/></filter></defs><g filter="url(#Hmac7mZraFWHw0G84Yxj4QuzeTFp0E7Y)"><path d=" M 301.848 455.466 L 241.359 280.725 L 250 275.324 L 311.57 453.543 L 301.848 455.466 Z " fill="rgb(235,235,235)"/></g><path d=" M 44.963 38.392 L 159.214 49.398 C 166.461 50.097 174.298 56.243 176.704 63.115 L 314.022 455.448 C 315.224 458.885 313.245 461.673 309.604 461.673 L 197.695 461.673 C 190.414 461.673 182.502 456.111 180.038 449.259 L 36.295 49.541 C 33.832 42.689 37.715 37.693 44.963 38.392 Z " fill="rgb(93,135,191)"/></g></svg> diff --git a/v_windows/v/examples/vweb/vweb_assets/favicon.ico b/v_windows/v/examples/vweb/vweb_assets/favicon.ico Binary files differnew file mode 100644 index 0000000..fa834c3 --- /dev/null +++ b/v_windows/v/examples/vweb/vweb_assets/favicon.ico diff --git a/v_windows/v/examples/vweb/vweb_assets/index.html b/v_windows/v/examples/vweb/vweb_assets/index.html new file mode 100644 index 0000000..6635329 --- /dev/null +++ b/v_windows/v/examples/vweb/vweb_assets/index.html @@ -0,0 +1,12 @@ +<html> +<header> + <title>@title</title> + @css 'index.css' +</header> +<body> + <h1>@title</h1> + <h2>@subtitle</h2> + <p>@message</p> +</body> +</html> + diff --git a/v_windows/v/examples/vweb/vweb_assets/vweb_assets.v b/v_windows/v/examples/vweb/vweb_assets/vweb_assets.v new file mode 100644 index 0000000..058f459 --- /dev/null +++ b/v_windows/v/examples/vweb/vweb_assets/vweb_assets.v @@ -0,0 +1,40 @@ +module main + +import vweb +// import vweb.assets +import time + +const ( + port = 8081 +) + +struct App { + vweb.Context +} + +fn main() { + mut app := &App{} + app.serve_static('/favicon.ico', 'favicon.ico') + // Automatically make available known static mime types found in given directory. + app.handle_static('assets', true) + vweb.run(app, port) +} + +pub fn (mut app App) index() vweb.Result { + // We can dynamically specify which assets are to be used in template. + // mut am := assets.new_manager() + // am.add_css('assets/index.css') + // css := am.include_css(false) + title := 'VWeb Assets Example' + subtitle := 'VWeb can serve static assets too!' + message := 'It also has an Assets Manager that allows dynamically specifying which CSS and JS files to be used.' + return $vweb.html() +} + +fn (mut app App) text() vweb.Result { + return app.Context.text('Hello, world from vweb!') +} + +fn (mut app App) time() vweb.Result { + return app.Context.text(time.now().format()) +} diff --git a/v_windows/v/examples/vweb/vweb_example.v b/v_windows/v/examples/vweb/vweb_example.v new file mode 100644 index 0000000..f26726c --- /dev/null +++ b/v_windows/v/examples/vweb/vweb_example.v @@ -0,0 +1,54 @@ +module main + +import vweb +import rand + +const ( + port = 8082 +) + +struct App { + vweb.Context +mut: + state shared State +} + +struct State { +mut: + cnt int +} + +fn main() { + println('vweb example') + vweb.run(&App{}, port) +} + +['/users/:user'] +pub fn (mut app App) user_endpoint(user string) vweb.Result { + id := rand.intn(100) + return app.json('{"$user": $id}') +} + +pub fn (mut app App) index() vweb.Result { + lock app.state { + app.state.cnt++ + } + show := true + hello := 'Hello world from vweb' + numbers := [1, 2, 3] + return $vweb.html() +} + +pub fn (mut app App) show_text() vweb.Result { + return app.text('Hello world from vweb') +} + +pub fn (mut app App) cookie() vweb.Result { + app.set_cookie(name: 'cookie', value: 'test') + return app.text('Response Headers\n$app.header') +} + +[post] +pub fn (mut app App) post() vweb.Result { + return app.text('Post body: $app.req.data') +} diff --git a/v_windows/v/examples/web_crawler/README.md b/v_windows/v/examples/web_crawler/README.md new file mode 100644 index 0000000..c8a741f --- /dev/null +++ b/v_windows/v/examples/web_crawler/README.md @@ -0,0 +1,22 @@ +# web_crawler +web_crawler is a very simple web crawler. +This web crawler fetches news from tuicool.com, +(a chinese site similar to hacker-news.firebaseio.com). + +# Compile and Run + +Use this to generate an executable, and then launch the web crawler: +```bash +v web_crawler.v +./web_crawler +``` + +And this to compile and launch the web crawler directly: +```bash +v run web_crawler.v +``` + +This project shows how to use http.fetch() to get http.Response, +and then html.parse() to parse the returned html. + +It's easy, isn't it? diff --git a/v_windows/v/examples/web_crawler/web_crawler.v b/v_windows/v/examples/web_crawler/web_crawler.v new file mode 100644 index 0000000..00d4dfa --- /dev/null +++ b/v_windows/v/examples/web_crawler/web_crawler.v @@ -0,0 +1,24 @@ +import net.http +import net.html + +fn main() { + // http.fetch() sends an HTTP request to the URL with the given method and configurations. + config := http.FetchConfig{ + user_agent: 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0' + } + resp := http.fetch(http.FetchConfig{ ...config, url: 'https://tuicool.com' }) or { + println('failed to fetch data from the server') + return + } + // html.parse() parses and returns the DOM from the given text. + mut doc := html.parse(resp.text) + // html.DocumentObjectModel.get_tag_by_attribute_value() retrieves all the tags in the document that has the given attribute name and value. + tags := doc.get_tag_by_attribute_value('class', 'list_article_item') + for tag in tags { + href := tag.children[0].attributes['href'] or { panic('key not found') } + title := tag.children[0].attributes['title'] or { panic('key not found') } + println('href: $href') + println('title: $title') + println('') + } +} diff --git a/v_windows/v/examples/websocket/client-server/client.v b/v_windows/v/examples/websocket/client-server/client.v new file mode 100644 index 0000000..b039415 --- /dev/null +++ b/v_windows/v/examples/websocket/client-server/client.v @@ -0,0 +1,54 @@ +module main + +import os +import net.websocket +import term + +// This client should be compiled an run in different consoles +// it connects to the server who will broadcast your messages +// to all other connected clients +fn main() { + mut ws := start_client() ? + println(term.green('client $ws.id ready')) + println('Write message and enter to send...') + for { + line := os.get_line() + if line == '' { + break + } + ws.write_string(line) ? + } + ws.close(1000, 'normal') or { println(term.red('panicing $err')) } + unsafe { + ws.free() + } +} + +fn start_client() ?&websocket.Client { + mut ws := websocket.new_client('ws://localhost:30000') ? + // mut ws := websocket.new_client('wss://echo.websocket.org:443')? + // use on_open_ref if you want to send any reference object + ws.on_open(fn (mut ws websocket.Client) ? { + println(term.green('websocket connected to the server and ready to send messages...')) + }) + // use on_error_ref if you want to send any reference object + ws.on_error(fn (mut ws websocket.Client, err string) ? { + println(term.red('error: $err')) + }) + // use on_close_ref if you want to send any reference object + ws.on_close(fn (mut ws websocket.Client, code int, reason string) ? { + println(term.green('the connection to the server successfully closed')) + }) + // on new messages from other clients, display them in blue text + ws.on_message(fn (mut ws websocket.Client, msg &websocket.Message) ? { + if msg.payload.len > 0 { + message := msg.payload.bytestr() + println(term.blue('$message')) + } + }) + + ws.connect() or { println(term.red('error on connect: $err')) } + + go ws.listen() // or { println(term.red('error on listen $err')) } + return ws +} diff --git a/v_windows/v/examples/websocket/client-server/server.v b/v_windows/v/examples/websocket/client-server/server.v new file mode 100644 index 0000000..db41913 --- /dev/null +++ b/v_windows/v/examples/websocket/client-server/server.v @@ -0,0 +1,43 @@ +module main + +import net.websocket +import term + +// this server accepts client connections and broadcast all messages to other connected clients +fn main() { + println('press ctrl-c to quit...') + start_server() ? +} + +fn start_server() ? { + mut s := websocket.new_server(.ip6, 30000, '') + // Make that in execution test time give time to execute at least one time + s.ping_interval = 100 + s.on_connect(fn (mut s websocket.ServerClient) ?bool { + // Here you can look att the client info and accept or not accept + // just returning a true/false + if s.resource_name != '/' { + return false + } + return true + }) ? + + // on_message_ref, broadcast all incoming messages to all clients except the one sent it + s.on_message_ref(fn (mut ws websocket.Client, msg &websocket.Message, mut m websocket.Server) ? { + // for _, cli in m.clients { + for i, _ in m.clients { + mut c := m.clients[i] + if c.client.state == .open && c.client.id != ws.id { + c.client.write(msg.payload, websocket.OPCode.text_frame) or { panic(err) } + } + } + }, s) + + s.on_close(fn (mut ws websocket.Client, code int, reason string) ? { + println(term.green('client ($ws.id) closed connection')) + }) + s.listen() or { println(term.red('error on server listen: $err')) } + unsafe { + s.free() + } +} diff --git a/v_windows/v/examples/websocket/ping.v b/v_windows/v/examples/websocket/ping.v new file mode 100644 index 0000000..3599ec3 --- /dev/null +++ b/v_windows/v/examples/websocket/ping.v @@ -0,0 +1,86 @@ +module main + +import time +import os +import net.websocket + +fn main() { + println('press enter to quit...\n') + go start_server() + time.sleep(100 * time.millisecond) + go start_client() + os.get_line() +} + +// start_server starts the websocket server, it receives messages +// and send it back to the client that sent it +fn start_server() ? { + mut s := websocket.new_server(.ip6, 30000, '') + // Make that in execution test time give time to execute at least one time + s.ping_interval = 100 + s.on_connect(fn (mut s websocket.ServerClient) ?bool { + // Here you can look att the client info and accept or not accept + // just returning a true/false + if s.resource_name != '/' { + return false + } + return true + }) ? + s.on_message(fn (mut ws websocket.Client, msg &websocket.Message) ? { + ws.write(msg.payload, msg.opcode) or { panic(err) } + }) + s.on_close(fn (mut ws websocket.Client, code int, reason string) ? { + // println('client ($ws.id) closed connection') + }) + s.listen() or { println('error on server listen: $err') } + unsafe { + s.free() + } +} + +// start_client starts the websocket client, it writes a message to +// the server and prints all the messages received +fn start_client() ? { + mut ws := websocket.new_client('ws://localhost:30000') ? + // mut ws := websocket.new_client('wss://echo.websocket.org:443')? + // use on_open_ref if you want to send any reference object + ws.on_open(fn (mut ws websocket.Client) ? { + println('open!') + }) + // use on_error_ref if you want to send any reference object + ws.on_error(fn (mut ws websocket.Client, err string) ? { + println('error: $err') + }) + // use on_close_ref if you want to send any reference object + ws.on_close(fn (mut ws websocket.Client, code int, reason string) ? { + println('closed') + }) + // use on_message_ref if you want to send any reference object + ws.on_message(fn (mut ws websocket.Client, msg &websocket.Message) ? { + if msg.payload.len > 0 { + message := msg.payload.bytestr() + println('client got type: $msg.opcode payload:\n$message') + } + }) + // you can add any pointer reference to use in callback + // t := TestRef{count: 10} + // ws.on_message_ref(fn (mut ws websocket.Client, msg &websocket.Message, r &SomeRef)? { + // // println('type: $msg.opcode payload:\n$msg.payload ref: $r') + // }, &r) + ws.connect() or { println('error on connect: $err') } + go write_echo(mut ws) // or { println('error on write_echo $err') } + ws.listen() or { println('error on listen $err') } + unsafe { + ws.free() + } +} + +fn write_echo(mut ws websocket.Client) ? { + message := 'echo this' + for i := 0; i <= 10; i++ { + // Server will send pings every 30 seconds + ws.write_string(message) or { println('panicing writing $err') } + time.sleep(100 * time.millisecond) + } + ws.close(1000, 'normal') or { println('panicing $err') } +} diff --git a/v_windows/v/examples/word_counter/README.md b/v_windows/v/examples/word_counter/README.md new file mode 100644 index 0000000..1316ad8 --- /dev/null +++ b/v_windows/v/examples/word_counter/README.md @@ -0,0 +1,25 @@ +``` +usage: word_counter [text_file] +using cinderella.txt +a => 25 +able => 2 +after => 1 +afterwards => 1 +again => 10 +against => 2 +all => 12 +allow => 1 +allowed => 2 +along => 1 +also => 2 +always => 2 +an => 4 +and => 140 +anew => 1 +anger => 1 +another => 2 +answered => 1 +any => 1 +anyone => 2 +... +``` diff --git a/v_windows/v/examples/word_counter/cinderella.txt b/v_windows/v/examples/word_counter/cinderella.txt new file mode 100644 index 0000000..7399d01 --- /dev/null +++ b/v_windows/v/examples/word_counter/cinderella.txt @@ -0,0 +1,250 @@ +A rich man's wife became sick, and when she felt that her end was drawing near, +she called her only daughter to her bedside and said, "Dear child, remain pious +and good, and then our dear God will always protect you, and I will look down +on you from heaven and be near you." With this she closed her eyes and died. + +The girl went out to her mother's grave every day and wept, and she remained +pious and good. When winter came the snow spread a white cloth over the grave, +and when the spring sun had removed it again, the man took himself another +wife. + +This wife brought two daughters into the house with her. They were beautiful, +with fair faces, but evil and dark hearts. Times soon grew very bad for the +poor stepchild. + +"Why should that stupid goose sit in the parlor with us?" they said. "If she +wants to eat bread, then she will have to earn it. Out with this kitchen maid!" + +They took her beautiful clothes away from her, dressed her in an old gray +smock, and gave her wooden shoes. "Just look at the proud princess! How decked +out she is!" they shouted and laughed as they led her into the kitchen. + +There she had to do hard work from morning until evening, get up before +daybreak, carry water, make the fires, cook, and wash. Besides this, the +sisters did everything imaginable to hurt her. They made fun of her, scattered +peas and lentils into the ashes for her, so that she had to sit and pick them +out again. In the evening when she had worked herself weary, there was no bed +for her. Instead she had to sleep by the hearth in the ashes. And because she +always looked dusty and dirty, they called her Cinderella. + +One day it happened that the father was going to the fair, and he asked his two +stepdaughters what he should bring back for them. + +"Beautiful dresses," said the one. + +"Pearls and jewels," said the other. + +"And you, Cinderella," he said, "what do you want?" + +"Father, break off for me the first twig that brushes against your hat on your +way home." + +So he bought beautiful dresses, pearls, and jewels for his two stepdaughters. +On his way home, as he was riding through a green thicket, a hazel twig brushed +against him and knocked off his hat. Then he broke off the twig and took it +with him. Arriving home, he gave his stepdaughters the things that they had +asked for, and he gave Cinderella the twig from the hazel bush. + +Cinderella thanked him, went to her mother's grave, and planted the branch on +it, and she wept so much that her tears fell upon it and watered it. It grew +and became a beautiful tree. + +Cinderella went to this tree three times every day, and beneath it she wept and +prayed. A white bird came to the tree every time, and whenever she expressed a +wish, the bird would throw down to her what she had wished for. + +Now it happened that the king proclaimed a festival that was to last three +days. All the beautiful young girls in the land were invited, so that his son +could select a bride for himself. When the two stepsisters heard that they too +had been invited, they were in high spirits. + +They called Cinderella, saying, "Comb our hair for us. Brush our shoes and +fasten our buckles. We are going to the festival at the king's castle." + +Cinderella obeyed, but wept, because she too would have liked to go to the +dance with them. She begged her stepmother to allow her to go. + +"You, Cinderella?" she said. "You, all covered with dust and dirt, and you want +to go to the festival?. You have neither clothes nor shoes, and yet you want to +dance!" + +However, because Cinderella kept asking, the stepmother finally said, "I have +scattered a bowl of lentils into the ashes for you. If you can pick them out +again in two hours, then you may go with us." + +The girl went through the back door into the garden, and called out, "You tame +pigeons, you turtledoves, and all you birds beneath the sky, come and help me +to gather: + +The good ones go into the pot, The bad ones go into your crop." Two white +pigeons came in through the kitchen window, and then the turtledoves, and +finally all the birds beneath the sky came whirring and swarming in, and lit +around the ashes. The pigeons nodded their heads and began to pick, pick, pick, +pick. And the others also began to pick, pick, pick, pick. They gathered all +the good grains into the bowl. Hardly one hour had passed before they were +finished, and they all flew out again. The girl took the bowl to her +stepmother, and was happy, thinking that now she would be allowed to go to the +festival with them. + +But the stepmother said, "No, Cinderella, you have no clothes, and you don't +know how to dance. Everyone would only laugh at you." + +Cinderella began to cry, and then the stepmother said, "You may go if you are +able to pick two bowls of lentils out of the ashes for me in one hour," +thinking to herself, "She will never be able to do that." + +The girl went through the back door into the garden, and called out, "You tame +pigeons, you turtledoves, and all you birds beneath the sky, come and help me +to gather: + +The good ones go into the pot, The bad ones go into your crop." Two white +pigeons came in through the kitchen window, and then the turtledoves, and +finally all the birds beneath the sky came whirring and swarming in, and lit +around the ashes. The pigeons nodded their heads and began to pick, pick, pick, +pick. And the others also began to pick, pick, pick, pick. They gathered all +the good grains into the bowls. Before a half hour had passed they were +finished, and they all flew out again. The girl took the bowls to her +stepmother, and was happy, thinking that now she would be allowed to go to the +festival with them. + +But the stepmother said, "It's no use. You are not coming with us, for you have +no clothes, and you don't know how to dance. We would be ashamed of you." With +this she turned her back on Cinderella, and hurried away with her two proud +daughters. + +Now that no one else was at home, Cinderella went to her mother's grave beneath +the hazel tree, and cried out: + +Shake and quiver, little tree, Throw gold and silver down to me. Then the bird +threw a gold and silver dress down to her, and slippers embroidered with silk +and silver. She quickly put on the dress and went to the festival. Her +stepsisters and her stepmother did not recognize her. They thought she must be +a foreign princess, for she looked so beautiful in the golden dress. They never +once thought it was Cinderella, for they thought that she was sitting at home +in the dirt, looking for lentils in the ashes. + +The prince approached her, took her by the hand, and danced with her. +Furthermore, he would dance with no one else. He never let go of her hand, and +whenever anyone else came and asked her to dance, he would say, "She is my +dance partner." + +She danced until evening, and then she wanted to go home. But the prince said, +"I will go along and escort you," for he wanted to see to whom the beautiful +girl belonged. However, she eluded him and jumped into the pigeon coop. The +prince waited until her father came, and then he told him that the unknown girl +had jumped into the pigeon coop. + +The old man thought, "Could it be Cinderella?" + +He had them bring him an ax and a pick so that he could break the pigeon coop +apart, but no one was inside. When they got home Cinderella was lying in the +ashes, dressed in her dirty clothes. A dim little oil-lamp was burning in the +fireplace. Cinderella had quickly jumped down from the back of the pigeon coop +and had run to the hazel tree. There she had taken off her beautiful clothes +and laid them on the grave, and the bird had taken them away again. Then, +dressed in her gray smock, she had returned to the ashes in the kitchen. + +The next day when the festival began anew, and her parents and her stepsisters +had gone again, Cinderella went to the hazel tree and said: + +Shake and quiver, little tree, Throw gold and silver down to me. Then the bird +threw down an even more magnificent dress than on the preceding day. When +Cinderella appeared at the festival in this dress, everyone was astonished at +her beauty. The prince had waited until she came, then immediately took her by +the hand, and danced only with her. When others came and asked her to dance +with them, he said, "She is my dance partner." When evening came she wanted to +leave, and the prince followed her, wanting to see into which house she went. +But she ran away from him and into the garden behind the house. A beautiful +tall tree stood there, on which hung the most magnificent pears. She climbed as +nimbly as a squirrel into the branches, and the prince did not know where she +had gone. He waited until her father came, then said to him, "The unknown girl +has eluded me, and I believe she has climbed up the pear tree. + +The father thought, "Could it be Cinderella?" He had an ax brought to him and +cut down the tree, but no one was in it. When they came to the kitchen, +Cinderella was lying there in the ashes as usual, for she had jumped down from +the other side of the tree, had taken the beautiful dress back to the bird in +the hazel tree, and had put on her gray smock. + +On the third day, when her parents and sisters had gone away, Cinderella went +again to her mother's grave and said to the tree: + +Shake and quiver, little tree, Throw gold and silver down to me. This time the +bird threw down to her a dress that was more splendid and magnificent than any +she had yet had, and the slippers were of pure gold. When she arrived at the +festival in this dress, everyone was so astonished that they did not know what +to say. The prince danced only with her, and whenever anyone else asked her to +dance, he would say, "She is my dance partner." When evening came Cinderella +wanted to leave, and the prince tried to escort her, but she ran away from him +so quickly that he could not follow her. The prince, however, had set a trap. +He had had the entire stairway smeared with pitch. When she ran down the +stairs, her left slipper stuck in the pitch. The prince picked it up. It was +small and dainty, and of pure gold. + +The next morning, he went with it to the man, and said to him, "No one shall be +my wife except for the one whose foot fits this golden shoe." + +The two sisters were happy to hear this, for they had pretty feet. With her +mother standing by, the older one took the shoe into her bedroom to try it on. +She could not get her big toe into it, for the shoe was too small for her. Then +her mother gave her a knife and said, "Cut off your toe. When you are queen you +will no longer have to go on foot." + +The girl cut off her toe, forced her foot into the shoe, swallowed the pain, +and went out to the prince. He took her on his horse as his bride and rode away +with her. However, they had to ride past the grave, and there, on the hazel +tree, sat the two pigeons, crying out: + +Rook di goo, rook di goo! There's blood in the shoe. The shoe is too tight, +This bride is not right! Then he looked at her foot and saw how the blood was +running from it. He turned his horse around and took the false bride home +again, saying that she was not the right one, and that the other sister should +try on the shoe. She went into her bedroom, and got her toes into the shoe all +right, but her heel was too large. Then her mother gave her a knife, and said, +"Cut a piece off your heel. When you are queen you will no longer have to go on +foot." + +The girl cut a piece off her heel, forced her foot into the shoe, swallowed the +pain, and went out to the prince. He took her on his horse as his bride and +rode away with her. When they passed the hazel tree, the two pigeons were +sitting in it, and they cried out: + +Rook di goo, rook di goo! There's blood in the shoe. The shoe is too tight, +This bride is not right! He looked down at her foot and saw how the blood was +running out of her shoe, and how it had stained her white stocking all red. +Then he turned his horse around and took the false bride home again. "This is +not the right one, either," he said. "Don't you have another daughter?" + +"No," said the man. "There is only a deformed little Cinderella from my first +wife, but she cannot possibly be the bride." + +The prince told him to send her to him, but the mother answered, "Oh, no, she +is much too dirty. She cannot be seen." + +But the prince insisted on it, and they had to call Cinderella. She first +washed her hands and face clean, and then went and bowed down before the +prince, who gave her the golden shoe. She sat down on a stool, pulled her foot +out of the heavy wooden shoe, and put it into the slipper, and it fitted her +perfectly. + +When she stood up the prince looked into her face, and he recognized the +beautiful girl who had danced with him. He cried out, "She is my true bride." + +The stepmother and the two sisters were horrified and turned pale with anger. +The prince, however, took Cinderella onto his horse and rode away with her. As +they passed by the hazel tree, the two white pigeons cried out: + +Rook di goo, rook di goo! No blood's in the shoe. The shoe's not too tight, +This bride is right! After they had cried this out, they both flew down and +lit on Cinderella's shoulders, one on the right, the other on the left, and +remained sitting there. When the wedding with the prince was to be held, the +two false sisters came, wanting to gain favor with Cinderella and to share her +good fortune. When the bridal couple walked into the church, the older sister +walked on their right side and the younger on their left side, and the pigeons +pecked out one eye from each of them. Afterwards, as they came out of the +church, the older one was on the left side, and the younger one on the right +side, and then the pigeons pecked out the other eye from each of them. And +thus, for their wickedness and falsehood, they were punished with blindness as +long as they lived. + + diff --git a/v_windows/v/examples/word_counter/word_counter.v b/v_windows/v/examples/word_counter/word_counter.v new file mode 100644 index 0000000..fc60a6f --- /dev/null +++ b/v_windows/v/examples/word_counter/word_counter.v @@ -0,0 +1,70 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +import os + +fn main() { + mut path := 'cinderella.txt' + if os.args.len != 2 { + println('usage: word_counter [text_file]') + println('using $path') + } else { + path = os.args[1] + } + contents := os.read_file(path.trim_space()) or { + println('failed to open $path') + return + } + mut m := map[string]int{} + for word in extract_words(contents) { + m[word]++ + } + // Sort the keys + mut keys := m.keys() + keys.sort() + // Print the map + for key in keys { + val := m[key] + println('$key => $val') + } +} + +// Creates an array of words from a given string +fn extract_words(contents string) []string { + mut splitted := []string{} + for space_splitted in contents.to_lower().split(' ') { + if space_splitted.contains('\n') { + splitted << space_splitted.split('\n') + } else { + splitted << space_splitted + } + } + + mut results := []string{} + for s in splitted { + result := filter_word(s) + if result == '' { + continue + } + results << result + } + + return results +} + +// Removes punctuation +fn filter_word(word string) string { + if word == '' || word == ' ' { + return '' + } + mut i := 0 + for i < word.len && !word[i].is_letter() { + i++ + } + start := i + for i < word.len && word[i].is_letter() { + i++ + } + end := i + return word[start..end] +} |