Skip to main content
Version: 0.2

Neurons & axons

Neurons from mesh vertices

During World::new, the constructor walks the flat vertex array with a stride of vertices_skip_step (default 2, so every second vertex), classifies each position into a cortical region, and creates a Neuron:

rust/brain_sim/src/lib.rs
let mut i = 0;
while i < total_verts {
let off = i * 3;
let p = Vec3::new(flat[off], flat[off + 1], flat[off + 2]);
let region = classify_region(p, min, max);
let neuron_idx = neurons.len();
neurons.push(Neuron::new(p.x, p.y, p.z, region));
region_neurons[region.as_u32() as usize].push(neuron_idx);
region_counts.add(region, 1.0);
i += settings.vertices_skip_step.max(1);
}

The skip step keeps the neuron count manageable on dense meshes. Each region also gets a bucket of neuron indices, used later when injecting stimulus into a specific region.

Axon connections

Axons connect nearby neurons. The constructor does an O(N²) pass over neuron pairs, subject to two constraints:

  • Each neuron may have at most max_connections_per_neuron connections (default 6).
  • Two neurons are connected only if they are closer than max_axon_dist (default 8.0).
rust/brain_sim/src/lib.rs
for j in 0..neuron_count {
for k in (j + 1)..neuron_count {
if neurons[j].connections.len() >= settings.max_connections_per_neuron
|| neurons[k].connections.len() >= settings.max_connections_per_neuron
{ continue; }
if n1_pos.distance(n2_pos) >= settings.max_axon_dist { continue; }
let axon = Axon::new(n1_pos, n2_pos, j, k, &mut rng);
// ... record connections on both neurons
}
}
Performance

This pairwise connection pass is O(N²) in the neuron count. It runs once at construction, not per frame, so it is a one-time startup cost. The vertices_skip_step and max_connections_per_neuron settings keep N and the edge count bounded.

Bezier curves and sampling

Each axon is not a straight line: it is a cubic Bezier curve with four control points, sampled into vertices:

rust/brain_sim/src/lib.rs
const SUBDIVISIONS_PER_AXON: usize = 8;
const SAMPLES_PER_AXON: usize = SUBDIVISIONS_PER_AXON + 1; // 9

So every axon produces 9 sampled vertices (8 segments). At construction the core bakes three flat render buffers from these samples:

  • axon_positions: SAMPLES_PER_AXON * 3 floats per axon.
  • axon_indices: line-segment pairs (SUBDIVISIONS_PER_AXON * 2 per axon).
  • axon_opacities: a random per-segment opacity in [0.005, 0.2], duplicated across each segment's two endpoints.

The control points themselves are stored in an AxonControlTable, which the signal system uses to sample positions along the curve as a signal travels (parameter t from 0 to 1). The same Bezier evaluation determines both the static rendered axon and the moving signal position.

The Brain reads these buffers through the pointer getters (axon_positions_ptr, axon_index_ptr, axon_opacity_ptr) and binds them to the axon LineSegments geometry. They are uploaded once and never change.