Skip to content

Particles and lines

Particle emitters on the CPU and the GPU, Line2D and Trail2D.

Files: particles.odin, gpu_particles.odin, lines.odin.

Particle emitters: a ParticleEmitter node emits particles from its position, moves them (velocity, acceleration, gravity, wind, spin), changes their size and color over their life (a start and end color, or a ColorRamp of up to MAX_COLOR_STOPS colors) and draws them as circles, ellipses, rectangles, pixels, lines, a texture, or with code.

Its settings (ParticleSettings) say how; scene files store the ones that differ from DEFAULT_PARTICLE_SETTINGS in a <particles> element (project_format.odin). Their struct tags (min, max, label, group) describe them for editors: ParticleSettings_Fields.

Emission runs in cycles of lifetime seconds: each cycle emits amount particles, spread over the first 1 - burst of it (burst 1: all at once). A continuous emitter starts a new cycle when one ends; otherwise it stops (ParticleEmitter_Start runs it again). A particle lives lifetime seconds too.

Particles move in the world (they stay behind when the emitter moves; their direction turns with its world rotation), or in its local space with localSpace (they move, turn and scale with it). Angles are degrees clockwise from +X, like rotations; angle 270 is up.

Randomness: each xSpread is a range around the base value, used in proportion to xVariance (0: none, 1: all of it). spread is the half-size of the area particles start in.

Collisions (collisionEnabled): particles bounce off each other and, in world space, off the edges of what the camera shows (or the game area without a camera), keeping elasticity of their speed. It compares every pair of live particles: keep amount low with it.

Example

sparks := e.CreateParticleEmitter(scene)
sparks.position = {400, 300}
sparks.settings.amount = 80
sparks.settings.angle = 270
sparks.settings.angleSpread = 40
sparks.settings.angleVariance = 1
sparks.settings.shape = .Rectangle
// A one-shot burst, e.g. an explosion
boom := e.CreateParticleEmitter(scene)
boom.settings.burst = 1
boom.settings.continuous = false
e.ParticleEmitter_Start(boom)

GPU particles: a ParticleEmitter with gpu on simulates gpuAmount particles (up to MAX_GPU_PARTICLES) on the graphics card, when the game draws with SDL’s GPU renderer.

How: nothing is kept per particle. The vertex shader (shaders/particles) works out each particle from its index and the time: when it was born in its cycle, where the emitter was then (the emitter’s path, recorded each update and handed over as a storage buffer), random numbers from a hash of its index and cycle, then the motion in closed form (velocity, acceleration, gravity, wind; spin), its size and its color (start and end, or the ramp: up to 8 stops). The particles are drawn into a picture of their own on SDL’s GPU device (instanced quads, the emitter’s blend: alpha or additive), which the renderer then draws where the emitter is in the scene. Measured on an Intel UHD 620 (integrated graphics), a frame’s update and draw: 500 particles on the CPU 1.6 ms; on the GPU 500 0.8 ms, 20 000 2.1 ms, 100 000 5.2 ms, 200 000 9.1 ms.

The same settings, with these differences: no collisions (the view’s edges); the Custom, Ellipse, Line and Pixel shapes draw as circles; localSpace works. Where the GPU renderer isn’t available (other renderers, the web) the emitter simulates on the CPU as usual, with amount particles.

Lines and trails: thick polylines drawn as triangles (one geometry call each).

  • Line2D nodes draw a line through their points (the node’s space; points="0,0 100,20" in scene files, Line2D_SetPoints / AddPoint from code): ropes, lasers, paths, outlines. A width, a color fading to endColor along it, joints (sharp, clamped to LINE_MITER_LIMIT widths, or round), round caps, closed loops, and an optional texture stretched along it or tiled every width pixels.
  • Trail2D nodes leave a ribbon behind their parent as it moves (swords, dashes, comets, tire tracks): they record the parent’s world position as it moves at least minDistance, forget points older than lifetime, and draw them in the world, from width / color at the newest point to endWidth / endColor at the oldest. emitting off stops adding points (the trail fades away); Trail2D_Clear empties it (after a teleport).

