const std = @import("std"); const rl = @import("raylib"); const builtin = @import("builtin"); const emscripten = std.os.emscripten; const pi = std.math.pi; const rmth = rl.math; const smth = std.math; const tst = std.testing; //yeah right const v2 = rl.Vector2; const cos = smth.cos; const sin = smth.sin; const pwr = smth.pow; //debug var debug_mode = false; //globals const scrn_wdt: f32 = 2000; const scrn_hgt: f32 = 1400; const hlf_scrn_hgt: f32 = @trunc(scrn_hgt * 0.5); const hlf_scrn_wdt: f32 = @trunc(scrn_wdt * 0.5); //game toggles var game_paused: bool = true; //color palette const Colors = struct { a: rl.Color, b: rl.Color, c: rl.Color, d: rl.Color, e: rl.Color, f: rl.Color, g: rl.Color, h: rl.Color, i: rl.Color, j: rl.Color, }; //has to be a var b/c I can't call extern fns at comptime I guess var colors: Colors = undefined; const FinPosition = enum { left, right, anal, tail, }; //shader definitions const fishy_shader_img = @embedFile("assets/aquamarinescales.png"); var fishy_texture: rl.Texture2D = undefined; const fin_shader_img = @embedFile("assets/bluescales.png"); var fins_texture: rl.Texture2D = undefined; //relevant info for hermite sampling const ControlPoint = struct { position: v2, rotation: f32, }; //Making generic structs for the draw functions fn BasePolygon(comptime N: usize) type { return struct { tip: ControlPoint, taint: ControlPoint, left: [N]ControlPoint, right: [N]ControlPoint, }; } fn QuadPoints(comptime N: usize) type { return struct { left: [N]v2, right: [N]v2, const length = N; }; } //get uv vertices from a solved polygon //we want to do this once per polygon, at comptime //should probably be a field for each polygon struct, along with its //real ribbon fn buildUVRibbon(return_type: type, ribbon: return_type) return_type { var uv_ribbon: return_type = undefined; var max_x: f32 = -smth.inf(f32); var min_x: f32 = smth.inf(f32); var max_y: f32 = -smth.inf(f32); var min_y: f32 = smth.inf(f32); //derive max and min y and x values to translate vertices for (0..ribbon.left.len) |ndx| { max_x = @max( max_x, ribbon.left[ndx].x, ribbon.right[ndx].x, ); min_x = @min( min_x, ribbon.left[ndx].x, ribbon.right[ndx].x, ); max_y = @max( max_y, ribbon.left[ndx].y, ribbon.right[ndx].y, ); min_y = @min( min_y, ribbon.left[ndx].y, ribbon.right[ndx].y, ); } const ribbon_width: f32 = max_x - min_x; const ribbon_height: f32 = max_y - min_y; //transpose polygon outline into uv outline for (0..return_type.length) |ndx| { const lft = &uv_ribbon.left[ndx]; const rgt = &uv_ribbon.right[ndx]; const rbn_lft = &ribbon.left[ndx]; const rbn_rgt = &ribbon.right[ndx]; lft.x = (rbn_lft.x - min_x) / ribbon_width; lft.y = (rbn_lft.y - min_y) / ribbon_height; rgt.x = (rbn_rgt.x - min_x) / ribbon_width; rgt.y = (rbn_rgt.y - min_y) / ribbon_height; } return uv_ribbon; } //fishy definitions const FishyInit = struct { max_rotation: f32, rotation_speed: f32, base_spd: f32, seg_cnt: usize, head_width: f32, fin_count: usize, point_count: usize, }; const fishy_list = [_]FishyInit{ FishyInit{ .max_rotation = pi * 0.125, .rotation_speed = pi * 0.75, .base_spd = 400, .seg_cnt = 10, .head_width = 50, .fin_count = 6, .point_count = 8, }, }; fn Fishy( comptime segment_count: usize, comptime point_count: usize, comptime fin_count: usize, ) type { return struct { pos: [segment_count]v2, speed: [segment_count]f32, rotation: [segment_count]f32, max_rotation: f32, rotation_speed: f32, radius: [segment_count]f32, segment_count: usize, point_count: usize, base_polygon: BasePolygon(segment_count), ribbon: QuadPoints((segment_count + 1) * point_count), uv_ribbon: QuadPoints((segment_count + 1) * point_count), curve_magnitude: f32, largest_segment: usize, sum_curve: f32, fins: [fin_count]BasePolygon(1), fins_ribbon: [fin_count]QuadPoints(point_count * 2), fins_uv: [fin_count]QuadPoints(2 * point_count), }; } fn buildFishy( return_type: type, segment_count: usize, point_count: usize, fin_count: usize, initial_position: v2, speed: f32, max_rotation: f32, rotation_speed: f32, head_width: f32, largest_segment: usize, curve_magnitude: f32, ) return_type { var fsh = Fishy( segment_count, point_count, fin_count, ){ .pos = [_]v2{initial_position} ** segment_count, .speed = [_]f32{speed} ** segment_count, .rotation = [_]f32{0} ** segment_count, .max_rotation = max_rotation, .rotation_speed = rotation_speed, .radius = [_]f32{head_width} ** segment_count, .segment_count = segment_count, .point_count = point_count, .base_polygon = undefined, .ribbon = undefined, .uv_ribbon = undefined, .curve_magnitude = curve_magnitude, .largest_segment = largest_segment, .sum_curve = 0, .fins = undefined, .fins_ribbon = undefined, .fins_uv = undefined, }; //set segment values for (1..segment_count) |ndx| { //set speed to get faster toward the tail fsh.speed[ndx] *= 1.2; //assigns segment radius //increase radius for 0..largest_seg, then decrease width to the tail if (ndx < largest_segment) fsh.radius[ndx] = fsh.radius[ndx - 1] * 1.01 else { fsh.radius[ndx] = fsh.radius[ndx - 1] * 0.85; } //set segment starting positions as an offset from the head fsh.pos[ndx].x = fsh.pos[ndx - 1].x - fsh.radius[ndx - 1]; } fsh.base_polygon = getFishyBodyPoints( BasePolygon(fsh.segment_count), @TypeOf(fsh), segment_count, &fsh, ); fsh.ribbon = buildRibbon( QuadPoints((segment_count + 1) * point_count), segment_count, point_count, &fsh.base_polygon, fsh.curve_magnitude, ); fsh.uv_ribbon = buildUVRibbon( QuadPoints((segment_count + 1) * point_count), fsh.ribbon, ); fsh.fins = getFishyFinPoints( [fin_count]BasePolygon(1), @TypeOf(fsh), &fsh, ); for (0..fsh.fins_ribbon.len) |ndx| { fsh.fins_ribbon[ndx] = buildRibbon( QuadPoints(2 * point_count), 1, point_count, fsh.fins[ndx], 10, ); } for (0..fsh.fins_uv.len) |ndx| { fsh.fins_uv[ndx] = buildUVRibbon( @TypeOf(fsh.fins_ribbon[ndx]), fsh.fins_ribbon[ndx], ); } return fsh; } var fishy = buildFishy( Fishy( fishy_list[0].seg_cnt, fishy_list[0].point_count, fishy_list[0].fin_count, ), fishy_list[0].seg_cnt, fishy_list[0].point_count, fishy_list[0].fin_count, .{ .x = hlf_scrn_wdt, .y = hlf_scrn_hgt }, fishy_list[0].base_spd, fishy_list[0].max_rotation, fishy_list[0].rotation_speed, fishy_list[0].head_width, 4, 10, ); //finds the average curvature... I think fn getSumCurve(FishyType: type, fish: FishyType) void { fish.sum_curve = 0.0; for (1..fish.segment_count) |ndx| { fish.sum_curve += findDeltaAngle( fish.rotation[ndx - 1], fish.rotation[ndx], ); } } //gets sample points for buildRibbon() fn getFishyBodyPoints( return_type: type, fish_type: type, segment_count: usize, fish: *const fish_type, ) return_type { //handle generic assignment const FishPolygon = return_type; var fish_polygon: FishPolygon = undefined; //add tip and tail, they are derived rather than simulated fish_polygon.tip = .{ .position = addFoundVector( fish.pos[0], fish.radius[0], fish.rotation[0], ), .rotation = fish.rotation[0], }; fish_polygon.taint = .{ .position = addFoundVector( fish.pos[segment_count - 1], fish.radius[segment_count - 1], invertAngle(fish.rotation[segment_count - 1]), ), .rotation = invertAngle(fish.rotation[segment_count - 1]), }; //populate left and right sample point arrays for (0..segment_count) |ndx| { const normal = findVector2( fish.radius[ndx], combineAngles(fish.rotation[ndx], pi * 0.5), ); fish_polygon.right[ndx] = .{ .position = combineVectors( fish.pos[ndx], normal, ), .rotation = fish.rotation[ndx], }; fish_polygon.left[ndx] = .{ .position = combineVectors( fish.pos[ndx], scaleVector(normal, -1), ), .rotation = fish.rotation[ndx], }; } return fish_polygon; } fn getFishyFinPoints( ReturnType: type, FishyType: type, fsh: *FishyType, ) ReturnType { const left_pec_angle = combineAngles( combineAngles( fsh.rotation[1], pi * -0.7, ), -fsh.sum_curve * 0.03, ); const right_pec_angle = combineAngles( combineAngles( fsh.rotation[1], pi * 0.7, ), fsh.sum_curve * 0.03, ); //I'm sorry, these fins are literally called the anal fins //I literally googled it const left_anal_angle = combineAngles( combineAngles( fsh.rotation[fsh.rotation.len - 2], pi * -0.55, ), fsh.sum_curve * 0.06, ); const right_anal_angle = combineAngles( combineAngles( fsh.rotation[fsh.rotation.len - 2], pi * 0.55, ), -fsh.sum_curve * 0.06, ); const tail_angle = combineAngles( invertAngle(fsh.rotation[fsh.rotation.len - 1]), -fsh.sum_curve * 0.15, ); const fishy_fins: ReturnType = [_]BasePolygon(1){ //left pectoral fin BasePolygon(1){ .taint = ControlPoint{ .position = fsh.pos[1], .rotation = invertAngle(left_pec_angle), }, .tip = ControlPoint{ .position = addFoundVector( fsh.pos[1], fsh.radius[1] * 2 + fsh.sum_curve * 5, left_pec_angle, ), .rotation = left_pec_angle, }, .left = [_]ControlPoint{ControlPoint{ .position = addFoundVector( fsh.pos[2], fsh.radius[2] / 2.5, combineAngles( fsh.rotation[2], -pi * 0.5, ), ), .rotation = combineAngles( fsh.base_polygon.left[2].rotation, -pi * 0.5, ), }}, .right = [_]ControlPoint{ControlPoint{ .position = fsh.base_polygon.left[1].position, .rotation = combineAngles( fsh.base_polygon.left[1].rotation, -pi * 0.5, ), }}, }, //right pectoral fin BasePolygon(1){ .tip = ControlPoint{ .position = fsh.pos[1], .rotation = invertAngle(right_pec_angle), }, .taint = ControlPoint{ .position = addFoundVector( fsh.pos[1], fsh.radius[1] * 2 - fsh.sum_curve * 5, right_pec_angle, ), .rotation = right_pec_angle, }, .left = [_]ControlPoint{ControlPoint{ .position = addFoundVector( fsh.pos[2], fsh.radius[2] / 2.5, combineAngles( fsh.rotation[2], pi * 0.5, ), ), .rotation = combineAngles( fsh.base_polygon.right[2].rotation, -pi * 0.5, ), }}, .right = [_]ControlPoint{ControlPoint{ .position = fsh.base_polygon.right[1].position, .rotation = combineAngles( fsh.base_polygon.right[1].rotation, -pi * 0.5, ), }}, }, //left anal fin BasePolygon(1){ .tip = ControlPoint{ .position = fsh.pos[fsh.pos.len - 5], .rotation = invertAngle(left_anal_angle), }, .taint = ControlPoint{ .position = addFoundVector( fsh.pos[fsh.pos.len - 3], fsh.radius[fsh.radius.len - 5] * 1.5 + fsh.sum_curve * 3, combineAngles( fsh.rotation[fsh.rotation.len - 3], -pi * 0.5, ), ), .rotation = left_anal_angle, }, .right = [_]ControlPoint{ControlPoint{ .position = fsh.base_polygon.left[fsh.base_polygon.left.len - 3].position, .rotation = combineAngles( fsh.base_polygon.left[fsh.base_polygon.left.len - 4].rotation, -pi * 0.75, ), }}, .left = [_]ControlPoint{ControlPoint{ .position = fsh.base_polygon.left[fsh.base_polygon.left.len - 5].position, .rotation = combineAngles( fsh.base_polygon.left[fsh.base_polygon.left.len - 5].rotation, -pi * 0.5, ), }}, }, //right anal fin BasePolygon(1){ .taint = ControlPoint{ .position = fsh.pos[fsh.pos.len - 5], .rotation = invertAngle(right_anal_angle), }, .tip = ControlPoint{ .position = addFoundVector( fsh.pos[fsh.pos.len - 3], fsh.radius[fsh.radius.len - 5] * 1.5 - fsh.sum_curve * 3, combineAngles( fsh.rotation[fsh.rotation.len - 3], pi * 0.5, ), ), .rotation = right_anal_angle, }, .right = [_]ControlPoint{ControlPoint{ .position = fsh.base_polygon.right[fsh.base_polygon.right.len - 3].position, .rotation = combineAngles( fsh.base_polygon.right[fsh.base_polygon.right.len - 4].rotation, pi * 0.75, ), }}, .left = [_]ControlPoint{ControlPoint{ .position = fsh.base_polygon.right[fsh.base_polygon.right.len - 5].position, .rotation = combineAngles( fsh.base_polygon.right[fsh.base_polygon.right.len - 5].rotation, -pi * 0.5, ), }}, }, //dorsal fin BasePolygon(1){ .tip = ControlPoint{ .position = fsh.pos[3], .rotation = fsh.rotation[3], }, .taint = ControlPoint{ .position = fsh.pos[6], .rotation = invertAngle(fsh.rotation[6]), }, .right = [_]ControlPoint{ ControlPoint{ .position = if (fsh.sum_curve >= 0) fsh.pos[4] else addFoundVector( fsh.pos[4], smth.clamp( @abs(fsh.sum_curve) * fsh.radius[4] / 2.5, 3, fsh.radius[4] * 0.8, ), combineAngles( fsh.rotation[4], pi * 0.5, ), ), .rotation = fsh.rotation[4], }, }, .left = [_]ControlPoint{ ControlPoint{ .position = if (fsh.sum_curve < 0) fsh.pos[4] else addFoundVector( fsh.pos[4], smth.clamp( @abs(fsh.sum_curve) * fsh.radius[4] / 2.5, 3, fsh.radius[4] * 0.8, ), combineAngles( fsh.rotation[4], -pi * 0.5, ), ), .rotation = fsh.rotation[4], }, }, }, //tail fin BasePolygon(1){ .right = [_]ControlPoint{ControlPoint{ .position = fsh.pos[fsh.pos.len - 1], .rotation = combineAngles( fsh.rotation[fsh.rotation.len - 1], -pi * 0.5, ), }}, .left = [_]ControlPoint{ControlPoint{ .position = addFoundVector( fsh.pos[fsh.pos.len - 1], fsh.radius[0] * 2, tail_angle, ), .rotation = combineAngles( invertAngle(fsh.rotation[fsh.rotation.len - 1]), fsh.sum_curve * 0.01, ), }}, .tip = ControlPoint{ .position = addFoundVector( fsh.pos[fsh.pos.len - 1], fsh.radius[0] * 2.5, combineAngles( tail_angle, @max( fsh.sum_curve * 0.3, pi * 0.05, ), ), ), .rotation = combineAngles( tail_angle, pi * 0.25, ), }, .taint = ControlPoint{ .position = addFoundVector( fsh.pos[fsh.pos.len - 1], fsh.radius[0] * 2.5, combineAngles( tail_angle, @min( fsh.sum_curve * 0.3, -pi * 0.05, ), ), ), .rotation = combineAngles( tail_angle, -pi * 0.25, ), }, }, }; return fishy_fins; } //working tail to tip so the head is on top if it wraps fn buildRibbon( RibbonType: type, segment_count: usize, point_count: usize, shape: anytype, magnitude: f32, ) RibbonType { //handle generic assignment const Vertices = RibbonType; var vertices: Vertices = undefined; //solve t spacing. we want even segments starting half a spacing from either //end (solves odd number of points problem) const t_spacing: f32 = 1.0 / @as(f32, @floatFromInt(point_count)); const t_start = t_spacing / 2; //run through all the points from tail to tip. Note: // //THE SEGMENT ORDER GETS REVERSED HERE // //also the multipliers are a quick hack b/c the tip and tail are so far off //the line of the other points. if roundness isn't desired I'll need to //change this to take a "tip roundness" arg const taint_velocity = findVector2( distanceTo( shape.left[shape.left.len - 1].position, shape.right[shape.right.len - 1].position, ), combineAngles( shape.taint.rotation, pi * 0.5, ), ); const tip_velocity = findVector2( distanceTo( shape.left[0].position, shape.right[0].position, ), combineAngles( shape.tip.rotation, pi * 0.5, ), ); for (0..segment_count + 1) |ndx| { var left_start: v2 = undefined; var left_start_velocity: v2 = undefined; var right_start: v2 = undefined; var right_start_velocity: v2 = undefined; var left_end: v2 = undefined; var left_end_velocity: v2 = undefined; var right_end: v2 = undefined; var right_end_velocity: v2 = undefined; if (ndx == 0) { left_start = shape.taint.position; left_start_velocity = taint_velocity; right_start = shape.taint.position; right_start_velocity = scaleVector(taint_velocity, -1); left_end = shape.left[segment_count - 1].position; left_end_velocity = findVector2( distanceTo( shape.left[segment_count - 1].position, shape.taint.position, ), shape.left[segment_count - 1].rotation, ); right_end = shape.right[segment_count - 1].position; right_end_velocity = findVector2( distanceTo( shape.right[segment_count - 1].position, shape.taint.position, ), shape.right[segment_count - 1].rotation, ); } else if (ndx == segment_count) { left_start = shape.left[0].position; left_start_velocity = findVector2( distanceTo( shape.tip.position, shape.left[0].position, ), shape.left[0].rotation, ); right_start = shape.right[0].position; right_start_velocity = findVector2( distanceTo( shape.tip.position, shape.left[0].position, ), shape.right[0].rotation, ); left_end = shape.tip.position; left_end_velocity = tip_velocity; right_end = shape.tip.position; right_end_velocity = scaleVector(tip_velocity, -1); } else { const lft = shape.left[segment_count - ndx]; const nxt_lft = shape.left[segment_count - 1 - ndx]; const rgt = shape.right[segment_count - ndx]; const nxt_rgt = shape.right[segment_count - 1 - ndx]; left_start = lft.position; left_start_velocity = findVector2( magnitude, lft.rotation, ); right_start = rgt.position; right_start_velocity = findVector2( magnitude, rgt.rotation, ); left_end = nxt_lft.position; left_end_velocity = findVector2( magnitude, nxt_lft.rotation, ); right_end = nxt_rgt.position; right_end_velocity = findVector2( magnitude, nxt_rgt.rotation, ); } //nesting for loops to scare the hoes for (0..point_count) |mdx| { const flmdx = @as(f32, @floatFromInt(mdx)); const current_index = point_count * ndx + mdx; vertices.right[current_index] = findHermitePoint( left_start, left_start_velocity, left_end, left_end_velocity, t_start + t_spacing * flmdx, ); vertices.left[current_index] = findHermitePoint( right_start, right_start_velocity, right_end, right_end_velocity, t_start + t_spacing * flmdx, ); } } return vertices; } //input fn getInput() rl.Vector2 { if (builtin.os.tag != .emscripten) { if (rl.isKeyDown(rl.KeyboardKey.q)) { std.process.exit(0); } } const mouse = rl.getMousePosition(); return mouse; } //math helper functions //sample a point on a curve fn findHermitePoint( start: v2, start_velocity: v2, end: v2, end_velocity: v2, t: f32, ) v2 { @setEvalBranchQuota(1e4); //these are really easy to mess up const h_00 = 2 * (t * t * t) - 3 * (t * t) + 1; const h_10 = (t * t * t) - 2 * (t * t) + t; const h_01 = -2 * (t * t * t) + 3 * (t * t); const h_11 = (t * t * t) - (t * t); //summing the vectors 2 at a time because I'm bad at math const step_start = combineVectors( scaleVector(start, h_00), scaleVector(start_velocity, h_10), ); const step_end = combineVectors( scaleVector(end, h_01), scaleVector(end_velocity, h_11), ); return combineVectors(step_start, step_end); } //add two vectors fn combineVectors(a: v2, b: v2) v2 { var c: v2 = undefined; c.x = a.x + b.x; c.y = a.y + b.y; return c; } //scale a vector by an angle fn scaleVector(v: v2, scale: f32) v2 { var c: v2 = undefined; c.x = v.x * scale; c.y = v.y * scale; return c; } fn distanceTo(to: v2, from: v2) f32 { const x_squared = (to.x - from.x) * (to.x - from.x); const y_squared = (to.y - from.y) * (to.y - from.y); return smth.sqrt(x_squared + y_squared); } //angle to a vector from another vector fn angleTo(to: v2, from: v2) f32 { return std.math.atan2(to.y - from.y, to.x - from.x); } //find a vector at a given angle in a given direction from a given point fn findVector2(distance: f32, angle: f32) rl.Vector2 { return rl.Vector2{ .x = cos(angle) * distance, .y = sin(angle) * distance, }; } //find the shortest angular difference b/t two angles //not sure if direction matters, but keeping it for clarity fn findDeltaAngle(to: f32, from: f32) f32 { return smth.atan2( smth.sin(to - from), smth.cos(to - from), ); } //the sum of two angles, wrapping around 2pi/-2pi //assumes sum, pass negative value to subtract fn combineAngles(angle_a: f32, angle_b: f32) f32 { const sum = angle_a + angle_b; if (sum > pi) return sum - pi * 2; if (sum < -pi) return sum + pi * 2; return sum; } //find the reverse of a direction fn invertAngle(angle: f32) f32 { if (angle <= 0) return angle + pi else return angle - pi; } fn addFoundVector( start: v2, distance: f32, angle: f32, ) v2 { return combineVectors(start, findVector2( distance, angle, )); } //update positions //not yet implemented, but this should eventually take a list of fish and call //appropriately for each one fn update(mouse: *const rl.Vector2) void { const dt = rl.getFrameTime(); if (rl.isKeyPressed(rl.KeyboardKey.space)) debug_mode = !debug_mode; if (rl.isMouseButtonPressed(rl.MouseButton.left) or rl.getGestureDetected().tap) { if (game_paused) game_paused = false; } if (game_paused) { rl.drawText( "Press or Click\nanywhere to begin", 500, 350, 100, colors.h, ); return; } moveFishy(&fishy, mouse, dt); fishy.base_polygon = getFishyBodyPoints( @TypeOf(fishy.base_polygon), @TypeOf(fishy), fishy.segment_count, &fishy, ); fishy.ribbon = buildRibbon( @TypeOf(fishy.ribbon), fishy.segment_count, fishy.point_count, fishy.base_polygon, fishy.curve_magnitude, ); fishy.fins = getFishyFinPoints( @TypeOf(fishy.fins), @TypeOf(fishy), &fishy, ); for (0..fishy.fins.len) |ndx| { fishy.fins_ribbon[ndx] = buildRibbon( @TypeOf(fishy.fins_ribbon[ndx]), 1, fishy.point_count, fishy.fins[ndx], 10, ); } } //move a fish fn moveFishy(fish: anytype, target: *const v2, dt: f32) void { const game_time = rl.getTime(); const time = @as(f32, @floatCast(game_time)); const amplitude: f32 = 0.8; const frequency: f32 = 1; const trgt_distance = distanceTo(target.*, fish.pos[0]); for (0..fish.segment_count) |ndx| { if (ndx == 0) { const target_angle = findDeltaAngle( angleTo(target.*, fish.pos[ndx]), fish.rotation[ndx], ); const sign: f32 = if (target_angle < 0) -1 else 1; fish.rotation[ndx] = combineAngles( fish.rotation[ndx], fish.rotation_speed * dt * sign, ); const deceleration: f32 = 1.0; //if (trgt_distance < 200) { // deceleration = trgt_distance / 200; //} //if (trgt_distance < 100) continue; fish.pos[ndx] = combineVectors( fish.pos[ndx], findVector2( fishy.speed[ndx] * dt * deceleration, fish.rotation[ndx], ), ); continue; } const flndx = @as(f32, @floatFromInt(ndx)); const displacement = amplitude * sin(2 * pi * frequency * time - flndx); fish.pos[ndx] = addFoundVector( fish.pos[ndx], displacement, combineAngles(fish.rotation[ndx - 1], pi * 0.5), ); const inverse_heading = invertAngle(fish.rotation[ndx - 1]); const max_heading = combineAngles( inverse_heading, fish.max_rotation, ); const min_heading = combineAngles( inverse_heading, -fish.max_rotation, ); var link_angle = angleTo(fish.pos[ndx], fish.pos[ndx - 1]); const link_difference = findDeltaAngle( link_angle, inverse_heading, ); if (link_difference > fish.max_rotation) link_angle = max_heading; if (link_difference < -fish.max_rotation) link_angle = min_heading; if (trgt_distance < 150 and link_difference < -1e-5) { link_angle = combineAngles(link_angle, pi * 0.15 * dt); } if (trgt_distance < 150 and link_difference > 1e-5) { link_angle = combineAngles(link_angle, -pi * 0.15 * dt); } fish.pos[ndx] = addFoundVector( fish.pos[ndx - 1], fish.radius[ndx - 1], link_angle, ); fish.rotation[ndx] = invertAngle(link_angle); } getSumCurve(@TypeOf(fish), fish); } //draw functions fn renderQuads( ribbon_type: type, ribbon: ribbon_type, uv_ribbon: ribbon_type, texture: c_uint, ) void { //doin a shader for (0..ribbon.left.len - 1) |ndx| { rl.gl.rlSetTexture(texture); rl.gl.rlBegin(rl.gl.rl_quads); rl.gl.rlColor4ub(255, 255, 255, 255); const l0 = ribbon.right[ndx]; const l1 = ribbon.right[ndx + 1]; const r0 = ribbon.left[ndx]; const r1 = ribbon.left[ndx + 1]; const uv_l0 = uv_ribbon.right[ndx]; const uv_l1 = uv_ribbon.right[ndx + 1]; const uv_r0 = uv_ribbon.left[ndx]; const uv_r1 = uv_ribbon.left[ndx + 1]; rl.gl.rlTexCoord2f(uv_r0.x, uv_r0.y); rl.gl.rlVertex2f(r0.x, r0.y); rl.gl.rlTexCoord2f(uv_r1.x, uv_r1.y); rl.gl.rlVertex2f(r1.x, r1.y); rl.gl.rlTexCoord2f(uv_l1.x, uv_l1.y); rl.gl.rlVertex2f(l1.x, l1.y); rl.gl.rlTexCoord2f(uv_l0.x, uv_l0.y); rl.gl.rlVertex2f(l0.x, l0.y); rl.gl.rlEnd(); rl.gl.rlSetTexture(0); } } fn drawFishy( fish_type: type, fish: *const fish_type, mouse: *const v2, ) void { if (debug_mode) { for (0..fish.segment_count) |ndx| { rl.drawCircleLinesV( fish.pos[ndx], fish.radius[ndx], colors.e, ); const line_end = combineVectors( fish.pos[ndx], findVector2( fish.radius[ndx] * 0.6, fish.rotation[ndx], ), ); rl.drawLineV( fish.pos[ndx], line_end, colors.h, ); //print debugging var buf: [128]u8 = undefined; const ol_reliable = std.fmt.bufPrintZ( &buf, "x: {any}\ny: {any}\nhead angle: {d:.1}\nsum curve: {d:.1}", .{ @trunc(fish.pos[0].x), @trunc(fish.pos[0].y), (fish.rotation[0]), fish.sum_curve, }, ) catch "error"; rl.drawText( ol_reliable, 40, 40, 25, colors.j, ); } for (0..fish.ribbon.left.len) |ndx| { rl.drawCircleV( fish.ribbon.left[ndx], 2, colors.h, ); rl.drawCircleV( fish.ribbon.right[ndx], 2, colors.j, ); } for (0..fish.fins_ribbon.len) |ndx| { for (0..fish.fins_ribbon[ndx].left.len) |mdx| { rl.drawCircleV( fish.fins_ribbon[ndx].left[mdx], 2, colors.h, ); rl.drawCircleV( fish.fins_ribbon[ndx].right[mdx], 2, colors.j, ); } } } else { for (0..fishy.fins.len - 2) |ndx| { renderQuads( @TypeOf(fish.fins_ribbon[ndx]), fish.fins_ribbon[ndx], fish.fins_uv[ndx], fins_texture.id, ); } renderQuads( @TypeOf(fish.ribbon), fish.ribbon, fish.uv_ribbon, fishy_texture.id, ); for (fishy.fins.len - 2..fishy.fins.len) |ndx| { renderQuads( @TypeOf(fish.fins_ribbon[ndx]), fish.fins_ribbon[ndx], fish.fins_uv[ndx], fins_texture.id, ); } } //draws some awesome googly eyes for (0..2) |ndx| { const angle: f32 = if (ndx == 0) pi * 0.35 else pi * -0.35; const eye_center = combineVectors( fish.pos[0], findVector2( fish.radius[0] * 0.65, fish.rotation[0] + angle, ), ); rl.drawCircleV( eye_center, 13, colors.d, ); rl.drawCircleGradient( eye_center, 11, colors.g, colors.e, ); rl.drawCircleGradient( combineVectors( eye_center, findVector2( 6, angleTo( mouse.*, eye_center, ), ), ), 7, colors.h, colors.b, ); } rl.drawFPS(40, 15); } //main loop fn frame() void { const input = getInput(); rl.beginDrawing(); rl.clearBackground(colors.c); drawFishy( @TypeOf(fishy), &fishy, &input, ); update(&input); rl.endDrawing(); } //main pub fn main(init: std.process.Init) !void { colors = Colors{ .a = rl.Color.fromInt(0x000924FF), .b = rl.Color.fromInt(0x041b38FF), .c = rl.Color.fromInt(0x093659FF), .d = rl.Color.fromInt(0x145d87FF), .e = rl.Color.fromInt(0x228399FF), .f = rl.Color.fromInt(0x31b0b0FF), .g = rl.Color.fromInt(0x46cfb3FF), .h = rl.Color.fromInt(0x73f0c6FF), .i = rl.Color.fromInt(0xabffd1FF), .j = rl.Color.fromInt(0xd9ffe2FF), }; _ = init; rl.initWindow(scrn_wdt, scrn_hgt, "fishy"); const fishy_image = try rl.loadImageFromMemory( ".png", fishy_shader_img, ); fishy_texture = try rl.loadTextureFromImage(fishy_image); rl.unloadImage(fishy_image); const fins_image = try rl.loadImageFromMemory( ".png", fin_shader_img, ); fins_texture = try rl.loadTextureFromImage(fins_image); rl.unloadImage(fins_image); if (builtin.os.tag == .emscripten) { emscripten.emscripten_set_main_loop( @ptrCast(&frame), 0, 1, ); } else { rl.setTargetFPS(120); while (!rl.windowShouldClose()) frame(); } rl.closeWindow(); return; }