From 73835ceb1b999bd0c211dfeafbc3dc97901239ce Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 4 Mar 2022 03:43:09 -0800 Subject: [PATCH] add 3d game I added a simple 3D maze game that is a pretty impressive demo of what Microwatt can do. It's based on #347 Signed-off-by: Jacob Lifshay --- include/console.h | 13 + include/liteuart_console.h | 11 + lib/console.c | 7 + usb_3d_game/.gitignore | 6 + usb_3d_game/Makefile | 44 ++ usb_3d_game/README.md | 39 ++ usb_3d_game/head.S | 107 ++++ usb_3d_game/powerpc.lds | 13 + usb_3d_game/usb_3d_game.cpp | 1065 +++++++++++++++++++++++++++++++++++ 9 files changed, 1305 insertions(+) create mode 100644 usb_3d_game/.gitignore create mode 100644 usb_3d_game/Makefile create mode 100644 usb_3d_game/README.md create mode 100644 usb_3d_game/head.S create mode 100644 usb_3d_game/powerpc.lds create mode 100644 usb_3d_game/usb_3d_game.cpp diff --git a/include/console.h b/include/console.h index 78d0a86..0a08e48 100644 --- a/include/console.h +++ b/include/console.h @@ -1,11 +1,24 @@ +#pragma once + +#include #include +#ifdef __cplusplus +extern "C" +{ +#endif + void console_init(void); void console_set_irq_en(bool rx_irq, bool tx_irq); int getchar(void); +bool console_havechar(void); int putchar(int c); int puts(const char *str); #ifndef __USE_LIBC size_t strlen(const char *s); #endif + +#ifdef __cplusplus +} +#endif diff --git a/include/liteuart_console.h b/include/liteuart_console.h index 851ca7a..276198f 100644 --- a/include/liteuart_console.h +++ b/include/liteuart_console.h @@ -1,7 +1,18 @@ #pragma once +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + int usb_getchar(void); bool usb_havechar(void); int usb_putchar(int c); int usb_puts(const char *str); void usb_console_init(void); + +#ifdef __cplusplus +} +#endif diff --git a/lib/console.c b/lib/console.c index 0750190..2d5860e 100644 --- a/lib/console.c +++ b/lib/console.c @@ -148,6 +148,13 @@ int getchar(void) } } +bool console_havechar(void) { + if (uart_is_std) + return !std_uart_rx_empty(); + else + return !potato_uart_rx_empty(); +} + int putchar(int c) { if (uart_is_std) { diff --git a/usb_3d_game/.gitignore b/usb_3d_game/.gitignore new file mode 100644 index 0000000..e183e76 --- /dev/null +++ b/usb_3d_game/.gitignore @@ -0,0 +1,6 @@ +usb_3d_game_emu +*.o +*.elf +*.hex +*.bin + diff --git a/usb_3d_game/Makefile b/usb_3d_game/Makefile new file mode 100644 index 0000000..c9e3016 --- /dev/null +++ b/usb_3d_game/Makefile @@ -0,0 +1,44 @@ +ARCH = $(shell uname -m) +ifneq ("$(ARCH)", "ppc64") +ifneq ("$(ARCH)", "ppc64le") + CROSS_COMPILE ?= powerpc64le-linux-gnu- +endif +endif + +CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +LD = $(CROSS_COMPILE)ld +OBJCOPY = $(CROSS_COMPILE)objcopy + +COMMON_FLAGS = -Os -g -Wall -msoft-float -mno-string -mno-multiple -mno-vsx -mno-altivec -mlittle-endian -fno-stack-protector -mstrict-align -ffreestanding -fdata-sections -ffunction-sections -I../include +COMMON_FLAGS += -Werror -Wextra +CXXFLAGS = $(COMMON_FLAGS) -std=c++14 -fno-exceptions +CFLAGS = $(COMMON_FLAGS) -std=c99 +ASFLAGS = $(CFLAGS) +LDFLAGS = -T powerpc.lds + +all: usb_3d_game.hex + +console.o: ../lib/console.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + +liteuart_console.o: ../lib/liteuart_console.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + +usb_3d_game.elf: usb_3d_game.o head.o console.o liteuart_console.o + $(LD) $(LDFLAGS) -o $@ $^ + +usb_3d_game.bin: usb_3d_game.elf + $(OBJCOPY) -O binary $^ $@ + +usb_3d_game.hex: usb_3d_game.bin + ../scripts/bin2hex.py $^ > $@ + +usb_3d_game_emu: usb_3d_game.cpp + c++ -g -Wall -std=c++14 -Werror -Wextra -o usb_3d_game_emu usb_3d_game.cpp -DEMULATE_TARGET + +clean: + @rm -f *.o usb_3d_game.elf usb_3d_game.bin usb_3d_game.hex usb_3d_game_emu +distclean: clean + rm -f *~ + diff --git a/usb_3d_game/README.md b/usb_3d_game/README.md new file mode 100644 index 0000000..2220745 --- /dev/null +++ b/usb_3d_game/README.md @@ -0,0 +1,39 @@ +# 3D Maze Game + +Based on: + +# Run without FPGA/hardware-simulation + +Resize your terminal to be at least 100x76. + +Building: +```bash +cd usb_3d_game +make usb_3d_game_emu +``` + +Running: +```bash +./usb_3d_game_emu +``` + +# Run on OrangeCrab v0.2.1 + +Set the OrangeCrab into firmware upload mode by plugging it in to USB while the button is pressed, then run the following commands: + +Building/Flashing: +```bash +(cd usb_3d_game; make) +sudo make FPGA_TARGET=ORANGE-CRAB-0.21 dfuprog DOCKER=1 LITEDRAM_GHDL_ARG=-gUSE_LITEDRAM=false RAM_INIT_FILE=usb_3d_game/usb_3d_game.hex MEMORY_SIZE=$((1<<18)) +``` + +Then, in a separate terminal that you've resized to be at least 100x76, run (replacing ttyACM0 with whatever serial device the OrangeCrab is): +```bash +sudo tio /dev/ttyACM0 +``` + +# Controls + +Use WASD or the Arrow keys to move around. Press Ctrl+C to quit or restart. + +The goal is a set of flashing blocks, nothing special yet happens when you reach them though. \ No newline at end of file diff --git a/usb_3d_game/head.S b/usb_3d_game/head.S new file mode 100644 index 0000000..9eb09a3 --- /dev/null +++ b/usb_3d_game/head.S @@ -0,0 +1,107 @@ +/* Copyright 2013-2014 IBM Corp. + * + * 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. + */ + +#define STACK_TOP 0x20000 + +#define FIXUP_ENDIAN \ + tdi 0,0,0x48; /* Reverse endian of b . + 8 */ \ + b 191f; /* Skip trampoline if endian is good */ \ + .long 0xa600607d; /* mfmsr r11 */ \ + .long 0x01006b69; /* xori r11,r11,1 */ \ + .long 0x05009f42; /* bcl 20,31,$+4 */ \ + .long 0xa602487d; /* mflr r10 */ \ + .long 0x14004a39; /* addi r10,r10,20 */ \ + .long 0xa64b5a7d; /* mthsrr0 r10 */ \ + .long 0xa64b7b7d; /* mthsrr1 r11 */ \ + .long 0x2402004c; /* hrfid */ \ +191: + + +/* Load an immediate 64-bit value into a register */ +#define LOAD_IMM64(r, e) \ + lis r,(e)@highest; \ + ori r,r,(e)@higher; \ + rldicr r,r, 32, 31; \ + oris r,r, (e)@h; \ + ori r,r, (e)@l; + + .section ".head","ax" + + /* + * Microwatt currently enters in LE mode at 0x0, so we don't need to + * do any endian fix ups> + */ + . = 0 +.global _start +_start: + b boot_entry + + /* QEMU enters at 0x10 */ + . = 0x10 + FIXUP_ENDIAN + b boot_entry + + . = 0x100 + FIXUP_ENDIAN + b boot_entry + +.global boot_entry +boot_entry: + /* setup stack */ + LOAD_IMM64(%r1, STACK_TOP - 0x100) + LOAD_IMM64(%r12, main) + mtctr %r12, + bctrl + b . + +#define EXCEPTION(nr) \ + .= nr ;\ + b . + + /* More exception stubs */ + EXCEPTION(0x300) + EXCEPTION(0x380) + EXCEPTION(0x400) + EXCEPTION(0x480) + EXCEPTION(0x500) + EXCEPTION(0x600) + EXCEPTION(0x700) + EXCEPTION(0x800) + EXCEPTION(0x900) + EXCEPTION(0x980) + EXCEPTION(0xa00) + EXCEPTION(0xb00) + EXCEPTION(0xc00) + EXCEPTION(0xd00) + EXCEPTION(0xe00) + EXCEPTION(0xe20) + EXCEPTION(0xe40) + EXCEPTION(0xe60) + EXCEPTION(0xe80) + EXCEPTION(0xf00) + EXCEPTION(0xf20) + EXCEPTION(0xf40) + EXCEPTION(0xf60) + EXCEPTION(0xf80) +#if 0 + EXCEPTION(0x1000) + EXCEPTION(0x1100) + EXCEPTION(0x1200) + EXCEPTION(0x1300) + EXCEPTION(0x1400) + EXCEPTION(0x1500) + EXCEPTION(0x1600) +#endif diff --git a/usb_3d_game/powerpc.lds b/usb_3d_game/powerpc.lds new file mode 100644 index 0000000..938999f --- /dev/null +++ b/usb_3d_game/powerpc.lds @@ -0,0 +1,13 @@ +SECTIONS +{ + . = 0; + .head : { + KEEP(*(.head)) + } + . = 0x1000; + .text : { *(.text) } + . = 0x2a000; + .data : { *(.data) } + .rodata : { *(.rodata) } + .bss : { *(.bss) } +} diff --git a/usb_3d_game/usb_3d_game.cpp b/usb_3d_game/usb_3d_game.cpp new file mode 100644 index 0000000..a0dd4d4 --- /dev/null +++ b/usb_3d_game/usb_3d_game.cpp @@ -0,0 +1,1065 @@ +/* + * Copyright 2018,2022 Jacob Lifshay + * + * 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. + * + */ + +// originally from https://github.com/programmerjake/rv32/tree/v0.1.0.1-alpha/software + +#include +#include + +#ifdef EMULATE_TARGET +#include +#include +#include +#include +#include +#include +#include + +static inline void usb_putchar(int ch) noexcept +{ + unsigned char buf = ch; + while(write(STDOUT_FILENO, static_cast(&buf), 1) < 0) + { + int err = errno; + switch(err) + { +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + case EAGAIN: + { + // stdin and stdout might be the same file, so we need to handle + // O_NONBLOCK stuff here too + pollfd fd = {.fd = STDOUT_FILENO, .events = POLLOUT, .revents = 0}; + while(poll(&fd, 1, -1) < 0) + { + err = errno; + if(err != EINTR) + exit(1); + } + break; + } + case EINTR: + break; + default: + exit(1); + } + } +} + +static termios original_tios; + +static void handle_exit() +{ + tcsetattr(0, TCSADRAIN, &original_tios); +} + +static void handle_signal(int sig) +{ + signal(sig, SIG_DFL); + handle_exit(); + raise(sig); +} + +static void usb_console_init() noexcept +{ + struct termios tios; + if(tcgetattr(0, &tios) < 0) + { + int err = errno; + if(err != ENOTTY) + exit(1); + } + else + { + original_tios = tios; + atexit(handle_exit); + cfmakeraw(&tios); + tios.c_lflag |= ISIG; + if(tcsetattr(0, TCSADRAIN, &tios) < 0) + exit(1); + if(signal(SIGINT, handle_signal) == SIG_IGN) + signal(SIGINT, SIG_IGN); + if(signal(SIGTERM, handle_signal) == SIG_IGN) + signal(SIGTERM, SIG_IGN); + } + int flags = fcntl(STDIN_FILENO, F_GETFL); + if(flags < 0) + exit(1); + flags |= O_NONBLOCK; + if(fcntl(STDIN_FILENO, F_SETFL, flags) < 0) + exit(1); +} + +static int usb_peek_buf = -1; + +static inline void usb_fill_buf() noexcept +{ + if(usb_peek_buf != -1) + return; + unsigned char buf; + int result = read(STDIN_FILENO, static_cast(&buf), 1); + if(result < 0) + { + int err = errno; + switch(err) + { +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + case EAGAIN: + case EINTR: + break; + default: + exit(1); + } + } + else if(result > 0) + usb_peek_buf = buf; +} + +static inline bool usb_havechar() noexcept +{ + usb_fill_buf(); + return usb_peek_buf != -1; +} + +static inline int usb_getchar() noexcept +{ + // we don't need to ever bother to block, since the code always checks usb_havechar first + usb_fill_buf(); + int retval = usb_peek_buf; + usb_peek_buf = -1; + return retval; +} +#else +#include "console.h" +#include "liteuart_console.h" +#endif + +#if USE_CP437 +#define USE_CP437 1 +#define USE_UTF8 0 +#else +#define USE_CP437 0 +#define USE_UTF8 1 +#endif + +static inline void my_putchar_inner(unsigned char ch) noexcept +{ + usb_putchar(ch); +#ifndef EMULATE_TARGET + putchar(ch); +#endif +} + +static inline void my_puts_inner(const char *s) noexcept +{ + while(*s) + my_putchar_inner(*s++); +} + +static inline void my_putchar(int ch) noexcept +{ + ch = static_cast(ch); +#if USE_UTF8 + switch(ch) + { + case 0xB2: + my_puts_inner("\u2593"); + break; + case 0xB1: + my_puts_inner("\u2592"); + break; + case 0xB0: + my_puts_inner("\u2591"); + break; + case 0xCE: + my_puts_inner("\u256C"); + break; + default: + my_putchar_inner(ch); + } +#else + my_putchar_inner(ch); +#endif +} + +static inline void write_hex_digit(int value) +{ + my_putchar("0123456789ABCDEF"[value]); +} + +static inline void write_hex_u8(std::uint8_t value) +{ + write_hex_digit(value >> 4); + write_hex_digit(value & 0xF); +} + +static inline void write_hex_u16(std::uint16_t value) +{ + write_hex_u8(value >> 8); + write_hex_u8(value & 0xFF); +} + +static inline void write_hex_u32(std::uint32_t value) +{ + write_hex_u16(value >> 16); + write_hex_u16(value & 0xFFFF); +} + +static inline void my_puts(const char *str) +{ + while(*str) + my_putchar(*str++); +} + +constexpr std::size_t screen_x_size = 800 / 8; +constexpr std::size_t screen_y_size = 600 / 8; + +template +struct get_double_length_type; + +template <> +struct get_double_length_type +{ + typedef std::uint16_t type; +}; + +template <> +struct get_double_length_type +{ + typedef std::uint32_t type; +}; + +template <> +struct get_double_length_type +{ + typedef std::uint64_t type; +}; + +template <> +struct get_double_length_type +{ + typedef std::int16_t type; +}; + +template <> +struct get_double_length_type +{ + typedef std::int32_t type; +}; + +template <> +struct get_double_length_type +{ + typedef std::int64_t type; +}; + +template +constexpr T bidirectional_shift_left(T value, int amount) noexcept +{ + int max_shift = std::numeric_limits::digits; + if(amount <= -max_shift) + return value < 0 ? -1 : 0; + if(amount < 0) + return value >> -amount; + return value << amount; +} + +template +constexpr T bidirectional_shift_right(T value, int amount) noexcept +{ + return bidirectional_shift_left(value, -amount); +} + +template +class Fixed +{ +public: + typedef T underlying_type; + typedef typename get_double_length_type::type double_length_type; + static constexpr std::size_t total_bits = std::numeric_limits::digits; + static constexpr std::size_t fractional_bits = FractionalBits; + static constexpr std::size_t integer_bits = total_bits - fractional_bits; + static constexpr T fraction_mask = (static_cast(1) << fractional_bits) - 1; + static constexpr T integer_mask = ~fraction_mask; + static_assert(total_bits >= fractional_bits, ""); + +private: + underlying_type value; + +public: + constexpr Fixed() noexcept : value(0) + { + } + constexpr Fixed(signed char v) noexcept : value(static_cast(v) << fractional_bits) + { + } + constexpr Fixed(short v) noexcept : value(static_cast(v) << fractional_bits) + { + } + constexpr Fixed(int v) noexcept : value(static_cast(v) << fractional_bits) + { + } + constexpr Fixed(long v) noexcept : value(static_cast(v) << fractional_bits) + { + } + constexpr Fixed(long long v) noexcept : value(static_cast(v) << fractional_bits) + { + } + constexpr Fixed(unsigned char v) noexcept : value(static_cast(v) << fractional_bits) + { + } + constexpr Fixed(char v) noexcept : value(static_cast(v) << fractional_bits) + { + } + constexpr Fixed(unsigned short v) noexcept : value(static_cast(v) << fractional_bits) + { + } + constexpr Fixed(unsigned v) noexcept : value(static_cast(v) << fractional_bits) + { + } + constexpr Fixed(unsigned long v) noexcept : value(static_cast(v) << fractional_bits) + { + } + constexpr Fixed(unsigned long long v) noexcept : value(static_cast(v) << fractional_bits) + { + } + constexpr Fixed(float v) noexcept + : value(static_cast(static_cast(1ULL << fractional_bits) * v)) + { + } + constexpr Fixed(double v) noexcept + : value(static_cast(static_cast(1ULL << fractional_bits) * v)) + { + } + constexpr explicit operator T() const noexcept + { + if(value < 0) + return (value + fraction_mask) >> fractional_bits; + return value >> fractional_bits; + } + constexpr explicit operator double() const noexcept + { + return value * (1.0 / (1ULL << fractional_bits)); + } + static constexpr Fixed make(T underlying_value) noexcept + { + Fixed retval; + retval.value = underlying_value; + return retval; + } + constexpr Fixed operator+() const noexcept + { + return *this; + } + constexpr Fixed operator-() const noexcept + { + return make(-value); + } + friend constexpr Fixed operator+(Fixed a, Fixed b) noexcept + { + return make(a.value + b.value); + } + friend constexpr Fixed operator-(Fixed a, Fixed b) noexcept + { + return make(a.value - b.value); + } + friend constexpr Fixed operator*(Fixed a, Fixed b) noexcept + { + return make(static_cast(a.value) * b.value >> fractional_bits); + } + friend constexpr Fixed operator/(Fixed a, Fixed b) noexcept + { + if(b.value == 0) + { + b.value = 1; + } + return make((static_cast(a.value) << fractional_bits) / b.value); + } + constexpr Fixed &operator+=(Fixed rt) noexcept + { + return *this = *this + rt; + } + constexpr Fixed &operator-=(Fixed rt) noexcept + { + return *this = *this - rt; + } + constexpr Fixed &operator*=(Fixed rt) noexcept + { + return *this = *this * rt; + } + constexpr Fixed &operator/=(Fixed rt) noexcept + { + return *this = *this / rt; + } + constexpr T underlying_value() const noexcept + { + return value; + } + friend constexpr bool operator==(Fixed a, Fixed b) noexcept + { + return a.value == b.value; + } + friend constexpr bool operator!=(Fixed a, Fixed b) noexcept + { + return a.value != b.value; + } + friend constexpr bool operator<=(Fixed a, Fixed b) noexcept + { + return a.value <= b.value; + } + friend constexpr bool operator>=(Fixed a, Fixed b) noexcept + { + return a.value >= b.value; + } + friend constexpr bool operator<(Fixed a, Fixed b) noexcept + { + return a.value < b.value; + } + friend constexpr bool operator>(Fixed a, Fixed b) noexcept + { + return a.value > b.value; + } + friend constexpr Fixed floor(Fixed v) noexcept + { + v.value &= integer_mask; + return v; + } + friend constexpr Fixed fracf(Fixed v) noexcept + { + v.value &= fraction_mask; + return v; + } + friend constexpr Fixed ceil(Fixed v) noexcept + { + v.value += fraction_mask; + return floor(v); + } + friend constexpr Fixed round(Fixed v) noexcept + { + constexpr Fixed one_half = 0.5; + v += one_half; + return floor(v); + } + friend constexpr T floori(Fixed v) noexcept + { + return v.value >> fractional_bits; + } + friend constexpr T ceili(Fixed v) noexcept + { + v.value += fraction_mask; + return floori(v); + } + friend constexpr T roundi(Fixed v) noexcept + { + constexpr Fixed one_half = 0.5; + v += one_half; + return floori(v); + } + friend constexpr Fixed abs(Fixed v) noexcept + { + if(v.value < 0) + return -v; + return v; + } + friend constexpr Fixed sqrt(Fixed v) noexcept + { + if(v <= 0) + return 0; + Fixed guess = 0; + double_length_type guess_squared = 0; + for(int bit_index = (integer_bits + 1) / 2; bit_index >= -static_cast(fractional_bits); + bit_index--) + { + Fixed new_guess = guess + make(static_cast(1) << (bit_index + fractional_bits)); + double_length_type new_guess_squared = guess_squared; + new_guess_squared += bidirectional_shift_left( + static_cast(guess.value), bit_index + 1); + new_guess_squared += bidirectional_shift_left( + static_cast(Fixed(1).value), 2 * bit_index); + if(new_guess_squared < v.value) + { + guess = new_guess; + guess_squared = new_guess_squared; + } + else if(new_guess_squared == v.value) + return new_guess; + } + return guess; + } +}; + +enum class Block : char +{ + Empty = ' ', + Wall = '|', + End = 'X' +}; + +constexpr double constexpr_sin2pi(double x) noexcept +{ + x -= static_cast(x); + if(x < 0) + x += 1; + if(x == 0) + return 0; + if(x == 0.25) + return 1; + if(x == 0.5) + return 0; + if(x == 0.75) + return -1; + double x2 = x * x; + const double coefficients[] = { + 1.5873670538243229332222957023504872028033458258785e-8, + -3.2649283479971170585768247133750680886632233028762e-7, + 5.8056524029499061679627827975252772363553363262495e-6, + -8.8235335992430051344844841671401871742374913922057e-5, + 1.1309237482517961877702180414488525515732161905954e-3, + -1.2031585942120627233202567845286556653885737182738e-2, + 1.0422916220813984117271044898760411097029995316417e-1, + -7.1812230177850051223174027860686238053986168884284e-1, + 3.8199525848482821277337920673404661254406128731422, + -1.5094642576822990391826616232531520514481435107371e1, + 4.205869394489765314498681114813355254161277992845e1, + -7.6705859753061385841630641093893125889966539055122e1, + 8.1605249276075054203397682678249495061413521767487e1, + -4.1341702240399760233968420089468526936300384754514e1, + 6.2831853071795864769252867665590057683943387987502, + }; + double v = 0; + for(double coeff : coefficients) + v = v * x2 + coeff; + return x * v; +} + +constexpr double constexpr_cos2pi(double x) noexcept +{ + x -= static_cast(x); + x += 0.25; + return constexpr_sin2pi(x); +} + +template +struct SinCosList +{ + static_assert(N > 1, ""); + constexpr std::size_t size() const noexcept + { + return N; + } + Fixed<> sin_table[N]; + constexpr SinCosList() noexcept : sin_table{} + { + for(std::size_t i = 0; i < N; i++) + { + double rotations = i / (4.0 * (N - 1)); + sin_table[i] = constexpr_sin2pi(rotations); + } + } + constexpr void get(Fixed<> &sin_out, Fixed<> &cos_out, Fixed<> rotations) const noexcept + { + rotations = fracf(rotations) * 4; + int quadrent = floori(rotations); + rotations = (N - 1) * fracf(rotations); + auto int_part = floori(rotations); + auto fraction = fracf(rotations); + auto sin_value = + sin_table[int_part] + fraction * (sin_table[int_part + 1] - sin_table[int_part]); + auto cos_value = + sin_table[N - 1 - int_part] + + fraction * (sin_table[N - 1 - int_part - 1] - sin_table[N - 1 - int_part]); + switch(quadrent) + { + case 1: + sin_out = cos_value; + cos_out = -sin_value; + break; + case 2: + sin_out = -sin_value; + cos_out = -cos_value; + break; + case 3: + sin_out = -cos_value; + cos_out = sin_value; + break; + default: + sin_out = sin_value; + cos_out = cos_value; + break; + } + } + constexpr Fixed<> get_sin(Fixed<> rotations) const noexcept + { + Fixed<> sin, cos; + get(sin, cos, rotations); + return sin; + } + constexpr Fixed<> get_cos(Fixed<> rotations) const noexcept + { + Fixed<> sin, cos; + get(sin, cos, rotations); + return cos; + } +}; + +constexpr auto sin_cos_list = SinCosList<>(); + +constexpr void rotate(Fixed<> &x, Fixed<> &y, Fixed<> rotations) +{ + Fixed<> sin, cos; + sin_cos_list.get(sin, cos, rotations); + auto new_x = x * cos - y * sin; + auto new_y = x * sin + y * cos; + x = new_x; + y = new_y; +} + +inline void write_fixed(Fixed<> v) +{ + write_hex_u32(floori(v)); + my_putchar('.'); + write_hex_u16(floori(fracf(v) * 0x10000)); +} + +template +struct Vec2D +{ + typedef T element_type; + T x, y; + constexpr Vec2D() noexcept : x(), y() + { + } + constexpr explicit Vec2D(T v) noexcept : x(v), y(v) + { + } + constexpr Vec2D(T x, T y) noexcept : x(x), y(y) + { + } + friend constexpr Vec2D operator+(Vec2D a, Vec2D b) noexcept + { + return Vec2D(a.x + b.x, a.y + b.y); + } + constexpr Vec2D operator-() const noexcept + { + return Vec2D(-x, -y); + } + friend constexpr Vec2D operator-(Vec2D a, Vec2D b) noexcept + { + return Vec2D(a.x - b.x, a.y - b.y); + } + friend constexpr Vec2D operator*(T a, Vec2D b) noexcept + { + return Vec2D(a * b.x, a * b.y); + } + friend constexpr Vec2D operator*(Vec2D a, T b) noexcept + { + return Vec2D(a.x * b, a.y * b); + } + friend constexpr Vec2D operator/(Vec2D a, T b) noexcept + { + return Vec2D(a.x / b, a.y / b); + } + constexpr Vec2D &operator+=(Vec2D rt) noexcept + { + return *this = *this + rt; + } + constexpr Vec2D &operator-=(Vec2D rt) noexcept + { + return *this = *this - rt; + } + constexpr Vec2D &operator*=(T rt) noexcept + { + return *this = *this * rt; + } + constexpr Vec2D &operator/=(T rt) noexcept + { + return *this = *this / rt; + } +}; + +constexpr Vec2D> rotate(Vec2D> v, Fixed<> rotations) noexcept +{ + rotate(v.x, v.y, rotations); + return v; +} + +constexpr void init_ray_cast_dimension(Fixed<> ray_direction, + Fixed<> ray_start_position, + Fixed<> &next_t, + Fixed<> &step_t, + std::int32_t &delta_position) +{ + if(ray_direction == 0) + return; + auto inverse_direction = 1 / ray_direction; + step_t = abs(inverse_direction); + std::int32_t target_position{}; + if(ray_direction < 0) + { + target_position = ceili(ray_start_position) - 1; + delta_position = -1; + } + else + { + target_position = floori(ray_start_position) + 1; + delta_position = 1; + } + next_t = (target_position - ray_start_position) * inverse_direction; +} + +struct RayCaster +{ + Vec2D> ray_start_position; + Vec2D> ray_direction; + Vec2D current_position; + Fixed<> current_t; + Vec2D> next_t; + Vec2D> step_t; + Vec2D delta_position; + int last_hit_dimension = -1; + constexpr RayCaster(Vec2D> ray_start_position, Vec2D> ray_direction) noexcept + : ray_start_position(ray_start_position), + ray_direction(ray_direction), + current_position(floori(ray_start_position.x), floori(ray_start_position.y)), + current_t(Fixed<>::make(1)), + next_t(0), + step_t(0), + delta_position(0) + { + init_ray_cast_dimension( + ray_direction.x, ray_start_position.x, next_t.x, step_t.x, delta_position.x); + init_ray_cast_dimension( + ray_direction.y, ray_start_position.y, next_t.y, step_t.y, delta_position.y); + } + constexpr void step() noexcept + { + if(ray_direction.x != 0 && (ray_direction.y == 0 || next_t.x < next_t.y)) + { + current_t = next_t.x; + next_t.x += step_t.x; + current_position.x += delta_position.x; + last_hit_dimension = 0; + } + else if(ray_direction.y != 0) + { + current_t = next_t.y; + next_t.y += step_t.y; + current_position.y += delta_position.y; + last_hit_dimension = 1; + } + } +}; + +struct KeyboardStatus +{ + std::int32_t right_count = 0; // positive for right, negative for left + std::int32_t up_count = 0; // positive for up, negative for down + bool reset = false; + void operator+=(KeyboardStatus rt) noexcept + { + right_count += rt.right_count; + up_count += rt.up_count; + reset |= rt.reset; + } +}; + +template +class KeyboardReader +{ +private: + enum class State + { + Initial, + GotEsc, + GotLBracket, + }; + State state = State::Initial; + +public: + KeyboardStatus poll() noexcept + { + KeyboardStatus status; + for(int i = 0; i < 32; i++) + { + if(!HAVECHAR()) + break; + std::uint8_t ch = GETCHAR(); + if(ch == 0x3) // Ctrl+C + { + *this = KeyboardReader(); + status = KeyboardStatus(); + status.reset = true; + break; + } + switch(state) + { + case State::Initial: + switch(ch) + { + case 0x1B: // Esc + state = State::GotEsc; + break; + case 'w': + case 'W': + status.up_count++; + break; + case 'a': + case 'A': + status.right_count--; + break; + case 's': + case 'S': + status.up_count--; + break; + case 'd': + case 'D': + status.right_count++; + break; + } + break; + case State::GotEsc: + switch(ch) + { + case 0x1B: + break; + case '[': + state = State::GotLBracket; + break; + default: + state = State::Initial; + } + break; + case State::GotLBracket: + state = State::Initial; + switch(ch) + { + case 0x1B: + state = State::GotEsc; + break; + case 'D': + status.right_count--; + break; + case 'C': + status.right_count++; + break; + case 'A': + status.up_count++; + break; + case 'B': + status.up_count--; + break; + } + break; + } + } + return status; + } +}; + +int main() +{ + usb_console_init(); +#ifndef EMULATE_TARGET + console_init(); +#endif + static std::uint8_t start_col[screen_x_size] = {}, end_col[screen_x_size] = {}; + static char col_color[screen_x_size] = {}; + constexpr std::size_t world_x_size = 16, world_z_size = 16; + static const char world[world_x_size][world_z_size] = { + // clang-format off + {'|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', 'X', 'X'}, + {'|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|', ' ', ' ', ' ', 'X'}, + {'|', ' ', '|', '|', '|', '|', '|', '|', '|', ' ', ' ', '|', ' ', ' ', ' ', 'X'}, + {'|', ' ', ' ', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', '|', ' ', '|', 'X', 'X'}, + {'|', ' ', ' ', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', '|', ' ', '|', '|', '|'}, + {'|', ' ', '|', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', ' ', '|'}, + {'|', ' ', '|', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', ' ', '|'}, + {'|', ' ', '|', ' ', ' ', ' ', ' ', ' ', '|', ' ', ' ', '|', '|', '|', ' ', '|'}, + {'|', ' ', '|', ' ', ' ', ' ', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', ' ', '|'}, + {'|', ' ', '|', '|', '|', '|', '|', '|', '|', ' ', ' ', '|', ' ', ' ', ' ', '|'}, + {'|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|', ' ', ' ', ' ', '|'}, + {'|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|', ' ', ' ', ' ', '|'}, + {'|', ' ', '|', '|', '|', '|', '|', '|', '|', ' ', ' ', '|', ' ', ' ', ' ', '|'}, + {'|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|'}, + {'|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|'}, + {'|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|'}, + // clang-format on + }; + constexpr Vec2D> initial_view_position(1.5, 1.5); + constexpr Fixed<> initial_view_angle(0); + constexpr std::uint32_t initial_flash_counter = 0; + auto view_position = initial_view_position; + auto view_angle = initial_view_angle; + auto flash_counter = initial_flash_counter; + constexpr std::uint32_t flash_period = 10; + KeyboardReader usb_reader; +#ifndef EMULATE_TARGET + KeyboardReader console_reader; +#endif + while(true) + { + flash_counter++; + if(flash_counter >= flash_period) + flash_counter = 0; + auto status = usb_reader.poll(); +#ifndef EMULATE_TARGET + status += console_reader.poll(); +#endif + if(status.reset) + { + view_position = initial_view_position; + view_angle = initial_view_angle; + flash_counter = initial_flash_counter; + } + while(status.right_count != 0) + { + if(status.right_count > 0) + { + view_angle -= 0.01; + view_angle = fracf(view_angle); + status.right_count--; + } + else + { + view_angle += 0.01; + view_angle = fracf(view_angle); + status.right_count++; + } + } + while(status.up_count != 0) + { + Vec2D> forward(0, 0.2); + if(status.up_count > 0) + { + status.up_count--; + } + else + { + forward = -forward; + status.up_count++; + } + forward = rotate(forward, view_angle); + auto new_view_position = view_position + forward; + Vec2D new_block_position(floori(new_view_position.x), + floori(new_view_position.y)); +#if 1 + auto block = world[new_block_position.x][new_block_position.y]; + if(block == ' ') + view_position = new_view_position; +#else + Fixed<> closest_distance(100); + for(int dx = -1; dx <= 1; dx++) + { + for(int dy = -1; dy <= 1; dy++) + { + auto block_position = new_block_position; + block_position.x += dx; + block_position.y += dy; + auto block = world[block_position.x][block_position.y]; + if(block == ' ') + continue; + auto closest_position = new_view_position; + if(closest_position.x < block_position.x) + closest_position.x = block_position.x; + else if(closest_position.x > block_position.x + 1) + closest_position.x = block_position.x + 1; + if(closest_position.y < block_position.y) + closest_position.y = block_position.y; + else if(closest_position.y > block_position.y + 1) + closest_position.y = block_position.y + 1; + auto current_distance_x = abs(closest_position.x - block_position.x); + auto current_distance_y = abs(closest_position.y - block_position.y); + auto current_distance = current_distance_x; + if(current_distance < current_distance_y) + current_distance = current_distance_y; + if(current_distance < closest_distance) + closest_distance = current_distance; + } + } + if(closest_distance >= 0.1) + view_position = new_view_position; +#endif + } + for(std::size_t x = 0; x < screen_x_size; x++) + { + Vec2D> ray_direction( + (Fixed<>(x) + (0.5 - screen_x_size / 2.0)) * (2.0 / screen_x_size), 1); + ray_direction = rotate(ray_direction, view_angle); + RayCaster ray_caster(view_position, ray_direction); + auto hit_block = world[ray_caster.current_position.x][ray_caster.current_position.y]; + while(hit_block == ' ') + { + ray_caster.step(); + hit_block = world[ray_caster.current_position.x][ray_caster.current_position.y]; + } + constexpr Fixed<> max_height = 10; + Fixed<> height = + ray_caster.current_t != Fixed<>::make(1) ? 1 / ray_caster.current_t : max_height; + if(height > max_height) + height = max_height; + height *= screen_x_size / 2.0; + auto iheight = roundi(height); + if(iheight > static_cast(screen_y_size)) + iheight = screen_y_size; + else if(iheight < 0) + iheight = 0; + start_col[x] = screen_y_size / 2 - iheight / 2; + end_col[x] = screen_y_size / 2 + (iheight + 1) / 2; + bool odd = (ray_caster.current_position.x + ray_caster.current_position.y) % 2; + if(hit_block == 'X' && flash_counter >= flash_period / 2) + { + col_color[x] = '#'; + if(ray_caster.last_hit_dimension == 0) + col_color[x] = 'X'; + } + else if(ray_caster.last_hit_dimension == 0) + { + col_color[x] = odd ? 0xB2 : 0xB1; + } + else + { + col_color[x] = odd ? 0xB1 : 0xB0; + } + } + my_puts("\x1B[H"); + for(std::size_t y = 0; y < screen_y_size; y++) + { + for(std::size_t x = 0, + x_end = (y == screen_y_size - 1 ? screen_x_size - 1 : screen_x_size); + x < x_end; + x++) + { + if(y >= end_col[x]) + my_putchar(0xCE); + else if(y >= start_col[x]) + my_putchar(col_color[x]); + else + my_putchar(0x20); + } + my_puts("\r\n"); + } + } +}