Both draw through a node’s shader (shaders.odin) when they have a texture.

Example

// Given: sword: ^e.Node
laser := e.CreateLine2D(scene, {{0, 0}, {400, 0}})
laser.settings.width = 6
laser.settings.color = {255, 60, 60, 255}
laser.settings.endColor = {255, 60, 60, 0}
trail := e.CreateTrail2D(scene)
e.Node_AddChild(sword, trail)
ColorStop :: struct {
// 0 (emitted) to 1 (dies)
position: f32,
// The color there
color: sdl.Color,
}

A color at a point of a particle’s life: position 0 is when it’s emitted, 1 when it dies

struct. Source: particles.odin:60

ColorRamp :: struct {
// The stops; the first `count` are used
stops: [MAX_COLOR_STOPS]ColorStop,
// How many stops it has (an empty ramp is white)
count: int,
}

Colors along a particle’s life, blended between the stops (kept sorted by position). Scene files store it as position:#color pairs: "0:#ffe664 0.5:#e63228 1:#28282800".

struct. Source: particles.odin:71

ParticleShape :: enum {
// A circle of radius size.x
Circle,
// Radii size.x and size.y, turned by the particle's rotation
Ellipse,
// size.x by size.y, centered, turned by the particle's rotation
Rectangle,
// One screen pixel
Pixel,
// A line size.x long, scale.y thick, along the particle's rotation
Line,
// The emitter's texture, size.x by size.y, tinted by the particle's color
Texture,
// Drawn by the emitter's `customDraw` (a circle without one)
Custom,
}

What a particle is drawn as

enum. Source: particles.odin:223

