├── .gitignore ├── images ├── virtex.jpg ├── multivox.jpg └── testcardf128.png ├── src ├── toys │ ├── eighty │ │ ├── tubeface.h │ │ ├── scooter.h │ │ ├── grid.h │ │ ├── eighty.c │ │ ├── scooter.c │ │ ├── grid.c │ │ └── tubeface.xbm │ ├── zander │ │ ├── ship.h │ │ ├── objects.h │ │ ├── terrain.h │ │ ├── particles.h │ │ ├── zander.h │ │ ├── zander.c │ │ ├── particles.c │ │ ├── terrain.c │ │ ├── ship.c │ │ └── zsintable.h │ ├── blit.c │ ├── tesseract.c │ ├── fireworks.c │ └── flight.c ├── multivox │ ├── carousel.h │ ├── multivox.h │ ├── cart.h │ ├── carousel.c │ ├── multivox.c │ └── cart.c ├── simulator │ ├── sim.h │ ├── glsl.cmake │ ├── volume_vert.glsl │ ├── volume_frag.glsl │ └── virtex.c ├── platform │ ├── image.h │ ├── rammel.h │ ├── array.h │ ├── input.h │ ├── voxel.c │ ├── timer.c │ ├── graphics.h │ ├── model.h │ ├── timer.h │ ├── array.c │ ├── image.c │ ├── voxel.h │ └── input.c └── driver │ ├── rotation.h │ ├── slicemap.h │ ├── gadgets │ ├── gadget_vortex.h │ └── gadget_rotovox.h │ ├── gpio.h │ ├── gpio.c │ ├── rotation.c │ └── slicemap.c ├── LICENSE ├── python ├── grid.py ├── calibration.py ├── colourwheel.py ├── pointvision.py ├── obj2c.py └── vortexstream.py ├── CMakeLists.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | build/ 3 | data/ 4 | gmon.out 5 | prof.txt -------------------------------------------------------------------------------- /images/virtex.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AncientJames/multivox/HEAD/images/virtex.jpg -------------------------------------------------------------------------------- /images/multivox.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AncientJames/multivox/HEAD/images/multivox.jpg -------------------------------------------------------------------------------- /images/testcardf128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AncientJames/multivox/HEAD/images/testcardf128.png -------------------------------------------------------------------------------- /src/toys/eighty/tubeface.h: -------------------------------------------------------------------------------- 1 | #ifndef _TUBEFACE_H_ 2 | #define _TUBEFACE_H_ 3 | 4 | #include "voxel.h" 5 | 6 | void tubeface_init(void); 7 | void tubeface_draw(pixel_t* volume); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/multivox/carousel.h: -------------------------------------------------------------------------------- 1 | #ifndef _CAROUSEL_H_ 2 | #define _CAROUSEL_H_ 3 | 4 | #include "voxel.h" 5 | 6 | void carousel_init(); 7 | void carousel_update(float dt); 8 | void carousel_draw(pixel_t* volume); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/multivox/multivox.h: -------------------------------------------------------------------------------- 1 | #ifndef _MULTIVOX_H_ 2 | #define _MULTIVOX_H_ 3 | 4 | #include "cart.h" 5 | 6 | cart_action_t multivox_cart_resume(void); 7 | cart_action_t multivox_cart_execute(cart_t* cart); 8 | 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/toys/eighty/scooter.h: -------------------------------------------------------------------------------- 1 | #ifndef _SCOOTER_H_ 2 | #define _SCOOTER_H_ 3 | 4 | #include "voxel.h" 5 | 6 | void scooter_init(void); 7 | void scooter_update(float dt); 8 | void scooter_draw(pixel_t* volume); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/toys/zander/ship.h: -------------------------------------------------------------------------------- 1 | #ifndef _SHIP_H_ 2 | #define _SHIP_H_ 3 | 4 | #include "mathc.h" 5 | #include "voxel.h" 6 | 7 | extern struct vec3 ship_position; 8 | 9 | void ship_init(void); 10 | void ship_update(float dt); 11 | void ship_draw(pixel_t* volume); 12 | 13 | #endif -------------------------------------------------------------------------------- /src/simulator/sim.h: -------------------------------------------------------------------------------- 1 | #ifndef _SIM_H_ 2 | #define _SIM_H_ 3 | 4 | #include 5 | 6 | bool sim_init(int argc, char** argv); 7 | void sim_resize(int w, int h); 8 | void sim_drag(int button, float dx, float dy); 9 | void sim_zoom(float d); 10 | void sim_draw(void); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/toys/zander/objects.h: -------------------------------------------------------------------------------- 1 | #ifndef _OBJECTS_H_ 2 | #define _OBJECTS_H_ 3 | 4 | #include 5 | #include "voxel.h" 6 | 7 | void objects_init(void); 8 | bool objects_hit_and_destroy(float* position); 9 | void objects_update(float dt); 10 | void objects_draw(pixel_t* volume); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/simulator/glsl.cmake: -------------------------------------------------------------------------------- 1 | file(READ "${INPUT_FILE}" CONTENT) 2 | string(REPLACE "\\" "\\\\" CONTENT "${CONTENT}") 3 | string(REPLACE "\"" "\\\"" CONTENT "${CONTENT}") 4 | string(REPLACE "\n" "\\n\"\n\"" CONTENT "${CONTENT}") 5 | get_filename_component(VARNAME "${INPUT_FILE}" NAME_WE) 6 | string(REPLACE "." "_" VARNAME "${VARNAME}") 7 | file(WRITE "${OUTPUT_FILE}" "static const char* ${VARNAME} = \"${CONTENT}\";\n") 8 | -------------------------------------------------------------------------------- /src/toys/zander/terrain.h: -------------------------------------------------------------------------------- 1 | #ifndef _TERRAIN_H_ 2 | #define _TERRAIN_H_ 3 | 4 | #include 5 | #include "voxel.h" 6 | 7 | static inline bool terrain_is_water(float altitude) {return altitude <= 1e-2f;} 8 | 9 | float terrain_get_altitude_raw(float x, float y); 10 | float terrain_get_altitude(float x, float y); 11 | void terrain_init(void); 12 | void terrain_draw(pixel_t* volume); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/platform/image.h: -------------------------------------------------------------------------------- 1 | #ifndef _IMAGE_H_ 2 | #define _IMAGE_H_ 3 | 4 | #include "voxel.h" 5 | 6 | typedef struct image_s { 7 | pixel_t* data; 8 | int width, height; 9 | 10 | uint8_t masked; 11 | pixel_t key; 12 | } image_t; 13 | 14 | image_t* image_load(const char* filename); 15 | pixel_t image_sample(image_t* image, const float* uv, bool* masked); 16 | void image_free(image_t* image); 17 | 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/toys/eighty/grid.h: -------------------------------------------------------------------------------- 1 | #ifndef _GRID_H_ 2 | #define _GRID_H_ 3 | 4 | #include "voxel.h" 5 | 6 | void grid_init(void); 7 | void grid_start_pose(int id, int* cell, int* direction); 8 | void grid_vox_pos(int* vox, const float* pos); 9 | bool grid_occupied(const int* cell); 10 | void grid_probe(int* cell, int* probes); 11 | void grid_mark(int id, const int* cell, uint8_t walls); 12 | void grid_draw(pixel_t* volume); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/driver/rotation.h: -------------------------------------------------------------------------------- 1 | #ifndef _ROTATION_H_ 2 | #define _ROTATION_H_ 3 | 4 | #define ROTATION_PRECISION 30 5 | #define ROTATION_FULL (1< s_ ? s_ : v_;}) 9 | #define min(a,b) ({ __typeof__(a) a_=(a); __typeof__(b) b_=(b); a_ < b_ ? a_ : b_;}) 10 | #define max(a,b) ({ __typeof__(a) a_=(a); __typeof__(b) b_=(b); a_ > b_ ? a_ : b_;}) 11 | 12 | #define sqr(a) ({ __typeof__(a) a_=(a); a_*a_;}) 13 | 14 | static inline int modulo(int a, int b) { 15 | a = a % b; 16 | if (a < 0) { 17 | a += b; 18 | } 19 | return a; 20 | } 21 | 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/multivox/cart.h: -------------------------------------------------------------------------------- 1 | #ifndef _CART_H_ 2 | #define _CART_H_ 3 | 4 | #include "voxel.h" 5 | 6 | typedef struct { 7 | const char* command; 8 | const char* arguments; 9 | const char* workingdir; 10 | const char* environment; 11 | 12 | pixel_t colour; 13 | 14 | const char* cartpath; 15 | pixel_t * voxel_shot[4]; 16 | } cart_t; 17 | 18 | typedef enum { 19 | CART_ACTION_NONE, 20 | CART_ACTION_PAUSE, 21 | CART_ACTION_EJECT, 22 | CART_ACTION_FAIL 23 | } cart_action_t; 24 | 25 | void cart_grab_voxshot(cart_t* cart, const pixel_t* volume); 26 | void cart_save_voxshot(cart_t* cart); 27 | bool cart_load(cart_t* cart, char* filename); 28 | void cart_draw(cart_t* cart, pixel_t* volume, float slot_angle); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/platform/array.h: -------------------------------------------------------------------------------- 1 | #ifndef _ARRAY_H_ 2 | #define _ARRAY_H_ 3 | 4 | #include 5 | 6 | typedef struct { 7 | size_t size; 8 | size_t capacity; 9 | size_t count; 10 | void* data; 11 | } array_t; 12 | 13 | static inline void* array_get(array_t* array, size_t index) { 14 | return array->data + index * array->size; 15 | } 16 | 17 | void array_initialise(array_t* array, size_t size, size_t capacity); 18 | void array_reserve(array_t* array, size_t capacity); 19 | void array_resize(array_t* array, size_t count); 20 | void array_clear(array_t* array); 21 | void array_destroy(array_t* array); 22 | 23 | void* array_push(array_t* array); 24 | void* array_pop(array_t* array); 25 | void array_clear_element(array_t* array, size_t index); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/driver/slicemap.h: -------------------------------------------------------------------------------- 1 | #ifndef _SLICEMAP_H_ 2 | #define _SLICEMAP_H_ 3 | 4 | #include "voxel.h" 5 | 6 | 7 | #define SLICE_COUNT 360 8 | #define SLICE_QUADRANT (SLICE_COUNT / 4) 9 | #define SLICE_WRAP(slice) ((slice) % (SLICE_COUNT)) 10 | 11 | #if SLICE_COUNT <= 256 12 | typedef uint8_t slice_index_t; 13 | #else 14 | typedef uint16_t slice_index_t; 15 | #endif 16 | 17 | 18 | typedef struct { 19 | slice_index_t slice; 20 | uint8_t column; 21 | } slice_polar_t; 22 | 23 | typedef struct { 24 | voxel_index_t x, y; 25 | } voxel_2D_t; 26 | 27 | typedef enum { 28 | SLICE_BRIGHTNESS_UNIFORM, 29 | SLICE_BRIGHTNESS_BOOSTED, 30 | SLICE_BRIGHTNESS_UNLIMITED 31 | } slice_brightness_t; 32 | 33 | 34 | extern voxel_2D_t slice_map[SLICE_COUNT][PANEL_WIDTH][PANEL_COUNT]; 35 | extern float eccentricity[2]; 36 | 37 | void slicemap_ebr(int* a, int n); 38 | void slicemap_init(slice_brightness_t brightness); 39 | 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/toys/zander/particles.h: -------------------------------------------------------------------------------- 1 | #ifndef _PARTICLES_H_ 2 | #define _PARTICLES_H_ 3 | 4 | #include 5 | #include "voxel.h" 6 | 7 | typedef enum { 8 | PARTICLE_COOL_DOWN = 0x01, 9 | PARTICLE_IS_ROCK = 0x02, 10 | PARTICLE_SPLASHES = 0x04, 11 | PARTICLE_BOUNCES = 0x08, 12 | PARTICLE_DROPS = 0x10, 13 | PARTICLE_DESTROYS = 0x20, 14 | PARTICLE_BIG_SPLASH = 0x40, 15 | PARTICLE_EXPLODES = 0x80, 16 | } particle_flags_t; 17 | 18 | typedef enum { 19 | PARTICLE_BULLET, 20 | PARTICLE_EXHAUST, 21 | PARTICLE_SMOKE, 22 | PARTICLE_DEBRIS, 23 | PARTICLE_SPARK, 24 | PARTICLE_SPRAY, 25 | PARTICLE_ROCK 26 | } particle_type_t; 27 | 28 | void particles_add(const float* position, const float* velocity, particle_type_t type); 29 | void particles_add_splash(const float* position, bool big_splash); 30 | void particles_add_explosion(const float* position, int clusters); 31 | 32 | void particles_init(void); 33 | void particles_update(float dt); 34 | void particles_draw(pixel_t* volume); 35 | 36 | #endif 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 James Brown 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/simulator/volume_frag.glsl: -------------------------------------------------------------------------------- 1 | #version 310 es 2 | 3 | precision mediump int; 4 | precision mediump float; 5 | precision mediump isampler3D; 6 | 7 | uniform isampler3D u_volume; 8 | uniform int u_bpcmask; 9 | uniform int u_dotlock; 10 | 11 | in vec3 v_texcoord; 12 | in vec2 v_dotcoord; 13 | in vec3 v_bpcscale; 14 | 15 | out vec4 ql_FragColor; 16 | 17 | float dot2(vec2 v) {return dot(v, v);} 18 | 19 | void main() { 20 | float rsq = dot2(fract(v_dotcoord)-vec2(0.5, 0.5)); 21 | float lum = max(0.0, 0.5 - rsq * 2.0); 22 | 23 | #ifndef LOW_QUALITY 24 | float dm = v_dotcoord.x + v_dotcoord.y; 25 | float dd = min(1.0, 0.125 / dot2(vec2(dFdx(dm), dFdy(dm)))); 26 | lum = mix(0.125, lum, dd); 27 | #endif 28 | 29 | vec3 texcoord = v_texcoord; 30 | #ifndef LOW_QUALITY 31 | if (u_dotlock != 0) { 32 | texcoord.yz = (texcoord.yz - vec2(0.5, 0.5)) * ((floor(v_dotcoord.x) + 0.5) / v_dotcoord.x) + vec2(0.5, 0.5); 33 | } 34 | #endif 35 | int pix = texture(u_volume, texcoord).r & u_bpcmask; 36 | vec3 colour = vec3(float(pix & 0xe0), float(pix & 0x1c), float(pix & 0x03)) * v_bpcscale; 37 | 38 | ql_FragColor.rgb = colour * lum; 39 | ql_FragColor.a = 1.0; 40 | } 41 | -------------------------------------------------------------------------------- /src/platform/input.h: -------------------------------------------------------------------------------- 1 | #ifndef _INPUT_H_ 2 | #define _INPUT_H_ 3 | 4 | #include 5 | #include 6 | 7 | typedef enum { 8 | AXIS_LS_X, 9 | AXIS_LS_Y, 10 | AXIS_LT, 11 | AXIS_RS_X, 12 | AXIS_RS_Y, 13 | AXIS_RT, 14 | AXIS_D_X, 15 | AXIS_D_Y, 16 | AXIS_COUNT 17 | } axis_t; 18 | 19 | typedef enum { 20 | BUTTON_A, 21 | BUTTON_B, 22 | BUTTON_X, 23 | BUTTON_Y, 24 | BUTTON_LB, 25 | BUTTON_RB, 26 | BUTTON_VIEW, 27 | BUTTON_MENU, 28 | BUTTON_LEFT, 29 | BUTTON_RIGHT, 30 | BUTTON_UP, 31 | BUTTON_DOWN, 32 | BUTTON_LT, 33 | BUTTON_RT, 34 | BUTTON_COUNT 35 | } button_t; 36 | 37 | typedef enum { 38 | BUTTON_UNPRESSED = 1, 39 | BUTTON_PRESSED = 2, 40 | BUTTON_HELD = 4 41 | } button_event_t; 42 | 43 | #define CONTROLLERS_MAX 4 44 | typedef uint8_t controller_id_t; 45 | 46 | void input_set_nonblocking(void); 47 | bool input_get_button(controller_id_t controller, button_t button, button_event_t event); 48 | float input_get_axis(controller_id_t controller, axis_t axis); 49 | bool input_get_combo(controller_id_t controller, const uint8_t* combo, uint8_t combo_length); 50 | void input_update(void); 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /src/toys/zander/zander.h: -------------------------------------------------------------------------------- 1 | #ifndef _ZANDER_H_ 2 | #define _ZANDER_H_ 3 | 4 | #include 5 | #include "mathc.h" 6 | #include "voxel.h" 7 | 8 | static const float world_gravity = 4.0f; 9 | 10 | extern float world_scale; 11 | extern struct vec3 world_position; 12 | 13 | extern int8_t height_map[VOXELS_Y][VOXELS_X][2]; 14 | #define HEIGHT_MAP_OBJECT(x, y) (height_map[y][x][1]) 15 | #define HEIGHT_MAP_TERRAIN(x, y) (height_map[y][x][0]) 16 | 17 | 18 | static inline void foxel_from_world(float* voxel, const float* position) { 19 | voxel[0] = ((position[0] - world_position.x) * world_scale) + (VOXELS_X / 2); 20 | voxel[1] = ((position[1] - world_position.y) * world_scale) + (VOXELS_Y / 2); 21 | voxel[2] = ((position[2] - world_position.z) * world_scale); 22 | } 23 | 24 | static inline void voxel_from_world(int32_t* voxel, const float* position) { 25 | float foxel[3]; 26 | foxel_from_world(foxel, position); 27 | for (int i = 0; i < 3; ++i) { 28 | voxel[i] = (int)(roundf(foxel[i])); 29 | 30 | } 31 | } 32 | 33 | void world_from_voxel(float* position, const int32_t* voxel); 34 | 35 | float rand_range(float inf, float sup); 36 | 37 | void zander_reset(void); 38 | 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/platform/voxel.c: -------------------------------------------------------------------------------- 1 | #include "voxel.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | voxel_double_buffer_t* voxel_buffer = NULL; 11 | static int voxel_fd = -1; 12 | 13 | bool voxel_buffer_map(void) { 14 | 15 | voxel_fd = shm_open("/vortex_double_buffer", O_RDWR, 0666); 16 | if (voxel_fd == -1) { 17 | perror("shm_open"); 18 | return false; 19 | } 20 | 21 | voxel_buffer = mmap(NULL, sizeof(*voxel_buffer), PROT_WRITE, MAP_SHARED, voxel_fd, 0); 22 | if (voxel_buffer == MAP_FAILED) { 23 | perror("mmap"); 24 | return false; 25 | } 26 | 27 | return true; 28 | } 29 | 30 | void voxel_buffer_unmap(void) { 31 | munmap(voxel_buffer, sizeof(*voxel_buffer)); 32 | close(voxel_fd); 33 | } 34 | 35 | pixel_t* voxel_buffer_get(VOXEL_BUFFER_T buffer) { 36 | return voxel_buffer->volume[(!voxel_buffer->page) == (buffer == VOXEL_BUFFER_BACK)]; 37 | } 38 | 39 | void voxel_buffer_clear(pixel_t* volume) { 40 | memset(volume, 0, sizeof(*voxel_buffer->volume)); 41 | } 42 | 43 | void voxel_buffer_swap(void) { 44 | voxel_buffer->page = !voxel_buffer->page; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/platform/timer.c: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "rammel.h" 10 | 11 | uint32_t timer_frame_count = 0; 12 | uint32_t timer_frame_time = 0; 13 | uint32_t timer_delta_time = 100; 14 | 15 | [[maybe_unused]] timespec_t timer_start, timer_frame_curr, timer_frame_prev, timer_prof; 16 | 17 | 18 | void timer_init() { 19 | clock_gettime(CLOCK_REALTIME, &timer_start); 20 | timer_frame_curr = timer_frame_prev = timer_start; 21 | } 22 | 23 | void timer_tick() { 24 | ++timer_frame_count; 25 | 26 | clock_gettime(CLOCK_REALTIME, &timer_frame_curr); 27 | int ms_elapsed = timer_diff_timespec_ms(&timer_frame_curr, &timer_frame_prev); 28 | timer_frame_prev = timer_frame_curr; 29 | 30 | timer_frame_time = timer_diff_timespec_ms(&timer_frame_curr, &timer_start); 31 | 32 | timer_delta_time = clamp(ms_elapsed, 1, 100); 33 | } 34 | 35 | void timer_sleep_until(timer_since_t offset, uint32_t ms) { 36 | timespec_t since; 37 | 38 | switch (offset) { 39 | case TIMER_SINCE_START: since = timer_start; break; 40 | default: 41 | case TIMER_SINCE_TICK: since = timer_frame_curr; break; 42 | } 43 | 44 | timer_add_timespec_ms(&since, ms); 45 | clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &since, NULL); 46 | } 47 | -------------------------------------------------------------------------------- /src/platform/graphics.h: -------------------------------------------------------------------------------- 1 | #ifndef _GRAPHICS_H_ 2 | #define _GRAPHICS_H_ 3 | 4 | #include "voxel.h" 5 | 6 | typedef uint32_t index_t; 7 | struct image_s; 8 | 9 | typedef struct { 10 | pixel_t colour; 11 | float texcoord[3][2]; 12 | struct image_s* texture; 13 | } triangle_state_t; 14 | 15 | typedef void (*graphics_draw_voxel_cb_t)(pixel_t* volume, const int* coordinate, const float* barycentric, const triangle_state_t* triangle); 16 | extern graphics_draw_voxel_cb_t graphics_triangle_shader_cb; 17 | 18 | float* vec3_transform(float* vdst, const float* vsrc, const float* matrix); 19 | float* mat4_apply_scale(float* matrix, const float* scale); 20 | float* mat4_apply_scale_f(float* matrix, float scale); 21 | float* mat4_apply_translation(float* matrix, const float* vector); 22 | float* mat4_apply_rotation_x(float* matrix, float angle); 23 | float* mat4_apply_rotation_y(float* matrix, float angle); 24 | float* mat4_apply_rotation_z(float* matrix, float angle); 25 | float* mat4_apply_rotation(float* matrix, const float* euler); 26 | 27 | void graphics_draw_line(pixel_t* volume, const float* one, const float* two, pixel_t colour); 28 | void graphics_triangle_colour(pixel_t colour); 29 | void graphics_triangle_texture(const float* uv0, const float* uv1, const float* uv2, struct image_s* texture); 30 | void graphics_draw_triangle(pixel_t* volume, const float* v0, const float* v1, const float* v2); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/platform/model.h: -------------------------------------------------------------------------------- 1 | #ifndef _MODEL_H_ 2 | #define _MODEL_H_ 3 | 4 | #include "mathc.h" 5 | #include "voxel.h" 6 | #include "graphics.h" 7 | 8 | typedef struct { 9 | char* name; 10 | pixel_t colour; 11 | char* image; 12 | } material_t; 13 | 14 | typedef struct { 15 | vec3_t position; 16 | vec2_t texcoord; 17 | //vec3_t normal; 18 | } vertex_t; 19 | 20 | typedef struct { 21 | index_t index[2]; 22 | pixel_t colour; 23 | } edge_t; 24 | 25 | typedef struct { 26 | uint32_t index_count; 27 | index_t* indices; 28 | pixel_t colour; 29 | struct image_s* image; 30 | } surface_t; 31 | 32 | typedef struct { 33 | uint32_t vertex_count; 34 | vertex_t* vertices; 35 | 36 | uint32_t edge_count; 37 | edge_t* edges; 38 | 39 | uint32_t surface_count; 40 | surface_t* surfaces; 41 | } model_t; 42 | 43 | typedef enum { 44 | STYLE_DEFAULT, 45 | STYLE_WIREFRAME_ALWAYS, 46 | STYLE_WIREFRAME_IF_UNDEFINED, 47 | STYLE_COUNT 48 | } model_style_t; 49 | 50 | 51 | model_t* model_load(const char* filename, model_style_t style); 52 | model_t* model_load_image(const char* filename); 53 | void model_set_colour(model_t* model, pixel_t colour); 54 | void model_free(model_t* model); 55 | void model_draw(pixel_t* volume, const model_t* model, float* matrix); 56 | void model_get_bounds(model_t* model, vec3_t* centre, float* radius, float* height); 57 | 58 | void model_dump(model_t* model); 59 | 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /python/grid.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import os 3 | import mmap 4 | import random 5 | 6 | voxels_x = 128 7 | voxels_y = 128 8 | voxels_z = 64 9 | 10 | class voxel_double_buffer_t(ctypes.Structure): 11 | _fields_ = [("buffers", ctypes.c_uint8 * voxels_z * voxels_x * voxels_y * 2), 12 | ("page", ctypes.c_uint8), 13 | ("bpc", ctypes.c_uint8), 14 | ("flags", ctypes.c_uint16), 15 | ("rpm", ctypes.c_uint16), 16 | ("uspf", ctypes.c_uint16)] 17 | 18 | shm_fd = os.open("/dev/shm/vortex_double_buffer", os.O_RDWR) 19 | shm_mm = mmap.mmap(shm_fd, ctypes.sizeof(voxel_double_buffer_t), mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE) 20 | buffer = voxel_double_buffer_t.from_buffer(shm_mm) 21 | 22 | #buffer.bpc = 1 23 | 24 | for z in range(voxels_z): 25 | for x in range(voxels_x): 26 | for y in range(voxels_y): 27 | c = 0 28 | if z == 0: 29 | if ((x^y)&1) == 0: 30 | c |= 0b00000010 31 | if ((x^y)&2) == 0: 32 | c |= 0b00010000 33 | if ((x^y)&4) == 0: 34 | c |= 0b10000000 35 | if z == voxels_z / 2: 36 | c = 0b10000000 37 | 38 | if z == voxels_z - 1: 39 | if ((x^y)&8) == 0: 40 | c |= 0b10000000 41 | else: 42 | c |= 0b00010000 43 | 44 | buffer.buffers[0][y][x][z] = c 45 | buffer.buffers[1][y][x][z] = c 46 | buffer.page = 1 - buffer.page 47 | -------------------------------------------------------------------------------- /src/toys/eighty/eighty.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "array.h" 18 | #include "mathc.h" 19 | #include "rammel.h" 20 | #include "input.h" 21 | #include "graphics.h" 22 | #include "model.h" 23 | #include "timer.h" 24 | #include "tubeface.h" 25 | #include "scooter.h" 26 | #include "grid.h" 27 | 28 | voxel_double_buffer_t* volume_buffer; 29 | 30 | void main_init(void) { 31 | timer_init(); 32 | 33 | tubeface_init(); 34 | grid_init(); 35 | scooter_init(); 36 | } 37 | 38 | void main_update(float dt) { 39 | input_update(); 40 | scooter_update(dt); 41 | } 42 | 43 | void main_draw(pixel_t* volume) { 44 | tubeface_draw(volume); 45 | grid_draw(volume); 46 | scooter_draw(volume); 47 | } 48 | 49 | int main(int argc, char** argv) { 50 | if (!voxel_buffer_map()) { 51 | exit(1); 52 | } 53 | 54 | main_init(); 55 | 56 | input_set_nonblocking(); 57 | 58 | for (int ch = 0; ch != 27; ch = getchar()) { 59 | timer_tick(); 60 | 61 | pixel_t* volume = voxel_buffer_get(VOXEL_BUFFER_BACK); 62 | voxel_buffer_clear(volume); 63 | 64 | main_update((float)timer_delta_time * 0.001f); 65 | main_draw(volume); 66 | 67 | voxel_buffer_swap(); 68 | 69 | timer_sleep_until(TIMER_SINCE_TICK, 30); 70 | } 71 | 72 | voxel_buffer_unmap(); 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /python/calibration.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import os 3 | import mmap 4 | import random 5 | 6 | voxels_x = 128 7 | voxels_y = 128 8 | voxels_z = 64 9 | 10 | class voxel_double_buffer_t(ctypes.Structure): 11 | _fields_ = [("buffers", ctypes.c_uint8 * voxels_z * voxels_x * voxels_y * 2), 12 | ("page", ctypes.c_uint8), 13 | ("bpc", ctypes.c_uint8), 14 | ("flags", ctypes.c_uint16), 15 | ("rpm", ctypes.c_uint16), 16 | ("uspf", ctypes.c_uint16)] 17 | 18 | shm_fd = os.open("/dev/shm/vortex_double_buffer", os.O_RDWR) 19 | shm_mm = mmap.mmap(shm_fd, ctypes.sizeof(voxel_double_buffer_t), mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE) 20 | buffer = voxel_double_buffer_t.from_buffer(shm_mm) 21 | 22 | #buffer.bpc = 1 23 | 24 | for z in range(voxels_z): 25 | for x in range(voxels_x): 26 | for y in range(voxels_y): 27 | c = 0 28 | if (z&31) == 0 or z == voxels_z-1: 29 | g = ((z+1)>>5) + 2 30 | 31 | if (x&((1<>g) ^ (y>>g)) & 1 38 | #c = (b<<7) | (b<<4) | (b<<1) 39 | if z < 16: 40 | if (y+1)//2 == voxels_y//4: 41 | c = c | 0b10000000 42 | if (x+1)//2 == voxels_x//4: 43 | c = c | 0b00010000 44 | 45 | buffer.buffers[0][y][x][z] = c 46 | buffer.buffers[1][y][x][z] = c 47 | buffer.page = 1 - buffer.page 48 | -------------------------------------------------------------------------------- /src/platform/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef _TIMER_H_ 2 | #define _TIMER_H_ 3 | 4 | #include 5 | #include 6 | 7 | typedef enum { 8 | TIMER_SINCE_START, 9 | TIMER_SINCE_TICK 10 | } timer_since_t; 11 | 12 | extern uint32_t timer_frame_count; 13 | extern uint32_t timer_frame_time; 14 | extern uint32_t timer_delta_time; 15 | 16 | typedef struct timespec timespec_t; 17 | 18 | static inline int32_t timer_diff_timespec_ms(const timespec_t* to, const timespec_t* from) { 19 | struct timespec diff; 20 | 21 | if ((to->tv_nsec - from->tv_nsec) < 0) { 22 | diff.tv_sec = to->tv_sec - from->tv_sec - 1; 23 | diff.tv_nsec = 1000000000 + to->tv_nsec - from->tv_nsec; 24 | } else { 25 | diff.tv_sec = to->tv_sec - from->tv_sec; 26 | diff.tv_nsec = to->tv_nsec - from->tv_nsec; 27 | } 28 | 29 | return (diff.tv_sec * 1000) + (diff.tv_nsec / 1000000); 30 | } 31 | 32 | static inline void timer_add_timespec_ms(timespec_t* time, int ms) { 33 | time->tv_nsec += ms * 1000000; 34 | 35 | while (time->tv_nsec >= 1000000000) { 36 | time->tv_nsec -= 1000000000; 37 | time->tv_sec += 1; 38 | } 39 | } 40 | 41 | static inline timespec_t timer_time_now(void) { 42 | timespec_t timenow; 43 | clock_gettime(CLOCK_REALTIME, &timenow); 44 | return timenow; 45 | } 46 | 47 | static inline int32_t timer_elapsed_ms(timespec_t* timer) { 48 | timespec_t timenow; 49 | clock_gettime(CLOCK_REALTIME, &timenow); 50 | int32_t elapsed = timer_diff_timespec_ms(&timenow, timer); 51 | *timer = timenow; 52 | return elapsed; 53 | } 54 | 55 | void timer_init(); 56 | void timer_tick(); 57 | void timer_sleep_until(timer_since_t offset, uint32_t ms); 58 | 59 | #endif 60 | 61 | -------------------------------------------------------------------------------- /src/driver/gadgets/gadget_vortex.h: -------------------------------------------------------------------------------- 1 | #ifndef _GADGET_H_ 2 | #define _GADGET_H_ 3 | 4 | #define SPIN_SYNC 1 5 | 6 | #define RGB_0_B1 12 7 | #define RGB_0_G1 9 8 | #define RGB_0_R1 6 9 | #define RGB_0_B2 5 10 | #define RGB_0_G2 8 11 | #define RGB_0_R2 7 12 | 13 | #define RGB_1_B1 21 14 | #define RGB_1_G1 13 15 | #define RGB_1_R1 20 16 | #define RGB_1_B2 26 17 | #define RGB_1_G2 19 18 | #define RGB_1_R2 16 19 | 20 | #define ADDR_CLK 4 21 | #define ADDR_DAT 18 22 | #define ADDR__EN 15 23 | #define ADDR__EN_MASK (1< 5 | 6 | //Pi 2, 3, 4 7 | #define BCM2708_PERI_BASE 0x20000000 8 | #define BCM2709_PERI_BASE 0x3F000000 9 | #define BCM2711_PERI_BASE 0xFE000000 10 | 11 | #define BCM_BASE BCM2711_PERI_BASE 12 | 13 | #define GPIO_BASE (BCM_BASE + 0x200000) 14 | #define TIMER_CTRL (BCM_BASE + 0x3000) 15 | 16 | #define GPFSEL0 0 17 | #define GPFSEL1 1 18 | #define GPFSEL2 2 19 | #define GPFSEL3 3 20 | #define GPFSEL4 4 21 | #define GPFSEL5 5 22 | #define GPSET0 7 23 | #define GPSET1 8 24 | #define GPCLR0 10 25 | #define GPCLR1 11 26 | #define GPLEV0 13 27 | #define GPLEV1 14 28 | 29 | #if (BCM_BASE) == (BCM2711_PERI_BASE) 30 | #define GPPUPPDN0 57 31 | #define GPPUPPDN1 58 32 | #define GPPUPPDN2 59 33 | #define GPPUPPDN3 60 34 | #else 35 | #define GPPUD 37 36 | #define GPPUDCLK0 38 37 | #define GPPUDCLK1 39 38 | #endif 39 | 40 | 41 | extern volatile uint32_t *gpio_base; 42 | extern volatile uint32_t *timer_uS; 43 | 44 | static inline void gpio_busy_wait(uint32_t uS) { 45 | uint32_t start = *timer_uS; 46 | while (*timer_uS - start <= uS); 47 | } 48 | 49 | static inline void gpio_set_bits(uint32_t bits) { 50 | gpio_base[GPSET0] = bits; 51 | } 52 | static inline void gpio_clear_bits(uint32_t bits) { 53 | gpio_base[GPCLR0] = bits; 54 | } 55 | static inline void gpio_set_pin(int pin) { 56 | gpio_set_bits(1ul << pin); 57 | } 58 | static inline void gpio_clear_pin(int pin) { 59 | gpio_clear_bits(1ul << pin); 60 | } 61 | 62 | static inline uint32_t gpio_get_bits(uint32_t bits) { 63 | return gpio_base[GPLEV0] & bits; 64 | } 65 | static inline int gpio_get_pin(int pin) { 66 | return gpio_get_bits(1ul << pin) != 0; 67 | } 68 | 69 | void gpio_init_pull(int pin, int pud); 70 | void gpio_init_in(int pin); 71 | void gpio_init_out(int pin); 72 | 73 | bool gpio_init(void); 74 | 75 | #endif 76 | 77 | -------------------------------------------------------------------------------- /src/driver/gadgets/gadget_rotovox.h: -------------------------------------------------------------------------------- 1 | #ifndef _GADGET_H_ 2 | #define _GADGET_H_ 3 | 4 | #define SPIN_SYNC 1 5 | 6 | #define RGB_0_R1 6 7 | #define RGB_0_G1 9 8 | #define RGB_0_B1 12 9 | #define RGB_0_R2 7 10 | #define RGB_0_G2 8 11 | #define RGB_0_B2 5 12 | 13 | #define RGB_1_R1 10 14 | #define RGB_1_G1 27 15 | #define RGB_1_B1 25 16 | #define RGB_1_R2 22 17 | #define RGB_1_G2 23 18 | #define RGB_1_B2 24 19 | 20 | #define ROW_A 4 21 | #define ROW_B 15 22 | #define ROW_C 18 23 | #define ROW_D 17 24 | #define ROW_E 14 25 | #define ADDR__EN_MASK (0) 26 | 27 | #define RGB_BLANK 11 28 | #define RGB_CLOCK 0 29 | #define RGB_STROBE 3 30 | #define RGB_BLANK_MASK (1< 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "rammel.h" 15 | #include "voxel.h" 16 | 17 | 18 | int content_fd; 19 | voxel_double_buffer_t* volume_buffer; 20 | 21 | void vox_blit(const char* filename) { 22 | FILE* cyc_fd = fopen(filename, "rb"); 23 | if (cyc_fd == NULL) { 24 | perror("open"); 25 | exit(1); 26 | } 27 | 28 | fseek(cyc_fd, 0, SEEK_END); 29 | int file_size = ftell(cyc_fd); 30 | fseek(cyc_fd, 0, SEEK_SET); 31 | 32 | int frames = file_size / (VOXELS_COUNT * sizeof(pixel_t)); 33 | 34 | 35 | size_t rtot = 0; 36 | do { 37 | uint8_t page = !volume_buffer->page; 38 | void* content = volume_buffer->volume[page]; 39 | 40 | size_t rnow = fread(content + rtot, 1, sizeof(volume_buffer->volume[0]) - rtot, cyc_fd); 41 | if (rnow == 0) { 42 | fseek(cyc_fd, 0, SEEK_SET); 43 | } 44 | 45 | rtot += rnow; 46 | 47 | if (rtot >= sizeof(volume_buffer->volume[0])) { 48 | volume_buffer->page = page; 49 | rtot = 0; 50 | usleep(100000); 51 | } 52 | 53 | } while (frames > 1); 54 | 55 | fclose(cyc_fd); 56 | } 57 | 58 | int main(int argc, char** argv) { 59 | 60 | content_fd = shm_open("/vortex_double_buffer", O_RDWR, 0666); 61 | if (content_fd == -1) { 62 | perror("shm_open"); 63 | exit(1); 64 | } 65 | 66 | struct stat sb; 67 | if (fstat(content_fd, &sb) == -1){ 68 | perror("fstat"); 69 | exit(1); 70 | } 71 | 72 | volume_buffer = mmap(NULL, sizeof(*volume_buffer), PROT_WRITE, MAP_SHARED, content_fd, 0); 73 | if (volume_buffer == MAP_FAILED) { 74 | perror("mmap"); 75 | exit(1); 76 | } 77 | 78 | if (argc > 1) { 79 | vox_blit(argv[1]); 80 | } else { 81 | volume_buffer->page = !volume_buffer->page; 82 | } 83 | 84 | 85 | munmap(volume_buffer, sizeof(*volume_buffer)); 86 | close(content_fd); 87 | 88 | return 0; 89 | } 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/driver/gpio.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "gadget.h" 14 | #include "rammel.h" 15 | 16 | #include "gpio.h" 17 | 18 | volatile uint32_t *gpio_base; 19 | volatile uint32_t *timer_uS; 20 | 21 | 22 | void gpio_init_pull(int pin, int pud) { 23 | // pud: 0:off 1:up 2:down 24 | _Static_assert(BCM_BASE==BCM2711_PERI_BASE, "2711 specific"); 25 | 26 | uint32_t bits = gpio_base[GPPUPPDN0 + (pin>>4)]; 27 | 28 | int shift = (pin & 0xf) << 1; 29 | bits &= ~(3 << shift); 30 | bits |= (pud << shift); 31 | 32 | gpio_base[GPPUPPDN0 + (pin>>4)] = bits; 33 | } 34 | 35 | void gpio_init_in(int pin) { 36 | gpio_base[pin / 10] &= ~(7ull << ((pin % 10) * 3)); 37 | } 38 | void gpio_init_out(int pin) { 39 | gpio_base[pin / 10] &= ~(7ull << ((pin % 10) * 3)); // apparently needed? 40 | gpio_base[pin / 10] |= (1ull << ((pin % 10) * 3)); 41 | 42 | gpio_init_pull(pin, 0); 43 | } 44 | 45 | static bool gpio_mapmem(void) { 46 | int memfd; 47 | 48 | if ((memfd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) { 49 | perror("Can't open /dev/mem (must be root)"); 50 | return NULL; 51 | } 52 | 53 | gpio_base = (uint32_t*)mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, memfd, GPIO_BASE); 54 | 55 | void* timer_base = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, memfd, TIMER_CTRL); 56 | timer_uS = (uint32_t*)(timer_base ? (uint8_t*)timer_base + 4 : NULL); // just ignore the upper 32 bits 57 | 58 | close(memfd); 59 | 60 | if (gpio_base == MAP_FAILED || timer_base == MAP_FAILED) { 61 | perror("mmap error"); 62 | return false; 63 | } 64 | 65 | return true; 66 | } 67 | 68 | bool gpio_init(void) { 69 | if (!gpio_mapmem()) { 70 | return false; 71 | } 72 | 73 | gpio_init_in(SPIN_SYNC); 74 | gpio_init_pull(SPIN_SYNC, 0); 75 | 76 | for (int i = 0; i < count_of(matrix_init_out); ++i) { 77 | gpio_init_out(matrix_init_out[i]); 78 | } 79 | 80 | gpio_set_pin(RGB_BLANK); 81 | 82 | return true; 83 | } 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/platform/array.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "array.h" 7 | 8 | void array_initialise(array_t* array, size_t size, size_t capacity) { 9 | array->size = size; 10 | array->capacity = capacity; 11 | array->count = 0; 12 | array->data = malloc(capacity * size); 13 | assert(array->data); 14 | } 15 | 16 | void array_reserve(array_t* array, size_t capacity) { 17 | if (capacity > array->capacity) { 18 | array->capacity = capacity; 19 | if (array->data) { 20 | array->data = realloc(array->data, array->capacity * array->size); 21 | assert(array->data); 22 | } 23 | } 24 | if (!array->data) { 25 | array->data = malloc(array->capacity * array->size); 26 | assert(array->data); 27 | array->count = 0; 28 | } 29 | } 30 | 31 | void array_resize(array_t* array, size_t count) { 32 | if (!array->data) { 33 | if (!array->capacity) { 34 | size_t mincap = 1024 / array->size; 35 | array->capacity = count > mincap ? count : mincap; 36 | } 37 | array->count = count; 38 | array->data = malloc(array->capacity * array->size); 39 | assert(array->data); 40 | } else { 41 | if (count > array->capacity) { 42 | array->capacity *= 2; 43 | array->capacity = count > array->capacity ? count : array->capacity; 44 | array->data = realloc(array->data, array->capacity * array->size); 45 | assert(array->data); 46 | } 47 | } 48 | array->count = count; 49 | } 50 | 51 | void array_clear(array_t* array) { 52 | array->count = 0; 53 | } 54 | 55 | void array_destroy(array_t* array) { 56 | free(array->data); 57 | array->data = NULL; 58 | array->capacity = 0; 59 | array->count = 0; 60 | } 61 | 62 | void* array_push(array_t* array) { 63 | array_resize(array, array->count + 1); 64 | return array_get(array, array->count - 1); 65 | } 66 | 67 | void* array_pop(array_t* array) { 68 | if (array->count < 1) { 69 | return NULL; 70 | } 71 | array_resize(array, array->count - 1); 72 | return array_get(array, array->count); 73 | } 74 | 75 | void array_clear_element(array_t* array, size_t index) { 76 | memset(array->data + index * array->size, 0, array->size); 77 | } 78 | -------------------------------------------------------------------------------- /python/colourwheel.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import os 3 | import mmap 4 | import math 5 | 6 | voxels_x = 128 7 | voxels_y = 128 8 | voxels_z = 64 9 | 10 | class voxel_double_buffer_t(ctypes.Structure): 11 | _fields_ = [("buffers", ctypes.c_uint8 * voxels_z * voxels_x * voxels_y * 2), 12 | ("page", ctypes.c_uint8), 13 | ("bpc", ctypes.c_uint8), 14 | ("flags", ctypes.c_uint16), 15 | ("rpm", ctypes.c_uint16), 16 | ("uspf", ctypes.c_uint16)] 17 | 18 | shm_fd = os.open("/dev/shm/vortex_double_buffer", os.O_RDWR) 19 | shm_mm = mmap.mmap(shm_fd, ctypes.sizeof(voxel_double_buffer_t), mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE) 20 | buffer = voxel_double_buffer_t.from_buffer(shm_mm) 21 | 22 | ctypes.memset(ctypes.addressof(buffer.buffers), 0, ctypes.sizeof(buffer.buffers)) 23 | 24 | def hsv_to_rgb(h, s, v): 25 | if s: 26 | h = ((h % 1) + 1) % 1 27 | 28 | i = int(h * 6) 29 | f = h * 6 - i 30 | 31 | w = v * (1 - s) 32 | q = v * (1 - s * f) 33 | t = v * (1 - s * (1 - f)) 34 | 35 | if i==0: 36 | return (v, t, w) 37 | if i==1: 38 | return (q, v, w) 39 | if i==2: 40 | return (w, v, t) 41 | if i==3: 42 | return (w, q, v) 43 | if i==4: 44 | return (t, w, v) 45 | if i==5: 46 | return (v, w, q) 47 | else: 48 | return (v, v, v) 49 | 50 | def rgb_to_pix(rgb): 51 | r = min(int(rgb[0]*8), 7) 52 | g = min(int(rgb[1]*8), 7) 53 | b = min(int(rgb[2]*4), 3) 54 | return (r << 5) | (g << 2) | b 55 | 56 | 57 | for y in range(voxels_y): 58 | vy = y - (voxels_y - 1) * 0.5 59 | for x in range(voxels_x): 60 | vx = x - (voxels_x - 1) * 0.5 61 | 62 | r = math.sqrt(vx**2 + vy**2) 63 | 64 | a = math.atan2(vy, vx) 65 | hue = (a / (2 * math.pi)) + 0.25 66 | rgb = hsv_to_rgb(hue, 1.0, min(max(0, (r - 16)/48),1)) 67 | c = rgb_to_pix(rgb) 68 | buffer.buffers[0][y][x][8] = c 69 | buffer.buffers[1][y][x][8] = c 70 | 71 | r = (x // 4) & 7 72 | g = (y // 4) & 7 73 | b = ((x // 64) & 1) * 2 | ((y // 64) & 1) 74 | c = (r << 5) | (g << 2) | b 75 | buffer.buffers[0][y][x][56] = c 76 | buffer.buffers[1][y][x][56] = c 77 | -------------------------------------------------------------------------------- /python/pointvision.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import os 3 | import mmap 4 | import threading 5 | import queue 6 | import numpy as np 7 | import asyncio 8 | import struct 9 | import gzip 10 | 11 | voxels_x = 128 12 | voxels_y = 128 13 | voxels_z = 64 14 | voxels_count = voxels_x * voxels_y * voxels_z 15 | 16 | class voxel_double_buffer_t(ctypes.Structure): 17 | _fields_ = [("buffers", ctypes.c_uint8 * voxels_z * voxels_x * voxels_y * 2), 18 | ("page", ctypes.c_uint8), 19 | ("bpc", ctypes.c_uint8), 20 | ("flags", ctypes.c_uint16), 21 | ("rpm", ctypes.c_uint16), 22 | ("uspf", ctypes.c_uint16)] 23 | 24 | 25 | data_queue = queue.Queue(maxsize=2) 26 | 27 | def process_data(data_queue): 28 | shm_fd = os.open("/dev/shm/vortex_double_buffer", os.O_RDWR) 29 | shm_mm = mmap.mmap(shm_fd, ctypes.sizeof(voxel_double_buffer_t), mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE) 30 | buffer = voxel_double_buffer_t.from_buffer(shm_mm) 31 | 32 | while True: 33 | if not data_queue.empty(): 34 | data = data_queue.get() 35 | 36 | page = 1 - buffer.page 37 | 38 | ctypes.memset(ctypes.byref(buffer, page * voxels_count), 0, voxels_count) 39 | 40 | point_data = np.frombuffer(data, dtype=np.uint8).reshape(-1, 4) 41 | x = point_data[:, 0] 42 | y = point_data[:, 1] 43 | z = point_data[:, 2] 44 | pix = point_data[:, 3] 45 | 46 | voxels = np.ctypeslib.as_array(buffer.buffers[page]).reshape((128,128,64)) 47 | voxels[y, x, z] = pix 48 | 49 | buffer.page = page 50 | 51 | async def handle_client(reader, data_queue): 52 | while True: 53 | try: 54 | header = await reader.readexactly(8) 55 | if header[:4] != b'\xff\xff\xff\xff': 56 | print("Invalid header") 57 | break 58 | 59 | packet_length = struct.unpack('!I', header[4:])[0] 60 | data = await reader.readexactly(packet_length) 61 | 62 | if not data_queue.full(): 63 | data_queue.put(gzip.decompress(data)) 64 | 65 | except asyncio.IncompleteReadError: 66 | print("Connection closed") 67 | break 68 | 69 | async def main(): 70 | host='vortex.local' 71 | port=0x5658 72 | 73 | data_queue = queue.Queue() 74 | server = await asyncio.start_server(lambda r, w: handle_client(r, data_queue), host, port) 75 | 76 | processor_thread = threading.Thread(target=process_data, args=(data_queue,)) 77 | processor_thread.start() 78 | 79 | async with server: 80 | await server.serve_forever() 81 | 82 | if __name__ == "__main__": 83 | asyncio.run(main()) 84 | 85 | -------------------------------------------------------------------------------- /src/toys/zander/zander.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "zander.h" 18 | 19 | #include "array.h" 20 | #include "mathc.h" 21 | #include "rammel.h" 22 | #include "input.h" 23 | #include "graphics.h" 24 | #include "model.h" 25 | #include "ship.h" 26 | #include "terrain.h" 27 | #include "particles.h" 28 | #include "objects.h" 29 | #include "timer.h" 30 | 31 | voxel_double_buffer_t* volume_buffer; 32 | 33 | int8_t height_map[VOXELS_Y][VOXELS_X][2]; 34 | 35 | float world_scale = 8.0f; 36 | vec3_t world_position = {.x=0, .y=0, .z=0}; 37 | 38 | void world_from_voxel(float* position, const int32_t* voxel) { 39 | position[0] = ((voxel[0] - (VOXELS_X / 2)) / world_scale) + world_position.x; 40 | position[1] = ((voxel[1] - (VOXELS_Y / 2)) / world_scale) + world_position.y; 41 | position[2] = ((voxel[2] ) / world_scale) + world_position.z; 42 | } 43 | 44 | float rand_range(float inf, float sup) { 45 | return ((float)rand() / (float)(RAND_MAX)) * (sup - inf) + inf; 46 | } 47 | 48 | void zander_reset(void) { 49 | objects_init(); 50 | terrain_init(); 51 | particles_init(); 52 | ship_init(); 53 | } 54 | 55 | void main_init(void) { 56 | timer_init(); 57 | zander_reset(); 58 | } 59 | 60 | void main_update(float dt) { 61 | input_update(); 62 | 63 | world_scale = clamp(world_scale * (1.0f + input_get_axis(0, AXIS_RS_Y) * dt), 3.0f, 12.0f); 64 | 65 | ship_update(dt); 66 | 67 | world_position.x = ship_position.x; 68 | world_position.y = ship_position.y + (16.0f / world_scale); 69 | world_position.z = max(0.0f, ship_position.z - 56.0f / world_scale); 70 | 71 | particles_update(dt); 72 | objects_update(dt); 73 | 74 | } 75 | 76 | void main_draw(pixel_t* volume) { 77 | terrain_draw(volume); 78 | objects_draw(volume); 79 | particles_draw(volume); 80 | ship_draw(volume); 81 | } 82 | 83 | int main(int argc, char** argv) { 84 | 85 | if (!voxel_buffer_map()) { 86 | exit(1); 87 | } 88 | 89 | main_init(); 90 | 91 | input_set_nonblocking(); 92 | 93 | for (int ch = 0; ch != 27; ch = getchar()) { 94 | timer_tick(); 95 | 96 | pixel_t* volume = voxel_buffer_get(VOXEL_BUFFER_BACK); 97 | voxel_buffer_clear(volume); 98 | 99 | main_update((float)timer_delta_time * 0.001f); 100 | main_draw(volume); 101 | 102 | voxel_buffer_swap(); 103 | 104 | timer_sleep_until(TIMER_SINCE_TICK, 30); 105 | } 106 | 107 | voxel_buffer_unmap(); 108 | 109 | return 0; 110 | } 111 | -------------------------------------------------------------------------------- /python/obj2c.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import re 4 | 5 | name = sys.argv[1] 6 | 7 | class Surface: 8 | def __init__(self): 9 | self.colour = (255,255,255) 10 | self.indices = [] 11 | 12 | vertices = [] 13 | edges = [] 14 | surfaces = [] 15 | surface = Surface() 16 | surfaces.append(surface) 17 | 18 | with open(name, 'r') as obj_file: 19 | for line in obj_file: 20 | tokens = line.split() 21 | if tokens[0] == 'v': 22 | vertices.append((tokens[1], tokens[2], tokens[3])) 23 | 24 | elif tokens[0] == 'f': 25 | zero = -1 26 | prev = int(tokens[-1].split('/')[0])-1 27 | for vtn in tokens[1:]: 28 | curr = int(vtn.split('/')[0])-1 29 | if zero < 0: 30 | zero = curr 31 | elif prev != zero: 32 | surface.indices.append(zero) 33 | surface.indices.append(prev) 34 | surface.indices.append(curr) 35 | prev = curr 36 | 37 | elif tokens[0] == 'l': 38 | prev = int(tokens[-1].split('/')[0])-1 39 | for vtn in tokens[1:]: 40 | curr = int(vtn.split('/')[0])-1 41 | edge = (min(prev, curr), max(prev, curr), surface.colour) 42 | if edge not in edges: 43 | edges.append(edge) 44 | prev = curr 45 | 46 | elif tokens[0] == 'usemtl': 47 | surface = Surface() 48 | surfaces.append(surface) 49 | 50 | 51 | if (len(vertices) >= 3): 52 | identifier = re.sub(r'[^a-zA-Z0-9_]', '_', os.path.splitext(os.path.basename(name))[0]) 53 | 54 | surfaces = [surface for surface in surfaces if surface.indices] 55 | 56 | print( '#define E_ RGBPIX(255,255,255)') 57 | print(f'static const model_t model_{identifier} = {{') 58 | print(f' .vertex_count = {len(vertices)},') 59 | print( ' .vertices = (vertex_t*)(float[][5]){', end='') 60 | for v, vert in enumerate(vertices): 61 | if ((v % 8) == 0): 62 | print('\n ', end='') 63 | print(f'{{{float(vert[0]):g}, {float(vert[2]):g}, {float(vert[1]):g}}}, ', end='') 64 | print( '\n },') 65 | 66 | print(f' .surface_count = {len(surfaces)},') 67 | print( ' .surfaces = (surface_t[]){') 68 | for surface in surfaces: 69 | print(f' {{{len(surface.indices)}, (index_t[]){{', end='') 70 | for i, idx in enumerate(surface.indices): 71 | if ((i % 40) == 0): 72 | print('\n ', end='') 73 | print(f'{idx}, ', end='') 74 | print(f'\n }}, RGBPIX{surface.colour}}},') 75 | print(' },') 76 | 77 | print(f' .edge_count = {len(edges)},') 78 | print( ' .edges = (edge_t[]){', end='') 79 | for e, edge in enumerate(edges): 80 | if ((e % 16) == 0): 81 | print('\n ', end='') 82 | print(f'{{{{{edge[0]}, {edge[1]}}}, E_}},', end='') 83 | print( '\n }') 84 | print( '};') 85 | print("#undef E_") -------------------------------------------------------------------------------- /src/platform/image.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "image.h" 7 | 8 | #include "rammel.h" 9 | 10 | #define STB_IMAGE_IMPLEMENTATION 11 | #include "stb_image.h" 12 | 13 | image_t* image_load(const char* filename) { 14 | image_t* image = NULL; 15 | 16 | int w = 0, h = 0, c = 0; 17 | uint8_t* data = stbi_load(filename, &w, &h, &c, STBI_default); 18 | 19 | if (data) { 20 | if (w > 0 && h > 0) { 21 | image = malloc(sizeof(image_t)); 22 | memset(image, 0, sizeof(image_t)); 23 | 24 | image->width = w; 25 | image->height = h; 26 | 27 | image->masked = false; 28 | image->key = HEXPIX(200000); 29 | 30 | int size = image->width * image->height; 31 | image->data = malloc(sizeof(pixel_t) * size); 32 | 33 | int r, g, b, a; 34 | switch (c) { 35 | case 2: r = g = b = 0; a = 1; break; 36 | case 3: r = 0; g = 1; b = 2; a = 0; break; 37 | case 4: r = 0; g = 1; b = 2; a = 3; break; 38 | default: r = g = b = a = 0; break; 39 | } 40 | 41 | if (a) { 42 | bool key_clash = false; 43 | for (int i = 0; i < size; ++i) { 44 | uint8_t* p = &data[i*c]; 45 | pixel_t colour = RGBPIX(p[r], p[g], p[b]); 46 | if (p[a] < 128) { 47 | image->masked = true; 48 | colour = image->key; 49 | } else if (colour == image->key) { 50 | key_clash = true; 51 | } 52 | image->data[i] = colour; 53 | } 54 | if (image->masked && key_clash) { 55 | for (int i = 0; i < size; ++i) { 56 | uint8_t* p = &data[i*c]; 57 | if (image->data[i] == image->key && p[a] >= 128) { 58 | image->data[i] ^= HEXPIX(200000); 59 | } 60 | } 61 | } 62 | } else { 63 | for (int i = 0; i < size; ++i) { 64 | uint8_t* p = &data[i*c]; 65 | image->data[i] = RGBPIX(p[r], p[g], p[b]); 66 | } 67 | } 68 | } 69 | stbi_image_free(data); 70 | } 71 | 72 | return image; 73 | } 74 | 75 | pixel_t image_sample(image_t* image, const float* uv, bool* masked) { 76 | if (!image->data || image->width <= 0 || image->height <= 0) { 77 | return 0; 78 | } 79 | 80 | int x = modulo((int)floor(uv[0] * image->width), image->width); 81 | int y = modulo((int)floor(uv[1] * image->height), image->height); 82 | 83 | y = (image->height - 1) - y; 84 | 85 | pixel_t colour = image->data[x + image->width * y]; 86 | 87 | if (masked) { 88 | *masked = image->masked && colour == image->key; 89 | } 90 | 91 | return colour; 92 | } 93 | 94 | void image_free(image_t* image) { 95 | if (image) { 96 | free(image->data); 97 | free(image); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/driver/rotation.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "rammel.h" 8 | #include "gpio.h" 9 | #include "gadget.h" 10 | 11 | #include "rotation.h" 12 | 13 | 14 | static uint32_t sync_prev = 0; 15 | static uint32_t rotation_angle = 0; 16 | static int32_t rotation_delta = 256; 17 | static int sync_level = 1; 18 | static uint32_t tick_prev = 0; 19 | static uint32_t rotation_history[8]; 20 | 21 | uint32_t rotation_zero = ROTATION_FULL / 360 * ROTATION_ZERO; 22 | bool rotation_stopped = true; 23 | uint32_t rotation_period_raw = 0; 24 | uint32_t rotation_period = 1<<26; 25 | bool rotation_lock = true; 26 | int32_t rotation_drift = 0; 27 | 28 | int compare_ints(const void *a, const void *b) { 29 | return *((int*)a) - *((int*)b); 30 | } 31 | 32 | #ifdef SYNC_PULSE_UNEQUAL 33 | static uint32_t median_period() { 34 | // treat the rising and falling edges separately 35 | static uint32_t sorted[2][count_of(rotation_history[0])]; 36 | memcpy(sorted, rotation_history, sizeof(sorted)); 37 | for (int i = 0; i < 2; ++i) { 38 | qsort(sorted[i], count_of(sorted[i]), sizeof(*sorted[i]), compare_ints); 39 | } 40 | 41 | return (sorted[0][(count_of(rotation_history[0])-1)/2] + sorted[0][count_of(rotation_history[0])/2] 42 | + sorted[1][(count_of(rotation_history[0])-1)/2] + sorted[1][count_of(rotation_history[0])/2]) / 2; 43 | } 44 | #else 45 | static uint32_t median_period() { 46 | static uint32_t sorted[count_of(rotation_history)]; 47 | memcpy(sorted, rotation_history, sizeof(sorted)); 48 | qsort(sorted, count_of(sorted), sizeof(*sorted), compare_ints); 49 | 50 | return (sorted[(count_of(rotation_history)-1)/2] + sorted[count_of(rotation_history)/2]); 51 | } 52 | #endif 53 | 54 | uint32_t rotation_current_angle(void) { 55 | uint32_t tick_curr = *timer_uS; 56 | uint32_t elapsed = tick_curr - sync_prev; 57 | 58 | static uint32_t current = 0; 59 | 60 | int sync = gpio_get_pin(SPIN_SYNC); 61 | if (sync != sync_level) { 62 | sync_level = sync; 63 | 64 | sync_prev = tick_curr; 65 | rotation_period_raw = elapsed * 2; 66 | if (elapsed > 10000 && elapsed < 10000000) { 67 | if (++current >= count_of(rotation_history)) { 68 | current = 0; 69 | } 70 | rotation_history[current] = elapsed; 71 | rotation_period = median_period(); 72 | rotation_period = max(10000, rotation_period); 73 | 74 | rotation_delta = ROTATION_FULL / rotation_period; 75 | if (rotation_lock) { 76 | int recentre = ((int32_t)((rotation_angle + (!sync * ROTATION_HALF)) & ROTATION_MASK) - ROTATION_HALF) >> 17; 77 | recentre = clamp(recentre, -rotation_delta / 16, rotation_delta / 16); 78 | rotation_delta -= recentre; 79 | } 80 | } 81 | } 82 | 83 | rotation_stopped = (elapsed > 1000000); 84 | 85 | uint32_t dtick = (tick_curr - tick_prev); 86 | tick_prev = tick_curr; 87 | 88 | uint32_t delta = dtick * rotation_delta; 89 | 90 | rotation_angle = (rotation_angle + delta) & ROTATION_MASK; 91 | 92 | rotation_zero = (rotation_zero + ROTATION_FULL + (dtick * rotation_drift)) & ROTATION_MASK; 93 | 94 | return (rotation_angle + rotation_zero) & ROTATION_MASK; 95 | } 96 | 97 | void rotation_init(void) { 98 | rotation_stopped = true; 99 | } 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/multivox/carousel.c: -------------------------------------------------------------------------------- 1 | #include "carousel.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "multivox.h" 12 | #include "input.h" 13 | #include "rammel.h" 14 | #include "timer.h" 15 | #include "cart.h" 16 | #include "array.h" 17 | 18 | 19 | static array_t carts = {sizeof(cart_t)}; 20 | 21 | 22 | static int selection_target; 23 | static float selection_current; 24 | 25 | 26 | static void load_carts(const char* directory) { 27 | DIR* dir; 28 | struct dirent* entry; 29 | 30 | if ((dir = opendir(directory)) == NULL) { 31 | perror("opendir"); 32 | return; 33 | } 34 | 35 | while ((entry = readdir(dir))) { 36 | size_t namelen = strlen(entry->d_name); 37 | if (namelen > 4 && strcmp(entry->d_name + namelen - 4, ".mct") == 0) { 38 | char filepath[PATH_MAX]; 39 | if (snprintf(filepath, sizeof(filepath), "%s/%s", directory, entry->d_name) >= 0) { 40 | if (!cart_load(array_push(&carts), filepath)) { 41 | array_pop(&carts); 42 | } 43 | } 44 | } 45 | } 46 | 47 | closedir(dir); 48 | } 49 | 50 | void carousel_init() { 51 | selection_target = 0; 52 | selection_current = 0; 53 | 54 | char directory_string[PATH_MAX]; 55 | const char* directory = getenv("MULTIVOX_CART_PATH"); 56 | if (!directory) { 57 | const char* home = getenv("HOME"); 58 | if (!home) { 59 | home = "/home/pi"; 60 | } 61 | snprintf(directory_string, sizeof(directory_string), "%s/Multivox/carts", home); 62 | directory = directory_string; 63 | } 64 | 65 | load_carts(directory); 66 | } 67 | 68 | void carousel_update(float dt) { 69 | const float speed = 3.0f; 70 | 71 | if (input_get_button(0, BUTTON_RB, BUTTON_PRESSED)) { 72 | selection_target = min(selection_target + 1, carts.count - 1); 73 | } 74 | if (input_get_button(0, BUTTON_LB, BUTTON_PRESSED)) { 75 | selection_target = max(selection_target - 1, 0); 76 | } 77 | 78 | float target = (float)selection_target; 79 | if (target > selection_current) { 80 | selection_current = min(target, selection_current + dt * speed); 81 | } else if (target < selection_current) { 82 | selection_current = max(target, selection_current - dt * speed); 83 | } 84 | 85 | if (input_get_button(0, BUTTON_VIEW, BUTTON_PRESSED)) { 86 | cart_action_t action = multivox_cart_resume(); 87 | (void)action; 88 | return; 89 | } 90 | if (input_get_button(0, BUTTON_A, BUTTON_PRESSED)) { 91 | cart_action_t action = multivox_cart_execute(array_get(&carts, selection_target)); 92 | (void)action; 93 | return; 94 | } 95 | 96 | if (input_get_button(0, BUTTON_UP, BUTTON_PRESSED)) { 97 | voxel_buffer->bits_per_channel = clamp(voxel_buffer->bits_per_channel + 1, 1, 3); 98 | } 99 | if (input_get_button(0, BUTTON_DOWN, BUTTON_PRESSED)) { 100 | voxel_buffer->bits_per_channel = clamp(voxel_buffer->bits_per_channel - 1, 1, 3); 101 | } 102 | 103 | 104 | } 105 | 106 | static float ease_turn(float a) { 107 | float a2 = a * a; 108 | return a2 / (2.0f * (a2 - a) + 1.0f); 109 | } 110 | 111 | void carousel_draw(pixel_t* volume) { 112 | float smooth = floorf(selection_current) + ease_turn(fmodf(selection_current, 1.0f)); 113 | 114 | for (int i = 0; i < carts.count; ++i) { 115 | float angle = atan((i - smooth) * 0.5f) * 1.2f; 116 | cart_draw(array_get(&carts, i), volume, angle); 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5.0) 2 | project(multivox VERSION 0.1.0 LANGUAGES C) 3 | 4 | set(CMAKE_C_STANDARD 11) 5 | 6 | SET(MULTIVOX_INSTALL_DIR $ENV{HOME}/Multivox) 7 | SET(CMAKE_INSTALL_PREFIX ${MULTIVOX_INSTALL_DIR}) 8 | 9 | set(SRC_DIR ${CMAKE_SOURCE_DIR}/src) 10 | set(DRIVER_SRC_DIR ${SRC_DIR}/driver) 11 | set(PLATFORM_SRC_DIR ${SRC_DIR}/platform) 12 | set(MULTIVOX_SRC_DIR ${SRC_DIR}/multivox) 13 | 14 | set(BUILD_DIR ${CMAKE_BINARY_DIR}) 15 | 16 | 17 | function(set_gadget GADGET_NAME) 18 | set(HEADER_PATH "${DRIVER_SRC_DIR}/gadgets/gadget_${GADGET_NAME}.h") 19 | set(OUTPUT_HEADER "${BUILD_DIR}/generated/gadget.h") 20 | if(EXISTS ${HEADER_PATH}) 21 | message(STATUS "Using gadget header: ${HEADER_PATH}") 22 | configure_file(${HEADER_PATH} ${OUTPUT_HEADER} COPYONLY) 23 | else() 24 | message(FATAL_ERROR "Gadget header ${HEADER_PATH} does not exist.") 25 | endif() 26 | endfunction() 27 | 28 | set(MULTIVOX_GADGET "vortex" CACHE STRING "Gadget-specific header file") 29 | set_gadget(${MULTIVOX_GADGET}) 30 | 31 | 32 | include_directories( 33 | ${DRIVER_SRC_DIR} 34 | ${PLATFORM_SRC_DIR} 35 | ${BUILD_DIR}/generated 36 | ) 37 | 38 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DMATHC_USE_UNIONS -O3 -Wall") 39 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DMATHC_USE_UNIONS -Og -Wall") 40 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DMATHC_USE_UNIONS -O3 -Wall") 41 | 42 | function(add_cart cart_name cart_args) 43 | install(CODE " 44 | if(NOT EXISTS \"${MULTIVOX_INSTALL_DIR}/carts/${cart_name}.mct\") 45 | file(WRITE \"${MULTIVOX_INSTALL_DIR}/carts/${cart_name}.mct\" \"colour=#00EFFF\n\") 46 | file(APPEND \"${MULTIVOX_INSTALL_DIR}/carts/${cart_name}.mct\" \"command=${CMAKE_INSTALL_PREFIX}/bin/${cart_name}\n\") 47 | file(APPEND \"${MULTIVOX_INSTALL_DIR}/carts/${cart_name}.mct\" \"arguments=${cart_args}\n\") 48 | endif() 49 | ") 50 | endfunction() 51 | 52 | file(GLOB DRIVER_SRC ${DRIVER_SRC_DIR}/*.c) 53 | add_executable(vortex 54 | ${DRIVER_SRC} 55 | ${PLATFORM_SRC_DIR}/mathc.c 56 | ${PLATFORM_SRC_DIR}/input.c 57 | ) 58 | target_link_libraries(vortex PRIVATE m rt pthread) 59 | install(TARGETS vortex) 60 | 61 | file(GLOB PLATFORM_SRC ${PLATFORM_SRC_DIR}/*.c) 62 | add_library(platform STATIC ${PLATFORM_SRC}) 63 | 64 | file(GLOB MULTIVOX_SRC ${MULTIVOX_SRC_DIR}/*.c) 65 | add_executable(multivox 66 | ${MULTIVOX_SRC} 67 | ) 68 | target_link_libraries(multivox PRIVATE platform m) 69 | install(TARGETS multivox) 70 | 71 | function(add_toy toy) 72 | if(EXISTS ${SRC_DIR}/toys/${toy}.c) 73 | add_executable(${toy} ${SRC_DIR}/toys/${toy}.c) 74 | else() 75 | file(GLOB TOY_SRC "${SRC_DIR}/toys/${toy}/*.c") 76 | add_executable(${toy} ${TOY_SRC}) 77 | endif() 78 | target_link_libraries(${toy} PRIVATE platform m) 79 | install(TARGETS ${toy}) 80 | add_cart(${toy} "") 81 | endfunction() 82 | 83 | add_cart(viewer "${MULTIVOX_INSTALL_DIR}/models/*.obj ${MULTIVOX_INSTALL_DIR}/images/*.png") 84 | 85 | add_toy(viewer) 86 | add_toy(zander) 87 | add_toy(eighty) 88 | add_toy(tesseract) 89 | add_toy(flight) 90 | add_toy(fireworks) 91 | 92 | 93 | install(DIRECTORY ${CMAKE_SOURCE_DIR}/models/ DESTINATION ${MULTIVOX_INSTALL_DIR}/models) 94 | install(DIRECTORY ${CMAKE_SOURCE_DIR}/images/ DESTINATION ${MULTIVOX_INSTALL_DIR}/images FILES_MATCHING PATTERN "*.png") 95 | 96 | 97 | set(SIMULATOR_SRC_DIR ${SRC_DIR}/simulator) 98 | file(GLOB VIRTEX_SRC ${SIMULATOR_SRC_DIR}/*.c) 99 | 100 | set(GLSL_DIRECTORY "${SIMULATOR_SRC_DIR}") 101 | file(GLOB GLSL_FILES "${GLSL_DIRECTORY}/*.glsl") 102 | 103 | set(GLSL_HEADERS) 104 | foreach(GLSL_FILE ${GLSL_FILES}) 105 | get_filename_component(BASENAME "${GLSL_FILE}" NAME_WE) 106 | set(GLSL_HEADER "${BUILD_DIR}/generated/${BASENAME}_glsl.h") 107 | 108 | add_custom_command( 109 | OUTPUT "${GLSL_HEADER}" 110 | COMMAND ${CMAKE_COMMAND} -DINPUT_FILE="${GLSL_FILE}" -DOUTPUT_FILE="${GLSL_HEADER}" -P ${SIMULATOR_SRC_DIR}/glsl.cmake 111 | DEPENDS "${GLSL_FILE}" 112 | COMMENT "Wrapping ${GLSL_FILE} to ${GLSL_HEADER}" 113 | ) 114 | 115 | list(APPEND GLSL_HEADERS ${GLSL_HEADER}) 116 | endforeach() 117 | 118 | add_library(glsl_headers INTERFACE) 119 | target_sources(glsl_headers INTERFACE ${GLSL_HEADERS}) 120 | 121 | add_executable(virtex 122 | ${VIRTEX_SRC} 123 | ${DRIVER_SRC_DIR}/slicemap.c 124 | ) 125 | target_link_libraries(virtex PRIVATE platform glsl_headers X11 EGL GLESv2 m) 126 | -------------------------------------------------------------------------------- /src/platform/voxel.h: -------------------------------------------------------------------------------- 1 | #ifndef _VOXEL_H_ 2 | #define _VOXEL_H_ 3 | 4 | // all the gadget-specific stuff goes in gadget_gadgetname.h, and is selected via `cmake -DMULTIVOX_GADGET=gadgetname ..` 5 | 6 | #include 7 | #include 8 | #include "gadget.h" 9 | 10 | #define R565(p) (((p)>>8) & 0xf8) 11 | #define G565(p) (((p)>>3) & 0xfc) 12 | #define B565(p) (((p)<<3) & 0xf8) 13 | #define RGB565(r,g,b) ((((r)<<8)&0xf800) | (((g)<<3)&0x07e0) | (((b)>>3)&0x001f)) 14 | 15 | #define R332(p) ((((p)>>5)& 7)*36) 16 | #define G332(p) ((((p)>>2)& 7)*36) 17 | #define B332(p) (((p) & 3)*85) 18 | #define RGB332(r,g,b) (((r)&0xe0) | (((g)>>3)&0x1c) | (((b)>>6)&0x03)) 19 | 20 | #ifdef HIGH_COLOUR 21 | 22 | typedef uint16_t pixel_t; 23 | 24 | #define R_PIX(a) R565(a) 25 | #define G_PIX(a) G565(a) 26 | #define B_PIX(a) B565(a) 27 | 28 | #define RGBPIX(r,g,b) RGB565(r,g,b) 29 | #define HEXPIX(hex) RGB565(((int)(0x##hex & 0xFF0000)>>16), ((int)(0x##hex & 0xFF00)>>8), (int)(0x##hex & 0xFF)) 30 | 31 | #define R_MTH_BIT(p, b) (((p)>>(15-b))&1) 32 | #define G_MTH_BIT(p, b) (((p)>>(10-b))&1) 33 | #define B_MTH_BIT(p, b) (((p)>>( 4-b))&1) 34 | 35 | #define R_THRESHOLD(p, t) (( (p) & 0b1111100000000000) >= (t)) 36 | #define G_THRESHOLD(p, t) ((((p << 5)) & 0b1111110000000000) >= (t)) 37 | #define B_THRESHOLD(p, t) ((((p << 11)) & 0b1111100000000000) >= (t)) 38 | 39 | #else 40 | 41 | typedef uint8_t pixel_t; 42 | 43 | #define R_PIX(a) R332(a) 44 | #define G_PIX(a) G332(a) 45 | #define B_PIX(a) B332(a) 46 | 47 | #define RGBPIX(r,g,b) RGB332(r,g,b) 48 | #define HEXPIX(hex) RGB332(((int)(0x##hex & 0xFF0000)>>16), ((int)(0x##hex & 0xFF00)>>8), (int)(0x##hex & 0xFF)) 49 | 50 | #define R_MTH_BIT(p, b) (((p)>>(7-b))&1) 51 | #define G_MTH_BIT(p, b) (((p)>>(4-b))&1) 52 | #define B_MTH_BIT(p, b) (((p)>>(~b&1))&1) 53 | 54 | #define R_THRESHOLD(p, t) (( (p) & 0b11100000) >= (t)) 55 | #define G_THRESHOLD(p, t) ((((p << 3)) & 0b11100000) >= (t)) 56 | #define B_THRESHOLD(p, t) ((((p << 6)) & 0b11000000) >= (t)) 57 | 58 | #endif 59 | 60 | #if (VOXELS_X <= 256) && (VOXELS_Y <= 256) && (VOXELS_Z <= 256) 61 | typedef uint8_t voxel_index_t; 62 | #else 63 | typedef uint16_t voxel_index_t; 64 | #endif 65 | 66 | #if defined (VOXEL_INDEX_SPLIT) 67 | static inline int VOXEL_INDEX(int x, int y, int z) { 68 | return (x*VOXEL_X_STRIDE + y*VOXEL_Y_STRIDE + (z&((VOXELS_Z/2)-1))*VOXEL_Z_STRIDE) * 2 + ((z/(VOXELS_Z/2))&1); 69 | } 70 | #define VOXEL_FIELD_STRIDE 1 71 | 72 | #elif defined (VOXEL_INDEX_MORTON) 73 | static inline int VOXEL_INDEX(int x, int y, int z) { 74 | x = (x | (x << 4)) & 0x0F0F0F0F0F0F0F0F; 75 | x = (x | (x << 2)) & 0x3333333333333333; 76 | x = (x | (x << 1)) & 0x5555555555555555; 77 | 78 | y = (y | (y << 4)) & 0x0F0F0F0F0F0F0F0F; 79 | y = (y | (y << 2)) & 0x3333333333333333; 80 | y = (y | (y << 1)) & 0x5555555555555555; 81 | 82 | int morton = x | (y << 1); 83 | return (morton + (z&(VOXELS_Z/2-1))*VOXEL_Z_STRIDE) * 2 + ((z / (VOXELS_Z/2)) & 1); 84 | } 85 | #else 86 | 87 | #define VOXEL_INDEX(x,y,z) ((x)*VOXEL_X_STRIDE + (y)*VOXEL_Y_STRIDE + (z)*VOXEL_Z_STRIDE) 88 | #define VOXEL_FIELD_STRIDE (PANEL_FIELD_HEIGHT * VOXEL_Z_STRIDE) 89 | #endif 90 | 91 | enum { 92 | VORTEX_BRIGHTNESS_UNIFORM = 0x0000, 93 | VORTEX_BRIGHTNESS_OVERDRIVE = 0x0001, 94 | VORTEX_BRIGHTNESS_SATURATE = 0x0002, 95 | VORTEX_BRIGHTNESS_MASK = 0x0003, 96 | VORTEX_DISABLE_PANEL_0 = 0x0004, 97 | VORTEX_DISABLE_PANEL_1 = 0x0008, 98 | VORTEX_DISABLE_TRAILS = 0x0010, 99 | VORTEX_STOP_AXIS_VERTICAL = 0x0020, 100 | VORTEX_ROTISSERIE = 0x0040 101 | }; 102 | 103 | typedef struct { 104 | pixel_t volume[2][VOXELS_COUNT]; 105 | uint8_t page; 106 | uint8_t bits_per_channel; 107 | uint16_t debug_flags; 108 | uint16_t revolutions_per_minute; 109 | uint16_t microseconds_per_frame; 110 | } voxel_double_buffer_t; 111 | 112 | typedef enum { 113 | VOXEL_BUFFER_FRONT, 114 | VOXEL_BUFFER_BACK 115 | } VOXEL_BUFFER_T; 116 | 117 | extern voxel_double_buffer_t* voxel_buffer; 118 | 119 | static inline bool voxel_in_cylinder(int x, int y) { 120 | x = (x * 2) - (VOXELS_X - 1); 121 | y = (y * 2) - (VOXELS_Y - 1); 122 | return (x * x + y * y) <= (((VOXELS_X + VOXELS_Y) / 2) * ((VOXELS_X + VOXELS_Y) / 2)); 123 | } 124 | 125 | bool voxel_buffer_map(void); 126 | void voxel_buffer_unmap(void); 127 | 128 | pixel_t* voxel_buffer_get(VOXEL_BUFFER_T buffer); 129 | void voxel_buffer_clear(pixel_t* volume); 130 | void voxel_buffer_swap(void); 131 | 132 | #endif 133 | -------------------------------------------------------------------------------- /src/toys/tesseract.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "array.h" 14 | #include "mathc.h" 15 | #include "rammel.h" 16 | #include "input.h" 17 | #include "graphics.h" 18 | #include "model.h" 19 | #include "voxel.h" 20 | 21 | static float tess_vertices[16][VEC4_SIZE] = { 22 | { 1, 1, 1, 1}, { 1, 1, 1,-1}, { 1, 1,-1, 1}, { 1, 1,-1,-1}, 23 | { 1,-1, 1, 1}, { 1,-1, 1,-1}, { 1,-1,-1, 1}, { 1,-1,-1,-1}, 24 | {-1, 1, 1, 1}, {-1, 1, 1,-1}, {-1, 1,-1, 1}, {-1, 1,-1,-1}, 25 | {-1,-1, 1, 1}, {-1,-1, 1,-1}, {-1,-1,-1, 1}, {-1,-1,-1,-1}, 26 | }; 27 | 28 | [[maybe_unused]] static int tess_faces[24][4] = { 29 | { 0, 1, 5, 4}, { 0, 2, 6, 4}, { 0, 8, 12, 4}, { 0, 2, 3, 1}, { 0, 1, 9, 8}, { 0, 2, 10, 8}, 30 | { 1, 3, 7, 5}, { 1, 9, 13, 5}, { 1, 9, 11, 3}, { 2, 3, 7, 6}, {11, 10, 2, 3}, { 2, 10, 14, 6}, 31 | { 3, 11, 15, 7}, { 4, 12, 13, 5}, { 4, 6, 14, 12}, { 4, 6, 7, 5}, { 5, 7, 15, 13}, { 7, 6, 14, 15}, 32 | { 8, 10, 14, 12}, { 8, 9, 13, 12}, { 9, 8, 10, 11}, { 9, 11, 15, 13}, {10, 11, 15, 14}, {12, 14, 15, 13}, 33 | }; 34 | 35 | static int tess_edges[32][2] = { 36 | {0, 1}, {0, 2}, {0, 4}, {0, 8}, 37 | {1, 3}, {1, 5}, {1, 9}, 38 | {2, 3}, {2, 6}, {2, 10}, 39 | {3, 7}, {3, 11}, 40 | {4, 5}, {4, 6}, {4, 12}, 41 | {5, 7}, {5, 13}, 42 | {6, 7}, {6, 14}, 43 | {7, 15}, 44 | {8, 9}, {8, 10}, {8, 12}, 45 | {9, 11}, {9, 13}, 46 | {10, 11}, {10, 14}, 47 | {11, 15}, 48 | {12, 13}, {12, 14}, 49 | {13, 15}, 50 | {14, 15} 51 | }; 52 | 53 | static const pixel_t colours[] = { 54 | HEXPIX(FF0000), 55 | HEXPIX(FFFF00), 56 | HEXPIX(00FF00), 57 | HEXPIX(00FFFF), 58 | HEXPIX(0000FF), 59 | }; 60 | 61 | 62 | static float *vec4_transform(float *result, float *v0, float *m0) { 63 | float x = v0[0]; 64 | float y = v0[1]; 65 | float z = v0[2]; 66 | float w = v0[3]; 67 | 68 | result[0] = m0[0] * x + m0[4] * y + m0[ 8] * z + m0[12] * w; 69 | result[1] = m0[1] * x + m0[5] * y + m0[ 9] * z + m0[13] * w; 70 | result[2] = m0[2] * x + m0[6] * y + m0[10] * z + m0[14] * w; 71 | result[3] = m0[3] * x + m0[7] * y + m0[11] * z + m0[15] * w; 72 | 73 | return result; 74 | } 75 | 76 | int main(int argc, char** argv) { 77 | 78 | if (!voxel_buffer_map()) { 79 | exit(1); 80 | } 81 | 82 | float volume_centre[VEC3_SIZE] = {VOXELS_X/2, VOXELS_Y/2, VOXELS_Z/2}; 83 | float model_rotation[VEC3_SIZE] = {0, 0, 0}; 84 | 85 | vec3_t transformed[count_of(tess_vertices)]; 86 | mfloat_t matrix[MAT4_SIZE]; 87 | 88 | float dist = 4; 89 | float fovt = dist*16; 90 | 91 | input_set_nonblocking(); 92 | 93 | bool show_faces = false; 94 | 95 | for (int ch = 0; ch != 27; ch = getchar()) { 96 | pixel_t* volume = voxel_buffer_get(VOXEL_BUFFER_BACK); 97 | voxel_buffer_clear(volume); 98 | 99 | if (ch == 'f') { 100 | show_faces = !show_faces; 101 | } 102 | 103 | model_rotation[0] = fmodf(model_rotation[0] + 0.013f, 2 * M_PI); 104 | model_rotation[2] = fmodf(model_rotation[2] + 0.017f, 2 * M_PI); 105 | 106 | mat4_identity(matrix); 107 | mat4_apply_rotation(matrix, model_rotation); 108 | 109 | for (uint i = 0; i < count_of(tess_vertices); ++i) { 110 | float vp[VEC4_SIZE]; 111 | vec4_transform(vp, tess_vertices[i], matrix); 112 | 113 | float s = fovt / (vp[2] + dist); 114 | transformed[i].x = vp[3] * s + volume_centre[0]; 115 | transformed[i].y = -vp[1] * s + volume_centre[1]; 116 | transformed[i].z = -vp[0] * s + volume_centre[2]; 117 | } 118 | 119 | if (show_faces) { 120 | for (uint i = 0; i < count_of(tess_faces); ++i) { 121 | pixel_t colour = colours[i % count_of(colours)] & 0b01101101; 122 | graphics_triangle_colour(colour); 123 | graphics_draw_triangle(volume, transformed[tess_faces[i][0]].v, transformed[tess_faces[i][1]].v, transformed[tess_faces[i][2]].v); 124 | graphics_draw_triangle(volume, transformed[tess_faces[i][0]].v, transformed[tess_faces[i][2]].v, transformed[tess_faces[i][3]].v); 125 | } 126 | } 127 | 128 | for (uint i = 0; i < count_of(tess_edges); ++i) { 129 | pixel_t colour = colours[i % count_of(colours)]; 130 | graphics_draw_line(volume, transformed[tess_edges[i][0]].v, transformed[tess_edges[i][1]].v, colour); 131 | } 132 | 133 | voxel_buffer_swap(); 134 | usleep(50000); 135 | } 136 | 137 | voxel_buffer_unmap(); 138 | 139 | return 0; 140 | } 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /python/vortexstream.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from record3d import Record3DStream 3 | import cv2 4 | from threading import Event 5 | import asyncio 6 | import gzip 7 | 8 | # this code runs on a PC. It obtains point cloud data from an iPhone running Record3D, connected via a USB cable, 9 | # and streams it to a vortex device running pointvision.py 10 | 11 | class VortexStream: 12 | def __init__(self): 13 | self.event = Event() 14 | self.session = None 15 | self.DEVICE_TYPE__TRUEDEPTH = 0 16 | self.DEVICE_TYPE__LIDAR = 1 17 | self.running = False 18 | self.socket = None 19 | 20 | def on_new_frame(self): 21 | self.event.set() # Notify the main thread to stop waiting and process new frame. 22 | 23 | def on_stream_stopped(self): 24 | self.running = False 25 | print('Stream stopped') 26 | 27 | def connect_to_device(self, dev_idx): 28 | print('Searching for devices') 29 | devs = Record3DStream.get_connected_devices() 30 | print('{} device(s) found'.format(len(devs))) 31 | for dev in devs: 32 | print('\tID: {}\n\tUDID: {}\n'.format(dev.product_id, dev.udid)) 33 | 34 | if len(devs) <= dev_idx: 35 | raise RuntimeError('Cannot connect to device #{}, try different index.' 36 | .format(dev_idx)) 37 | 38 | dev = devs[dev_idx] 39 | self.session = Record3DStream() 40 | self.session.on_new_frame = self.on_new_frame 41 | self.session.on_stream_stopped = self.on_stream_stopped 42 | self.session.connect(dev) # Initiate connection and start capturing 43 | 44 | def make_intrinsic_mat(self, coeffs): 45 | return np.array([[coeffs.fx, 0, coeffs.tx], 46 | [ 0, coeffs.fy, coeffs.ty], 47 | [ 0, 0, 1]]) 48 | 49 | def make_inv_intrinsic_mat(self, coeffs): 50 | return np.array([[1/coeffs.fx, 0, -coeffs.tx/coeffs.fx], 51 | [ 0, 1/coeffs.fy, -coeffs.ty/coeffs.fy], 52 | [ 0, 0, 1]]) 53 | 54 | async def start(self, host, port): 55 | reader, writer = await asyncio.open_connection(host, 0x5658) 56 | 57 | ''' 58 | cv2.namedWindow('VoxelsX', cv2.WINDOW_NORMAL) 59 | cv2.namedWindow('VoxelsY', cv2.WINDOW_NORMAL) 60 | cv2.namedWindow('VoxelsZ', cv2.WINDOW_NORMAL) 61 | cv2.resizeWindow('VoxelsX', 512, 256) 62 | cv2.resizeWindow('VoxelsY', 512, 256) 63 | cv2.resizeWindow('VoxelsZ', 512, 512) 64 | ''' 65 | 66 | def adjust_gamma(image, gamma): 67 | invGamma = 1.0 / gamma 68 | table = np.array([((i / 255.0) ** invGamma) * 255 for i in range(256)]).astype("uint8") 69 | return cv2.LUT(image, table) 70 | 71 | 72 | self.running = True 73 | try: 74 | while self.running: 75 | self.event.wait() 76 | self.event.clear() 77 | depth = self.session.get_depth_frame() 78 | color = self.session.get_rgb_frame() 79 | 80 | downscale = 2 81 | depth = cv2.resize(depth, (depth.shape[1]//downscale, depth.shape[0]//downscale), interpolation=cv2.INTER_NEAREST) 82 | color = cv2.resize(color, (depth.shape[1], depth.shape[0]), interpolation=cv2.INTER_NEAREST) 83 | 84 | r = (color[:, :, 0] >> 5) & 0x07 85 | g = (color[:, :, 1] >> 5) & 0x07 86 | b = (color[:, :, 2] >> 6) & 0x03 87 | pixel_t = (r << 5) | (g << 2) | b 88 | 89 | fx = self.session.get_intrinsic_mat().fx 90 | fy = self.session.get_intrinsic_mat().fy 91 | tx = self.session.get_intrinsic_mat().tx 92 | ty = self.session.get_intrinsic_mat().ty 93 | scale = 256 94 | 95 | rows, cols = depth.shape 96 | row_indices = np.arange(rows).reshape(-1, 1) 97 | col_indices = np.arange(cols) 98 | x_transformed = 64 + ((col_indices * downscale - tx) / fx) * depth * scale 99 | y_transformed = 32 - ((row_indices * downscale - ty) / fy) * depth * scale 100 | 101 | points = np.stack([x_transformed, (depth - 0.25) * scale, y_transformed, pixel_t], axis=-1).reshape(-1, 4) 102 | points = points[~np.isnan(points).any(axis=1)] 103 | points[:, :3] = np.round(points[:, :3]) 104 | points = points[(points[:, 0] >= 0) & (points[:, 0] < 128) & (points[:, 1] >= 0) & (points[:, 1] < 128) & (points[:, 2] >= 0) & (points[:, 2] < 64)] 105 | points = np.array(points, np.uint8) 106 | points = np.unique(points[:, :4], axis=0) 107 | 108 | 109 | cv2.imshow('Color', cv2.cvtColor(color, cv2.COLOR_RGB2BGR)) 110 | cv2.imshow('Depth', depth) 111 | 112 | data = gzip.compress(points.tobytes()) 113 | await writer.drain() 114 | writer.write(b'\xff\xff\xff\xff' + (len(data).to_bytes(4, 'big'))) 115 | writer.write(data) 116 | 117 | 118 | pressed = cv2.waitKey(1) 119 | if pressed == 27 or pressed == ord('q'): 120 | break 121 | 122 | finally: 123 | 124 | writer.close() 125 | await writer.wait_closed() 126 | 127 | 128 | 129 | if __name__ == '__main__': 130 | app = VortexStream() 131 | app.connect_to_device(dev_idx=0) 132 | asyncio.run(app.start('vortex.local', 0x5658)) 133 | -------------------------------------------------------------------------------- /src/toys/eighty/scooter.c: -------------------------------------------------------------------------------- 1 | #include "scooter.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "rammel.h" 8 | #include "input.h" 9 | #include "grid.h" 10 | #include "mathc.h" 11 | 12 | typedef struct { 13 | int controller; 14 | int isplaying; 15 | bool isalive; 16 | int position[2]; 17 | int direction; 18 | int steer; 19 | float trail; 20 | float speed; 21 | } scooter_t; 22 | 23 | static const int direction[4][2] = {{1,0}, {0,1}, {-1,0}, {0,-1}}; 24 | #define SCOOTER_AI -1 25 | 26 | static scooter_t scooters[2] = {[0 ... (count_of(scooters)-1)] = {.controller=SCOOTER_AI, .speed=4, .isalive=true}}; 27 | 28 | void scooter_init(void) { 29 | grid_init(); 30 | 31 | for (int i = 0; i < count_of(scooters); ++i) { 32 | grid_start_pose(i, scooters[i].position, &scooters[i].direction); 33 | scooters[i].steer = 0; 34 | scooters[i].trail = 0; 35 | scooters[i].isalive = true; 36 | 37 | if (--scooters[i].isplaying < 0) { 38 | scooters[i].controller = SCOOTER_AI; 39 | scooters[i].isplaying = 0; 40 | } 41 | } 42 | } 43 | 44 | static void scooter_think(scooter_t* scooter) { 45 | // twinkle emoji 46 | 47 | int next[2]; 48 | vec2i_add(next, scooter->position, direction[scooter->direction & 3]); 49 | 50 | int probes[4]; 51 | grid_probe(next, probes); 52 | if (probes[scooter->direction & 3] < 10) { 53 | if (probes[(scooter->direction + 1) & 3] > 4 54 | && probes[(scooter->direction - 1) & 3] > 4 55 | && (rand()&256)) { 56 | scooter->steer = (rand()&2) - 1; 57 | } else { 58 | if (probes[(scooter->direction + 1) & 3] > probes[(scooter->direction - 1) & 3]) { 59 | if (probes[(scooter->direction + 1) & 3] >= probes[scooter->direction & 3]) { 60 | scooter->steer = 1; 61 | } 62 | } else { 63 | if (probes[(scooter->direction - 1) & 3] >= probes[scooter->direction & 3]) { 64 | scooter->steer = -1; 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | void scooter_update(float dt) { 72 | static int restart_timeout = 128; 73 | 74 | for (int i = 0; i < count_of(scooters); ++i) { 75 | if (input_get_button(i, BUTTON_MENU, BUTTON_PRESSED)) { 76 | restart_timeout = 0; 77 | scooters[i].controller = i; 78 | scooters[i].isplaying = 4; 79 | } 80 | } 81 | if (--restart_timeout <= 0) { 82 | restart_timeout = 128; 83 | scooter_init(); 84 | } 85 | 86 | for (int i = 0; i < count_of(scooters); ++i) { 87 | scooter_t* scooter = &scooters[i]; 88 | 89 | if (scooters[i].isalive) { 90 | restart_timeout = 128; 91 | } 92 | 93 | if (scooter->controller != SCOOTER_AI) { 94 | if (input_get_button(scooter->controller, BUTTON_LB, BUTTON_PRESSED)) { 95 | scooter->isplaying = 2; 96 | scooter->steer = 1; 97 | } 98 | if (input_get_button(scooter->controller, BUTTON_RB, BUTTON_PRESSED)) { 99 | scooter->isplaying = 2; 100 | scooter->steer = -1; 101 | } 102 | } else { 103 | if (scooter->isalive && scooter->trail + scooter->speed * dt >= 0.5f) { 104 | scooter_think(scooter); 105 | } 106 | } 107 | } 108 | 109 | for (int i = 0; i < count_of(scooters); ++i) { 110 | scooter_t* scooter = &scooters[i]; 111 | 112 | if (scooter->isalive) { 113 | float was = scooter->trail; 114 | scooter->trail += scooter->speed * dt; 115 | bool crossed = (was <= 0 && scooter->trail > 0); 116 | 117 | while (scooter->trail >= 0.5f) { 118 | grid_mark(i, scooter->position, 0); 119 | 120 | vec2i_add(scooter->position, scooter->position, direction[scooter->direction & 3]); 121 | 122 | if (grid_occupied(scooter->position)) { 123 | //do_spectacular_explosion(); 124 | scooter->isalive = false; 125 | scooter->trail = 0; 126 | } else { 127 | grid_mark(i, scooter->position, 1 << ((scooter->direction + 2) & 3)); 128 | scooter->trail -= 1.0f; 129 | } 130 | } 131 | 132 | if (crossed) { 133 | scooter->direction = (scooter->direction + scooter->steer) & 3; 134 | scooter->steer = 0; 135 | grid_mark(i, scooter->position, 1 << (scooter->direction & 3)); 136 | } 137 | } 138 | } 139 | } 140 | 141 | void scooter_draw(pixel_t* volume) { 142 | 143 | for (int i = 0; i < count_of(scooters); ++i) { 144 | scooter_t* scooter = &scooters[i]; 145 | 146 | if (scooter->isalive) { 147 | int vox[3]; 148 | float pos[2] = { 149 | scooter->position[0] + direction[scooter->direction & 3][0] * scooter->trail, 150 | scooter->position[1] + direction[scooter->direction & 3][1] * scooter->trail 151 | }; 152 | grid_vox_pos(vox, pos); 153 | 154 | const int length = 2; 155 | pixel_t colour = HEXPIX(AAAAAA); 156 | 157 | int inf[2] = {max( 0, min(vox[0], vox[0] - direction[scooter->direction & 3][0] * length) - 1), 158 | max( 0, min(vox[1], vox[1] - direction[scooter->direction & 3][1] * length) - 1)}; 159 | int sup[2] = {min(VOXELS_X-1, max(vox[0], vox[0] - direction[scooter->direction & 3][0] * length) + 1), 160 | min(VOXELS_Y-1, max(vox[1], vox[1] - direction[scooter->direction & 3][1] * length) + 1)}; 161 | 162 | for (int y = inf[1]; y <= sup[1]; ++y) { 163 | for (int x = inf[0]; x <= sup[0]; ++x) { 164 | for (int z = vox[2]+1; z < vox[2]+4; ++z) { 165 | volume[VOXEL_INDEX(x, y, z)] = colour; 166 | } 167 | } 168 | } 169 | } 170 | } 171 | } 172 | 173 | -------------------------------------------------------------------------------- /src/toys/fireworks.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "rammel.h" 10 | #include "input.h" 11 | #include "voxel.h" 12 | 13 | 14 | void draw_sphere(int x, int y, int z, int radius, uint8_t colour) { 15 | pixel_t* volume = voxel_buffer_get(VOXEL_BUFFER_FRONT); 16 | 17 | int xi = (x - radius < 0) ? -x : -radius; 18 | int xs = (x + radius >= VOXELS_X) ? (VOXELS_X-1 - x) : radius; 19 | int yi = (y - radius < 0) ? -y : -radius; 20 | int ys = (y + radius >= VOXELS_Y) ? (VOXELS_Y-1 - y) : radius; 21 | int zi = (z - radius < 0) ? -z : -radius; 22 | int zs = (z + radius >= VOXELS_Z) ? (VOXELS_Z-1 - z) : radius; 23 | 24 | int rosq = radius * radius / 4; 25 | 26 | for (int xx = xi; xx <= xs; ++xx) { 27 | for (int yy = yi; yy <= ys; ++yy) { 28 | for (int zz = zi; zz <= zs; ++zz) { 29 | int lsq = xx*xx + yy*yy + zz*zz; 30 | if (lsq <= rosq) { 31 | volume[VOXEL_INDEX(x+xx, y+yy, z+zz)] = colour; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | 38 | void draw_sphereaa(int x, int y, int z, int radius, uint8_t colour) { 39 | pixel_t* volume = voxel_buffer_get(VOXEL_BUFFER_FRONT); 40 | 41 | int xi = (x - radius < 0) ? -x : -radius; 42 | int xs = (x + radius >= VOXELS_X) ? (VOXELS_X-1 - x) : radius; 43 | int yi = (y - radius < 0) ? -y : -radius; 44 | int ys = (y + radius >= VOXELS_Y) ? (VOXELS_Y-1 - y) : radius; 45 | int zi = (z - radius < 0) ? -z : -radius; 46 | int zs = (z + radius >= VOXELS_Z) ? (VOXELS_Z-1 - z) : radius; 47 | 48 | int rosq = radius * radius; 49 | int risq = (radius-1) * (radius-1); 50 | uint8_t ci = (colour&0b10010010)>>1; 51 | 52 | for (int xx = xi; xx <= xs; ++xx) { 53 | for (int yy = yi; yy <= ys; ++yy) { 54 | for (int zz = zi; zz <= zs; ++zz) { 55 | int lsq = xx*xx + yy*yy + zz*zz; 56 | if (lsq <= rosq) { 57 | uint c = lsq > risq ? ci : colour; 58 | volume[VOXEL_INDEX(x+xx, y+yy, z+zz)] = c; 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | typedef struct { 66 | int x, y, z, w; 67 | } vec4i_t; 68 | 69 | typedef struct { 70 | float x, y, z; 71 | } vec3_t; 72 | 73 | typedef struct { 74 | vec3_t position; 75 | vec3_t velocity; 76 | int radius; 77 | uint8_t colour; 78 | } ball_t; 79 | 80 | 81 | #define TRAIL_LEN 256 82 | ball_t balls[256]; 83 | vec4i_t trails[count_of(balls)][TRAIL_LEN]; 84 | int trail_index = 0; 85 | 86 | 87 | #define ROCKET_RADIUS 2 88 | #define SPARK_RADIUS 1 89 | 90 | void spawn_rocket(int i) { 91 | balls[i].position.x = (float)(rand()&31)+48; 92 | balls[i].position.y = (float)(rand()&31)+48; 93 | balls[i].position.z = 0; 94 | 95 | balls[i].velocity.x = (float)((rand()&31)-15) * 0.001f; 96 | balls[i].velocity.y = (float)((rand()&31)-15) * 0.001f; 97 | balls[i].velocity.z = (float)((rand()&3)+2) * (VOXELS_Z / 2560.0f); 98 | 99 | balls[i].radius = ROCKET_RADIUS; 100 | 101 | uint8_t c = (rand()%6)+1; 102 | balls[i].colour = ((c&4)<<5) | ((c&2)<<3) | ((c&1)<<1); 103 | } 104 | 105 | void spawn_sparks(float x, float y, float z, uint8_t colour, int count) { 106 | for (uint b = 0; b < count_of(balls) && count > 0; ++b) { 107 | if (balls[b].radius == 0) { 108 | balls[b].position.x = x; 109 | balls[b].position.y = y; 110 | balls[b].position.z = z; 111 | balls[b].radius = SPARK_RADIUS; 112 | balls[b].colour = colour; 113 | balls[b].velocity.x = (float)((rand()&31)-15) * 0.003f; 114 | balls[b].velocity.y = (float)((rand()&31)-15) * 0.003f; 115 | balls[b].velocity.z = (float)((rand()&31)-15) * 0.003f; 116 | --count; 117 | } 118 | } 119 | } 120 | 121 | int count_rockets() { 122 | int count = 0; 123 | for (uint b = 0; b < count_of(balls); ++b) { 124 | if (balls[b].radius == ROCKET_RADIUS) { 125 | ++count; 126 | } 127 | } 128 | return count; 129 | } 130 | 131 | void update_ball(int b) { 132 | ball_t* ball = &balls[b]; 133 | 134 | if (ball->radius <= 0) { 135 | return; 136 | } 137 | 138 | if (ball->radius == ROCKET_RADIUS && ball->velocity.z <= -0.001f) { 139 | ball->radius = 0; 140 | spawn_sparks(ball->position.x, ball->position.y, ball->position.z, ball->colour, 32); 141 | return; 142 | } 143 | 144 | ball->position.x += ball->velocity.x; 145 | ball->position.y += ball->velocity.y; 146 | ball->position.z += ball->velocity.z; 147 | 148 | ball->velocity.z -= 0.0002f; 149 | 150 | 151 | if ( ball->position.x < 0 152 | ||ball->position.x >= VOXELS_X 153 | ||ball->position.y < 0 154 | ||ball->position.y >= VOXELS_Y 155 | ||ball->position.z < 0 156 | ||ball->position.z >= VOXELS_Z) { 157 | ball->radius = 0; 158 | if (count_rockets() < 2) { 159 | spawn_rocket(b); 160 | } 161 | return; 162 | } 163 | 164 | int x = ball->position.x; 165 | int y = ball->position.y; 166 | int z = ball->position.z; 167 | trails[b][trail_index].x = x; 168 | trails[b][trail_index].y = y; 169 | trails[b][trail_index].z = z; 170 | trails[b][trail_index].w = ball->radius; 171 | draw_sphere(x, y, z, ball->radius, ball->colour); 172 | } 173 | 174 | 175 | 176 | int main(int argc, char** argv) { 177 | (void)argc; 178 | (void)argv; 179 | 180 | if (!voxel_buffer_map()) { 181 | exit(1); 182 | } 183 | 184 | for (int i = 0; i < 8; ++i) { 185 | balls[i].position.x = 0; 186 | balls[i].position.y = 0; 187 | balls[i].position.z = (float)(rand()&(VOXELS_Z-1)); 188 | balls[i].velocity.x = 0; 189 | balls[i].velocity.y = 0; 190 | balls[i].velocity.z = 0; 191 | balls[i].colour = 255; 192 | balls[i].radius = SPARK_RADIUS; 193 | } 194 | 195 | pixel_t* volume = voxel_buffer_get(VOXEL_BUFFER_FRONT); 196 | voxel_buffer_clear(volume); 197 | 198 | input_set_nonblocking(); 199 | 200 | for (int ch = 0; ch != 27; ch = getchar()) { 201 | trail_index = (trail_index+1) & (TRAIL_LEN-1); 202 | 203 | for (uint b = 0; b < count_of(balls); ++b) { 204 | if (trails[b][trail_index].w > 0) { 205 | draw_sphere(trails[b][trail_index].x, trails[b][trail_index].y, trails[b][trail_index].z, trails[b][trail_index].w, 0); 206 | trails[b][trail_index].w = 0; 207 | } 208 | update_ball(b); 209 | } 210 | 211 | usleep(1000); 212 | } 213 | 214 | voxel_buffer_unmap(); 215 | 216 | return 0; 217 | } 218 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /src/multivox/multivox.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "multivox.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "array.h" 20 | #include "mathc.h" 21 | #include "rammel.h" 22 | #include "input.h" 23 | #include "voxel.h" 24 | #include "graphics.h" 25 | #include "model.h" 26 | #include "timer.h" 27 | #include "carousel.h" 28 | 29 | 30 | static const uint8_t combo_shutdown[] = {BUTTON_MENU, BUTTON_MENU, BUTTON_MENU, BUTTON_MENU, BUTTON_MENU}; 31 | static const uint8_t combo_reboot[] = {BUTTON_MENU, BUTTON_MENU, BUTTON_MENU, BUTTON_MENU, BUTTON_X}; 32 | //static const uint8_t combo_konami[] = {BUTTON_UP, BUTTON_UP, BUTTON_DOWN, BUTTON_DOWN, BUTTON_LEFT, BUTTON_RIGHT, BUTTON_LEFT, BUTTON_RIGHT, BUTTON_B, BUTTON_A}; 33 | 34 | void main_init(void) { 35 | timer_init(); 36 | carousel_init(); 37 | } 38 | 39 | void main_update(float dt) { 40 | input_update(); 41 | carousel_update(dt); 42 | 43 | if (input_get_combo(0, combo_shutdown, sizeof(combo_shutdown))) { 44 | system("sudo shutdown -P now"); 45 | } 46 | if (input_get_combo(0, combo_reboot, sizeof(combo_reboot))) { 47 | system("sudo reboot"); 48 | } 49 | } 50 | 51 | void main_draw(pixel_t* volume) { 52 | carousel_draw(volume); 53 | } 54 | 55 | int main(int argc, char** argv) { 56 | 57 | if (!voxel_buffer_map()) { 58 | printf("waiting for voxel buffer\n"); 59 | do { 60 | sleep(1); 61 | } while (!voxel_buffer_map()); 62 | } 63 | 64 | input_set_nonblocking(); 65 | 66 | main_init(); 67 | 68 | for (int ch = 0; ch != 27; ch = getchar()) { 69 | pixel_t* volume = voxel_buffer_get(VOXEL_BUFFER_BACK); 70 | voxel_buffer_clear(volume); 71 | 72 | timer_tick(); 73 | 74 | main_update((float)timer_delta_time * 0.001f); 75 | main_draw(volume); 76 | 77 | voxel_buffer_swap(); 78 | 79 | timer_sleep_until(TIMER_SINCE_TICK, 30); 80 | } 81 | 82 | voxel_buffer_unmap(); 83 | 84 | return 0; 85 | } 86 | 87 | 88 | 89 | 90 | typedef struct { 91 | cart_t* cart; 92 | pid_t process_id; 93 | 94 | } cart_context_t; 95 | 96 | static cart_context_t cart_context = {.process_id = -1}; 97 | 98 | static bool background_process() { 99 | input_update(); 100 | 101 | if (input_get_button(0, BUTTON_VIEW, BUTTON_PRESSED)) { 102 | return false; 103 | } 104 | 105 | usleep(100000); 106 | 107 | return true; 108 | } 109 | 110 | static cart_action_t background_loop(pid_t pid) { 111 | 112 | while (true) { 113 | int status; 114 | pid_t result = waitpid(pid, &status, WNOHANG); 115 | 116 | if (result == 0) { 117 | if (!background_process()) { 118 | //printf("suspend\n"); 119 | if (kill(cart_context.process_id, SIGSTOP) == 0) { 120 | cart_grab_voxshot(cart_context.cart, voxel_buffer_get(VOXEL_BUFFER_FRONT)); 121 | cart_save_voxshot(cart_context.cart); 122 | return CART_ACTION_PAUSE; 123 | 124 | } else { 125 | perror("suspend"); 126 | } 127 | } 128 | 129 | } else if (result == -1) { 130 | perror("waitpid"); 131 | return CART_ACTION_FAIL; 132 | 133 | } else { 134 | if (WIFEXITED(status)) { 135 | printf("Child process exited with status %d\n", WEXITSTATUS(status)); 136 | 137 | } else if (WIFSIGNALED(status)) { 138 | printf("Child process killed by signal %d\n", WTERMSIG(status)); 139 | 140 | } 141 | 142 | cart_grab_voxshot(cart_context.cart, voxel_buffer_get(VOXEL_BUFFER_FRONT)); 143 | return CART_ACTION_EJECT; 144 | } 145 | } 146 | } 147 | 148 | cart_action_t multivox_cart_resume(void) { 149 | if (cart_context.process_id > 0 && cart_context.cart) { 150 | return multivox_cart_execute(cart_context.cart); 151 | } 152 | 153 | return CART_ACTION_NONE; 154 | } 155 | 156 | cart_action_t multivox_cart_execute(cart_t* cart) { 157 | static uint32_t nospam = 0; 158 | if ((timer_frame_count - nospam) < 10) { 159 | printf("nospam\n"); 160 | return CART_ACTION_FAIL; 161 | } 162 | nospam = timer_frame_count; 163 | 164 | if (!cart || !cart->command) { 165 | return CART_ACTION_FAIL; 166 | } 167 | 168 | #define IF_STRING(str) ((str) ? (str) : "") 169 | printf("cartecute %s %s %s\n", IF_STRING(cart->command), IF_STRING(cart->workingdir), IF_STRING(cart->environment)); 170 | 171 | if (cart_context.process_id > 0) { 172 | if (cart_context.cart == cart) { 173 | //printf("resume\n"); 174 | if (kill(cart_context.process_id, SIGCONT) == 0) { 175 | return background_loop(cart_context.process_id); 176 | } 177 | perror("process"); 178 | } 179 | 180 | //printf("kill\n"); 181 | kill(cart_context.process_id, SIGKILL); 182 | } 183 | 184 | pid_t pid = fork(); 185 | cart_context.process_id = pid; 186 | cart_context.cart = cart; 187 | 188 | printf("pid %d\n", pid); 189 | 190 | if (pid == -1) { 191 | perror("fork"); 192 | return CART_ACTION_FAIL; 193 | } 194 | 195 | if (pid == 0) { 196 | //execute the cart 197 | 198 | if (cart->workingdir) { 199 | chdir(cart->workingdir); 200 | } 201 | 202 | size_t argslen = cart->arguments ? strlen(cart->arguments) : 0; 203 | size_t envslen = cart->environment ? strlen(cart->environment) : 0; 204 | char items[max(argslen, envslen) + 1]; 205 | 206 | char* args[argslen / 2 + 2]; 207 | int a = 0; 208 | args[a++] = (char*)cart->command; 209 | if (argslen > 0) { 210 | strcpy(items, cart->arguments); 211 | char* arg = strtok(items, " "); 212 | while (arg) { 213 | args[a++] = arg; 214 | arg = strtok(NULL, " "); 215 | } 216 | } 217 | args[a] = NULL; 218 | 219 | char *envs[envslen / 2 + 1]; 220 | int e = 0; 221 | if (envslen > 0) { 222 | strcpy(items, (char*)cart->environment); 223 | char* env = strtok(items, " "); 224 | while (env) { 225 | envs[e++] = env; 226 | env = strtok(NULL, " "); 227 | } 228 | } 229 | envs[e] = NULL; 230 | 231 | execvpe(cart->command, args, envs); 232 | exit(0); 233 | } 234 | 235 | // wait until we get regain focus 236 | return background_loop(pid); 237 | } 238 | 239 | 240 | 241 | 242 | -------------------------------------------------------------------------------- /src/toys/eighty/grid.c: -------------------------------------------------------------------------------- 1 | #include "grid.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "mathc.h" 7 | #include "timer.h" 8 | #include "rammel.h" 9 | 10 | enum { 11 | OCCUPANT_EMPTY, 12 | OCCUPANT_CORE, 13 | OCCUPANT_WALL, 14 | OCCUPANT_SCOOTER_0, 15 | OCCUPANT_SCOOTER_1 16 | }; 17 | 18 | typedef struct { 19 | pixel_t colour; 20 | int height; 21 | } occupant_t; 22 | 23 | #define OCCUPANT_TYPES 8 24 | static occupant_t occupants[OCCUPANT_TYPES] = { 25 | {.colour=0, .height=0}, 26 | {.colour=HEXPIX(005555), .height=2}, 27 | {.colour=HEXPIX(FF5500), .height=2}, 28 | {.colour=HEXPIX(0000FF), .height=8}, 29 | {.colour=HEXPIX(AA5500), .height=8}, 30 | }; 31 | 32 | #define GRID_FLOOR 6 33 | #define GRID_WIDTH 32 34 | #define GRID_HEIGHT 32 35 | #define GRID_SPACING ((int)(VOXELS_X+VOXELS_Y)/(int)(GRID_WIDTH+GRID_HEIGHT)) 36 | 37 | 38 | 39 | typedef struct { 40 | uint32_t spawned; 41 | uint8_t occupant; 42 | uint8_t walls; 43 | } cell_t; 44 | 45 | static cell_t grid_map[GRID_HEIGHT][GRID_WIDTH]; 46 | 47 | static float ease_out_elastic(float v) { 48 | if (v <= 0) { 49 | return 0; 50 | } 51 | if (v >= 1) { 52 | return 1; 53 | } 54 | 55 | const float c4 = (2.0f * M_PI) / 3.0f; 56 | return powf(2.0f, -10.0f * v) * sinf((v * 10.0f - 0.75f) * c4) + 1.0f; 57 | } 58 | 59 | void grid_init(void) { 60 | const int outer = sqr((GRID_WIDTH+GRID_HEIGHT)/2 - 2); 61 | const int inner = sqr(36/GRID_SPACING); 62 | 63 | memset(grid_map, 0, sizeof(grid_map)); 64 | 65 | for (int y = 0; y < GRID_HEIGHT; ++y) { 66 | for (int x = 0; x < GRID_WIDTH; ++x) { 67 | 68 | int rsq4 = sqr((x*2)-(GRID_WIDTH-1)) + sqr((y*2)-(GRID_HEIGHT-1)); 69 | 70 | if (rsq4 <= inner) { 71 | grid_map[y][x].occupant = OCCUPANT_CORE; 72 | grid_map[y][x].walls = 0xff; 73 | } else if (rsq4 >= outer) { 74 | grid_map[y][x].occupant = OCCUPANT_WALL; 75 | grid_map[y][x].walls = 0xff; 76 | } 77 | } 78 | } 79 | } 80 | 81 | void grid_start_pose(int id, int* cell, int* direction) { 82 | int start_pose[4][3] = { 83 | {GRID_WIDTH/2 - GRID_WIDTH/4, GRID_HEIGHT/2, 1}, 84 | {GRID_WIDTH/2 + GRID_WIDTH/4, GRID_HEIGHT/2, 3}, 85 | {GRID_WIDTH/2, GRID_HEIGHT/2 - GRID_HEIGHT/4, 2}, 86 | {GRID_WIDTH/2, GRID_HEIGHT/2 + GRID_HEIGHT/4, 0}, 87 | }; 88 | 89 | vec2i_assign(cell, start_pose[id % count_of(start_pose)]); 90 | *direction = start_pose[id % count_of(start_pose)][2]; 91 | } 92 | 93 | void grid_vox_pos(int* vox, const float* pos) { 94 | vox[0] = (int)((pos[0] - (GRID_WIDTH/2) ) * GRID_SPACING) + (VOXELS_X/2) + (GRID_SPACING/2); 95 | vox[1] = (int)((pos[1] - (GRID_HEIGHT/2)) * GRID_SPACING) + (VOXELS_Y/2) + (GRID_SPACING/2); 96 | vox[2] = GRID_FLOOR; 97 | } 98 | 99 | bool grid_occupied(const int* cell) { 100 | if ((uint)cell[0] >= GRID_WIDTH || (uint)cell[1] >= GRID_HEIGHT) { 101 | return true; 102 | } 103 | return (grid_map[cell[1]][cell[0]].occupant != 0); 104 | } 105 | 106 | void grid_probe(int* cell, int* probes) { 107 | int px = cell[0]; 108 | int py = cell[1]; 109 | 110 | memset(probes, 0, 4*sizeof(int)); 111 | 112 | for (int x = px + 1; x < GRID_WIDTH && !grid_map[py][x].occupant; ++x) { 113 | ++probes[0]; 114 | } 115 | for (int x = px - 1; x >= 0 && !grid_map[py][x].occupant; --x) { 116 | ++probes[2]; 117 | } 118 | for (int y = py + 1; y < GRID_HEIGHT && !grid_map[y][px].occupant; ++y) { 119 | ++probes[1]; 120 | } 121 | for (int y = py - 1; y >= 0 && !grid_map[y][px].occupant; --y) { 122 | ++probes[3]; 123 | } 124 | } 125 | 126 | void grid_mark(int id, const int* cell, uint8_t walls) { 127 | if ((uint)cell[0] >= GRID_WIDTH || (uint)cell[1] >= GRID_HEIGHT) { 128 | return; 129 | } 130 | 131 | cell_t* grid = &grid_map[cell[1]][cell[0]]; 132 | grid->spawned = timer_frame_time; 133 | grid->occupant = id + OCCUPANT_SCOOTER_0; 134 | grid->walls |= walls; 135 | } 136 | 137 | void grid_draw(pixel_t* volume) { 138 | const int z0 = GRID_FLOOR; 139 | const pixel_t basecol = HEXPIX(55FFFF); 140 | 141 | for (int y = 0; y < GRID_WIDTH; ++y) { 142 | for (int x = 0; x < GRID_HEIGHT; ++x) { 143 | cell_t* cell = &grid_map[y][x]; 144 | 145 | if (cell->occupant) { 146 | occupant_t o = occupants[cell->occupant & (OCCUPANT_TYPES-1)]; 147 | int height = o.height; 148 | if (cell->occupant >= OCCUPANT_SCOOTER_0) { 149 | height *= ease_out_elastic((float)(timer_frame_time - cell->spawned) * 0.0001f); 150 | } 151 | height = max(1, height); 152 | 153 | int vx = ((x - (GRID_WIDTH/2)) * GRID_SPACING) + (VOXELS_X/2) + (GRID_SPACING/2); 154 | int vy = ((y - (GRID_HEIGHT/2))* GRID_SPACING) + (VOXELS_Y/2) + (GRID_SPACING/2); 155 | 156 | if (cell->walls & 0x10) { 157 | int vxi = vx - (GRID_SPACING/2 - 1); 158 | int vxs = vx + (GRID_SPACING/2 - 1); 159 | int vyi = vy - (GRID_SPACING/2 - 1); 160 | int vys = vy + (GRID_SPACING/2 - 1); 161 | 162 | for (int fvy = vyi; fvy <= vys; ++fvy) { 163 | for (int fvx = vxi; fvx <= vxs; ++fvx) { 164 | for (int z = 0; z < height; ++z) { 165 | pixel_t colour = z ? o.colour : basecol; 166 | volume[VOXEL_INDEX(fvx, fvy, z + z0)] = colour; 167 | } 168 | } 169 | } 170 | } else { 171 | if (cell->walls & 0x0f) { 172 | for (int i = 0; i < GRID_SPACING/2; ++i) { 173 | for (int z = 0; z < height; ++z) { 174 | pixel_t colour = z ? o.colour : basecol; 175 | if (cell->walls & 1) { 176 | volume[VOXEL_INDEX(vx+i, vy, z + z0)] = colour; 177 | } 178 | if (cell->walls & 2) { 179 | volume[VOXEL_INDEX(vx, vy+i, z + z0)] = colour; 180 | } 181 | if (cell->walls & 4) { 182 | volume[VOXEL_INDEX(vx-i-1, vy, z + z0)] = colour; 183 | } 184 | if (cell->walls & 8) { 185 | volume[VOXEL_INDEX(vx, vy-i-1, z + z0)] = colour; 186 | } 187 | } 188 | } 189 | } 190 | } 191 | 192 | } 193 | } 194 | } 195 | 196 | } 197 | 198 | -------------------------------------------------------------------------------- /src/toys/flight.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "array.h" 9 | #include "mathc.h" 10 | #include "rammel.h" 11 | #include "input.h" 12 | #include "graphics.h" 13 | #include "model.h" 14 | #include "voxel.h" 15 | 16 | #include "flight_tiles.h" 17 | 18 | static array_t tile_set = {sizeof(model_t*)}; 19 | 20 | typedef struct { 21 | model_t* model; 22 | float position[VEC3_SIZE]; 23 | float rotation[VEC3_SIZE]; 24 | float scale; 25 | } tiledef_t; 26 | 27 | #define TILE_ROWS 10 28 | #define TILES_WIDE 10 29 | static tiledef_t tile_defs[TILE_ROWS][TILES_WIDE]; 30 | static int first_row = 0; 31 | 32 | static void load_tiles(void) { 33 | array_reserve(&tile_set, 256); 34 | array_clear(&tile_set); 35 | 36 | // if there's a directory called tiles with a bunch of .objs in it, load them all up 37 | char tiles_dir[1024] = "tiles/"; 38 | char* filename = &tiles_dir[strlen(tiles_dir)]; 39 | DIR* dir = opendir(tiles_dir); 40 | if (dir) { 41 | struct dirent* entry; 42 | while ((entry = readdir(dir)) != NULL) { 43 | const char* ext = strchr(entry->d_name, '.'); 44 | if (ext && strcmp(ext, ".obj") == 0) { 45 | strncpy(filename, entry->d_name, tiles_dir + sizeof(tiles_dir) - 2 - filename); 46 | 47 | model_t* model = model_load(tiles_dir, STYLE_WIREFRAME_ALWAYS); 48 | model_set_colour(model, HEXPIX(55FFFF)); 49 | *(model_t**)array_push(&tile_set) = model; 50 | } 51 | } 52 | } 53 | 54 | #ifdef TILES_COMPILED 55 | // add any compiled-in tiles 56 | size_t tile0 = tile_set.count; 57 | array_resize(&tile_set, tile_set.count + count_of(tile_models)); 58 | for (int i = 0; i < count_of(tile_models); ++i) { 59 | *(model_t**)array_get(&tile_set, tile0 + i) = (model_t*)&tile_models[i]; 60 | } 61 | #endif 62 | } 63 | 64 | static float wall_rotations[4][VEC3_SIZE] = { 65 | { M_PI_2, M_PI_2, 0}, 66 | { 0, M_PI_2, 0}, 67 | {-M_PI_2, M_PI_2, 0}, 68 | { M_PI, M_PI_2, 0} 69 | }; 70 | static float floor_rotations[4][VEC3_SIZE] = { 71 | {0, 0, 0}, 72 | {0, 0, M_PI_2}, 73 | {0, 0, M_PI}, 74 | {0, 0,-M_PI_2} 75 | }; 76 | 77 | static void step_tiles(void) { 78 | const float tile_size = 10.0f; 79 | const float tile_scale = 0.95f / tile_size; 80 | const float far_row = TILE_ROWS * 0.5f; 81 | 82 | const float wall_x = 1.2f; 83 | const float wall_z = 0.1f; 84 | const float floor_z = -1.0f; 85 | const float surface_x = 1.7f; 86 | 87 | int back_row = first_row; 88 | int double_back = (back_row + TILE_ROWS - 1) % TILE_ROWS; 89 | first_row = (first_row + 1) % TILE_ROWS; 90 | 91 | int tidx = 0; 92 | 93 | tiledef_t* tile; 94 | 95 | // walls 96 | for (int i = 0; i < 2; ++i) { 97 | float side = (i*2 - 1); 98 | if (tile_defs[double_back][tidx].model && !tile_defs[double_back][tidx+1].model) { 99 | // occupied by a double 100 | tile_defs[back_row][tidx ].model = NULL; 101 | tile_defs[back_row][tidx+1].model = NULL; 102 | } else { 103 | if ((rand() & 0xc0) == 0) { 104 | // double 105 | tile = &tile_defs[back_row][tidx]; 106 | tile->model = *(model_t**)array_get(&tile_set, rand() % tile_set.count); 107 | vec3(tile->position, wall_x * side, far_row + 0.5f, wall_z); 108 | vec3_assign(tile->rotation, wall_rotations[(rand() >> 11) & 3]); 109 | tile->scale = tile_scale * 2.0f * -side; 110 | 111 | tile_defs[back_row][tidx+1].model = NULL; 112 | } else { 113 | // singles 114 | tile = &tile_defs[back_row][tidx]; 115 | tile->model = *(model_t**)array_get(&tile_set, rand() % tile_set.count); 116 | vec3(tile->position, wall_x * side, far_row, wall_z + 0.5f); 117 | vec3_assign(tile->rotation, wall_rotations[(rand() >> 11) & 3]); 118 | tile->scale = tile_scale * -side; 119 | 120 | tile = &tile_defs[back_row][tidx+1]; 121 | tile->model = *(model_t**)array_get(&tile_set, rand() % tile_set.count); 122 | vec3(tile->position, wall_x * side, far_row, wall_z - 0.5f); 123 | vec3_assign(tile->rotation, wall_rotations[(rand() >> 11) & 3]); 124 | tile->scale = tile_scale * -side; 125 | } 126 | } 127 | 128 | tidx += 2; 129 | } 130 | 131 | // floor 132 | for (int i = 0; i < 2; ++i) { 133 | float side = (i*2 - 1); 134 | 135 | tile = &tile_defs[back_row][tidx++]; 136 | tile->model = *(model_t**)array_get(&tile_set, rand() % tile_set.count); 137 | vec3(tile->position, side * 0.5f, far_row, floor_z); 138 | vec3_assign(tile->rotation, floor_rotations[(rand() >> 11) & 3]); 139 | tile->scale = tile_scale; 140 | } 141 | 142 | // surface 143 | for (int side = -1; side <= 1; side += 2) { 144 | for (int i = 0; i < 2; ++i) { 145 | tile = &tile_defs[back_row][tidx++]; 146 | tile->model = *(model_t**)array_get(&tile_set, rand() % tile_set.count); 147 | vec3(tile->position, (surface_x + i) * side, far_row, 1.0f); 148 | vec3_assign(tile->rotation, floor_rotations[(rand() >> 11) & 3]); 149 | tile->scale = tile_scale; 150 | } 151 | } 152 | 153 | 154 | 155 | 156 | for (int r = 0; r < TILE_ROWS-1; ++r) { 157 | int row = (first_row + r) % TILE_ROWS; 158 | for (int x = 0; x < TILES_WIDE; ++x) { 159 | tile = &tile_defs[row][x]; 160 | if (tile->model) { 161 | tile->position[1] -= 1; 162 | } 163 | } 164 | } 165 | } 166 | 167 | 168 | int main(int argc, char** argv) { 169 | 170 | if (!voxel_buffer_map()) { 171 | exit(1); 172 | } 173 | 174 | load_tiles(); 175 | if (tile_set.count <= 0) { 176 | printf("failed to load tiles\n"); 177 | exit(1); 178 | } 179 | 180 | srand(time(NULL)); 181 | memset(tile_defs, 0, sizeof(tile_defs)); 182 | for (int i = 0; i < TILE_ROWS; ++i) { 183 | step_tiles(); 184 | } 185 | 186 | float volume_centre[VEC3_SIZE] = {(VOXELS_X-1)*0.5f, (VOXELS_Y-1)*0.5f, (VOXELS_Z-1)*0.5f}; 187 | float world_rotation[VEC3_SIZE] = {0, 0, 0}; 188 | float world_position[VEC3_SIZE] = {0, 0, 0}; 189 | 190 | // float zoom = 32.0f; 191 | float delta = 0.05f; 192 | float wobble = 0.0f; 193 | 194 | input_set_nonblocking(); 195 | 196 | for (int ch = 0; ch != 27; ch = getchar()) { 197 | 198 | pixel_t* volume = voxel_buffer_get(VOXEL_BUFFER_BACK); 199 | voxel_buffer_clear(volume); 200 | 201 | float zoom = 24.0f; 202 | 203 | world_position[1] -= delta; 204 | if (world_position[1] < 0) { 205 | world_position[1] += 1; 206 | step_tiles(); 207 | } 208 | 209 | wobble = fmodf(wobble + 0.03f, M_PI*2); 210 | world_rotation[1] = sinf(wobble) * 0.06f; 211 | 212 | float world[MAT4_SIZE]; 213 | mat4_identity(world); 214 | mat4_apply_translation(world, volume_centre); 215 | mat4_apply_scale_f(world, zoom); 216 | mat4_apply_translation(world, world_position); 217 | mat4_apply_rotation(world, world_rotation); 218 | 219 | float matrix[MAT4_SIZE]; 220 | for (int y = 0; y < TILE_ROWS; ++y) { 221 | for (int x = 0; x < TILES_WIDE; ++x) { 222 | tiledef_t* tile = &tile_defs[y][x]; 223 | if (tile->model) { 224 | memcpy(matrix, world, sizeof(matrix)); 225 | mat4_apply_translation(matrix, tile->position); 226 | mat4_apply_rotation(matrix, tile->rotation); 227 | mat4_apply_scale_f(matrix, tile->scale); 228 | model_draw(volume, tile->model, matrix); 229 | } 230 | } 231 | } 232 | 233 | voxel_buffer_swap(); 234 | usleep(50000); 235 | } 236 | 237 | voxel_buffer_unmap(); 238 | 239 | return 0; 240 | } 241 | 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /src/simulator/virtex.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "sim.h" 13 | #include "rammel.h" 14 | #include "timer.h" 15 | 16 | static const char* window_title = "virtex"; 17 | static long event_mask = KeyPressMask|ButtonPressMask|ButtonReleaseMask|PointerMotionMask|ExposureMask|StructureNotifyMask; 18 | 19 | typedef struct { 20 | EGLDisplay display; 21 | EGLSurface surface; 22 | EGLConfig config; 23 | EGLContext context; 24 | } egl_state_t; 25 | 26 | typedef struct { 27 | Display* display; 28 | Window window; 29 | XWindowAttributes attributes; 30 | Atom wm_delete_msg; 31 | } x11_state_t; 32 | 33 | 34 | 35 | static bool init_x11(x11_state_t* x11) { 36 | x11->display = XOpenDisplay(NULL); 37 | if (x11->display == NULL) { 38 | printf("XOpenDisplay failed.\n"); 39 | return false; 40 | } 41 | 42 | x11->window = XCreateWindow( 43 | x11->display, 44 | DefaultRootWindow(x11->display), 45 | 0, 0, 800, 600, 0, 46 | CopyFromParent, 47 | InputOutput, 48 | CopyFromParent, 49 | CWEventMask, 50 | &(XSetWindowAttributes){.event_mask = event_mask}); 51 | 52 | XStoreName(x11->display, x11->window, window_title); 53 | XSetWMHints(x11->display, x11->window, &(XWMHints){.input=True, .flags=InputHint}); 54 | 55 | XMapWindow(x11->display, x11->window); 56 | 57 | x11->wm_delete_msg = XInternAtom(x11->display, "WM_DELETE_WINDOW", False); 58 | XSetWMProtocols(x11->display, x11->window, &x11->wm_delete_msg, 1); 59 | 60 | XGetWindowAttributes(x11->display, x11->window, &x11->attributes); 61 | 62 | 63 | return true; 64 | } 65 | 66 | static void resize_egl(const x11_state_t* x11, egl_state_t* egl) { 67 | if (egl->surface != EGL_NO_SURFACE) { 68 | eglDestroySurface(egl->display, egl->surface); 69 | } 70 | egl->surface = eglCreateWindowSurface(egl->display, egl->config, x11->window, NULL); 71 | if (egl->surface == EGL_NO_SURFACE) { 72 | printf("eglCreateWindowSurface failed.\n"); 73 | exit(1); 74 | } 75 | 76 | eglMakeCurrent(egl->display, egl->surface, egl->surface, egl->context); 77 | 78 | sim_resize(x11->attributes.width, x11->attributes.height); 79 | } 80 | 81 | static bool init_egl(const x11_state_t* x11, egl_state_t* egl) { 82 | 83 | egl->display = eglGetDisplay((EGLNativeDisplayType)x11->display); 84 | if (egl->display == EGL_NO_DISPLAY) { 85 | printf("eglGetDisplay failed.\n"); 86 | return false; 87 | } 88 | 89 | EGLint major, minor; 90 | if (!eglInitialize(egl->display, &major, &minor)) { 91 | printf("eglInitialize failed.\n"); 92 | return false; 93 | } 94 | 95 | EGLint num_config; 96 | EGLint attrib_list[] = {EGL_SURFACE_TYPE, EGL_WINDOW_BIT, 97 | EGL_RED_SIZE, 8, 98 | EGL_GREEN_SIZE, 8, 99 | EGL_BLUE_SIZE, 8, 100 | EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, 101 | EGL_CONFIG_CAVEAT, EGL_NONE, 102 | EGL_NONE}; 103 | if (!eglChooseConfig(egl->display, attrib_list, &egl->config, 1, &num_config) || num_config != 1) { 104 | printf("eglChooseConfig failed. (%d)\n", eglGetError()); 105 | return false; 106 | } 107 | 108 | egl->context = eglCreateContext(egl->display, egl->config, EGL_NO_CONTEXT, (EGLint[]){EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}); 109 | if (egl->context == EGL_NO_CONTEXT) { 110 | printf("eglCreateContext failed. (%d)\n", eglGetError()); 111 | return false; 112 | } 113 | 114 | resize_egl(x11, egl); 115 | 116 | eglSwapInterval(egl->display, 1); 117 | 118 | EGLint value; 119 | if (!eglQueryContext(egl->display, egl->context, EGL_RENDER_BUFFER, &value)) { 120 | printf("eglQueryContext failed: %d\n", eglGetError()); 121 | } 122 | 123 | return true; 124 | } 125 | 126 | static void close_egl(egl_state_t* egl) { 127 | eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 128 | eglDestroyContext(egl->display, egl->context); 129 | eglDestroySurface(egl->display, egl->surface); 130 | eglTerminate(egl->display); 131 | } 132 | 133 | static void close_x11(x11_state_t* x11) { 134 | XDestroyWindow(x11->display, x11->window); 135 | XCloseDisplay(x11->display); 136 | } 137 | 138 | static bool process_events(x11_state_t* x11, egl_state_t* egl) { 139 | static int wheel = 0; 140 | static int mousex = 0; 141 | static int mousey = 0; 142 | 143 | typedef struct { 144 | bool held; 145 | int x, y; 146 | } drag_t; 147 | 148 | static drag_t dragging[3] = {0}; 149 | 150 | while (XPending(x11->display)) { 151 | XEvent xev; 152 | XNextEvent(x11->display, &xev); 153 | switch (xev.type) { 154 | case ConfigureNotify: { 155 | XGetWindowAttributes(x11->display, x11->window, &x11->attributes); 156 | resize_egl(x11, egl); 157 | } break; 158 | 159 | case ClientMessage: { 160 | if (xev.xclient.data.l[0] == x11->wm_delete_msg) { 161 | return false; 162 | } 163 | } break; 164 | 165 | case MotionNotify: { 166 | mousex = xev.xmotion.x; 167 | mousey = xev.xmotion.y; 168 | } break; 169 | 170 | case ButtonPress: { 171 | switch (xev.xbutton.button) { 172 | case 1: 173 | case 2: 174 | case 3: 175 | int b = xev.xbutton.button - 1; 176 | dragging[b].held = true; 177 | mousex = xev.xbutton.x; 178 | mousey = xev.xbutton.y; 179 | dragging[b].x = mousex; 180 | dragging[b].y = mousey; 181 | break; 182 | 183 | case 4: 184 | ++wheel; 185 | break; 186 | 187 | case 5: 188 | --wheel; 189 | break; 190 | } 191 | } break; 192 | 193 | case ButtonRelease: { 194 | if (xev.xbutton.button > 0 && xev.xbutton.button <= count_of(dragging)) { 195 | dragging[xev.xbutton.button-1].held = false; 196 | } 197 | } break; 198 | } 199 | } 200 | 201 | for (int i = 0; i < count_of(dragging); ++i) { 202 | if (dragging[i].held) { 203 | if (mousex != dragging[i].x || mousey != dragging[i].y) { 204 | float scale = (float)(x11->attributes.width + x11->attributes.height) * 0.5f; 205 | sim_drag(i+1, (float)(mousex - dragging[i].x) / scale, (float)(mousey - dragging[i].y) / scale); 206 | dragging[i].x = mousex; 207 | dragging[i].y = mousey; 208 | } 209 | } 210 | } 211 | 212 | if (wheel) { 213 | sim_zoom((float)wheel / 12.0f); 214 | wheel = 0; 215 | } 216 | 217 | return true; 218 | } 219 | 220 | int main(int argc, char** argv) { 221 | x11_state_t x11 = {0}; 222 | if (!init_x11(&x11)) { 223 | printf("init_x11 failed.\n"); 224 | return 1; 225 | } 226 | 227 | egl_state_t egl = {0}; 228 | if (!init_egl(&x11, &egl)) { 229 | printf("init_egl failed.\n"); 230 | return 1; 231 | } 232 | 233 | if (!sim_init(argc, argv)) { 234 | return 1; 235 | } 236 | #ifdef PROFILE 237 | timespec_t timer = timer_time_now(); 238 | int perf = 0; 239 | #endif 240 | 241 | do { 242 | glClear(GL_COLOR_BUFFER_BIT); 243 | 244 | sim_draw(); 245 | 246 | eglSwapBuffers(egl.display, egl.surface); 247 | 248 | #ifdef PROFILE 249 | if (++perf >= 16) { 250 | printf("%d ms\n", timer_elapsed_ms(&timer) / perf); 251 | perf = 0; 252 | } 253 | #endif 254 | } while (process_events(&x11, &egl)); 255 | 256 | close_egl(&egl); 257 | close_x11(&x11); 258 | 259 | return 0; 260 | } 261 | 262 | -------------------------------------------------------------------------------- /src/driver/slicemap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "slicemap.h" 8 | 9 | #include "mathc.h" 10 | #include "rammel.h" 11 | #include "rotation.h" 12 | #include "gadget.h" 13 | 14 | //#define SLICE_INFO 15 | #define DESHIMMER_PERIMETER 16 | 17 | voxel_2D_t slice_map[SLICE_COUNT][PANEL_WIDTH][PANEL_COUNT]; 18 | 19 | _Static_assert(((SLICE_COUNT)&3)==0, "slice count must be a multiple of 4"); 20 | _Static_assert((1<<(sizeof(((slice_polar_t*)0)->slice )*8)) >= SLICE_COUNT, "slice precision overflow"); 21 | _Static_assert((1<<(sizeof(((slice_polar_t*)0)->column)*8)) >= PANEL_WIDTH, "matrix width overflow"); 22 | 23 | #ifndef PANEL_0_ECCENTRICITY 24 | #define ECCENTRICITY_0 0 25 | #else 26 | #define ECCENTRICITY_0 PANEL_0_ECCENTRICITY 27 | #endif 28 | 29 | #ifndef PANEL_1_ECCENTRICITY 30 | #define ECCENTRICITY_1 0 31 | #else 32 | #define ECCENTRICITY_1 PANEL_1_ECCENTRICITY 33 | #endif 34 | 35 | float eccentricity[2] = {ECCENTRICITY_0, ECCENTRICITY_1}; // each panel's offset from the axis, in units of led pitch 36 | 37 | 38 | // extended bit reversal 39 | void slicemap_ebr(int* a, int n) { 40 | if (n == 1) { 41 | a[0] = 0; 42 | return; 43 | } 44 | 45 | int k = n / 2; 46 | 47 | #define S(i_) ((i_) < k ? (i_) : ((i_) + 1)) 48 | 49 | slicemap_ebr(a, k); 50 | if (!(n & 1)) { 51 | for (int i = k - 1; i >= 1; i -= 1) { 52 | a[2 * i] = a[i]; 53 | } 54 | for (int i = 1; i <= n - 1; i += 2) { 55 | a[i] = a[i - 1] + k; 56 | } 57 | } else { 58 | for (int i = k - 1; i >= 1; i -= 1) { 59 | a[S(2 * i)] = a[i]; 60 | } 61 | for (int i = 1; i <= n - 2; i += 2) { 62 | a[S(i)] = a[S(i - 1)] + k + 1; 63 | } 64 | a[k] = k; 65 | } 66 | } 67 | 68 | void slicemap_init(slice_brightness_t brightness) { 69 | // build the lookup table mapping slices to voxels 70 | // each voxel should be visited exactly once by each side of each panel - four times in total 71 | // relaxing this rule allows a brighter image at the cost of uniformity across the volume 72 | 73 | int slice[SLICE_QUADRANT]; 74 | slicemap_ebr(slice, count_of(slice)); 75 | 76 | memset(slice_map, 0xff, sizeof(slice_map)); 77 | 78 | uint8_t taken[VOXELS_Y][VOXELS_X][PANEL_COUNT][2] = {0}; 79 | 80 | const vec2_t vox_centre = {.x=(float)(VOXELS_X-1) * 0.5f, .y=(float)(VOXELS_Y-1) * 0.5f}; 81 | 82 | const float tolerancesq[] = {sqr(0.0625f), sqr(0.125f), sqr(0.25f), sqr(0.5f), sqr(0.7f)}; 83 | 84 | const int passes = (brightness == SLICE_BRIGHTNESS_BOOSTED ? 2 : 1); 85 | for (int pass = 0; pass < passes; ++pass) { 86 | for (int tolerance = 0; tolerance < count_of(tolerancesq); ++tolerance) { 87 | int found = 0; 88 | for (int ia = 0; ia < SLICE_QUADRANT; ++ia) { 89 | int a = slice[ia]; 90 | float angle = (float)a * M_PI * 2.0f / SLICE_COUNT; 91 | vec2_t slope = {.x = cosf(angle), .y = sinf(angle)}; 92 | 93 | vec2_t eccoff[2]= { 94 | {.x = slope.y * eccentricity[0], .y = -slope.x * eccentricity[0]}, 95 | {.x = -slope.y * eccentricity[1], .y = slope.x * eccentricity[1]} 96 | }; 97 | 98 | for (int column = 0; column < PANEL_WIDTH; ++column) { 99 | float coff = (float)column - ((float)(PANEL_WIDTH - 1) * 0.5f); 100 | int side = coff > 0; 101 | 102 | for (int panel = 0; panel < 2; ++panel) { 103 | #ifdef DESHIMMER_PERIMETER 104 | if ((panel == 0) && (column == 0 || column == PANEL_WIDTH-1)) { 105 | // skipping the outer columns of panel 0 keeps it inside the same radius as 106 | // panel 1, reducing shimmer around the edge 107 | continue; 108 | } 109 | #endif 110 | if (slice_map[a][column][panel].x >= VOXELS_X) { 111 | vec2_t voxel_actual = { 112 | .x = vox_centre.x + (eccoff[panel].x + slope.x * coff) * (1 - panel * 2), 113 | .y = vox_centre.y + (eccoff[panel].y + slope.y * coff) * (1 - panel * 2), 114 | }; 115 | vec2i_t voxel_virtual = { 116 | .x = (int)roundf(voxel_actual.x), 117 | .y = (int)roundf(voxel_actual.y), 118 | }; 119 | 120 | float closest = FLT_MAX; 121 | voxel_2D_t voxel; 122 | 123 | for (int y = max(0, voxel_virtual.y - 1); y <= min(VOXELS_Y-1, voxel_virtual.y + 1); ++y) { 124 | for (int x = max(0, voxel_virtual.x - 1); x <= min(VOXELS_X-1, voxel_virtual.x + 1); ++x) { 125 | if ((brightness == SLICE_BRIGHTNESS_UNLIMITED) || taken[y][x][panel][side] <= pass) { 126 | float distsq = vec2_distance_squared(voxel_actual.v, (float[]){x, y}); 127 | if (distsq < closest) { 128 | closest = distsq; 129 | voxel.x = x; 130 | voxel.y = y; 131 | } 132 | } 133 | } 134 | } 135 | 136 | if (closest <= tolerancesq[tolerance]) { 137 | for (int q = 0; q < 4; ++q) { 138 | slice_map[a + q * SLICE_QUADRANT][column][panel] = voxel; 139 | taken[voxel.y][voxel.x][panel][side]++; 140 | 141 | int swap = voxel.x; 142 | voxel.x = VOXELS_X - 1 - voxel.y; 143 | voxel.y = swap; 144 | } 145 | ++found; 146 | } 147 | } 148 | } 149 | } 150 | } 151 | 152 | #ifdef SLICE_INFO 153 | int coverage = 0; 154 | int luminance = 0; 155 | 156 | for (int y = 0; y < VOXELS_Y; ++y) { 157 | for (int x = 0; x < VOXELS_X; ++x) { 158 | coverage += (taken[y][x][0][0]!=0) + (taken[y][x][0][1]!=0) + (taken[y][x][1][0]!=0) + (taken[y][x][1][1]!=0); 159 | } 160 | } 161 | 162 | for (int a = 0; a < SLICE_COUNT; ++a) { 163 | for (int c = 0; c < PANEL_WIDTH; ++c) { 164 | for (int p = 0; p < 2; ++p) { 165 | luminance += (slice_map[a][c][p].x < VOXELS_X); 166 | } 167 | } 168 | } 169 | 170 | printf("[%d %8d] coverage %2d%%, luminance %2d%%\n", tolerance, found, (coverage*127)/(VOXELS_X*VOXELS_Y*4), (luminance*100)/(SLICE_COUNT*PANEL_WIDTH*PANEL_COUNT)); 171 | #endif 172 | } 173 | } 174 | 175 | #ifdef SLICE_INFO 176 | { 177 | char boxes[16][4] = {" ", "▗", "▖", "▄", "▝", "▐", "▞", "▟", "▘", "▚", "▌", "▙", "▀", "▜", "▛", "█"}; 178 | char ansi_reset[] = "\x1b[30m"; 179 | char ansi_black[] = "\x1b[0m"; 180 | char ansi_grey170[] = "\x1b[37m"; 181 | char ansi_grey85[] = "\x1b[90m"; 182 | 183 | 184 | for (int y = 0; y < VOXELS_Y; ++y) { 185 | for (int x = 0; x < VOXELS_X; ++x) { 186 | uint b = (taken[y][x][0][0]!=0) | ((taken[y][x][0][1]!=0)<<1) | ((taken[y][x][1][0]!=0)<<2) | ((taken[y][x][1][1]!=0)<<3); 187 | if (taken[y][x][0][0]>1 || taken[y][x][0][1]>1 || taken[y][x][1][0]>1 || taken[y][x][1][1]>1) { 188 | printf(ansi_black); 189 | } else { 190 | if ((x^y)&1) { 191 | printf(ansi_grey85); 192 | } else { 193 | printf(ansi_grey170); 194 | } 195 | } 196 | printf("%s", boxes[b]); 197 | } 198 | 199 | printf("\n"); 200 | printf(ansi_reset); 201 | } 202 | } 203 | #endif 204 | } 205 | -------------------------------------------------------------------------------- /src/toys/zander/particles.c: -------------------------------------------------------------------------------- 1 | #include "particles.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "rammel.h" 7 | #include "graphics.h" 8 | #include "terrain.h" 9 | #include "zander.h" 10 | #include "objects.h" 11 | 12 | typedef struct { 13 | vec3_t position; 14 | vec3_t velocity; 15 | float lifespan; 16 | pixel_t colour; 17 | uint8_t flags; 18 | } particle_t; 19 | 20 | particle_t particles[4096]; 21 | size_t particle_count = 0; 22 | 23 | static const float tick_scale = 1.0f / 12.0f; 24 | static const float velocity_scale = 8.0f / ((float)0x01000000); 25 | 26 | void randomise_velocity(float* velocity, float amount) { 27 | float direction[VEC3_SIZE]; 28 | do { 29 | for (int i = 0; i < 3; ++i) { 30 | direction[i] = ((float)rand() / (float)(RAND_MAX)) * 2.0f - 1.0f; 31 | } 32 | } while (vec3_length_squared(direction) > 1.0f); 33 | 34 | vec3_multiply_f(direction, direction, amount); 35 | vec3_add(velocity, velocity, direction); 36 | } 37 | 38 | void particles_init(void) { 39 | particle_count = 0; 40 | } 41 | 42 | void particles_add(const float* position, const float* velocity, particle_type_t type) { 43 | if (particle_count >= count_of(particles)) { 44 | return; 45 | } 46 | 47 | particle_t* particle = &particles[particle_count++]; 48 | vec3_assign(particle->position.v, position); 49 | vec3_assign(particle->velocity.v, velocity); 50 | 51 | switch (type) { 52 | case PARTICLE_BULLET: { 53 | particle->colour = HEXPIX(FFFFAA); 54 | particle->lifespan = 20 * tick_scale; 55 | particle->flags = PARTICLE_SPLASHES | PARTICLE_BOUNCES /*| PARTICLE_DROPS*/ | PARTICLE_DESTROYS | PARTICLE_BIG_SPLASH | PARTICLE_EXPLODES; 56 | } break; 57 | 58 | case PARTICLE_EXHAUST: { 59 | particle->colour = HEXPIX(FFFFFF); 60 | particle->lifespan = rand_range(8, 8+8) * tick_scale; 61 | particle->flags = PARTICLE_COOL_DOWN | PARTICLE_SPLASHES | PARTICLE_BOUNCES | PARTICLE_DROPS; 62 | randomise_velocity(particle->velocity.v, 0x400000 * velocity_scale); 63 | } break; 64 | 65 | case PARTICLE_DEBRIS: { 66 | uint8_t r = (1 + (rand() % 3)) * 64; 67 | uint8_t g = (1 + (rand() % 3)) * 64; 68 | uint8_t b = (1 + (rand() % 3)) * 64; 69 | particle->colour = RGBPIX(r, g, b); 70 | particle->lifespan = rand_range(15, 15+64) * tick_scale; 71 | particle->flags = PARTICLE_SPLASHES | PARTICLE_BOUNCES | PARTICLE_DROPS; 72 | randomise_velocity(particle->velocity.v, 0x400000 * velocity_scale); 73 | } break; 74 | 75 | case PARTICLE_SPARK: { 76 | particle->colour = HEXPIX(FFFFFF); 77 | particle->lifespan = rand_range(8 , 8+8) * tick_scale; 78 | particle->flags = PARTICLE_COOL_DOWN | PARTICLE_SPLASHES | PARTICLE_BOUNCES | PARTICLE_DROPS; 79 | randomise_velocity(particle->velocity.v, 0x1000000 * velocity_scale); 80 | } break; 81 | 82 | case PARTICLE_SPRAY: { 83 | uint8_t b = ((rand() & 1) + 2) * 64; 84 | uint8_t rg = (rand() % b); 85 | particle->colour = RGBPIX(rg, rg, b); 86 | particle->lifespan = rand_range(20, 20+64) * tick_scale; 87 | particle->flags = PARTICLE_DROPS; 88 | randomise_velocity(particle->velocity.v, 0x400000 * velocity_scale); 89 | } break; 90 | 91 | case PARTICLE_SMOKE: { 92 | uint8_t rgb = (1 + (rand() % 3)) * 64; 93 | particle->colour = RGBPIX(rgb, rgb, rgb); 94 | particle->lifespan = rand_range(15, 15+128) * tick_scale; 95 | particle->flags = PARTICLE_BOUNCES; 96 | particle->velocity.z = 0x80000 * velocity_scale; 97 | randomise_velocity(particle->velocity.v, 0x80000 * velocity_scale); 98 | } break; 99 | 100 | case PARTICLE_ROCK: { 101 | particle->colour = HEXPIX(FFFF55); 102 | particle->lifespan = rand_range(170, 170+32) * tick_scale; 103 | particle->flags = PARTICLE_SPLASHES | PARTICLE_BOUNCES | PARTICLE_DROPS | PARTICLE_DESTROYS | PARTICLE_BIG_SPLASH | PARTICLE_EXPLODES; 104 | randomise_velocity(particle->velocity.v, 0x400000 * velocity_scale); 105 | } break; 106 | } 107 | } 108 | 109 | void particles_add_splash(const float* position, bool big_splash) { 110 | int splash_count = big_splash ? 256 : 16; 111 | splash_count = min(splash_count, (count_of(particles) - particle_count) / 2); 112 | for (int i = 0; i < splash_count; ++i) { 113 | particles_add(position, (float[3]){0, 0, 1}, PARTICLE_SPRAY); 114 | } 115 | } 116 | 117 | void particles_add_explosion(const float* position, int clusters) { 118 | clusters = min(clusters, (count_of(particles) - particle_count) / 4); 119 | 120 | for (int i = 0; i < clusters; ++i) { 121 | particles_add(position, (float[3]){0, 0, 0}, PARTICLE_SPARK); 122 | particles_add(position, (float[3]){0, 0, 0}, PARTICLE_DEBRIS); 123 | particles_add(position, (float[3]){0, 0, 0}, PARTICLE_SMOKE); 124 | particles_add(position, (float[3]){0, 0, 0}, PARTICLE_SPARK); 125 | } 126 | } 127 | 128 | void particles_delete(size_t particle) { 129 | if (particle >= particle_count) { 130 | return; 131 | } 132 | if (particle == --particle_count) { 133 | return; 134 | } 135 | 136 | memcpy(&particles[particle], &particles[particle_count], sizeof(*particles)); 137 | } 138 | 139 | void particles_update(float dt) { 140 | //printf("\r%d particles ", particle_count); 141 | 142 | for (int p = 0; p < particle_count; ++p) { 143 | particle_t* particle = &particles[p]; 144 | 145 | particle->lifespan -= dt; 146 | if (particle->lifespan < 0) { 147 | particles_delete(p--); 148 | continue; 149 | } 150 | 151 | float dpos[VEC3_SIZE]; 152 | vec3_multiply_f(dpos, particle->velocity.v, dt); 153 | vec3_add(particle->position.v, particle->position.v, dpos); 154 | 155 | if (particle->flags & PARTICLE_DROPS) { 156 | particle->velocity.z -= world_gravity * dt; 157 | } 158 | 159 | float ground = terrain_get_altitude(particle->position.x, particle->position.y); 160 | if (particle->position.z <= ground) { 161 | particle->position.z = ground; 162 | 163 | if (terrain_is_water(ground) && (particle->flags & PARTICLE_SPLASHES)) { 164 | particles_add_splash(particle->position.v, (particle->flags & PARTICLE_BIG_SPLASH) != 0); 165 | particles_delete(p--); 166 | continue; 167 | } 168 | 169 | if (!(particle->flags & PARTICLE_BOUNCES)) { 170 | particles_delete(p--); 171 | continue; 172 | } 173 | 174 | if (particle->flags & PARTICLE_EXPLODES) { 175 | particles_add_explosion(particle->position.v, 3*4); 176 | particles_delete(p--); 177 | continue; 178 | } 179 | 180 | 181 | particle->velocity.z = fabsf(particle->velocity.z) * 0.5f; 182 | vec2_multiply_f(particle->velocity.v, particle->velocity.v, 0.75f); 183 | } 184 | 185 | if (particle->flags & PARTICLE_DESTROYS) { 186 | if (objects_hit_and_destroy(particle->position.v)) { 187 | particles_delete(p--); 188 | continue; 189 | } 190 | } 191 | 192 | if (particle->flags & PARTICLE_COOL_DOWN) { 193 | int r = 255; 194 | int g = min((int)(particle->lifespan * (20/tick_scale)), 255); 195 | int b = min((int)(particle->lifespan * (10/tick_scale)), 255); 196 | particle->colour = RGBPIX(r, g, b); 197 | } 198 | } 199 | 200 | 201 | } 202 | 203 | void particles_draw(pixel_t* volume) { 204 | for (int p = 0; p < particle_count; ++p) { 205 | particle_t* particle = &particles[p]; 206 | 207 | float head[VEC3_SIZE]; 208 | foxel_from_world(head, particle->position.v); 209 | 210 | int32_t voxel[VEC3_SIZE] = {(int)roundf(head[0]), (int)roundf(head[1]), (int)roundf(head[2])}; 211 | if ((uint32_t)voxel[0] < VOXELS_X && (uint32_t)voxel[1] < VOXELS_Y && (uint32_t)voxel[2] < VOXELS_Z) { 212 | 213 | float tail[VEC3_SIZE]; 214 | vec3_multiply_f(tail, particle->velocity.v, 0.05f * world_scale); 215 | 216 | if (vec3_length_squared(tail) > 1.0f) { 217 | vec3_subtract(tail, head, tail); 218 | graphics_draw_line(volume, head, tail, particle->colour); 219 | } else { 220 | volume[VOXEL_INDEX(voxel[0], voxel[1], voxel[2])] = particle->colour; 221 | } 222 | 223 | // shadow 224 | int8_t surface = max(0, HEIGHT_MAP_OBJECT(voxel[0], voxel[1])); 225 | if (voxel[2] > surface) { 226 | uint32_t idx = VOXEL_INDEX(voxel[0], voxel[1], surface); 227 | volume[idx] = (volume[idx]&0b10010010)>>1; 228 | } 229 | 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/multivox/cart.c: -------------------------------------------------------------------------------- 1 | #include "cart.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "mathc.h" 12 | #include "model.h" 13 | #include "rammel.h" 14 | 15 | static model_t cart_model = { 16 | .vertex_count = 24, 17 | .vertices = (vertex_t*)(float[][5]){ 18 | {16.3145, 2, 15.2947}, {20, 2, 18.9803}, {20, 2, 24}, {-20, 2, 24}, {-20, 2, 18.9803}, {-16.3145, 2, 15.2947}, {-16.3145, 2, 9.86573}, {-20, 2, 6.18021}, 19 | {-20, 2, -16}, {20, 2, -16}, {20, 2, -6.7141}, {16.3145, 2, -3.02859}, {20, -2, 18.9803}, {20, -2, 24}, {-20, -2, 24}, {-20, -2, 18.9803}, 20 | {-16.3145, -2, 15.2947}, {-16.3145, -2, 9.86573}, {-20, -2, 6.18022}, {-20, -2, -16}, {20, -2, -16}, {20, -2, -6.7141}, {16.3145, -2, -3.02859}, {16.3145, -2, 15.2947}, 21 | }, 22 | .surface_count = 1, 23 | .surfaces = (surface_t[]){ 24 | {132, (index_t[]){ 25 | 1, 2, 0, 0, 2, 3, 0, 3, 5, 5, 3, 4, 5, 6, 0, 0, 6, 11, 11, 6, 8, 11, 8, 9, 6, 7, 8, 9, 10, 11, 12, 13, 1, 1, 13, 2, 13, 14, 2, 2, 26 | 14, 3, 14, 15, 3, 3, 15, 4, 15, 16, 4, 4, 16, 5, 16, 17, 5, 5, 17, 6, 17, 18, 6, 6, 18, 7, 18, 19, 7, 7, 19, 8, 19, 20, 8, 8, 20, 9, 20, 21, 27 | 9, 9, 21, 10, 21, 22, 10, 10, 22, 11, 22, 23, 11, 11, 23, 0, 23, 12, 0, 0, 12, 1, 12, 23, 13, 13, 23, 14, 14, 23, 16, 14, 16, 15, 16, 23, 17, 17, 23, 22, 28 | 17, 22, 19, 19, 22, 20, 20, 22, 21, 19, 18, 17, 29 | }, HEXPIX(FFFFFF)}, 30 | }, 31 | }; 32 | 33 | 34 | static char* key_value(char* line, const char* key) { 35 | 36 | const char whitespace[] = " \f\n\r\t\v"; 37 | char* token = line + strspn(line, whitespace); 38 | 39 | size_t keylen = strlen(key); 40 | if (strncmp(token, key, keylen) == 0) { 41 | token = strchr(token, '='); 42 | if (token && *++token) { 43 | token += strspn(token, whitespace); 44 | 45 | size_t len = strlen(token); 46 | while (len > 0 && strchr(whitespace, token[len-1])) { 47 | --len; 48 | } 49 | char* value = malloc(len + 1); 50 | memcpy(value, token, len); 51 | value[len] = '\0'; 52 | return value; 53 | } 54 | } 55 | 56 | return NULL; 57 | } 58 | 59 | static float slot_height(float a) { 60 | return powf(0.5f * (1 + cosf(a)), 80) * 24 - 12; 61 | } 62 | 63 | void cart_grab_voxshot(cart_t* cart, const pixel_t* volume) { 64 | if (!cart->voxel_shot[0]) { 65 | cart->voxel_shot[0] = malloc(VOXELS_COUNT * sizeof(pixel_t)); 66 | memset(cart->voxel_shot[0], 0, VOXELS_COUNT * sizeof(pixel_t)); 67 | } 68 | 69 | // grab the cylinder of visible voxels currently in the display buffer 70 | for (int y = 0; y < VOXELS_Y; ++y) { 71 | for (int x = 0; x < VOXELS_X; ++x) { 72 | if (voxel_in_cylinder(x, y)) { 73 | for (int z = 0; z < VOXELS_Z; ++z) { 74 | cart->voxel_shot[0][x * VOXELS_Z + y * VOXELS_X * VOXELS_Z + z] = volume[VOXEL_INDEX(x, y, z)]; 75 | } 76 | } 77 | } 78 | } 79 | 80 | // generate a few vipvap levels 81 | int count[3] = {VOXELS_X, VOXELS_Y, VOXELS_Z}; 82 | for (int m = 1; m < count_of(cart->voxel_shot); ++m) { 83 | count[0] /= 2; 84 | count[1] /= 2; 85 | count[2] /= 2; 86 | 87 | if (!cart->voxel_shot[m]) { 88 | cart->voxel_shot[m] = malloc(count[0] * count[1] * count[2] * sizeof(pixel_t)); 89 | memset(cart->voxel_shot[m], 0, count[0] * count[1] * count[2] * sizeof(pixel_t)); 90 | } 91 | 92 | for (int y = 0; y < count[1]; ++y) { 93 | for (int x = 0; x < count[0]; ++x) { 94 | for (int z = 0; z < count[2]; ++z) { 95 | 96 | int rgb[3] = {0,0,0}; 97 | for (int j = 0; j < 2; ++j) { 98 | for (int i = 0; i < 2; ++i) { 99 | for (int k = 0; k < 2; ++k) { 100 | pixel_t colour = cart->voxel_shot[m - 1][((x*2+i) * count[2]*2) + ((y*2+j) * count[0]*count[2]*4) + (z*2+k)]; 101 | rgb[0] += R_PIX(colour); 102 | rgb[1] += G_PIX(colour); 103 | rgb[2] += B_PIX(colour); 104 | } 105 | } 106 | } 107 | 108 | rgb[0] = min(255, rgb[0] / 3); 109 | rgb[1] = min(255, rgb[1] / 3); 110 | rgb[2] = min(255, rgb[2] / 3); 111 | 112 | cart->voxel_shot[m][(x * count[2]) + (y * count[0]*count[2]) + (z)] = RGBPIX(rgb[0], rgb[1], rgb[2]); 113 | } 114 | } 115 | } 116 | } 117 | 118 | } 119 | 120 | static bool try_load_voxshot(cart_t* cart, char* filename) { 121 | int fd = open(filename, O_RDONLY); 122 | if (fd == -1) { 123 | return false; 124 | } 125 | 126 | struct stat sb; 127 | if (fstat(fd, &sb) == -1) { 128 | perror("fstat"); 129 | close(fd); 130 | return false; 131 | } 132 | 133 | size_t size = sb.st_size; 134 | if (size != VOXELS_COUNT) { 135 | close(fd); 136 | return false; 137 | } 138 | 139 | void* mapped = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0); 140 | if (mapped == MAP_FAILED) { 141 | perror("mmap"); 142 | close(fd); 143 | return false; 144 | } 145 | 146 | cart_grab_voxshot(cart, mapped); 147 | 148 | munmap(mapped, size); 149 | close(fd); 150 | 151 | return true; 152 | } 153 | 154 | void cart_save_voxshot(cart_t* cart) { 155 | if (!cart->cartpath || !cart->voxel_shot[0]) { 156 | return; 157 | } 158 | 159 | size_t namelen = strlen(cart->cartpath); 160 | if (namelen > 4 && cart->cartpath[namelen-4] == '.') { 161 | char shotname[namelen+1]; 162 | memcpy(shotname, cart->cartpath, namelen - 4); 163 | memcpy(shotname + namelen - 4, ".rvx", 5); 164 | 165 | FILE* fd = fopen(shotname, "wb"); 166 | if (!fd) { 167 | perror("save"); 168 | } 169 | 170 | fwrite(cart->voxel_shot[0], sizeof(pixel_t), VOXELS_COUNT, fd); 171 | fclose(fd); 172 | } 173 | } 174 | 175 | bool cart_load(cart_t* cart, char* filename) { 176 | memset(cart, 0, sizeof(*cart)); 177 | 178 | FILE* file = fopen(filename, "r"); 179 | if (!file) { 180 | perror("fopen"); 181 | return false; 182 | } 183 | 184 | size_t namelen = strlen(filename); 185 | 186 | char* cartpath = malloc(namelen + 1); 187 | memcpy(cartpath, filename, namelen); 188 | cartpath[namelen] = '\0'; 189 | cart->cartpath = cartpath; 190 | 191 | cart->colour = HEXPIX(FF00FF); 192 | 193 | char line[1024]; 194 | while (fgets(line, sizeof(line), file)) { 195 | if (!cart->command) cart->command = key_value(line, "command"); 196 | if (!cart->arguments) cart->arguments = key_value(line, "arguments"); 197 | if (!cart->workingdir) cart->workingdir = key_value(line, "workingdir"); 198 | if (!cart->environment) cart->environment = key_value(line, "environment"); 199 | 200 | char* value; 201 | if ((value = key_value(line, "colour"))) { 202 | int r, g, b; 203 | if (sscanf(value, "#%02x%02x%02x", &r, &g, &b) == 3) { 204 | cart->colour = RGBPIX(r, g, b); 205 | } 206 | free(value); 207 | } 208 | } 209 | 210 | fclose(file); 211 | 212 | if (namelen > 4 && filename[namelen-4] == '.') { 213 | char shotname[namelen+1]; 214 | memcpy(shotname, filename, namelen - 4); 215 | memcpy(shotname + namelen - 4, ".rvx", 5); 216 | 217 | try_load_voxshot(cart, shotname); 218 | } 219 | 220 | 221 | return true; 222 | } 223 | 224 | void cart_draw(cart_t* cart, pixel_t* volume, float slot_angle) { 225 | float matrix[MAT4_SIZE]; 226 | 227 | mat4_identity(matrix); 228 | mat4_apply_translation(matrix, (float[3]){(VOXELS_X-1)*0.5f, (VOXELS_Y-1)*0.5f, 0}); 229 | mat4_apply_rotation_z(matrix, slot_angle); 230 | mat4_apply_translation(matrix, (float[3]){40.0f, 4.0f, slot_height(slot_angle)}); 231 | 232 | cart_model.surfaces[0].colour = cart->colour; 233 | model_draw(volume, &cart_model, matrix); 234 | 235 | int z0 = slot_angle * slot_angle * 320; 236 | const int m = 1; 237 | if (cart->voxel_shot[m]) { 238 | for (int y = 0; y < VOXELS_Y>>m; ++y) { 239 | int vy = y+(VOXELS_Y/2)-(VOXELS_Y>>(m+1)); 240 | for (int x = 0; x < VOXELS_X>>m; ++x) { 241 | for (int z = 0; z < VOXELS_Z>>m; ++z) { 242 | int vz = z0 + z+(VOXELS_Z)-(VOXELS_Z>>m)*3/2; 243 | if ((uint)vz < VOXELS_Z) { 244 | volume[VOXEL_INDEX(x, vy, vz)] = cart->voxel_shot[m][(x * (VOXELS_Z>>m)) + (y * (VOXELS_X>>m) * (VOXELS_Z>>m)) + z]; 245 | } 246 | } 247 | } 248 | } 249 | } 250 | 251 | } 252 | 253 | -------------------------------------------------------------------------------- /src/toys/zander/terrain.c: -------------------------------------------------------------------------------- 1 | #include "terrain.h" 2 | 3 | #include "mathc.h" 4 | #include "rammel.h" 5 | #include "graphics.h" 6 | #include "model.h" 7 | #include "zander.h" 8 | #include "zsintable.h" 9 | #include "particles.h" 10 | #include "timer.h" 11 | 12 | #define STEEP_SLOPES 13 | #define SMOOTH_COASTLINE 14 | #define CHEESY_WAVES 15 | //#define HEIGHT_DITHER 16 | 17 | static const int32_t TILE_SIZE = 0x01000000; 18 | static const int32_t LAND_MID_HEIGHT = 0x05000000; 19 | static const int32_t SEA_LEVEL = 0x05500000; 20 | static const int32_t LAUNCHPAD_ALTITUDE = 0x03500000; 21 | static const int32_t LAUNCHPAD_SIZE = 8; 22 | //static const int32_t UNDERCARRIAGE_Y = 0x00640000; 23 | 24 | static const float TILE_SCALE = 1.0f / (float)TILE_SIZE; 25 | 26 | static const float terrain_max_height = 10.0f; 27 | 28 | static pixel_t current_tile_colour = 0; 29 | [[maybe_unused]] static const pixel_t colour_sea = HEXPIX(3F3FFF); 30 | [[maybe_unused]] static const pixel_t colour_sand = HEXPIX(FFFF00); 31 | [[maybe_unused]] static const pixel_t colour_launchpad = HEXPIX(7F7F7F); 32 | 33 | static uint16_t bit_reverse(uint16_t n) { 34 | n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); 35 | n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); 36 | n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); 37 | n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); 38 | return n; 39 | } 40 | 41 | int32_t zsin(int32_t v) { 42 | // I'm getting something wrong here - I think it should be v >> 22, but this is visually closer in horizontal scale 43 | return zsinTable[(v >> 21) & 1023]; 44 | } 45 | 46 | static int32_t GetLandscapeAltitude(int32_t x, int32_t z) { 47 | int32_t r; 48 | 49 | r = zsin( x - 2 * z) / 128; 50 | r += zsin( 4 * x + 3 * z) / 128; 51 | r += zsin( 3 * z - 5 * x) / 128; 52 | r += zsin( 3 * x + 3 * z) / 128; 53 | r += zsin( 5 * x + 11 * z) / 256; 54 | r += zsin(10 * x + 7 * z) / 256; 55 | 56 | return LAND_MID_HEIGHT - r; 57 | } 58 | 59 | static pixel_t GetLandscapeTileColour(int32_t altitude) { 60 | #ifdef SMOOTH_COASTLINE 61 | if (altitude >= SEA_LEVEL - TILE_SIZE/8) { 62 | return colour_sand; 63 | } 64 | #else 65 | if (altitude == SEA_LEVEL) { 66 | return colour_sea; 67 | } 68 | #endif 69 | 70 | if (altitude == LAUNCHPAD_ALTITUDE) { 71 | return colour_launchpad; 72 | } 73 | 74 | int r = ((altitude>>2)&1) * 128; 75 | int g = ((altitude>>3)&1) * 128 + 64; 76 | 77 | return (RGBPIX(r, g, 0) | 0b00000100); 78 | } 79 | 80 | float bilerp(const float corners[2][2], const float local[2]) { 81 | float a0 = corners[0][0] * (1.0f - local[0]) + corners[1][0] * local[0]; 82 | float a1 = corners[0][1] * (1.0f - local[0]) + corners[1][1] * local[0]; 83 | return a0 * (1.0f - local[1]) + a1 * local[1]; 84 | } 85 | 86 | static const int bayer_4x4[4][4] = {{ 0, 8, 2,10}, {12, 4,14, 6}, { 3,11, 1, 9}, {15, 7,13, 5}}; 87 | 88 | bool depth_dither(int32_t depth, float* position) { 89 | int x = (int)(position[0] * world_scale); 90 | int y = (int)(position[1] * world_scale); 91 | return depth*2 > -bayer_4x4[x&3][y&3]; 92 | } 93 | 94 | float terrain_get_altitude_raw(float x, float y) { 95 | int32_t altitude = GetLandscapeAltitude((int)roundf(x*1024) * (TILE_SIZE/1024), (int)roundf(y*1024) * (TILE_SIZE/1024)); 96 | return (SEA_LEVEL - altitude) * TILE_SCALE; 97 | } 98 | 99 | float terrain_get_altitude(float x, float y) { 100 | static float heights[2][2] = {}; 101 | static int32_t cached[VEC2_SIZE] = {INT32_MAX, INT32_MAX}; 102 | 103 | int32_t tile[VEC2_SIZE] = {floorf(x), floorf(y)}; 104 | 105 | int32_t altitude; 106 | if (cached[0] != tile[0] || cached[1] != tile[1]) { 107 | static int32_t altitudes[2][2] = {}; 108 | for (int i = 0; i < 2; ++i) { 109 | for (int j = 0; j < 2; ++j) { 110 | int32_t t[VEC2_SIZE] = {tile[0]+i, tile[1]+j}; 111 | if ((uint32_t)t[0] < LAUNCHPAD_SIZE && (uint32_t)t[1] < LAUNCHPAD_SIZE) { 112 | altitude = LAUNCHPAD_ALTITUDE; 113 | } else { 114 | altitude = GetLandscapeAltitude(t[0]*TILE_SIZE, t[1]*TILE_SIZE); 115 | if (altitude > SEA_LEVEL) { 116 | altitude = SEA_LEVEL; 117 | } 118 | } 119 | 120 | altitudes[i][j] = altitude; 121 | heights[i][j] = (SEA_LEVEL - altitude) * TILE_SCALE; 122 | } 123 | } 124 | 125 | current_tile_colour = GetLandscapeTileColour(altitudes[1][0]); 126 | memcpy(cached, tile, sizeof(tile)); 127 | } 128 | 129 | float local[VEC2_SIZE] = {x - tile[0], y - tile[1]}; 130 | return bilerp(heights, local); 131 | } 132 | 133 | 134 | void draw_ground(pixel_t* volume) { 135 | #ifdef HEIGHT_DITHER 136 | const float height_fuzz = 0.25f; 137 | uint fuzz_origin = ((((int)world_position.x)&1)<<1) | (((int)world_position.y)&1); 138 | #endif 139 | 140 | for (int y = 0; y < VOXELS_Y; ++y) { 141 | for (int x = 0; x < VOXELS_X; ++x) { 142 | vec3_t pos = {.x=((float)x - (VOXELS_X-1)*0.5f) / world_scale + world_position.x, .y=((float)y - (VOXELS_Y-1)*0.5f) / world_scale + world_position.y, 0}; 143 | 144 | float altitude = terrain_get_altitude(pos.x, pos.y); 145 | 146 | #ifdef HEIGHT_DITHER 147 | float dither = ((float)((((x&1)<<1)|(y&1))^fuzz_origin) - 1.5f) * height_fuzz; 148 | #else 149 | const float dither = 0; 150 | #endif 151 | int32_t z = ((altitude - world_position.z) * world_scale) + dither; 152 | height_map[y][x][0] = height_map[y][x][1] = clamp(z, -127, 127); 153 | 154 | if (z < 0 && depth_dither(z, pos.v)) { 155 | z = 0; 156 | } 157 | 158 | #ifdef STEEP_SLOPES 159 | if (z >= 0) { 160 | int zinf = z; 161 | int zsup = z; 162 | 163 | if (y > 0) { 164 | zinf = min(zinf, height_map[y-1][x][0]); 165 | zsup = max(zsup, height_map[y-1][x][0]); 166 | } 167 | if (x > 0) { 168 | zinf = min(zinf, height_map[y][x-1][0]); 169 | zsup = max(zsup, height_map[y][x-1][0]); 170 | } 171 | 172 | zinf = max(0, zinf + 1); 173 | zsup = min(VOXELS_Z-1, zsup - 1); 174 | 175 | for (z = zinf; z <= zsup; ++z) { 176 | volume[VOXEL_INDEX(x, y, z)] = current_tile_colour; 177 | } 178 | } 179 | #endif 180 | 181 | pixel_t colour = current_tile_colour; 182 | 183 | if (terrain_is_water(altitude)) { 184 | #ifdef SMOOTH_COASTLINE 185 | colour = colour_sea; 186 | #endif 187 | #ifdef CHEESY_WAVES 188 | //if ((x^y)&1) { 189 | #define WAVE_PERIOD_MS (2500) 190 | #define WAVE_HEIGHT (0.25f) 191 | #define CREST_HEIGHT (WAVE_HEIGHT * 0.95f) 192 | float depth = terrain_get_altitude_raw(pos.x, pos.y); 193 | float crest = depth + sinf((((timer_frame_time + (int32_t)(pos.y*2000)) % (WAVE_PERIOD_MS * 256)) * 2 * M_PI / WAVE_PERIOD_MS) + depth*depth*16); 194 | if (crest > 0) { 195 | crest = powf(crest, 0.1f) * WAVE_HEIGHT; 196 | if (crest > CREST_HEIGHT) { 197 | //particles_add(pos.v, (float[3]){0,0,0}, PARTICLE_SPRAY); 198 | colour = HEXPIX(BFBFFF); 199 | } 200 | 201 | z = ((crest - world_position.z) * world_scale) + dither; 202 | } 203 | //} 204 | #endif 205 | } 206 | 207 | if ((uint32_t)z < VOXELS_Z) { 208 | volume[VOXEL_INDEX(x, y, z)] = colour; 209 | } 210 | } 211 | } 212 | } 213 | extern vec3_t ship_position; 214 | 215 | void draw_stars(pixel_t* volume) { 216 | vec2i_t tile0 = {.x=floorf((-(VOXELS_X-1)*0.5f) / world_scale + world_position.x), 217 | .y=floorf((-(VOXELS_Y-1)*0.5f) / world_scale + world_position.y)}; 218 | 219 | int tiles = (int)ceilf((float)VOXELS_X / world_scale) + 1; 220 | 221 | vec3_t star = {.x=tile0.x, .y=tile0.y}; 222 | 223 | for (int y = 0; y < tiles; ++y) { 224 | for (int x = 0; x < tiles; ++x) { 225 | star.x = tile0.x + x; 226 | star.y = tile0.y + y; 227 | star.z = sqrtf(fabsf(fmodf((float)bit_reverse(tile0.x+x) * 3.0f + (float)bit_reverse(tile0.y+y) * 7.0f, 1987))) + terrain_max_height; 228 | 229 | vec3i_t voxel; 230 | voxel_from_world(voxel.v, star.v); 231 | 232 | if ((uint32_t)voxel.x < VOXELS_X && (uint32_t)voxel.y < VOXELS_Y && (uint32_t)voxel.z < VOXELS_Z) { 233 | volume[VOXEL_INDEX(voxel.x, voxel.y, voxel.z)] = HEXPIX(FFFFFF); 234 | } 235 | 236 | } 237 | } 238 | } 239 | 240 | void terrain_init(void) { 241 | } 242 | 243 | void terrain_draw(pixel_t* volume) { 244 | 245 | draw_ground(volume); 246 | draw_stars(volume); 247 | } 248 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Multivox 4 | 5 | 6 | 7 | This is the code I currently use to drive my [volumetric displays](https://www.youtube.com/watch?v=pcAEqbYwixU). 8 | 9 | It supports two closely related devices which are configured in the `src/driver/gadgets` directory: 10 | - [Rotovox](https://youtu.be/97cLIO7FNtw) is a 400mm Orb featuring two 128x64 panels arranged vertically side by side. 11 | - [Vortex](https://youtu.be/Ozzpirkhi5c) is a 300mm Orb featuring two 128x64 panels arranged horizontally, back to back. 12 | 13 | Rotovox has a higher vertical resolution and better horizontal density; Vortex is brighter and has a higher refresh rate. 14 | 15 | The 3D printable parts for Vortex are available [here](https://github.com/AncientJames/VortexParts). 16 | 17 | ![A photograph of two orbs, one running Doom, the other GTA](images/multivox.jpg) 18 | 19 | 20 | ## Hardware 21 | 22 | This code was originally written for a single display, and the device specific code was later somewhat abstracted 23 | out to support a second similar gadget. There are assumptions about the hardware that are pretty well baked in: 24 | 25 | * It consists of two HUB75 LED panels spinning around a vertical axis. 26 | * The panels use either ABCDE addressing or ABC shift register addressing. 27 | * It uses a single GPIO (a photodiode or similar) to sync to rotation - high for 180°, low for 180°. 28 | * It's running on a Raspberry Pi 4. 29 | 30 | The GPIO mappings and panel layout are defined in `src/driver/gadgets/gadget_.h`. GPIO is via memory mapped 31 | access - if you're using a different model of Pi you'll need to change `BCM_BASE` in the GPIO code. I haven't tested 32 | this, and you should probably assume it doesn't work. 33 | 34 | Input is via a bluetooth gamepad - I've been using an Xbox controller, and the input system is based on the default 35 | mapping for that. 36 | 37 | Audio out is also via bluetooth. I haven't had success with the higher quality codecs, but the headset protocol works. 38 | 39 | 40 | ## Layout 41 | 42 | There are two parts to this code - the driver, which creates a voxel buffer in shared memory and scans its contents out 43 | in sync with rotation, and the client code which generates content and writes it into the voxel buffer. Both driver 44 | and client code are designed to run on the same device, a Raspberry Pi embedded in the hardware and spinning at several 45 | hundred RPM. There is a demo included in the Python directory which streams point clouds from a PC over wifi to the 46 | device, but fundamentally it's designed as a self contained gadget, like an alternate timeline Vectrex. A bluetooth 47 | gamepad is used to control the demos. 48 | 49 | 50 | ├── src 51 | │ ├── driver 52 | │ │ ├── gadgets -- the different volumetric display configurations 53 | │ │ │ └── 54 | │ │ └── vortex.c -- driver code - creates a voxel buffer in shared memory, 55 | │ │ and handles scanning it out to the led panels in sync with 56 | │ │ the rotation 57 | │ ├── simulator 58 | │ │ └── virtex.c -- software simulator - presents the same voxel buffer as 59 | │ │ the driver would, but renders the contents into an X11 window 60 | │ │ 61 | │ ├── multivox -- front end / launcher for the various volumetric toys 62 | │ │ └── 63 | │ ├── platform -- common client code 64 | │ │ └── 65 | │ └── toys -- a collection of volumetric demos using the shared voxel buffer 66 | │ ├── eighty -- multiplayer light cycles 67 | │ ├── fireworks.c -- cheesy first demo 68 | │ ├── flight.c -- some kind of 70s scifi thing 69 | │ ├── tesseract.c -- a 4D cubube 70 | │ ├── viewer.c -- viewer for .obj and .png files 71 | │ └── zander -- lander/zarch/virus-esque 72 | ├── python 73 | │ ├── calibration.py - 74 | │ ├── grid.py -- some pattern generators, useful when calibrating the device 75 | │ ├── colourwheel.py - 76 | │ ├── obj2c.py -- tool for embedding .obj models in a header file 77 | │ ├── pointvision.py -- receive point clouds streamed from vortexstream.py 78 | │ └── vortexstream.py -- stream point clouds to pointvision.py 79 | └── README.md -- you are here 80 | 81 | 82 | 83 | 84 | ## Building 85 | 86 | On the Raspberry Pi, clone the repository: 87 | 88 | git clone https://github.com/AncientJames/multivox.git 89 | 90 | Configure the project for your hardware: 91 | 92 | cd multivox 93 | mkdir build 94 | cd build 95 | cmake -DMULTIVOX_GADGET=vortex .. 96 | cmake --build . 97 | 98 | 99 | ## Running 100 | 101 | First, the driver has to be running: 102 | 103 | sudo ./vortex 104 | 105 | When invoked from the command line it periodically outputs profiling information (frame rate, rotation rate), and accepts keyboard input for various diagnostics: 106 | 107 | | Key | Effect | 108 | | --- | ------ | 109 | | esc | Exit | 110 | | b | Bit depth - cycles through 1, 2 or 3 bits per channel. Higher bit depths result in lower refresh rates | 111 | | u | Uniformity - cycles through different strategies for trading off brightness against uniformity | 112 | | t | Trails - adjusts how far back to accumulate skipped voxels when the rotation rate is too high for the refresh rate | 113 | | l | Lock - whether to adjust the rotation sync to keep it facing one way | 114 | | d D | Drift - rotisserie mode. Introduces some explicit drift to the rotation sync | 115 | | p | Panel - selectively disable the panels | 116 | | xyz | Axis - When the display isn't spinning, it shows an othographic view. This lets you choose the axis | 117 | 118 | 119 | 120 | While that's running, try one of the toys: 121 | 122 | ./tesseract 123 | 124 | The `viewer` takes a list of *.obj* and *.png* files as arguments. You can scale, rotate and so on using the gamepad, and it 125 | also accepts keyboard input when run remotely from the command line. 126 | 127 | ./viewer ~/Multivox/models/*.obj 128 | 129 | 130 | |Control| Key | Effect | 131 | | ----- | - | ------ | 132 | | | esc | Exit | 133 | | LB/RB |[ / ]| Cycle through models | 134 | | A | | Walkthrough / Orbit | 135 | | X | | Zoom to fit | 136 | | Y | | Toggle wireframe | 137 | 138 | 139 | ## Simulator 140 | 141 | If you don't have a physical volumetric display, there's a simulator, `virtex`, which you can run in place of `vortex`. It exposes the same voxel buffer in shared memory, but renders the contents using OpenGL in an X11 window. 142 | 143 | ![Screenshot of a tesseract rendered in Virtex](images/virtex.jpg) 144 | 145 | Run without command line arguments it creates a display compatible with the currently configured gadget, but there are some options to let you experiment with different geometries: 146 | 147 | | Option | Effect | 148 | | ------ | ------ | 149 | | -s X | slice count - the number of vertical slices per revolution | 150 | | -o X X | offsets - distance the front and back screens are offset from the axis, as a fraction of screen radius | 151 | | -b X | bits per channel (1 - 3) | 152 | | -w X Y | panel resolution | 153 | | -g X | scan geometry - radial or linear. Linear looks better, but it's a lot harder to build. | 154 | 155 | 156 | 157 | 158 | An idealised device with linear scanning and 3 bits per channel can be invoked like this: 159 | 160 | ./virtex -g l -s 128 -w 1280 1280 -b 3 161 | 162 | The simulator is fill rate intensive; if you're running it on a Raspberry Pi you'll probably want to reduce the slice count. 163 | 164 | 165 | 166 | ## Installing 167 | 168 | If you want it to start up automatically on boot, you can install `vortex` as a service, and set `multivox` to run on startup. 169 | 170 | First install everything to its default location `~/Multivox`: 171 | 172 | > make install 173 | 174 | This will build the executable files and copy them into the destination directory, as well as creating `.mct` files in `~/Multivox/carts` for the built in toys. 175 | 176 | 177 | Create the driver service: 178 | 179 | sudo nano /usr/lib/systemd/system/vortex.service 180 | 181 | and fill in the following information: 182 | 183 | [Unit] 184 | Description=Vortex Display Driver 185 | After=multi-user.target 186 | 187 | [Service] 188 | ExecStart=/home/pi/Multivox/bin/vortex 189 | 190 | [Install] 191 | WantedBy=multi-user.target 192 | 193 | Then start it up: 194 | 195 | sudo systemctl daemon-reload 196 | sudo systemctl enable vortex.service 197 | 198 | The driver assigns itself to core 3 - you can add `isolcpus=3` to the end of `/boot/cmdline.txt` to ensure it's the only thing running on that core. 199 | 200 | You'll also want the launcher to start up on boot: 201 | 202 | crontab -e 203 | 204 | And add the line: 205 | 206 | @reboot /home/pi/Multivox/bin/multivox 207 | 208 | 209 | 210 | ## Multivox 211 | 212 | If everything goes smoothly, when you turn on the device it will boot up into `Multivox`. This is a fantasy console which 213 | acts as a launcher for all the games and demos you run on the hardware. The bundled toys are automatically installed in 214 | the `~/Multivox/carts/` directory as `.mct` files, and external apps can be launched by adding a `.mct` file containing 215 | its command, path and arguments. 216 | 217 | Each `.mct` file appears as a cartridge in the Multivox front end. They should each have a label on the side; at the moment 218 | all you can do to distinguish between them is change their colour in the `.mct`. 219 | 220 | When you exit an app back to the launcher, it saves a snapshot of the voxel volume, and this gives a preview of what you'll 221 | see when you launch a cart. This means there are two competing representations of the same information, and any future work 222 | on the front end will probably start with overhauling the entire approach. 223 | 224 | Some basic UI for controls such as changing bit depth, rebooting and so on would also be a boon. 225 | 226 | |Control| Effect | 227 | | ----- | ------ | 228 | | LB/RB | Cycle through carts | 229 | | A | Launch cart | 230 | | ⧉ | Exit / resume running cart | 231 | | △ ▽ | Change bit depth 232 | |☰ x5 | Power off | 233 | 234 | 235 | --- 236 | -------------------------------------------------------------------------------- /src/toys/zander/ship.c: -------------------------------------------------------------------------------- 1 | #include "ship.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "mathc.h" 7 | #include "rammel.h" 8 | #include "graphics.h" 9 | #include "model.h" 10 | #include "zander.h" 11 | #include "particles.h" 12 | #include "terrain.h" 13 | #include "objects.h" 14 | #include "input.h" 15 | 16 | #define ZFLOAT(f) ((float)((double)((int32_t)f) / (double)(0x01000000))) 17 | 18 | static const model_t ship_model = { 19 | .vertices = (vertex_t*)(float[][5]){ 20 | {ZFLOAT(0x01000000), ZFLOAT(0x00800000), -ZFLOAT(0x00500000)}, 21 | {ZFLOAT(0x01000000), ZFLOAT(0xFF800000), -ZFLOAT(0x00500000)}, 22 | {ZFLOAT(0x00000000), ZFLOAT(0xFECCCCCD), -ZFLOAT(0x000A0000)}, 23 | {ZFLOAT(0xFF19999A), ZFLOAT(0x00000000), -ZFLOAT(0x00500000)}, 24 | {ZFLOAT(0x00000000), ZFLOAT(0x01333333), -ZFLOAT(0x000A0000)}, 25 | {ZFLOAT(0xFFE66667), ZFLOAT(0x00000000), -ZFLOAT(0xFF880000)}, 26 | {ZFLOAT(0x00555555), ZFLOAT(0x00400000), -ZFLOAT(0x00500000)}, 27 | {ZFLOAT(0x00555555), ZFLOAT(0xFFC00000), -ZFLOAT(0x00500000)}, 28 | {ZFLOAT(0xFFCCCCCD), ZFLOAT(0x00000000), -ZFLOAT(0x00500000)}, 29 | }, 30 | .vertex_count = 9, 31 | 32 | .surfaces = (surface_t[]){ 33 | {15, (index_t[]){0, 1, 5, 1, 2, 5, 0, 5, 4, 2, 3, 5, 3, 4, 5}, HEXPIX(00FF55)}, 34 | { 9, (index_t[]){1, 2, 3, 0, 3, 4, 0, 1, 3}, HEXPIX(005555)}, 35 | { 3, (index_t[]){6, 7, 8}, HEXPIX(FFFF00)}, 36 | }, 37 | .surface_count = 3, 38 | }; 39 | 40 | typedef struct { 41 | bool detected; 42 | voxel_index_t x, y; 43 | } intersection_t; 44 | 45 | const float ship_engine_vector[VEC3_SIZE] = {0, 0,-1}; 46 | const float ship_cannon_vector[VEC3_SIZE] = {1, 0, 0}; 47 | 48 | static const float ship_yaw_speed = M_PI * 10.0f; 49 | static const float ship_pitch_max = M_PI * 0.55f; 50 | static const float ship_thrust_max = 30.0f; 51 | static const float ship_exhaust_rate = 0.1f; 52 | static const float ship_exhaust_speed = 6.0f; 53 | static const float ship_bullet_speed = 8.0f; 54 | static const float ship_damping = 0.8f; 55 | 56 | 57 | static const float ship_undercarriage = 0.5f; 58 | 59 | vec3_t ship_position = {.x = 3.5f, .y = 3.5f, .z = 2 + ship_undercarriage}; 60 | vec3_t ship_rotation = {.x=0, .y=0, .z=0}; 61 | vec3_t ship_velocity = {.x=0, .y=0, .z=0}; 62 | 63 | static const float bullet_recharge = 0.1f; 64 | static float bullet_time = 0; 65 | 66 | 67 | static intersection_t intersection = {0}; 68 | static bool debug_collision = false; 69 | 70 | 71 | float angle_diff(float to, float from) { 72 | double diff = fmod(to - from, 2 * M_PI); 73 | if (diff < -M_PI) { 74 | diff += 2 * M_PI; 75 | } else if (diff > M_PI) { 76 | diff -= 2 * M_PI; 77 | } 78 | return diff; 79 | } 80 | 81 | 82 | static bool autopilot = false; 83 | static float autopilot_reset = 30.0f; 84 | static float autopilot_heading = 0.0f; 85 | 86 | static vec2_t control_stick; 87 | static float control_thrust; 88 | static bool control_fire; 89 | 90 | bool autopilot_update(float dt) { 91 | autopilot_reset -= dt; 92 | 93 | bool idle = fabsf(input_get_axis(0, AXIS_LS_X)) <= 0.01f 94 | && fabsf(input_get_axis(0, AXIS_LS_Y)) <= 0.01f 95 | && fabsf(input_get_axis(0, AXIS_RT)) <= 0.01f 96 | && !input_get_button(0, BUTTON_RB, BUTTON_HELD); 97 | 98 | if (!idle) { 99 | autopilot_reset = 30.0f; 100 | autopilot = false; 101 | return false; 102 | } 103 | 104 | if (!autopilot) { 105 | if (autopilot_reset > 0) { 106 | return false; 107 | } 108 | 109 | autopilot = true; 110 | 111 | zander_reset(); 112 | vec2_zero(control_stick.v); 113 | autopilot_heading = 2.0f; 114 | autopilot_reset = 300.0f; 115 | } 116 | 117 | if (autopilot_reset < 0.0f) { 118 | autopilot = false; 119 | 120 | vec2_zero(control_stick.v); 121 | control_thrust = 0.0f; 122 | control_fire = false; 123 | 124 | autopilot_reset = 2.0f; 125 | 126 | return true; 127 | } 128 | 129 | autopilot_heading -= dt; 130 | if (autopilot_heading <= 0) { 131 | control_stick.x = rand_range(-0.5f, 0.5f); 132 | control_stick.y = rand_range(-0.5f, 0.5f); 133 | 134 | autopilot_heading = rand_range(1.0f, 5.0f); 135 | } 136 | 137 | if (intersection.detected) { 138 | autopilot_reset -= 30.0f; 139 | vec2_zero(control_stick.v); 140 | autopilot_heading = 2.0f; 141 | } 142 | 143 | control_thrust = 0.35f; 144 | 145 | control_fire = autopilot_heading < 0.5f; 146 | 147 | return true; 148 | } 149 | 150 | void ship_init(void) { 151 | vec3_assign(ship_position.v, (float[3]){3.5f, 3.5f, 2.0f + ship_undercarriage}); 152 | vec3_zero(ship_rotation.v); 153 | vec3_zero(ship_velocity.v); 154 | } 155 | 156 | void ship_update(float dt) { 157 | 158 | if (!autopilot_update(dt)) { 159 | control_stick.x = input_get_axis(0, AXIS_LS_X); 160 | control_stick.y = input_get_axis(0, AXIS_LS_Y); 161 | control_thrust = input_get_axis(0, AXIS_RT); 162 | control_fire = input_get_button(0, BUTTON_RB, BUTTON_HELD); 163 | } 164 | 165 | debug_collision = intersection.detected; 166 | 167 | if (intersection.detected) { 168 | if (intersection.x < VOXELS_X) { 169 | float hitpos[VEC3_SIZE]; 170 | world_from_voxel(hitpos, (int32_t[]){intersection.x, intersection.y, 0}); 171 | objects_hit_and_destroy(hitpos); 172 | particles_add_splash(hitpos, false); 173 | } 174 | intersection = (intersection_t){false, ~0, ~0}; 175 | } 176 | 177 | float control_magnitude = vec2_length(control_stick.v); 178 | float control_direction = atan2f(-control_stick.y, control_stick.x); 179 | 180 | ship_rotation.y = control_magnitude * ship_pitch_max; 181 | 182 | float yaw = angle_diff(control_direction, ship_rotation.z); 183 | float yaw_speed = control_magnitude * ship_yaw_speed * dt; 184 | yaw = clamp(yaw, -yaw_speed, yaw_speed); 185 | 186 | ship_rotation.z += yaw; 187 | 188 | vec3_multiply_f(ship_velocity.v, ship_velocity.v, powf(ship_damping, dt)); 189 | 190 | float matrix[MAT4_SIZE]; 191 | mat4_identity(matrix); 192 | mat4_apply_rotation(matrix, ship_rotation.v); 193 | 194 | if (control_thrust > 0.0f) { 195 | float engine[VEC3_SIZE]; 196 | vec3_transform(engine, ship_engine_vector, matrix); 197 | 198 | float thrust[VEC3_SIZE]; 199 | float max_thrust = ship_thrust_max / max(1.0f, ship_position.z * 0.5f); 200 | vec3_multiply_f(thrust, engine, -control_thrust * max_thrust * dt); 201 | 202 | vec3_add(ship_velocity.v, ship_velocity.v, thrust); 203 | 204 | float exhaust[VEC3_SIZE]; 205 | vec3_multiply_f(exhaust, engine, ship_exhaust_speed); 206 | vec3_add(exhaust, exhaust, ship_velocity.v); 207 | 208 | vec3_multiply_f(engine, engine, ship_undercarriage); 209 | vec3_add(engine, engine, ship_position.v); 210 | 211 | int rate = clamp((int)(control_thrust * ship_exhaust_rate / dt), 1, 100); 212 | for (int i = 0; i < rate; ++i) { 213 | particles_add(engine, exhaust, PARTICLE_EXHAUST); 214 | } 215 | 216 | } 217 | ship_velocity.z -= world_gravity * dt; 218 | 219 | float dpos[VEC3_SIZE]; 220 | vec3_multiply_f(dpos, ship_velocity.v, dt); 221 | vec3_add(ship_position.v, ship_position.v, dpos); 222 | 223 | float ground = terrain_get_altitude(ship_position.x, ship_position.y); 224 | 225 | if (ship_position.z < ground + ship_undercarriage) { 226 | ship_position.z = ground + ship_undercarriage; 227 | 228 | vec3_multiply_f(ship_velocity.v, ship_velocity.v, 0.5f); 229 | ship_velocity.z = fabsf(ship_velocity.z); 230 | } 231 | 232 | if (control_fire) { 233 | bullet_time += dt; 234 | 235 | float cannon[VEC3_SIZE]; 236 | vec3_transform(cannon, ship_cannon_vector, matrix); 237 | 238 | float bullet[VEC3_SIZE]; 239 | vec3_multiply_f(bullet, cannon, ship_bullet_speed); 240 | vec3_add(bullet, bullet, ship_velocity.v); 241 | 242 | vec3_add(cannon, cannon, ship_position.v); 243 | 244 | while (bullet_time >= bullet_recharge) { 245 | bullet_time -= bullet_recharge; 246 | particles_add(cannon, bullet, PARTICLE_BULLET); 247 | } 248 | } else { 249 | bullet_time = bullet_recharge; 250 | } 251 | 252 | //printf("%g, %g\n", ship_position.x, ship_position.y); 253 | } 254 | 255 | 256 | static void draw_voxel(pixel_t* volume, const int* coordinate, const float* barycentric, const triangle_state_t* triangle) { 257 | int8_t surface = HEIGHT_MAP_OBJECT(coordinate[0], coordinate[1]); 258 | int8_t ground = HEIGHT_MAP_TERRAIN(coordinate[0], coordinate[1]); 259 | 260 | if (coordinate[2] <= surface) { 261 | intersection.detected = true; 262 | if (surface > ground) { 263 | intersection.x = coordinate[0]; 264 | intersection.y = coordinate[1]; 265 | } 266 | } 267 | 268 | if (coordinate[2] > ground) { 269 | volume[VOXEL_INDEX(coordinate[0], coordinate[1], coordinate[2])] = debug_collision ? ~triangle->colour : triangle->colour; 270 | 271 | // shadow 272 | surface = max(0, surface); 273 | if ((uint8_t)surface < VOXELS_Z) { 274 | uint32_t idx = VOXEL_INDEX(coordinate[0], coordinate[1], surface); 275 | if (volume[idx]&0b00100100) { 276 | volume[idx] = ((volume[idx]&0b10010010)>>1) | ((coordinate[0]^coordinate[1])&1); 277 | } 278 | } 279 | } 280 | } 281 | 282 | void ship_draw(pixel_t* volume) { 283 | float matrix[MAT4_SIZE]; 284 | float position[VEC3_SIZE]; 285 | 286 | graphics_triangle_shader_cb = draw_voxel; 287 | 288 | vec3_subtract(position, ship_position.v, world_position.v); 289 | 290 | mat4_identity(matrix); 291 | mat4_apply_translation(matrix, (float[3]){(VOXELS_X-1)*0.5f, (VOXELS_Y-1)*0.5f, 0}); 292 | mat4_apply_scale_f(matrix, world_scale); 293 | mat4_apply_translation(matrix, position); 294 | mat4_apply_rotation(matrix, ship_rotation.v); 295 | model_draw(volume, &ship_model, matrix); 296 | 297 | graphics_triangle_shader_cb = NULL; 298 | } 299 | -------------------------------------------------------------------------------- /src/platform/input.c: -------------------------------------------------------------------------------- 1 | #include "input.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "rammel.h" 17 | #include "timer.h" 18 | 19 | typedef struct { 20 | uint8_t button_states[BUTTON_COUNT]; 21 | float axis_states[AXIS_COUNT]; 22 | 23 | uint8_t combo_buffer[16]; 24 | uint32_t combo_cursor; 25 | uint32_t combo_last_time; 26 | 27 | int handle; 28 | uint8_t button_map[16]; 29 | uint8_t axis_map[8]; 30 | } controller_t; 31 | 32 | static controller_t input_controllers[CONTROLLERS_MAX] = {[0 ... (CONTROLLERS_MAX - 1)].handle = -1}; 33 | 34 | struct termios termzero; 35 | 36 | void reset_terminal_mode() { 37 | tcsetattr(0, TCSANOW, &termzero); 38 | } 39 | 40 | void input_set_nonblocking(void) { 41 | struct termios term; 42 | 43 | tcgetattr(STDIN_FILENO, &term); 44 | memcpy(&termzero, &term, sizeof(term)); 45 | 46 | atexit(reset_terminal_mode); 47 | 48 | term.c_lflag &= ~(ICANON | ECHO); 49 | tcsetattr(STDIN_FILENO, TCSANOW, &term); 50 | 51 | int fc = fcntl(STDIN_FILENO, F_GETFL, 0); 52 | fcntl(STDIN_FILENO, F_SETFL, fc | O_NONBLOCK); 53 | } 54 | 55 | bool input_get_button(controller_id_t controller, button_t button, button_event_t event) { 56 | if ((uint)controller >= count_of(input_controllers) || 57 | (uint)button >= count_of(input_controllers[controller].button_states)) { 58 | return false; 59 | } 60 | 61 | return (input_controllers[controller].button_states[button] & event) != 0; 62 | } 63 | 64 | float input_get_axis(controller_id_t controller, axis_t axis) { 65 | if ((uint)controller >= count_of(input_controllers) || 66 | (uint)axis >= count_of(input_controllers[controller].axis_states)) { 67 | return 0; 68 | } 69 | 70 | return input_controllers[controller].axis_states[axis]; 71 | } 72 | 73 | static void combo_press(controller_id_t controller, uint8_t button, uint32_t time) { 74 | controller_t* ctrl = &input_controllers[controller]; 75 | if (time - ctrl->combo_last_time >= 1000) { 76 | ctrl->combo_buffer[ctrl->combo_cursor] = 0; 77 | ctrl->combo_cursor = (ctrl->combo_cursor + 1) % count_of(ctrl->combo_buffer); 78 | } 79 | 80 | ctrl->combo_buffer[ctrl->combo_cursor] = button; 81 | ctrl->combo_cursor = (ctrl->combo_cursor + 1) % count_of(ctrl->combo_buffer); 82 | 83 | ctrl->combo_last_time = time; 84 | } 85 | 86 | bool input_get_combo(controller_id_t controller, const uint8_t* combo, uint8_t combo_length) { 87 | if ((uint)controller >= count_of(input_controllers)) { 88 | return false; 89 | } 90 | 91 | controller_t* ctrl = &input_controllers[controller]; 92 | uint32_t c = modulo(ctrl->combo_cursor - combo_length, count_of(ctrl->combo_buffer)); 93 | for (int i = 0; i < combo_length; ++i) { 94 | if (ctrl->combo_buffer[c] != combo[i]) { 95 | return false; 96 | } 97 | c = (c + 1) % count_of(ctrl->combo_buffer); 98 | } 99 | return true; 100 | } 101 | 102 | static bool try_open_controller(controller_id_t c) { 103 | char device[16]; 104 | if (snprintf(device, sizeof(device), "/dev/input/js%d", c) > sizeof(device)) { 105 | return false; 106 | } 107 | 108 | controller_t* ctrl = &input_controllers[c]; 109 | 110 | int handle = open(device, O_RDONLY | O_NONBLOCK); 111 | if (handle == -1) { 112 | return false; 113 | } 114 | 115 | memset(ctrl, 0, sizeof(*ctrl)); 116 | ctrl->handle = handle; 117 | 118 | uint16_t button_mapping[KEY_MAX - BTN_MISC + 1] = {0}; 119 | if (ioctl(ctrl->handle, JSIOCGBTNMAP, button_mapping) < 0) { 120 | printf("failed to get button mapping\n"); 121 | for (int i = 0; i < count_of(ctrl->button_map); ++i) { 122 | ctrl->button_map[i] = i; 123 | } 124 | 125 | } else { 126 | memset(ctrl->button_map, 0xff, sizeof(ctrl->button_map)); 127 | for (int i = 0; i < count_of(ctrl->button_map); ++i) { 128 | switch (button_mapping[i]) { 129 | case BTN_A: ctrl->button_map[i] = BUTTON_A; break; 130 | case BTN_B: ctrl->button_map[i] = BUTTON_B; break; 131 | case BTN_X: ctrl->button_map[i] = BUTTON_X; break; 132 | case BTN_Y: ctrl->button_map[i] = BUTTON_Y; break; 133 | case BTN_Z: ctrl->button_map[i] = BUTTON_VIEW; break; 134 | case BTN_TL: ctrl->button_map[i] = BUTTON_LB; break; 135 | case BTN_TR: ctrl->button_map[i] = BUTTON_RB; break; 136 | case BTN_TL2: ctrl->button_map[i] = BUTTON_LT; break; 137 | case BTN_TR2: ctrl->button_map[i] = BUTTON_RT; break; 138 | case BTN_SELECT: ctrl->button_map[i] = BUTTON_VIEW; break; 139 | case BTN_START: ctrl->button_map[i] = BUTTON_MENU; break; 140 | case BTN_MODE: ctrl->button_map[i] = BUTTON_MENU; break; 141 | } 142 | } 143 | } 144 | 145 | 146 | uint8_t axis_mapping[ABS_MAX + 1] = {0}; 147 | if (ioctl(ctrl->handle, JSIOCGAXMAP, axis_mapping) < 0) { 148 | printf("failed to get axis mapping\n"); 149 | for (int i = 0; i < count_of(ctrl->axis_map); ++i) { 150 | ctrl->axis_map[i] = i; 151 | } 152 | } else { 153 | memset(ctrl->axis_map, 0xff, sizeof(ctrl->axis_map)); 154 | for (int i = 0; i < count_of(ctrl->axis_map); ++i) { 155 | switch (axis_mapping[i]) { 156 | case ABS_X: ctrl->axis_map[i] = AXIS_LS_X; break; 157 | case ABS_Y: ctrl->axis_map[i] = AXIS_LS_Y; break; 158 | case ABS_Z: ctrl->axis_map[i] = AXIS_LT; break; 159 | case ABS_RX: ctrl->axis_map[i] = AXIS_RS_X; break; 160 | case ABS_RY: ctrl->axis_map[i] = AXIS_RS_Y; break; 161 | case ABS_RZ: ctrl->axis_map[i] = AXIS_RT; break; 162 | case ABS_HAT0X: ctrl->axis_map[i] = AXIS_D_X; break; 163 | case ABS_HAT0Y: ctrl->axis_map[i] = AXIS_D_Y; break; 164 | } 165 | } 166 | } 167 | 168 | return true; 169 | } 170 | 171 | static void button_press(controller_id_t c, button_t button, bool pressed, uint32_t time) { 172 | controller_t* ctrl = &input_controllers[c]; 173 | 174 | if (pressed) { 175 | combo_press(c, button, time); 176 | ctrl->button_states[button] |= BUTTON_PRESSED | BUTTON_HELD; 177 | } else { 178 | ctrl->button_states[button] = (ctrl->button_states[button] & ~BUTTON_HELD) | BUTTON_UNPRESSED; 179 | } 180 | } 181 | 182 | void input_update(void) { 183 | static uint32_t throttle = 0; 184 | ++throttle; 185 | 186 | for (int c = 0; c < CONTROLLERS_MAX; ++c) { 187 | controller_t* ctrl = &input_controllers[c]; 188 | 189 | if (ctrl->handle == -1) { 190 | if (throttle % (31 + c * 10) == 1) { 191 | try_open_controller(c); 192 | } 193 | } 194 | 195 | if (ctrl->handle != -1) { 196 | for (int i = 0; i < count_of(ctrl->button_states); ++i) { 197 | ctrl->button_states[i] &= BUTTON_HELD; 198 | } 199 | 200 | ssize_t events; 201 | struct js_event event; 202 | while ((events = read(ctrl->handle, &event, sizeof(event))) > 0) { 203 | if (event.type == JS_EVENT_BUTTON) { 204 | uint8_t button = 0xff; 205 | if (event.number < count_of(ctrl->button_map)) { 206 | button = ctrl->button_map[event.number]; 207 | } 208 | if (button < BUTTON_COUNT) { 209 | button_press(c, button, event.value, event.time); 210 | switch (button) { 211 | case BUTTON_LT: ctrl->axis_states[AXIS_LT] = event.value ? 1.0f : 0.0f; break; 212 | case BUTTON_RT: ctrl->axis_states[AXIS_RT] = event.value ? 1.0f : 0.0f; break; 213 | } 214 | } 215 | } else if (event.type == JS_EVENT_AXIS) { 216 | uint8_t axis = 0xff; 217 | if (event.number < count_of(ctrl->axis_map)) { 218 | axis = ctrl->axis_map[event.number]; 219 | } 220 | switch (axis) { 221 | case AXIS_LS_X: 222 | case AXIS_LS_Y: 223 | case AXIS_RS_X: 224 | case AXIS_RS_Y: { 225 | ctrl->axis_states[axis] = (float)event.value / 32767.0f; 226 | } break; 227 | 228 | case AXIS_LT: 229 | case AXIS_RT: { 230 | button_t button = (axis == AXIS_LT ? BUTTON_LT : BUTTON_RT); 231 | bool pressed = ctrl->axis_states[axis] <= 0.25f && event.value >= 16384; 232 | button_press(c, button, pressed, event.time); 233 | 234 | ctrl->axis_states[axis] = clamp((float)(event.value + 32000) / 64000.0f, 0.0f, 1.0f); 235 | } break; 236 | 237 | case AXIS_D_X: 238 | case AXIS_D_Y: { 239 | button_t button = (axis == AXIS_D_X ? BUTTON_LEFT : BUTTON_UP) + (event.value > 0); 240 | bool pressed = (fabsf(ctrl->axis_states[axis]) <= 0.25f && abs(event.value) >= 16384); 241 | button_press(c, button, pressed, event.time); 242 | 243 | ctrl->axis_states[axis] = (float)event.value / 32767.0f; 244 | } break; 245 | } 246 | } else { 247 | // printf("event time:%d value:%d type:%d number:%d\n", event.time, event.value, event.type, event.number); 248 | } 249 | } 250 | if (events < 0 && errno != EAGAIN) { 251 | close(ctrl->handle); 252 | ctrl->handle = -1; 253 | } 254 | } 255 | } 256 | 257 | static timespec_t timer = {0}; 258 | if (timer_elapsed_ms(&timer) > 1000) { 259 | // discard any button presses that happened during that suspiciously long pause 260 | memset(input_controllers->button_states, 0, sizeof(input_controllers->button_states)); 261 | } 262 | 263 | 264 | 265 | } 266 | 267 | -------------------------------------------------------------------------------- /src/toys/eighty/tubeface.xbm: -------------------------------------------------------------------------------- 1 | #define tubeface_width 128 2 | #define tubeface_height 128 3 | static unsigned char tubeface_bits[] = { 4 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 5 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 6 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 7 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 8 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 9 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 10 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 11 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 12 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 13 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 14 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 18 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 20 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 21 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 22 | 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 23 | 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 24 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 25 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 26 | 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 37 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 39 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 40 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 41 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 43 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 44 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 46 | 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 47 | 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 49 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 50 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 51 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 56 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 57 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 58 | 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 59 | 0x00, 0x00, 0xc0, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 60 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0xff, 0x03, 0x00, 0x00, 61 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 62 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 63 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 64 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 65 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 66 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 67 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 68 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 69 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 70 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 73 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 74 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 75 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 76 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 77 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 78 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 79 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 80 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 82 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 83 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 84 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 85 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 86 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 87 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 88 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 89 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 90 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 91 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 92 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 93 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 94 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 95 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 96 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 97 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 98 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 99 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 100 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 101 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 102 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 103 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 104 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 105 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 106 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 107 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 108 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 109 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 110 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 111 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 112 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 113 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 114 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 115 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 116 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 117 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 118 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 119 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 120 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 121 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 122 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 123 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 124 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 125 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 126 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 127 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 128 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 129 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 130 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 131 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 132 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 133 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 134 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 135 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 136 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 137 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 138 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 139 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 140 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 141 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 142 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 143 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 144 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 145 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 146 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 147 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 148 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 149 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 150 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 151 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 152 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 153 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 154 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 155 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 156 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 157 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 158 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 159 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 160 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 161 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 162 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 163 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 164 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 165 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 166 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 167 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 168 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 169 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 170 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 171 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 172 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 173 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 174 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 175 | -------------------------------------------------------------------------------- /src/toys/zander/zsintable.h: -------------------------------------------------------------------------------- 1 | #ifndef _ZSINTABLE_H_ 2 | #define _ZSINTABLE_H_ 3 | 4 | static const int32_t zsinTable[] = { 5 | 0x00000000, 0x00C90F87, 0x01921D1F, 0x025B26D7, 6 | 0x03242ABE, 0x03ED26E6, 0x04B6195D, 0x057F0034, 7 | 0x0647D97C, 0x0710A344, 0x07D95B9E, 0x08A2009A, 8 | 0x096A9049, 0x0A3308BC, 0x0AFB6805, 0x0BC3AC35, 9 | 0x0C8BD35D, 0x0D53DB92, 0x0E1BC2E3, 0x0EE38765, 10 | 0x0FAB272B, 0x1072A047, 0x1139F0CE, 0x120116D4, 11 | 0x12C8106E, 0x138EDBB0, 0x145576B1, 0x151BDF85, 12 | 0x15E21444, 0x16A81304, 0x176DD9DE, 0x183366E8, 13 | 0x18F8B83C, 0x19BDCBF2, 0x1A82A025, 0x1B4732EE, 14 | 0x1C0B826A, 0x1CCF8CB2, 0x1D934FE4, 0x1E56CA1D, 15 | 0x1F19F97A, 0x1FDCDC1A, 0x209F701C, 0x2161B39F, 16 | 0x2223A4C5, 0x22E541AE, 0x23A6887E, 0x24677757, 17 | 0x25280C5D, 0x25E845B5, 0x26A82185, 0x27679DF3, 18 | 0x2826B928, 0x28E5714A, 0x29A3C484, 0x2A61B101, 19 | 0x2B1F34EB, 0x2BDC4E6E, 0x2C98FBBA, 0x2D553AFB, 20 | 0x2E110A61, 0x2ECC681D, 0x2F875262, 0x3041C760, 21 | 0x30FBC54C, 0x31B54A5D, 0x326E54C6, 0x3326E2C2, 22 | 0x33DEF286, 0x3496824F, 0x354D9056, 0x36041AD8, 23 | 0x36BA2013, 0x376F9E45, 0x382493AF, 0x38D8FE92, 24 | 0x398CDD31, 0x3A402DD1, 0x3AF2EEB6, 0x3BA51E28, 25 | 0x3C56BA6F, 0x3D07C1D5, 0x3DB832A5, 0x3E680B2C, 26 | 0x3F1749B7, 0x3FC5EC97, 0x4073F21C, 0x4121589A, 27 | 0x41CE1E63, 0x427A41D0, 0x4325C135, 0x43D09AEC, 28 | 0x447ACD4F, 0x452456BC, 0x45CD358E, 0x46756827, 29 | 0x471CECE6, 0x47C3C22E, 0x4869E664, 0x490F57ED, 30 | 0x49B41533, 0x4A581C9D, 0x4AFB6C97, 0x4B9E038E, 31 | 0x4C3FDFF3, 0x4CE10033, 0x4D8162C3, 0x4E210616, 32 | 0x4EBFE8A4, 0x4F5E08E2, 0x4FFB654C, 0x5097FC5D, 33 | 0x5133CC93, 0x51CED46D, 0x5269126D, 0x53028517, 34 | 0x539B2AEF, 0x5433027C, 0x54CA0A49, 0x556040E2, 35 | 0x55F5A4D1, 0x568A34A8, 0x571DEEF8, 0x57B0D255, 36 | 0x5842DD53, 0x58D40E8B, 0x59646497, 0x59F3DE11, 37 | 0x5A827999, 0x5B1035CE, 0x5B9D1153, 0x5C290ACB, 38 | 0x5CB420DF, 0x5D3E5236, 0x5DC79D7B, 0x5E50015C, 39 | 0x5ED77C89, 0x5F5E0DB2, 0x5FE3B38C, 0x60686CCD, 40 | 0x60EC382E, 0x616F146A, 0x61F1003E, 0x6271FA68, 41 | 0x62F201AB, 0x637114CB, 0x63EF328E, 0x646C59BE, 42 | 0x64E88925, 0x6563BF91, 0x65DDFBD2, 0x66573CBA, 43 | 0x66CF811E, 0x6746C7D6, 0x67BD0FBB, 0x683257A9, 44 | 0x68A69E80, 0x6919E31F, 0x698C246B, 0x69FD6149, 45 | 0x6A6D98A3, 0x6ADCC963, 0x6B4AF277, 0x6BB812CF, 46 | 0x6C24295F, 0x6C8F351A, 0x6CF934FB, 0x6D6227F9, 47 | 0x6DCA0D13, 0x6E30E349, 0x6E96A99C, 0x6EFB5F11, 48 | 0x6F5F02B1, 0x6FC19384, 0x70231098, 0x708378FE, 49 | 0x70E2CBC5, 0x71410803, 0x719E2CD1, 0x71FA3947, 50 | 0x72552C84, 0x72AF05A6, 0x7307C3CF, 0x735F6625, 51 | 0x73B5EBD0, 0x740B53F9, 0x745F9DD0, 0x74B2C882, 52 | 0x7504D344, 0x7555BD4A, 0x75A585CE, 0x75F42C09, 53 | 0x7641AF3B, 0x768E0EA4, 0x76D94988, 0x77235F2C, 54 | 0x776C4EDA, 0x77B417DE, 0x77FAB987, 0x78403327, 55 | 0x78848413, 0x78C7ABA0, 0x7909A92B, 0x794A7C10, 56 | 0x798A23B0, 0x79C89F6C, 0x7A05EEAC, 0x7A4210D7, 57 | 0x7A7D055A, 0x7AB6CBA2, 0x7AEF6322, 0x7B26CB4E, 58 | 0x7B5D039C, 0x7B920B88, 0x7BC5E28E, 0x7BF8882F, 59 | 0x7C29FBED, 0x7C5A3D4E, 0x7C894BDC, 0x7CB72723, 60 | 0x7CE3CEB0, 0x7D0F4217, 0x7D3980EB, 0x7D628AC5, 61 | 0x7D8A5F3F, 0x7DB0FDF6, 0x7DD6668D, 0x7DFA98A7, 62 | 0x7E1D93E8, 0x7E3F57FD, 0x7E5FE492, 0x7E7F3955, 63 | 0x7E9D55FB, 0x7EBA3A38, 0x7ED5E5C5, 0x7EF0585E, 64 | 0x7F0991C2, 0x7F2191B3, 0x7F3857F5, 0x7F4DE44F, 65 | 0x7F62368E, 0x7F754E7E, 0x7F872BF2, 0x7F97CEBC, 66 | 0x7FA736B3, 0x7FB563B2, 0x7FC25595, 0x7FCE0C3D, 67 | 0x7FD8878D, 0x7FE1C76A, 0x7FE9CBBF, 0x7FF09477, 68 | 0x7FF62181, 0x7FFA72D0, 0x7FFD8859, 0x7FFF6216, 69 | 0x7FFFFFFE, 0x7FFF6216, 0x7FFD8859, 0x7FFA72D0, 70 | 0x7FF62181, 0x7FF09477, 0x7FE9CBBF, 0x7FE1C76A, 71 | 0x7FD8878D, 0x7FCE0C3D, 0x7FC25595, 0x7FB563B2, 72 | 0x7FA736B3, 0x7F97CEBC, 0x7F872BF2, 0x7F754E7E, 73 | 0x7F62368E, 0x7F4DE44F, 0x7F3857F5, 0x7F2191B3, 74 | 0x7F0991C2, 0x7EF0585E, 0x7ED5E5C5, 0x7EBA3A38, 75 | 0x7E9D55FB, 0x7E7F3955, 0x7E5FE492, 0x7E3F57FD, 76 | 0x7E1D93E8, 0x7DFA98A7, 0x7DD6668D, 0x7DB0FDF6, 77 | 0x7D8A5F3F, 0x7D628AC5, 0x7D3980EB, 0x7D0F4217, 78 | 0x7CE3CEB0, 0x7CB72723, 0x7C894BDC, 0x7C5A3D4E, 79 | 0x7C29FBED, 0x7BF8882F, 0x7BC5E28E, 0x7B920B88, 80 | 0x7B5D039C, 0x7B26CB4E, 0x7AEF6322, 0x7AB6CBA2, 81 | 0x7A7D055A, 0x7A4210D7, 0x7A05EEAC, 0x79C89F6C, 82 | 0x798A23B0, 0x794A7C10, 0x7909A92B, 0x78C7ABA0, 83 | 0x78848413, 0x78403327, 0x77FAB987, 0x77B417DE, 84 | 0x776C4EDA, 0x77235F2C, 0x76D94988, 0x768E0EA4, 85 | 0x7641AF3B, 0x75F42C09, 0x75A585CE, 0x7555BD4A, 86 | 0x7504D344, 0x74B2C882, 0x745F9DD0, 0x740B53FA, 87 | 0x73B5EBD0, 0x735F6625, 0x7307C3CF, 0x72AF05A5, 88 | 0x72552C84, 0x71FA3948, 0x719E2CD1, 0x71410803, 89 | 0x70E2CBC5, 0x708378FD, 0x70231098, 0x6FC19384, 90 | 0x6F5F02B0, 0x6EFB5F11, 0x6E96A99C, 0x6E30E348, 91 | 0x6DCA0D13, 0x6D6227F9, 0x6CF934FB, 0x6C8F351A, 92 | 0x6C24295F, 0x6BB812D0, 0x6B4AF277, 0x6ADCC963, 93 | 0x6A6D98A3, 0x69FD6149, 0x698C246B, 0x6919E320, 94 | 0x68A69E80, 0x683257AA, 0x67BD0FBC, 0x6746C7D6, 95 | 0x66CF811E, 0x66573CBA, 0x65DDFBD1, 0x6563BF91, 96 | 0x64E88925, 0x646C59BF, 0x63EF328E, 0x637114CB, 97 | 0x62F201AC, 0x6271FA68, 0x61F1003E, 0x616F146B, 98 | 0x60EC382E, 0x60686CCE, 0x5FE3B38D, 0x5F5E0DB2, 99 | 0x5ED77C89, 0x5E50015D, 0x5DC79D7B, 0x5D3E5236, 100 | 0x5CB420DF, 0x5C290ACB, 0x5B9D1153, 0x5B1035CF, 101 | 0x5A82799A, 0x59F3DE11, 0x59646497, 0x58D40E8C, 102 | 0x5842DD53, 0x57B0D256, 0x571DEEFA, 0x568A34A8, 103 | 0x55F5A4D2, 0x556040E2, 0x54CA0A49, 0x5433027D, 104 | 0x539B2AEF, 0x53028516, 0x5269126E, 0x51CED46E, 105 | 0x5133CC93, 0x5097FC5D, 0x4FFB654D, 0x4F5E08E2, 106 | 0x4EBFE8A4, 0x4E210617, 0x4D8162C4, 0x4CE10033, 107 | 0x4C3FDFF3, 0x4B9E0390, 0x4AFB6C97, 0x4A581C9D, 108 | 0x49B41533, 0x490F57ED, 0x4869E664, 0x47C3C22F, 109 | 0x471CECE6, 0x46756827, 0x45CD358F, 0x452456BC, 110 | 0x447ACD50, 0x43D09AEC, 0x4325C134, 0x427A41D0, 111 | 0x41CE1E64, 0x4121589B, 0x4073F21C, 0x3FC5EC98, 112 | 0x3F1749B8, 0x3E680B2C, 0x3DB832A6, 0x3D07C1D6, 113 | 0x3C56BA6F, 0x3BA51E28, 0x3AF2EEB7, 0x3A402DD0, 114 | 0x398CDD32, 0x38D8FE93, 0x382493AF, 0x376F9E45, 115 | 0x36BA2013, 0x36041AD7, 0x354D9056, 0x3496824F, 116 | 0x33DEF287, 0x3326E2C1, 0x326E54C7, 0x31B54A5E, 117 | 0x30FBC54C, 0x3041C760, 0x2F875262, 0x2ECC681D, 118 | 0x2E110A61, 0x2D553AFC, 0x2C98FBB9, 0x2BDC4E6F, 119 | 0x2B1F34EB, 0x2A61B100, 0x29A3C484, 0x28E5714B, 120 | 0x2826B927, 0x27679DF3, 0x26A82186, 0x25E845B6, 121 | 0x25280C5D, 0x24677757, 0x23A6887F, 0x22E541AE, 122 | 0x2223A4C5, 0x2161B3A0, 0x209F701B, 0x1FDCDC1A, 123 | 0x1F19F97B, 0x1E56CA1D, 0x1D934FE5, 0x1CCF8CB3, 124 | 0x1C0B8269, 0x1B4732EF, 0x1A82A026, 0x19BDCBF1, 125 | 0x18F8B83C, 0x183366E8, 0x176DD9DD, 0x16A81304, 126 | 0x15E21444, 0x151BDF86, 0x145576B0, 0x138EDBB1, 127 | 0x12C8106F, 0x120116D4, 0x1139F0CF, 0x1072A048, 128 | 0x0FAB272A, 0x0EE38765, 0x0E1BC2E4, 0x0D53DB91, 129 | 0x0C8BD35E, 0x0BC3AC35, 0x0AFB6804, 0x0A3308BC, 130 | 0x096A9049, 0x08A20099, 0x07D95B9E, 0x0710A345, 131 | 0x0647D97D, 0x057F0034, 0x04B6195D, 0x03ED26E7, 132 | 0x03242ABE, 0x025B26D7, 0x01921D20, 0x00C90F87, 133 | 0x00000000, 0xFF36F079, 0xFE6DE2E0, 0xFDA4D929, 134 | 0xFCDBD542, 0xFC12D91A, 0xFB49E6A3, 0xFA80FFCD, 135 | 0xF9B82684, 0xF8EF5CBC, 0xF826A463, 0xF75DFF67, 136 | 0xF6956FB7, 0xF5CCF745, 0xF50497FC, 0xF43C53CB, 137 | 0xF3742CA3, 0xF2AC2470, 0xF1E43D1D, 0xF11C789B, 138 | 0xF054D8D6, 0xEF8D5FB9, 0xEEC60F32, 0xEDFEE92D, 139 | 0xED37EF92, 0xEC712450, 0xEBAA8950, 0xEAE4207B, 140 | 0xEA1DEBBC, 0xE957ECFC, 0xE8922624, 0xE7CC9918, 141 | 0xE70747C5, 0xE642340F, 0xE57D5FDB, 0xE4B8CD12, 142 | 0xE3F47D97, 0xE330734D, 0xE26CB01C, 0xE1A935E4, 143 | 0xE0E60685, 0xE02323E6, 0xDF608FE5, 0xDE9E4C61, 144 | 0xDDDC5B3C, 0xDD1ABE53, 0xDC597782, 0xDB9888A9, 145 | 0xDAD7F3A4, 0xDA17BA4A, 0xD957DE7B, 0xD898620D, 146 | 0xD7D946DA, 0xD71A8EB6, 0xD65C3B7C, 0xD59E4F01, 147 | 0xD4E0CB15, 0xD423B192, 0xD3670447, 0xD2AAC505, 148 | 0xD1EEF59F, 0xD13397E3, 0xD078AD9E, 0xCFBE38A1, 149 | 0xCF043AB5, 0xCE4AB5A3, 0xCD91AB3A, 0xCCD91D3F, 150 | 0xCC210D79, 0xCB697DB1, 0xCAB26FAB, 0xC9FBE529, 151 | 0xC945DFED, 0xC89061BC, 0xC7DB6C52, 0xC727016E, 152 | 0xC67322CF, 0xC5BFD230, 0xC50D114A, 0xC45AE1D8, 153 | 0xC3A94592, 0xC2F83E2B, 0xC247CD5B, 0xC197F4D5, 154 | 0xC0E8B649, 0xC03A1369, 0xBF8C0DE5, 0xBEDEA766, 155 | 0xBE31E19D, 0xBD85BE31, 0xBCDA3ECD, 0xBC2F6514, 156 | 0xBB8532B1, 0xBADBA945, 0xBA32CA72, 0xB98A97DA, 157 | 0xB8E3131B, 0xB83C3DD2, 0xB796199D, 0xB6F0A814, 158 | 0xB64BEACD, 0xB5A7E364, 0xB504936A, 0xB461FC71, 159 | 0xB3C0200E, 0xB31EFFCE, 0xB27E9D3D, 0xB1DEF9EA, 160 | 0xB140175D, 0xB0A1F71F, 0xB0049AB4, 0xAF6803A3, 161 | 0xAECC336E, 0xAE312B93, 0xAD96ED93, 0xACFD7AEA, 162 | 0xAC64D511, 0xABCCFD84, 0xAB35F5B7, 0xAA9FBF1E, 163 | 0xAA0A5B2F, 0xA975CB58, 0xA8E21107, 0xA84F2DAB, 164 | 0xA7BD22AD, 0xA72BF174, 0xA69B9B69, 0xA60C21EF, 165 | 0xA57D8667, 0xA4EFCA32, 0xA462EEAE, 0xA3D6F536, 166 | 0xA34BDF21, 0xA2C1ADCB, 0xA2386286, 0xA1AFFEA4, 167 | 0xA1288378, 0xA0A1F24F, 0xA01C4C74, 0x9F979333, 168 | 0x9F13C7D2, 0x9E90EB96, 0x9E0EFFC4, 0x9D8E0599, 169 | 0x9D0DFE55, 0x9C8EEB37, 0x9C10CD72, 0x9B93A642, 170 | 0x9B1776DD, 0x9A9C4070, 0x9A22042E, 0x99A8C347, 171 | 0x99307EE2, 0x98B93829, 0x9842F046, 0x97CDA857, 172 | 0x97596180, 0x96E61CE2, 0x9673DB96, 0x96029EB6, 173 | 0x9592675E, 0x9523369D, 0x94B50D88, 0x9447ED31, 174 | 0x93DBD6A1, 0x9370CAE4, 0x9306CB06, 0x929DD807, 175 | 0x9235F2EC, 0x91CF1CB8, 0x91695664, 0x9104A0EE, 176 | 0x90A0FD50, 0x903E6C7C, 0x8FDCEF67, 0x8F7C8703, 177 | 0x8F1D343B, 0x8EBEF7FC, 0x8E61D32F, 0x8E05C6B8, 178 | 0x8DAAD37D, 0x8D50FA5B, 0x8CF83C31, 0x8CA099DC, 179 | 0x8C4A1430, 0x8BF4AC06, 0x8BA06231, 0x8B4D377E, 180 | 0x8AFB2CBC, 0x8AAA42B6, 0x8A5A7A32, 0x8A0BD3F6, 181 | 0x89BE50C5, 0x8971F15C, 0x8926B678, 0x88DCA0D5, 182 | 0x8893B126, 0x884BE821, 0x88054679, 0x87BFCCD9, 183 | 0x877B7BED, 0x87385460, 0x86F656D5, 0x86B583EF, 184 | 0x8675DC51, 0x86376094, 0x85FA1154, 0x85BDEF29, 185 | 0x8582FAA6, 0x8549345D, 0x85109CDE, 0x84D934B2, 186 | 0x84A2FC63, 0x846DF478, 0x843A1D72, 0x840777D1, 187 | 0x83D60413, 0x83A5C2B1, 0x8376B424, 0x8348D8DD, 188 | 0x831C3150, 0x82F0BDEA, 0x82C67F15, 0x829D753B, 189 | 0x8275A0C2, 0x824F020A, 0x82299973, 0x8205675A, 190 | 0x81E26C18, 0x81C0A803, 0x81A01B6F, 0x8180C6AB, 191 | 0x8162AA05, 0x8145C5C9, 0x812A1A3B, 0x810FA7A2, 192 | 0x80F66E3E, 0x80DE6E4D, 0x80C7A80B, 0x80B21BB1, 193 | 0x809DC972, 0x808AB181, 0x8078D40E, 0x80683144, 194 | 0x8058C94D, 0x804A9C4E, 0x803DAA6B, 0x8031F3C3, 195 | 0x80277873, 0x801E3896, 0x80163441, 0x800F6B89, 196 | 0x8009DE7F, 0x80058D30, 0x800277A7, 0x80009DEB, 197 | 0x80000001, 0x80009DEB, 0x800277A7, 0x80058D30, 198 | 0x8009DE7F, 0x800F6B89, 0x80163441, 0x801E3895, 199 | 0x80277873, 0x8031F3C3, 0x803DAA6B, 0x804A9C4E, 200 | 0x8058C94D, 0x80683145, 0x8078D40E, 0x808AB182, 201 | 0x809DC972, 0x80B21BB1, 0x80C7A80B, 0x80DE6E4D, 202 | 0x80F66E3E, 0x810FA7A1, 0x812A1A3B, 0x8145C5C8, 203 | 0x8162AA05, 0x8180C6AA, 0x81A01B6E, 0x81C0A803, 204 | 0x81E26C18, 0x8205675A, 0x82299972, 0x824F0209, 205 | 0x8275A0C2, 0x829D753B, 0x82C67F15, 0x82F0BDEA, 206 | 0x831C314F, 0x8348D8DD, 0x8376B424, 0x83A5C2B1, 207 | 0x83D60413, 0x840777D0, 0x843A1D71, 0x846DF478, 208 | 0x84A2FC63, 0x84D934B2, 0x85109CDE, 0x8549345D, 209 | 0x8582FAA6, 0x85BDEF29, 0x85FA1153, 0x86376093, 210 | 0x8675DC50, 0x86B583EF, 0x86F656D4, 0x87385460, 211 | 0x877B7BED, 0x87BFCCD8, 0x88054679, 0x884BE821, 212 | 0x8893B126, 0x88DCA0D4, 0x8926B678, 0x8971F15B, 213 | 0x89BE50C5, 0x8A0BD3F6, 0x8A5A7A32, 0x8AAA42B6, 214 | 0x8AFB2CBB, 0x8B4D377D, 0x8BA06231, 0x8BF4AC06, 215 | 0x8C4A1430, 0x8CA099DB, 0x8CF83C30, 0x8D50FA5A, 216 | 0x8DAAD37D, 0x8E05C6B8, 0x8E61D32F, 0x8EBEF7FB, 217 | 0x8F1D343A, 0x8F7C8702, 0x8FDCEF66, 0x903E6C7B, 218 | 0x90A0FD4F, 0x9104A0EE, 0x91695664, 0x91CF1CB7, 219 | 0x9235F2EC, 0x929DD806, 0x9306CB05, 0x9370CAE4, 220 | 0x93DBD6A0, 0x9447ED31, 0x94B50D88, 0x9523369D, 221 | 0x9592675E, 0x96029EB6, 0x9673DB95, 0x96E61CE2, 222 | 0x9759617F, 0x97CDA856, 0x9842F045, 0x98B93829, 223 | 0x99307EE2, 0x99A8C347, 0x9A22042D, 0x9A9C406F, 224 | 0x9B1776DC, 0x9B93A641, 0x9C10CD72, 0x9C8EEB36, 225 | 0x9D0DFE54, 0x9D8E0598, 0x9E0EFFC3, 0x9E90EB95, 226 | 0x9F13C7D2, 0x9F979334, 0xA01C4C73, 0xA0A1F24E, 227 | 0xA1288376, 0xA1AFFEA3, 0xA2386285, 0xA2C1ADC9, 228 | 0xA34BDF21, 0xA3D6F535, 0xA462EEAC, 0xA4EFCA31, 229 | 0xA57D8667, 0xA60C21ED, 0xA69B9B69, 0xA72BF175, 230 | 0xA7BD22AB, 0xA84F2DAA, 0xA8E21108, 0xA975CB56, 231 | 0xAA0A5B2E, 0xAA9FBF1F, 0xAB35F5B5, 0xABCCFD83, 232 | 0xAC64D512, 0xACFD7AE8, 0xAD96ED92, 0xAE312B93, 233 | 0xAECC336C, 0xAF6803A3, 0xB0049AB5, 0xB0A1F71D, 234 | 0xB140175C, 0xB1DEF9EA, 0xB27E9D3C, 0xB31EFFCD, 235 | 0xB3C0200F, 0xB461FC70, 0xB5049369, 0xB5A7E365, 236 | 0xB64BEACD, 0xB6F0A813, 0xB796199A, 0xB83C3DD1, 237 | 0xB8E3131A, 0xB98A97D7, 0xBA32CA71, 0xBADBA944, 238 | 0xBB8532AF, 0xBC2F6514, 0xBCDA3ECC, 0xBD85BE2F, 239 | 0xBE31E19C, 0xBEDEA767, 0xBF8C0DE2, 0xC03A1368, 240 | 0xC0E8B649, 0xC197F4D3, 0xC247CD5A, 0xC2F83E2C, 241 | 0xC3A9458F, 0xC45AE1D8, 0xC50D114B, 0xC5BFD22E, 242 | 0xC67322CE, 0xC727016F, 0xC7DB6C4F, 0xC89061BB, 243 | 0xC945DFEE, 0xC9FBE527, 0xCAB26FAA, 0xCB697DB2, 244 | 0xCC210D78, 0xCCD91D3E, 0xCD91AB3B, 0xCE4AB5A2, 245 | 0xCF043AB4, 0xCFBE38A2, 0xD078AD9D, 0xD13397E3, 246 | 0xD1EEF5A0, 0xD2AAC504, 0xD3670446, 0xD423B18F, 247 | 0xD4E0CB14, 0xD59E4F00, 0xD65C3B7A, 0xD71A8EB5, 248 | 0xD7D946D9, 0xD898620B, 0xD957DE7A, 0xDA17BA4B, 249 | 0xDAD7F3A1, 0xDB9888A8, 0xDC597783, 0xDD1ABE50, 250 | 0xDDDC5B3B, 0xDE9E4C62, 0xDF608FE2, 0xE02323E5, 251 | 0xE0E60686, 0xE1A935E1, 0xE26CB01B, 0xE330734F, 252 | 0xE3F47D95, 0xE4B8CD11, 0xE57D5FDC, 0xE642340C, 253 | 0xE70747C4, 0xE7CC9919, 0xE8922621, 0xE957ECFC, 254 | 0xEA1DEBBD, 0xEAE4207A, 0xEBAA894F, 0xEC712451, 255 | 0xED37EF91, 0xEDFEE92C, 0xEEC60F33, 0xEF8D5FB8, 256 | 0xF054D8D5, 0xF11C7898, 0xF1E43D1C, 0xF2AC246F, 257 | 0xF3742CA0, 0xF43C53CA, 0xF50497FC, 0xF5CCF742, 258 | 0xF6956FB6, 0xF75DFF67, 0xF826A460, 0xF8EF5CBB, 259 | 0xF9B82685, 0xFA80FFCA, 0xFB49E6A2, 0xFC12D91B, 260 | 0xFCDBD540, 0xFDA4D928, 0xFE6DE2E1, 0xFF36F077, 261 | }; 262 | 263 | #endif --------------------------------------------------------------------------------