├── .gitignore ├── LICENSE ├── README.md ├── main.jai └── thumbnail.png /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | .build/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Alexey Kutepov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rope Simulation 2 | 3 | ![thumbnail](./thumbnail.png) 4 | 5 | ## Quick Start 6 | 7 | ```console 8 | $ jai -version 9 | Version: beta 0.1.047, built on 27 November 2022. 10 | $ jai main.jai 11 | $ ./main 12 | ``` 13 | -------------------------------------------------------------------------------- /main.jai: -------------------------------------------------------------------------------- 1 | #import "Basic"; 2 | #import "Window_Creation"; 3 | #import "Input"; 4 | #import "Math"; 5 | #import "Random"; 6 | Simp :: #import "Simp"; 7 | 8 | WINDOW_FACTOR :: 100; 9 | WINDOW_WIDTH :: 16*WINDOW_FACTOR; 10 | WINDOW_HEIGHT :: 9*WINDOW_FACTOR; 11 | COLOR_RED :: Vector4.{1, 0, 0, 1}; 12 | COLOR_GREEN :: Vector4.{0, 1, 0, 1}; 13 | COLOR_BLUE :: Vector4.{0, 0, 1, 1}; 14 | CIRCLE_RESOLUTION :: 30; 15 | KNOT_RADIUS :: 30; 16 | EPSILON :: 0.000001; 17 | 18 | window_width := WINDOW_WIDTH; 19 | window_height := WINDOW_HEIGHT; 20 | 21 | immediate_thicc_line :: (p0: Vector2, p1: Vector2, t: float, color: Vector4) { 22 | v1 := p1 - p0; 23 | v2 := make_vector2(-v1.y, v1.x); 24 | v2l := sqrt(v2.x*v2.x + v2.y*v2.y); 25 | if v2l > EPSILON { 26 | v2.x /= v2l; 27 | v2.y /= v2l; 28 | Simp.immediate_quad(p0 + v2*(t/2), p0 - v2*(t/2), p1 - v2*(t/2), p1 + v2*(t/2), color); 29 | } 30 | } 31 | 32 | immediate_circle :: (center: Vector2, radius: float, color: Vector4) { 33 | STEP_ANGLE :: 2*PI/CIRCLE_RESOLUTION; 34 | for 0..CIRCLE_RESOLUTION-1 { 35 | p0 := center; 36 | p1 := center + make_vector2(cos(STEP_ANGLE*it), sin(STEP_ANGLE*it))*radius; 37 | p2 := center + make_vector2(cos(STEP_ANGLE*(it + 1)), sin(STEP_ANGLE*(it + 1)))*radius; 38 | Simp.immediate_triangle( 39 | make_vector3(p0, 0), make_vector3(p1, 0), make_vector3(p2, 0), 40 | color, color, color); 41 | } 42 | } 43 | 44 | mouse_position :: () -> Vector2 { 45 | x, y, success := get_mouse_pointer_position(); 46 | assert(success); 47 | return make_vector2(xx x, xx (window_height - y)); 48 | } 49 | 50 | compute_knot_velocity :: (using knot: *Knot, graph: []Knot) { 51 | TARGET_DISTANCE :: 100; 52 | STIFFNESS :: 20; 53 | velocity = make_vector2(0); 54 | for neighbors { 55 | neighbor := *graph[it]; 56 | if neighbor.dead continue; 57 | len := length(position - neighbor.position); 58 | dir := ifx len > EPSILON then (position - neighbor.position)/len else make_vector2(1, 0); 59 | target := neighbor.position + dir*TARGET_DISTANCE; 60 | velocity += (target - position)*STIFFNESS; 61 | } 62 | 63 | for *other: graph if !other.dead { 64 | if other != knot && length(knot.position - other.position) <= KNOT_RADIUS*2 { 65 | len := length(knot.position - other.position); 66 | dir := ifx len > EPSILON then (knot.position - other.position)/len else make_vector2(1, 0); 67 | REPULSION :: 0.5; 68 | velocity += (knot.position - other.position)*REPULSION; 69 | } 70 | } 71 | } 72 | 73 | Knot :: struct { 74 | position: Vector2; 75 | velocity: Vector2; 76 | neighbors: [..]int; 77 | dead: bool; 78 | } 79 | 80 | main :: () { 81 | the_window := create_window(window_width, window_height, "Rope Simulation"); 82 | Simp.set_render_target(the_window); 83 | quit := false; 84 | drag := -1; 85 | knots : [..]Knot; 86 | array_add(*knots, .{ 87 | position = make_vector2(xx (window_width/2), xx (window_height/2)) 88 | }); 89 | last_time := get_time(); 90 | while !quit { 91 | now := get_time(); 92 | delta : float64 = now - last_time; 93 | current_dt := cast(float) delta; 94 | last_time = now; 95 | 96 | update_window_events(); 97 | for get_window_resizes() { 98 | Simp.update_window(it.window); 99 | window_width = it.width; 100 | window_height = it.height; 101 | } 102 | 103 | for events_this_frame { 104 | if it.type == { 105 | case .QUIT; quit = true; 106 | case .KEYBOARD; if it.key_code == { 107 | case .MOUSE_BUTTON_MIDDLE; if !it.key_pressed { 108 | for *knots { 109 | if length(mouse_position() - it.position) <= KNOT_RADIUS { 110 | it.dead = true; 111 | break; 112 | } 113 | } 114 | } 115 | 116 | case .MOUSE_BUTTON_RIGHT; if !it.key_pressed { 117 | for *knots if !it.dead { 118 | if length(mouse_position() - it.position) <= KNOT_RADIUS { 119 | new_knot : Knot; 120 | new_knot.position = mouse_position(); 121 | array_add(*new_knot.neighbors, it_index); 122 | array_add(*it.neighbors, knots.count); 123 | array_add(*knots, new_knot); 124 | break; 125 | } 126 | } 127 | } 128 | 129 | case .MOUSE_BUTTON_LEFT; if it.key_pressed { 130 | for *knots if !it.dead { 131 | if length(mouse_position() - it.position) <= KNOT_RADIUS { 132 | drag = it_index; 133 | break; 134 | } 135 | } 136 | } else { 137 | drag = -1; 138 | } 139 | } 140 | } 141 | } 142 | Simp.clear_render_target(.08, .08, .08, 1); 143 | 144 | Simp.set_shader_for_color(); 145 | 146 | if drag >= 0 { 147 | knots[drag].position = mouse_position(); 148 | } 149 | 150 | for *knots if !it.dead { 151 | compute_knot_velocity(it, knots); 152 | if it_index != drag then it.position += it.velocity*current_dt; 153 | } 154 | 155 | for *knot: knots if !knot.dead { 156 | for knot.neighbors { 157 | neighbor := *knots[it]; 158 | if !neighbor.dead { 159 | immediate_thicc_line(knot.position, neighbor.position, 30, make_vector4(.5, .5, .5, 1)); 160 | } 161 | } 162 | } 163 | 164 | for *knot: knots if !knot.dead { 165 | immediate_circle(knot.position, KNOT_RADIUS, COLOR_GREEN); 166 | } 167 | 168 | Simp.swap_buffers(the_window); 169 | 170 | sleep_milliseconds(10); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/rope-jai/6a709b3eaa76f0567f39e583b42f3a6f64bcca16/thumbnail.png --------------------------------------------------------------------------------