ParticleSettings :: struct {
// Emits from the start (otherwise ParticleEmitter_Start begins)
emitting: bool `label:"Emitting" group:"Emission"`,
// Starts a new cycle when one ends; otherwise stops after one
continuous: bool `label:"Continuous"`,
// Particles per cycle
amount: int `min:"1" max:"500" label:"Amount"`,
// Seconds a cycle lasts, and a particle lives
lifetime: f32 `min:"0.01" max:"5" label:"Lifetime"`,
// 0: particles are spread evenly over the cycle; 1: all emitted at once
burst: f32 `min:"0" max:"1" label:"Burst"`,
// Half-size of the area particles start in, around the emitter
spread: [2]f32 `min:"0" max:"1280" label:"Spread"`,
// Particles move with the emitter (its local space) instead of staying in the world
localSpace: bool `label:"Local space"`,
// Simulated and drawn on the graphics card (gpu_particles.odin: the GPU renderer; no
// collisions), with `gpuAmount` particles; elsewhere on the CPU with `amount`
gpu: bool `label:"On the GPU"`,
// Particles per cycle on the GPU
gpuAmount: int `min:"1" max:"200000" label:"GPU amount"`,
// What each particle is drawn as
shape: ParticleShape `label:"Shape" group:"Shape"`,
// A particle's size in pixels (what x and y mean depends on `shape`)
size: [2]f32 `min:"0.01" max:"100" label:"Size"`,
// How much of sizeSpread is used: 0 none, 1 all of it
sizeVariance: f32 `min:"0" max:"1" label:"Size var."`,
// Random change of each particle's size, up to this many pixels either way (per axis)
sizeSpread: f32 `min:"0" max:"50" label:"Size spread"`,
// Scale at birth, blended toward scaleEnd over the particle's life
scaleStart: [2]f32 `min:"0" max:"1" label:"Scale start"`,
// Scale at death
scaleEnd: [2]f32 `min:"0" max:"1" label:"Scale end"`,
// Starting rotation of each particle
rotation: f32 `min:"0" max:"359" label:"Rotation" group:"Rotation"`,
// How much of rotationSpread is used: 0 none, 1 all of it
rotationVariance: f32 `min:"0" max:"1" label:"Rotation var."`,
// Random change of the starting rotation, up to this many degrees either way
rotationSpread: f32 `min:"0" max:"359" label:"Rot. spread"`,
// Spin, degrees per second
torque: f32 `min:"-2000" max:"2000" label:"Torque"`,
// How much of torqueSpread is used: 0 none, 1 all of it
torqueVariance: f32 `min:"0" max:"1" label:"Torque var."`,
// Random change of the spin, up to this many degrees per second either way
torqueSpread: f32 `min:"0" max:"2000" label:"Torque spread"`,
// How fast the spin changes, degrees per second²
torqueAcceleration: f32 `min:"-2000" max:"2000" label:"Torque accel."`,
// Colors from colorRamp; otherwise from particleColor to colorEnd
useColorRamp: bool `label:"Color ramp" group:"Color"`,
// The colors over a particle's life (with useColorRamp)
colorRamp: ColorRamp `label:"Ramp"`,
// The color at birth (without useColorRamp)
particleColor: sdl.Color `label:"Start color"`,
// The color at death (without useColorRamp)
colorEnd: sdl.Color `label:"End color"`,
// Random change of each particle's red, green and blue
colorVariance: f32 `min:"0" max:"1" label:"Color var."`,
// Direction particles are emitted in
angle: f32 `min:"0" max:"359" label:"Angle" group:"Motion"`,
// How much of angleSpread is used: 0 none, 1 all of it
angleVariance: f32 `min:"0" max:"1" label:"Angle var."`,
// Random change of the direction, up to this many degrees either way
angleSpread: f32 `min:"0" max:"360" label:"Angle spread"`,
// Starting speed, px/s
velocity: f32 `min:"0" max:"2000" label:"Velocity"`,
// How much of velocitySpread is used: 0 none, 1 all of it
velocityVariance: f32 `min:"0" max:"1" label:"Velocity var."`,
// Random change of the starting speed, up to this many px/s either way
velocitySpread: f32 `min:"0" max:"2000" label:"Vel. spread"`,
// Multiplies how far particles move each frame
velocityScale: f32 `min:"0" max:"10" label:"Speed scale"`,
// Speeds particles up (or down) along their emitted direction, px/s²
acceleration: f32 `min:"-2000" max:"2000" label:"Acceleration"`,
// Gravity's direction (normalized when used; {0, 0} turns gravity off)
gravityDirection: [2]f32 `min:"-1" max:"1" label:"Gravity dir." group:"Forces"`,
// Gravity's strength, px/s² (negative pulls the other way)
gravityVelocity: f32 `min:"-2000" max:"2000" label:"Gravity"`,
// Pushes every particle, px/s²
wind: [2]f32 `min:"-1000" max:"1000" label:"Wind"`,
// Particles bounce off each other and the edges of the view (see the top of particles.odin)
collisionEnabled: bool `label:"Collide" group:"Collision"`,
// Speed kept after a bounce
elasticity: f32 `min:"0" max:"1" label:"Elasticity"`,
}

How an emitter emits and what its particles do. Pixels, seconds and degrees (clockwise).

Each field’s tags describe it for editors: min / max (the range a slider covers; code may go outside it), label, and group on the first field of each group.

struct. Source: particles.odin:246

px/s
Particle :: struct {
// Pixels, in the world or the emitter's space
position: [2]f32,
velocity: [2]f32,
// px/s², along its emitted direction
acceleration: [2]f32,
// Pixels, before scaling
size: [2]f32,
// Its scale at birth
scaleStart: [2]f32,
// Its scale at death
scaleEnd: [2]f32,
// Its scale now (drawn size: size × scale)
scale: [2]f32,
// Degrees, clockwise
rotation: f32,
// Its spin, degrees per second
torque: f32,
// How fast its spin changes, degrees per second²
torqueAcceleration: f32,
// Seconds it lives
lifetime: f32,
// Seconds it has lived
age: f32,
// The fraction of its life gone, 0 to 1
agePercent: f32,
// Without a color ramp: its color at birth, blended toward colorEnd
colorStart: sdl.Color,
// Its color at death (without a color ramp)
colorEnd: sdl.Color,
// Random change of red, green and blue (colorVariance), kept for its whole life
colorJitter: [3]f32,
// Its color now
color: sdl.Color,
// In use; dead particles' slots are reused
alive: bool,
}

