-
-
Save Hammer2900/30f9370de65366d0cd6ed6adfefdfde3 to your computer and use it in GitHub Desktop.
Odin ECS Particle Life Evolution
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| odin build . -o:speed |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package particle_life | |
| import "core:fmt" | |
| import "core:math" | |
| import "core:math/linalg" | |
| import "core:math/rand" | |
| // import "core:slice" // Больше не нужен | |
| import rl "vendor:raylib" | |
| // --- КОНСТАНТЫ --- | |
| SCREEN_W :: 1200 | |
| SCREEN_H :: 800 | |
| NUM_TYPES :: 6 | |
| COLOR_STEP :: 360.0 / f32(NUM_TYPES) | |
| FRICTION :: 0.85 | |
| MIN_POPULATION :: 15 | |
| NUM_FOOD :: 200 | |
| FOOD_RANGE :: 5.0 | |
| FOOD_ENERGY :: 100.0 | |
| REPRODUCTION_ENERGY :: 1000.0 | |
| STARTING_ENERGY :: 400.0 | |
| K_FACTOR :: 0.2 | |
| PARTICLES_PER_ORG :: 40 | |
| // --- КОМПОНЕНТЫ И ДАННЫЕ --- | |
| Genetics :: struct { | |
| internal_forces: [NUM_TYPES][NUM_TYPES]f32, | |
| external_forces: [NUM_TYPES][NUM_TYPES]f32, | |
| internal_mins: [NUM_TYPES][NUM_TYPES]f32, | |
| external_mins: [NUM_TYPES][NUM_TYPES]f32, | |
| internal_radii: [NUM_TYPES][NUM_TYPES]f32, | |
| external_radii: [NUM_TYPES][NUM_TYPES]f32, | |
| } | |
| Particle :: struct { | |
| pos: [2]f32, | |
| vel: [2]f32, | |
| type: int, | |
| } | |
| Organism :: struct { | |
| energy: f32, | |
| genetics: Genetics, | |
| swarm: #soa[dynamic]Particle, | |
| } | |
| Food :: struct { | |
| pos: [2]f32, | |
| } | |
| World :: struct { | |
| organisms: [dynamic]Organism, | |
| food: [dynamic]Food, | |
| } | |
| // --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ --- | |
| wrap_vector :: proc(vec: [2]f32) -> [2]f32 { | |
| res := vec | |
| if res.x > SCREEN_W * 0.5 { res.x -= SCREEN_W } | |
| if res.x < SCREEN_W * -0.5 { res.x += SCREEN_W } | |
| if res.y > SCREEN_H * 0.5 { res.y -= SCREEN_H } | |
| if res.y < SCREEN_H * -0.5 { res.y += SCREEN_H } | |
| return res | |
| } | |
| create_random_genetics :: proc() -> Genetics { | |
| g: Genetics | |
| for i in 0..<NUM_TYPES { | |
| for j in 0..<NUM_TYPES { | |
| g.internal_forces[i][j] = rand.float32_range(0.1, 1.0) | |
| g.external_forces[i][j] = rand.float32_range(-1.0, 1.0) | |
| g.internal_mins[i][j] = rand.float32_range(40, 70) | |
| g.external_mins[i][j] = rand.float32_range(40, 70) | |
| g.internal_radii[i][j] = rand.float32_range(g.internal_mins[i][j] * 2, 300) | |
| g.external_radii[i][j] = rand.float32_range(g.external_mins[i][j] * 2, 300) | |
| } | |
| } | |
| return g | |
| } | |
| create_organism :: proc(x, y: f32, genetics: Genetics) -> Organism { | |
| org := Organism{ | |
| energy = STARTING_ENERGY, | |
| genetics = genetics, | |
| } | |
| for _ in 0..<PARTICLES_PER_ORG { | |
| px := rand.float32_range(x - 50, x + 50) | |
| py := rand.float32_range(y - 50, y + 50) | |
| pt := rand.int_max(NUM_TYPES) | |
| append(&org.swarm, Particle{ | |
| pos = {px, py}, | |
| vel = {0, 0}, | |
| type = pt, | |
| }) | |
| } | |
| return org | |
| } | |
| mutate_genetics :: proc(g: ^Genetics) { | |
| mutate_matrix :: proc(mat: ^[NUM_TYPES][NUM_TYPES]f32, strength, min_v, max_v: f32) { | |
| for i in 0..<NUM_TYPES { | |
| for j in 0..<NUM_TYPES { | |
| if rand.float32() < 0.1 { | |
| mat[i][j] += rand.float32_range(-strength, strength) | |
| } | |
| } | |
| } | |
| } | |
| mutate_matrix(&g.internal_forces, 0.1, 0, 0) | |
| mutate_matrix(&g.external_forces, 0.1, 0, 0) | |
| mutate_matrix(&g.internal_mins, 5.0, 0, 0) | |
| mutate_matrix(&g.external_mins, 5.0, 0, 0) | |
| mutate_matrix(&g.internal_radii, 10.0, 0, 0) | |
| mutate_matrix(&g.external_radii, 10.0, 0, 0) | |
| } | |
| // --- СИСТЕМЫ ECS --- | |
| sys_physics :: proc(world: ^World) { | |
| for i in 0..<len(world.organisms) { | |
| org := &world.organisms[i] | |
| // Получаем длину массива напрямую | |
| count := len(org.swarm) | |
| // 1. Внутренние силы | |
| for p_idx in 0..<count { | |
| total_force: [2]f32 | |
| // Прямой доступ к полям SOA (Odin сам оптимизирует это) | |
| p_pos := org.swarm[p_idx].pos | |
| p_type := org.swarm[p_idx].type | |
| for other_idx in 0..<count { | |
| if p_idx == other_idx { continue } | |
| other_pos := org.swarm[other_idx].pos | |
| other_type := org.swarm[other_idx].type | |
| delta := wrap_vector(other_pos - p_pos) | |
| dist := linalg.length(delta) | |
| if dist > 0 { | |
| min_d := org.genetics.internal_mins[p_type][other_type] | |
| max_d := org.genetics.internal_radii[p_type][other_type] | |
| force_val := org.genetics.internal_forces[p_type][other_type] | |
| f: f32 = 0 | |
| if dist < min_d { | |
| f = -3.0 * K_FACTOR * abs(force_val) * (1.0 - dist / min_d) | |
| } else if dist < max_d { | |
| f = K_FACTOR * force_val * (1.0 - dist / max_d) | |
| } | |
| total_force += (delta / dist) * f | |
| } | |
| } | |
| // 2. Внешние силы | |
| for j in 0..<len(world.organisms) { | |
| if i == j { continue } | |
| other_org := &world.organisms[j] | |
| other_count := len(other_org.swarm) | |
| for k in 0..<other_count { | |
| ext_pos := other_org.swarm[k].pos | |
| ext_type := other_org.swarm[k].type | |
| delta := wrap_vector(ext_pos - p_pos) | |
| dist := linalg.length(delta) | |
| if dist > 0 { | |
| min_d := org.genetics.external_mins[p_type][ext_type] | |
| max_d := org.genetics.external_radii[p_type][ext_type] | |
| force_val := org.genetics.external_forces[p_type][ext_type] | |
| f: f32 = 0 | |
| if dist < min_d { | |
| f = -3.0 * K_FACTOR * abs(force_val) * (1.0 - dist / min_d) | |
| } else if dist < max_d { | |
| f = K_FACTOR * force_val * (1.0 - dist / max_d) | |
| } | |
| total_force += (delta / dist) * f | |
| } | |
| } | |
| } | |
| // Применяем силу (доступ через индекс к SOA работает как ссылка на элемент массива) | |
| org.swarm[p_idx].vel += total_force | |
| } | |
| // 3. Интеграция | |
| for p_idx in 0..<count { | |
| org.swarm[p_idx].vel *= FRICTION | |
| org.swarm[p_idx].pos += org.swarm[p_idx].vel | |
| // Wrap | |
| if org.swarm[p_idx].pos.x < 0 { org.swarm[p_idx].pos.x += SCREEN_W } | |
| if org.swarm[p_idx].pos.x > SCREEN_W { org.swarm[p_idx].pos.x -= SCREEN_W } | |
| if org.swarm[p_idx].pos.y < 0 { org.swarm[p_idx].pos.y += SCREEN_H } | |
| if org.swarm[p_idx].pos.y > SCREEN_H { org.swarm[p_idx].pos.y -= SCREEN_H } | |
| } | |
| org.energy -= 1.0 | |
| } | |
| } | |
| sys_eating :: proc(world: ^World) { | |
| for i in 0..<len(world.organisms) { | |
| org := &world.organisms[i] | |
| for p_idx in 0..<len(org.swarm) { | |
| if org.swarm[p_idx].type == 1 { | |
| mouth_pos := org.swarm[p_idx].pos | |
| for f_idx := len(world.food) - 1; f_idx >= 0; f_idx -= 1 { | |
| f := world.food[f_idx] | |
| if linalg.distance(mouth_pos, f.pos) < FOOD_RANGE { | |
| org.energy += FOOD_ENERGY | |
| unordered_remove(&world.food, f_idx) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| sys_evolution :: proc(world: ^World) { | |
| for i := len(world.organisms) - 1; i >= 0; i -= 1 { | |
| org := &world.organisms[i] | |
| if org.energy <= 0 { | |
| delete(org.swarm) | |
| unordered_remove(&world.organisms, i) | |
| } else if org.energy > REPRODUCTION_ENERGY { | |
| org.energy -= STARTING_ENERGY | |
| child_genetics := org.genetics | |
| mutate_genetics(&child_genetics) | |
| center: [2]f32 = {0,0} | |
| if len(org.swarm) > 0 { center = org.swarm[0].pos } | |
| child := create_organism(center.x, center.y, child_genetics) | |
| for k in 0..<len(child.swarm) { | |
| if k < len(org.swarm) { | |
| child.swarm[k].type = org.swarm[k].type | |
| } | |
| if rand.float32() < 0.1 { | |
| child.swarm[k].type = rand.int_max(NUM_TYPES) | |
| } | |
| } | |
| append(&world.organisms, child) | |
| } | |
| } | |
| if len(world.organisms) < MIN_POPULATION { | |
| if len(world.organisms) > 0 { | |
| parent := &world.organisms[rand.int_max(len(world.organisms))] | |
| new_gen := parent.genetics | |
| mutate_genetics(&new_gen) | |
| new_org := create_organism(rand.float32_range(0, SCREEN_W), rand.float32_range(0, SCREEN_H), new_gen) | |
| append(&world.organisms, new_org) | |
| } else { | |
| gen := create_random_genetics() | |
| org := create_organism(rand.float32_range(0, SCREEN_W), rand.float32_range(0, SCREEN_H), gen) | |
| append(&world.organisms, org) | |
| } | |
| } | |
| if rand.float32() < 0.1 { | |
| append(&world.food, Food{ | |
| pos = {rand.float32_range(0, SCREEN_W), rand.float32_range(0, SCREEN_H)}, | |
| }) | |
| } | |
| } | |
| sys_draw :: proc(world: ^World) { | |
| rl.BeginDrawing() | |
| rl.ClearBackground(rl.BLACK) | |
| for f in world.food { | |
| rl.DrawCircle(i32(f.pos.x), i32(f.pos.y), 3, rl.GREEN) | |
| } | |
| for org in world.organisms { | |
| for i in 0..<len(org.swarm) { | |
| pos := org.swarm[i].pos | |
| type := org.swarm[i].type | |
| color := rl.ColorFromHSV(f32(type) * COLOR_STEP, 1.0, 1.0) | |
| rl.DrawCircle(i32(pos.x), i32(pos.y), 4, color) | |
| } | |
| } | |
| rl.DrawFPS(10, 10) | |
| rl.DrawText(fmt.ctprintf("Organisms: %d", len(world.organisms)), 10, 30, 20, rl.WHITE) | |
| rl.DrawText(fmt.ctprintf("Food: %d", len(world.food)), 10, 50, 20, rl.WHITE) | |
| rl.EndDrawing() | |
| } | |
| main :: proc() { | |
| rl.InitWindow(SCREEN_W, SCREEN_H, "Odin ECS Particle Life Evolution") | |
| rl.SetTargetFPS(60) | |
| defer rl.CloseWindow() | |
| world := World{} | |
| for _ in 0..<MIN_POPULATION { | |
| gen := create_random_genetics() | |
| org := create_organism( | |
| rand.float32_range(0, SCREEN_W), | |
| rand.float32_range(0, SCREEN_H), | |
| gen, | |
| ) | |
| append(&world.organisms, org) | |
| } | |
| for _ in 0..<NUM_FOOD { | |
| append(&world.food, Food{ | |
| pos = {rand.float32_range(0, SCREEN_W), rand.float32_range(0, SCREEN_H)}, | |
| }) | |
| } | |
| for !rl.WindowShouldClose() { | |
| sys_physics(&world) | |
| sys_eating(&world) | |
| sys_evolution(&world) | |
| sys_draw(&world) | |
| } | |
| for org in world.organisms { | |
| delete(org.swarm) | |
| } | |
| delete(world.organisms) | |
| delete(world.food) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment