diff --git a/buildsettings.h b/buildsettings.h index 1473356..9cc09be 100644 --- a/buildsettings.h +++ b/buildsettings.h @@ -18,7 +18,7 @@ // Intensive profiling means profiling a lot of little tiny stuff. Not always enabled because tanks performance // #define INTENSIVE_PROFILING // #define DEBUG_RENDERING -#define DEBUG_WORLD +// #define DEBUG_WORLD // #define UNLOCK_ALL #define TIME_BETWEEN_WORLD_SAVE 1000000.0f // #define TIME_BETWEEN_WORLD_SAVE 1.0f @@ -26,7 +26,7 @@ #define DEBUG_TOOLS #define CHIPMUNK_INTEGRITY_CHECK // #define FAT_THRUSTERS -#define NO_GRAVITY +// #define NO_GRAVITY // #define NO_SUNS #else diff --git a/flight.rdbg b/flight.rdbg index de8dda0..f60eb77 100644 Binary files a/flight.rdbg and b/flight.rdbg differ diff --git a/gamestate.c b/gamestate.c index 04a769e..a8534f4 100644 --- a/gamestate.c +++ b/gamestate.c @@ -350,8 +350,10 @@ static void raycast_query_callback(cpShape *shape, cpVect point, cpVect normal, static THREADLOCAL cpVect from_point = {0}; static int sort_bodies_callback(const void *a, const void *b) { - double a_dist = cpvdist(cpBodyGetPosition((cpBody *)a), from_point); - double b_dist = cpvdist(cpBodyGetPosition((cpBody *)b), from_point); + cpVect a_pos = cpBodyGetPosition(* (cpBody **)a); + cpVect b_pos = cpBodyGetPosition(* (cpBody **)b); + double a_dist = cpvdist(a_pos, from_point); + double b_dist = cpvdist(b_pos, from_point); if (a_dist - b_dist < 0.0) { return -1; @@ -694,6 +696,29 @@ void box_create(GameState *gs, Entity *new_box, Entity *grid, cpVect pos, enum B box_add_to_boxes(gs, grid, new_box); } +int platonic_detection_compare(const void *a, const void *b) +{ + PlatonicDetection *a_detection = (PlatonicDetection *)a; + PlatonicDetection *b_detection = (PlatonicDetection *)b; + double a_intensity = a_detection->intensity; + double b_intensity = b_detection->intensity; + if(a_detection->intensity == 0.0) a_intensity = INFINITY; + if(b_detection->intensity == 0.0) b_intensity = INFINITY; + double result = (a_intensity - b_intensity); + if (result < 0.0) + { + return -1; + } + else if (result > 0.0) + { + return 1; + } + else + { + return 0; + } +} + cpVect box_compass_vector(Entity *box) { @@ -1572,6 +1597,11 @@ SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e) SER_MAYBE_RETURN(ser_f(ser, &e->currently_scanning_progress)); SER_VAR(&e->blueprints_learned); SER_MAYBE_RETURN(ser_f(ser, &e->scanner_head_rotate)); + for (int i = 0; i < SCANNER_MAX_PLATONICS; i++) + { + SER_MAYBE_RETURN(ser_V2(ser, &e->detected_platonics[i].direction)); + SER_MAYBE_RETURN(ser_f(ser, &e->detected_platonics[i].intensity)); + } for (int i = 0; i < SCANNER_MAX_POINTS; i++) { SER_VAR(&e->scanner_points[i]); @@ -2407,8 +2437,8 @@ void create_initial_world(GameState *gs) entity_set_pos(grid, cpv(1.5, 0.0)); BOX_AT_TYPE(grid, cpv(0.0, 0.0), BoxExplosive); BOX_AT_TYPE(grid, cpv(-BOX_SIZE, 0.0), BoxScanner); - BOX_AT_TYPE(grid, cpv(-BOX_SIZE*2.0, 0.0), BoxSolarPanel); - BOX_AT_TYPE(grid, cpv(-BOX_SIZE*3.0, 0.0), BoxSolarPanel); + BOX_AT_TYPE(grid, cpv(-BOX_SIZE * 2.0, 0.0), BoxSolarPanel); + BOX_AT_TYPE(grid, cpv(-BOX_SIZE * 3.0, 0.0), BoxSolarPanel); entity_ensure_in_orbit(gs, grid); } @@ -2419,7 +2449,7 @@ void create_initial_world(GameState *gs) BOX_AT_TYPE(grid, cpv(0.0, 0.0), BoxHullpiece); entity_ensure_in_orbit(gs, grid); } - + #endif #if 0 // merge box @@ -3218,26 +3248,32 @@ void process(struct GameState *gs, double dt) { for (int i = 0; i < SCANNER_MAX_POINTS; i++) cur_box->scanner_points[i] = (struct ScannerPoint){0}; + for (int i = 0; i < SCANNER_MAX_PLATONICS; i++) + cur_box->detected_platonics[i] = (PlatonicDetection){0}; if (cur_box->energy_effectiveness >= 1.0) { - /* cpVect from_pos = entity_pos(cur_box); - cpVect nearest = {0}; - double nearest_dist = INFINITY; + PlatonicDetection detections[MAX_BOX_TYPES] = {0}; for (int i = 0; i < MAX_BOX_TYPES; i++) { cpVect cur_pos = gs->platonic_positions[i]; if (cpvlength(cur_pos) > 0.0) // zero is uninitialized, the platonic solid doesn't exist (probably) @Robust do better { - double length_to_cur = cpvdist(from_pos, cur_pos); - if (length_to_cur < nearest_dist) - { - nearest_dist = length_to_cur; - nearest = cur_pos; - } + cpVect towards = cpvsub(cur_pos, from_pos); + double length_to_cur = cpvlength(towards); + detections[i].direction = cpvnormalize(towards); + detections[i].intensity = length_to_cur; // so it sorts correctly, changed to intensity correctly after sorting + } + } + qsort(detections, MAX_BOX_TYPES, sizeof(detections[0]), platonic_detection_compare); + for (int i = 0; i < SCANNER_MAX_PLATONICS; i++) + { + cur_box->detected_platonics[i] = detections[i]; + if (cur_box->detected_platonics[i].intensity > 0.0) + { + cur_box->detected_platonics[i].intensity = max(0.1, 1.0 - clamp01(cur_box->detected_platonics[i].intensity / 100.0)); } } - */ circle_query(gs->space, entity_pos(cur_box), SCANNER_MAX_RANGE); cpBody *body_results[512] = {0}; @@ -3262,18 +3298,16 @@ void process(struct GameState *gs, double dt) } } from_point = entity_pos(cur_box); - qsort(body_results, cur_results_len, sizeof(cpBody *), sort_bodies_callback); + size_t sizeof_element = sizeof(body_results[0]); + qsort(body_results, cur_results_len, sizeof_element, sort_bodies_callback); size_t bodies_detected = cur_results_len < SCANNER_MAX_POINTS ? cur_results_len : SCANNER_MAX_POINTS; for (int i = 0; i < bodies_detected; i++) { cpVect rel_vect = cpvsub(cpBodyGetPosition(body_results[i]), from_point); double vect_length = cpvlength(rel_vect); - if (vect_length > SCANNER_MIN_RANGE) + if (SCANNER_MIN_RANGE < vect_length && vect_length < SCANNER_MAX_RANGE) { - if (vect_length > SCANNER_MAX_VIEWPORT_RANGE) - { - rel_vect = cpvmult(cpvnormalize(rel_vect), SCANNER_MAX_RANGE); - } + cpVect radar_vect = cpvmult(cpvnormalize(rel_vect), clamp01(vect_length / SCANNER_MAX_VIEWPORT_RANGE)); enum ScannerPointKind kind = Platonic; Entity *body_entity = cp_body_entity(body_results[i]); @@ -3297,10 +3331,13 @@ void process(struct GameState *gs, double dt) { kind = Enemy; } + cpVect into_char_vect = cpvmult(radar_vect, 126.0); + flight_assert(fabs(into_char_vect.x) <= 126.0); + flight_assert(fabs(into_char_vect.y) <= 126.0); cur_box->scanner_points[i] = (struct ScannerPoint){ .kind = (char)kind, - .x = (char)((rel_vect.x / SCANNER_MAX_VIEWPORT_RANGE) * 128.0), - .y = (char)((rel_vect.y / SCANNER_MAX_VIEWPORT_RANGE) * 128.0), + .x = (char)(into_char_vect.x), + .y = (char)(into_char_vect.y), }; } } diff --git a/horizontal_lightning.glsl b/horizontal_lightning.glsl new file mode 100644 index 0000000..a890175 --- /dev/null +++ b/horizontal_lightning.glsl @@ -0,0 +1,145 @@ +@module horizontal_lightning + +@vs vs +in vec4 coord; +out vec2 texUV; +void main() { + gl_Position = vec4(coord.xy, 0.0, 1.0); + texUV = coord.zw; +} +@end + +@fs fs +uniform uniforms { + float iTime; + float alpha; +}; +in vec2 texUV; +out vec4 fragColor; +vec4 permute(vec4 t) { + return t * (t * 34.0 + 133.0); +} + +// Gradient set is a normalized expanded rhombic dodecahedron +vec3 grad(float hash) { + + // Random vertex of a cube, +/- 1 each + vec3 cube = mod(floor(hash / vec3(1.0, 2.0, 4.0)), 2.0) * 2.0 - 1.0; + + // Random edge of the three edges connected to that vertex + // Also a cuboctahedral vertex + // And corresponds to the face of its dual, the rhombic dodecahedron + vec3 cuboct = cube; + int selected_edge =int(hash / 16.0) ; + if(selected_edge == 0) + cuboct.x = 0.0; + if(selected_edge == 1) + cuboct.y = 0.0; + if(selected_edge == 2) + cuboct.z = 0.0; + + // In a funky way, pick one of the four points on the rhombic face + float type = mod(floor(hash / 8.0), 2.0); + vec3 rhomb = (1.0 - type) * cube + type * (cuboct + cross(cube, cuboct)); + + // Expand it so that the new edges are the same length + // as the existing ones + vec3 grad = cuboct * 1.22474487139 + rhomb; + + // To make all gradients the same length, we only need to shorten the + // second type of vector. We also put in the whole noise scale constant. + // The compiler should reduce it into the existing floats. I think. + grad *= (1.0 - 0.042942436724648037 * type) * 3.5946317686139184; + + return grad; +} + +// BCC lattice split up into 2 cube lattices +vec4 bccNoiseDerivativesPart(vec3 X) { + vec3 b = floor(X); + vec4 i4 = vec4(X - b, 2.5); + + // Pick between each pair of oppposite corners in the cube. + vec3 v1 = b + floor(dot(i4, vec4(.25))); + vec3 v2 = b + vec3(1, 0, 0) + vec3(-1, 1, 1) * floor(dot(i4, vec4(-.25, .25, .25, .35))); + vec3 v3 = b + vec3(0, 1, 0) + vec3(1, -1, 1) * floor(dot(i4, vec4(.25, -.25, .25, .35))); + vec3 v4 = b + vec3(0, 0, 1) + vec3(1, 1, -1) * floor(dot(i4, vec4(.25, .25, -.25, .35))); + + // Gradient hashes for the four vertices in this half-lattice. + vec4 hashes = permute(mod(vec4(v1.x, v2.x, v3.x, v4.x), 289.0)); + hashes = permute(mod(hashes + vec4(v1.y, v2.y, v3.y, v4.y), 289.0)); + hashes = mod(permute(mod(hashes + vec4(v1.z, v2.z, v3.z, v4.z), 289.0)), 48.0); + + // Gradient extrapolations & kernel function + vec3 d1 = X - v1; vec3 d2 = X - v2; vec3 d3 = X - v3; vec3 d4 = X - v4; + vec4 a = max(0.75 - vec4(dot(d1, d1), dot(d2, d2), dot(d3, d3), dot(d4, d4)), 0.0); + vec4 aa = a * a; vec4 aaaa = aa * aa; + vec3 g1 = grad(hashes.x); vec3 g2 = grad(hashes.y); + vec3 g3 = grad(hashes.z); vec3 g4 = grad(hashes.w); + vec4 extrapolations = vec4(dot(d1, g1), dot(d2, g2), dot(d3, g3), dot(d4, g4)); + + // Derivatives of the noise + vec3 derivative = -8.0 * mat4x3(d1, d2, d3, d4) * (aa * a * extrapolations) + + mat4x3(g1, g2, g3, g4) * aaaa; + + // Return it all as a vec4 + return vec4(derivative, dot(aaaa, extrapolations)); +} + + +// Gives X and Y a triangular alignment, and lets Z move up the main diagonal. +// Might be good for terrain, or a time varying X/Y plane. Z repeats. +vec4 bccNoiseDerivatives_XYBeforeZ(vec3 X) { + + // Not a skew transform. + mat3 orthonormalMap = mat3( + 0.788675134594813, -0.211324865405187, -0.577350269189626, + -0.211324865405187, 0.788675134594813, -0.577350269189626, + 0.577350269189626, 0.577350269189626, 0.577350269189626); + + X = orthonormalMap * X; + vec4 result = bccNoiseDerivativesPart(X) + bccNoiseDerivativesPart(X + 144.5); + + return vec4(result.xyz * orthonormalMap, result.w); +} + + +void main() +{ + vec2 uv = texUV; + vec2 p = uv; + uv = uv * 2. -1.; + + float tickle = 0.001*1000*iTime; + vec3 offset = vec3(cos(tickle), sin(tickle), 0.0); + vec3 p3 = vec3(p, 0.0) + offset; + + vec3 noise_input = vec3(p3*25.0+12.0); + //float intensity = noise(noise_input); + float intensity = 0.0; + intensity += bccNoiseDerivatives_XYBeforeZ(noise_input).w*0.4; + intensity += bccNoiseDerivatives_XYBeforeZ(noise_input*0.55).w*0.7; + intensity += bccNoiseDerivatives_XYBeforeZ(noise_input*0.44).w*0.8; + + + float t = clamp((uv.x * -uv.x * 0.16) + 0.15, 0., 1.); + //fragColor.rgb = vec3(t); + + float dist = length(uv); + + //float y = abs( dist - 0.5 - intensity * 0.1); + float y = abs(uv.y/1.5 - intensity * 0.1); + + float g = pow(y, 0.2); + + vec3 col = vec3(2.0, 1.8, 0.5); + col = col * -g + col; + col = col * col; + col = col * col; + + fragColor.rgb = col; + fragColor.a = ((col.r + col.g + col.b)/3.0) * alpha; +} +@end + +@program program vs fs diff --git a/main.c b/main.c index 808d4ff..a927396 100644 --- a/main.c +++ b/main.c @@ -38,11 +38,13 @@ // shaders #include "goodpixel.gen.h" +#include "horizontal_lightning.gen.h" #include "hueshift.gen.h" #include "lightning.gen.h" static sg_pipeline hueshift_pipeline; static sg_pipeline goodpixel_pipeline; static sg_pipeline lightning_pipeline; +static sg_pipeline horizontal_lightning_pipeline; static struct GameState gs = {0}; static int my_player_index = -1; @@ -694,6 +696,20 @@ static void init(void) quit_with_popup("Couldn't make a shader! Uhhh ooooohhhhhh!!!", "Shader error BONED"); } } + { + sgp_pipeline_desc pip_desc = { + .shader = *horizontal_lightning_program_shader_desc(sg_query_backend()), + .blend_mode = SGP_BLENDMODE_BLEND, + }; + + horizontal_lightning_pipeline = sgp_make_pipeline(&pip_desc); + sg_resource_state errstate = sg_query_pipeline_state(horizontal_lightning_pipeline); + if (errstate != SG_RESOURCESTATE_VALID) + { + Log("Failed to make horizontal_lightning pipeline\n"); + quit_with_popup("Couldn't make a shader! Uhhh ooooohhhhhh!!!", "Shader error BONED"); + } + } } // images loading @@ -1839,7 +1855,7 @@ static void frame(void) { // "commit" the input. each input must be on a successive tick. // if (tick(&gs) > last_input_committed_tick) - while(tick(&gs) > last_input_committed_tick) + while (tick(&gs) > last_input_committed_tick) { if (replay_inputs_from != NULL) { @@ -2338,6 +2354,29 @@ static void frame(void) draw_circle(entity_pos(b), SCANNER_RADIUS * 0.75); set_color(WHITE); + + for (int i = 0; i < SCANNER_MAX_PLATONICS; i++) + if (b->detected_platonics[i].intensity > 0.0) + { + + pipeline_scope(horizontal_lightning_pipeline) + { + sgp_set_image(0, (sg_image){0}); + horizontal_lightning_uniforms_t uniform = { + .iTime = (float)(iTime + hash11((double)get_id(&gs, b).index)), + .alpha = (float)b->detected_platonics[i].intensity, + }; + sgp_set_uniform(&uniform, sizeof(uniform)); + transform_scope() + { + cpVect pos = cpvadd(entity_pos(b) , cpvmult(b->detected_platonics[i].direction, SCANNER_RADIUS/2.0)); + rotate_at(cpvangle(b->detected_platonics[i].direction), pos.x,pos.y); + draw_color_rect_centered(pos, SCANNER_RADIUS); + } + sgp_reset_image(0); + } + } + set_color(colhexcode(0xf2d75c)); sgp_set_image(0, image_radardot); for (int i = 0; i < SCANNER_MAX_POINTS; i++) diff --git a/shadergen.bat b/shadergen.bat index 478b6cb..a4812d9 100644 --- a/shadergen.bat +++ b/shadergen.bat @@ -6,4 +6,5 @@ IF %ERRORLEVEL% NEQ 0 ECHO ERROR download sokol-shdc from https://github.com/flo @REM example of how to compile shaders: sokol-shdc.exe --input triangle.glsl --output triangle.gen.h --slang glsl330:hlsl5:metal_macos sokol-shdc.exe --format sokol --input hueshift.glsl --output hueshift.gen.h --slang glsl330:hlsl5:metal_macos sokol-shdc.exe --format sokol --input lightning.glsl --output lightning.gen.h --slang glsl330:hlsl5:metal_macos +sokol-shdc.exe --format sokol --input horizontal_lightning.glsl --output horizontal_lightning.gen.h --slang glsl330:hlsl5:metal_macos sokol-shdc.exe --format sokol --input goodpixel.glsl --output goodpixel.gen.h --slang glsl330:hlsl5:metal_macos \ No newline at end of file diff --git a/types.h b/types.h index 9754655..6ff07ab 100644 --- a/types.h +++ b/types.h @@ -71,6 +71,7 @@ #define SCANNER_MAX_VIEWPORT_RANGE 400.0 #define SCANNER_MIN_RANGE 1.0 #define SCANNER_MAX_POINTS 10 +#define SCANNER_MAX_PLATONICS 3 #define MAX_SERVER_TO_CLIENT 1024 * 512 // maximum size of serialized gamestate buffer #define MAX_CLIENT_TO_SERVER 1024 * 10 // maximum size of serialized inputs and mic data @@ -217,7 +218,8 @@ typedef struct EntityID static inline bool entityids_same(EntityID a, EntityID b) { - return (a.generation == b.generation) && (a.index == b.index); + + return (a.generation == b.generation) && (a.index == b.index); } enum ScannerPointKind @@ -251,6 +253,12 @@ typedef struct InputFrame enum CompassRotation build_rotation; } InputFrame; +typedef struct PlatonicDetection +{ + cpVect direction; + double intensity; +} PlatonicDetection; + typedef struct Entity { bool exists; @@ -358,6 +366,8 @@ typedef struct Entity double scanner_head_rotate_speed; // not serialized, cosmetic double scanner_head_rotate; + PlatonicDetection detected_platonics[SCANNER_MAX_PLATONICS]; // intensity of 0.0 means undetected + struct ScannerPoint { char kind; // is of ScannerPointKind