One particle, in the emitter’s world or local space (ParticleSettings.localSpace)

struct. Source: particles.odin:359

ParticleEmitter :: struct {
// The node part: transform, hierarchy, hooks
using node: Node,
// How it emits and what its particles do
settings: ParticleSettings,
// Emitting now (`settings.emitting` is how it starts; a non-continuous emitter stops after a cycle)
emitting: bool,
// For the Texture shape
texture: ^sdl.Texture,
// For the Custom shape: draws one particle. The draw state is the particle's space, so the
// Draw* helpers draw at it (origin at the particle, turned by its rotation; sizes aren't
// scaled: use `particle.size * particle.scale`)
customDraw: proc(emitter: ^ParticleEmitter, particle: ^Particle, renderer: ^sdl.Renderer),
// MAX_PARTICLES slots, used as a ring (in the node's arena)
particles: []Particle,
// Seconds since the current cycle started
cycleTime: f32,
// Seconds toward the next emission
emitTimer: f32,
// Particles emitted in the current cycle
emittedInCycle: int,
// A cycle is under way (false: the next update starts one)
cycleStarted: bool,
// The slot the next particle goes in
head: int,
/*
Notified once when a run has ended: emitting stopped (a one-shot cycle ended, or
`ParticleEmitter_Stop`) and its last particle died. `source` is the emitter, no data. A
continuous emitter that's never stopped never finishes; `ParticleEmitter_Start` begins
another run, which notifies again when it ends. In the node's arena.
Example:
// Given: scene: ^e.Scene, boom: ^e.ParticleEmitter
e.Event_Subscribe(&boom.onFinished, proc(_: ^e.EventSubject, source: rawptr, _: e.EventData, userData: rawptr) {
emitter := cast(^e.ParticleEmitter)source
e.Scene_RemoveNode(emitter.scene, emitter.id)
}, nil)
*/
onFinished: EventSubject `event:"A run ended: it stopped emitting and its last particle died"`,
// The run stopped and onFinished is still to come (when the last particle dies)
finishPending: bool,
// On the GPU (`settings.gpu`: gpu_particles.odin; nil until then)
gpu: ^GPUParticles,
}

A node that emits particles (see the top of particles.odin). Change settings at any time; new particles use the new values, and live ones the forces and colors.

struct. Source: particles.odin:402

ParticleFieldInfo :: struct {
// The field's name, as scene files store it
name: string,
// The field's type (f32, int, bool, [2]f32, sdl.Color, ParticleShape, ColorRamp)
type: typeid,
// What editors show (the `label` tag; the name without one)
label: string,
// Set on the first field of each group
group: string,
// The slider range (when `ranged`)
min, max: f32,
// It has a `max` tag (a slider range)
ranged: bool,
}

A ParticleSettings field, described by its tags (ParticleSettings_Fields)

struct. Source: particles.odin:641

GPUParticles :: struct {
// Seconds since the emitter was made (its own clock), when this run started and stopped
clock, start, stopped: f64,
// The emitter's path: (time, x, y, rotation), a ring
ring: [GPU_PATH_RING][4]f32,
ringHead, ringCount: int,
// Drawn with the GPU last time (the CPU simulation is off then)
active: bool,
// The picture the particles are drawn into, as SDL's texture too; its renderer and size
target: ^sdl.GPUTexture,
targetTexture: ^sdl.Texture,
renderer: ^sdl.Renderer,
size: [2]i32,
// The path's storage buffer and its upload buffer
path: ^sdl.GPUBuffer,
upload: ^sdl.GPUTransferBuffer,
device: ^sdl.GPUDevice,
}

An emitter’s GPU state (ParticleEmitter.gpu, the node’s arena; SDL objects freed with the node)

struct. Source: gpu_particles.odin:36

GPUParticleResources :: struct {
device: ^sdl.GPUDevice,
// Alpha (premultiplied) and additive
blend, add: ^sdl.GPUGraphicsPipeline,
sampler: ^sdl.GPUSampler,
// A white pixel, for shapes without a texture
white: ^sdl.GPUTexture,
failed: bool,
}

The pipelines and helpers GPU particles share on a device (Game.gpuParticles)

struct. Source: gpu_particles.odin:56

LineJoint :: enum {
// Pointed (a miter), cut at LINE_MITER_LIMIT widths
Sharp,
// Rounded
Round,
}

How a line’s corners are drawn

enum. Source: lines.odin:35

LineTextureMode :: enum {
// Once along the whole line
Stretch,
// Repeated every `width` pixels of length
Tile,
}

How a line’s texture covers it

enum. Source: lines.odin:43

LineSettings :: struct {
// Thickness in pixels (a trail's at its newest point)
width: f32,
// A trail's thickness at its oldest point (lines: unused)
endWidth: f32,
// The color at the start (a trail's newest point) and at the end, blended along the length
color, endColor: sdl.Color,
// Corners and ends
joint: LineJoint,
roundCaps: bool,
// A Line2D joins its last point to its first
closed: bool,
// How a texture (the node's `texture`) covers it
textureMode: LineTextureMode,
// Trail2D: seconds a point lasts; the distance the parent must move to add one; adding points
lifetime: f32,
minDistance: f32,
emitting: bool,
}

A Line2D’s or Trail2D’s look (scene files’ attributes; a plain value)

struct. Source: lines.odin:51

Line2D :: struct {
using node: Node,
settings: LineSettings,
// The points, in the node's space (the node's arena)
points: [dynamic][2]f32,
// Drawn along it (nil: plain color)
texture: ^sdl.Texture,
}

A line through points

struct. Source: lines.odin:78

Trail2D :: struct {
using node: Node,
settings: LineSettings,
// The points (world space), newest last, with the time each was added; the parent's position
// now (the trail's head, drawn after the newest point)
points: [dynamic]TrailPoint,
head: [2]f32,
// Seconds since it started (points' clock)
clock: f32,
texture: ^sdl.Texture,
}

A ribbon left behind its parent

struct. Source: lines.odin:88

TrailPoint :: struct {
position: [2]f32,
time: f32,
}

One of a trail’s points

struct. Source: lines.odin:101

ColorRamp_Evaluate :: proc(ramp: ColorRamp, t: f32) -> sdl.Color

The color of a ramp at a point of a particle’s life.

Inputs

  • ramp: The ramp
  • t: 0 (emitted) to 1 (dies); clamped

Returns

  • The blended color (white for an empty ramp)

procedure. Source: particles.odin:98

ColorRamp_Sort :: proc(ramp: ^ColorRamp, tracking: ^int = nil)

Sorts a ramp’s stops by position.

Inputs

  • ramp: The ramp
  • tracking: Optional: a stop’s index, updated to where that stop ends up (an editor’s selection)

procedure. Source: particles.odin:122

ColorRamp_AddStop :: proc(ramp: ^ColorRamp, position: f32, color: sdl.Color) -> int

Adds a stop (kept sorted).

Inputs

  • ramp: The ramp
  • position: 0 (emitted) to 1 (dies); clamped
  • color: The color there

Returns

  • Its index, or -1 if the ramp is full (MAX_COLOR_STOPS)

procedure. Source: particles.odin:148

ColorRamp_RemoveStop :: proc(ramp: ^ColorRamp, index: int) -> bool

Removes a stop. A ramp keeps at least two.

Inputs

  • ramp: The ramp
  • index: The stop’s index

Returns

  • false if it wasn’t removed

procedure. Source: particles.odin:167

FormatColorRamp :: proc(ramp: ColorRamp, allocator := context.allocator) -> string

Formats a ramp the way scene files store it: "0:#ffe664 0.5:#e63228 1:#28282800".

Inputs

  • ramp: The ramp
  • allocator: For the text

Returns

  • The text (the caller frees it with allocator)

procedure. Source: particles.odin:185

ParseColorRamp :: proc(text: string) -> (ColorRamp, bool)

Parses position:#color pairs separated by spaces (sorted afterwards; extra stops past MAX_COLOR_STOPS are dropped).

Inputs

Returns

  • The ramp, and false if a pair couldn’t be read (it’s skipped)

procedure. Source: particles.odin:204

CreateParticleEmitter :: proc(scene: ^Scene, settings := DEFAULT_PARTICLE_SETTINGS) -> ^ParticleEmitter

Creates a particle emitter.

Inputs

  • scene: The scene
  • settings: How it emits (DEFAULT_PARTICLE_SETTINGS unless given)

Returns

  • The emitter; it starts emitting on the scene’s next update if settings.emitting

procedure. Source: particles.odin:457

ParticleEmitter_Start :: proc(emitter: ^ParticleEmitter)

Starts emitting: a new cycle begins on the next update (live particles carry on). Use it to fire a one-shot (non-continuous) emitter again.

Inputs

  • emitter: The emitter

Example

// Given: boom: ^e.ParticleEmitter, at: [2]f32
boom.position = at
e.ParticleEmitter_Start(boom)

procedure. Source: particles.odin:483

ParticleEmitter_Stop :: proc(emitter: ^ParticleEmitter)

Stops emitting; live particles finish their lives, then onFinished is notified.

Inputs

  • emitter: The emitter

procedure. Source: particles.odin:499

ParticleEmitter_Clear :: proc(emitter: ^ParticleEmitter)

Removes every live particle (emitting goes on if it was).

Inputs

  • emitter: The emitter

procedure. Source: particles.odin:512

ParticleEmitter_AliveCount :: proc(emitter: ^ParticleEmitter) -> int

How many particles are alive.

Inputs

  • emitter: The emitter

Returns

  • The count, at most MAX_PARTICLES

procedure. Source: particles.odin:525

ParticleSettings_Fields :: proc() -> []ParticleFieldInfo

The settings’ fields, for editors: name, type and the tags (min, max, label, group). Temp allocated.

procedure. Source: particles.odin:622

ParticleSettings_Get :: proc(settings: ParticleSettings, name: string) -> (string, bool)

A setting’s value as scene files store it (numbers, true/false, “x,y”, #rrggbb[aa], enum names, a ramp’s position:#color pairs).

Inputs

Returns

  • The text (temp allocated), and false for an unknown name

procedure. Source: particles.odin:667

ParticleSettings_Set :: proc(settings: ^ParticleSettings, name, text: string) -> bool

Sets a setting from text (the format of ParticleSettings_Get).

Inputs

  • settings: The settings to change
  • name: The field’s name
  • text: Its value (surrounding spaces are ignored)

Returns

  • false if the name is unknown or the text isn’t a value for it (nothing changes)

Example

// Given: emitter: ^e.ParticleEmitter
e.ParticleSettings_Set(&emitter.settings, "particleColor", "#ff8800")
e.ParticleSettings_Set(&emitter.settings, "spread", "16,4")

procedure. Source: particles.odin:699

FormatParticleSettings :: proc(settings: ParticleSettings) -> []ScriptPropData

The settings that differ from DEFAULT_PARTICLE_SETTINGS, as name/value text in field order: the attributes of a scene file’s <particles> element.

Inputs

  • settings: The settings

Returns

  • The pairs (temp allocated)

procedure. Source: particles.odin:746

ParticleEmitter_OnGPU :: proc(emitter: ^ParticleEmitter) -> bool

Whether an emitter is drawing on the GPU now

procedure. Source: gpu_particles.odin:80

CreateLine2D :: proc(scene: ^Scene, points: [][2]f32 = nil, settings := DEFAULT_LINE_SETTINGS) -> ^Line2D

Creates a Line2D and adds it to the scene (at the start of the next update).

Inputs

  • scene: The scene
  • points: Its points, in its own space (copied)
  • settings: Its look

Returns

  • The line

procedure. Source: lines.odin:117

Line2D_SetPoints :: proc(line: ^Line2D, points: [][2]f32)

Replaces a line’s points (copied)

procedure. Source: lines.odin:138

Line2D_AddPoint :: proc(line: ^Line2D, point: [2]f32)

Adds a point at the end of a line

procedure. Source: lines.odin:144

Line2D_Clear :: proc(line: ^Line2D)

Removes a line’s points

procedure. Source: lines.odin:149

CreateTrail2D :: proc(scene: ^Scene, settings := DEFAULT_LINE_SETTINGS) -> ^Trail2D

Creates a Trail2D and adds it to the scene (at the start of the next update). Make it a child of what leaves the trail.

Inputs

  • scene: The scene
  • settings: Its look and how long points last

Returns

  • The trail

procedure. Source: lines.odin:164

Trail2D_Clear :: proc(trail: ^Trail2D)

Forgets a trail’s points (after a teleport: no streak across the screen)

procedure. Source: lines.odin:174

Trail2D_SetEmitting :: proc(trail: ^Trail2D, emitting: bool)

Starts or stops adding points (stopped, the trail fades away as its points age)

procedure. Source: lines.odin:179

Line_Geometry :: proc(points: [][2]f32, widths: []f32, colors: []sdl.FColor, us: []f32, joint: LineJoint, roundCaps, closed: bool, vertices: ^[dynamic]sdl.Vertex, indices: ^[dynamic]c.int)

A thick polyline’s triangles: points in screen space, each with its width and color, and its u along the line (texture coordinates). Closed lines join the last point to the first.

Inputs

  • points, widths, colors, us: Per point (same length)
  • joint, roundCaps, closed: The look
  • vertices, indices: Appended to

procedure. Source: lines.odin:192

MAX_COLOR_STOPS :: 8

Most colors a ColorRamp holds

constant. Source: particles.odin:55

MAX_PARTICLES :: 1000

Most particles an emitter has alive at once; the oldest are reused past it

constant. Source: particles.odin:57

DEFAULT_COLOR_RAMP :: ColorRamp{
stops = {
0 = {0, {255, 230, 100, 255}},
1 = {0.5, {230, 50, 40, 255}},
2 = {1, {40, 40, 40, 0}},
},
count = 3,
}

Warm yellow when emitted, fiery red at mid-life, fading out at the end

constant. Source: particles.odin:79

DEFAULT_PARTICLE_SETTINGS :: ParticleSettings{
emitting = true,
continuous = true,
amount = 50,
gpuAmount = 5000,
lifetime = 2,
shape = .Circle,
size = {5, 5},
scaleStart = {1, 1},
scaleEnd = {1, 1},
useColorRamp = true,
colorRamp = DEFAULT_COLOR_RAMP,
particleColor = {255, 255, 255, 255},
colorEnd = {255, 255, 255, 255},
velocity = 100,
velocityScale = 1,
gravityDirection = {0, 1},
gravityVelocity = 200,
elasticity = 0.7,
}

The settings of a new emitter, and of attributes a scene file leaves out

constant. Source: particles.odin:337

MAX_GPU_PARTICLES :: 200000

The most particles an emitter simulates on the GPU

constant. Source: gpu_particles.odin:28

GPU_PATH_SAMPLES :: 128

How many samples of the emitter’s path the shader reads

constant. Source: gpu_particles.odin:30

DEFAULT_LINE_SETTINGS :: LineSettings{width = 4, endWidth = 0, color = {255, 255, 255, 255}, endColor = {255, 255, 255, 255}, lifetime = 0.4, minDistance = 4, emitting = true}

A line’s defaults (a trail’s too)

constant. Source: lines.odin:72

LINE_MITER_LIMIT :: 2

How far a sharp corner may stick out, in widths (sharper corners are cut flat)

constant. Source: lines.odin:75