Even though the fragment and vertex shader use OpenGL version 1.4, the compute shaders which run the simulation requires OpenGL 4.3 (version from 2012, where compute shaders were introduced).
- Add
RAND
value accessible for rules - Add custom Storage fields (maybe transform the cell data texture into a texture array)
- Can be used for lifetime, remaining grow height of plants etc.
- Add particle systems (maybe separate compute shader to run in parallel to the falling sand sim?)
- Improve pixel lighting
- respect the canvas size/ look similar even on multiple canvas sizes
- be customizable
- maybe remove flood fill in favor of more static lighting? Or improve flood fill
- include preview GIFs in
README.md
- provide more example materials in
data.yaml
- continue to improve
physics
branch an merge withmain
branch - add some sort of "Modification" System like explosions, cell placements (drawing)
- add more diverse material shading options, modify noise based on Material or provide lookup textures for color
- explore methods of 3x3 Margolus neighborhood
- provide builds (win, mac, linux x86/ arm)
- performance optimizations (profiling, work group size, dispatch size etc.)
-
wrapped2D (Box2D) on the CPU simulates bodies
-
each Cell stores its position relative one body
-
only static, non moving materials can be part of a rigidbody
-
after the CPU physics step, the transforms of all rigidbodies are transferred to the GPU using a uniform array
-
have 2 more uniform arrays for "new" and "queued for deletion" bodies
-
the cell checks if its part of a body that is queued for deletion and if so, sets itself to empty
-
all other cells check the "queued for deletion" list to see if a body is being deleted, that has a lower idx in the list of bodies and depending on how many bodies that affects, subtract its own body index ("2 bodies before my body are getting deleted?
my_body_idx -= 2
) -
maybe have a struct
RBContructor
to describe the texture (so that a cell can set itself to the material on the texture if it is inside that rbs texture), pos, rot of the new rb
vec2 rotatePoint(vec2 pt, float rot) {
return mat4(cos(rot), sin(rot), -sin(rot), cos(rot)) * pt;
}
vec2 rotatePoint(vec2 pt, float rot, vec2 origin) {
return rotatePoint(pt - origin, rot) + origin;
}
vec2 new_pos = rotatePoint(self.pos, bodies[my_body_idx].rotation, bodies[my_body_idx].position);
- ???
- Maybe flood fill but each time it travels through a solid cell: light / 4
- Maybe Bresenham/ DDA in 360° from center point (SpotLight) or from line (DirectionalLight)
- egui vs. imgui
- Convert sprites into SimMaterial
- Using chunks, save all relevant textures
In general, most global scope things should be named in all uppercase so that you can recognize it and avoid confusion.
SELF
- The current cellDOWN
- The cell belowRIGHT
- The cell to the right- (
LEFT
- The cell to the left; automatically causes the rule to be assymetric (mirrored: false
)) DOWNRIGHT
- The cell down and to the right
- WIP:
is
- Checks if something is an instance of a type or material (typeof
) <
,>
,==
,!=
,&&
/and
,||
/or
,not
- Logical Operators
SWAP <Cell 1> <Cell 2>
- Swaps both cells- Example:
SWAP SELF DOWN
- Example:
SET <Cell> <Material>
- Replaces theCell
with a new cell of that material- Example:
SET SELF stone
(assuming there is astone
material)
- Example:
isType_<your type>(<Cell>)
- For each type defined intypes
there will be a checker function that returns true if the argument (for exampleSELF
) is that type. This accounts for inheritance, meaning if the typeplant
inherits the typeorganism
(viainherits: organism
) thenisType_organism(<some plant cell>)
will be true
- (
TYPE_
)EMPTY
- The air/ empty material/ type for a cell - (
TYPE_
)NULL
- The material/ type that is returned if its nothing else is - (
TYPE_
)WALL
- The material/ type just outside the screen
Both WALL
and NULL
can not be swapped with another cell.
Rules will be processed in the order that they are defined.
This simulation uses the margolus neighbourhood, meaning each cell can only freely access groups of 2x2 pixels.
That means that "normally" you would only have access to the own pixel, right,
down and downright pixel.
In order to not limit the user too strictly, after each frame the offset is shifted
so that when you write RIGHT
in a if
or do
, then you can assume that it
will be either the left (LEFT
) or right (RIGHT
) cell depending on the frame.
The same applies for DOWNRIGHT
: It is either the cell down-right from
the current cell or the cell down-left depending on the frame.
This is what is called mirroring in the YAML syntax and which can be turned off
using the mirrored: false
attribute.
Doing this will cause the parser to look for either access of LEFT
or RIGHT
,
turning it into a rule that will be only be run for one of the two options.
The following lists all possible attributes of a rule, with the values being the default values:
# Collection of all rules, this field name cannot be changed and
# will throw an error if non-existent
rules:
# Name of the rule
rulename:
# Condition that needs to be true in order for the do action to run
if: <condition>
# Action that will be run when the if condition is true
do: <some action>
# Alternative do syntax (for multiple actions):
# do:
# - <action 1>
# - <action 2>
# - ...
# OPTIONAL: Only if a random value from 0-1 is smaller than this value
# the rule will be run
probability: 1.0
# OPTIONAL: Provides an alternative path for when the if condition is false
else:
# OPTIONAL: The if here can be left out to always execute the do action
if: <condition>
do: <action>
# OPTIONAL: Same as the other chance
chance: 1.0
# OPTIONAL: Whether the rule should be mirrored to the left
# see "Concept of mirrored rules"
mirrored: true
# OPTIONAL: The parser will detect on which type or material the rule was used and
# will only allow that specific type/ material to run the rule (if SELF is that mat/ type)
# Note, that if there is no precondition, the use of 'isType_<type>' is often neccesary
precondition: true
rules:
gravity:
if: SELF.mat.density > DOWN.mat.density
do: swap(SELF, DOWN)
rules:
grow:
# check if there is a plant cell below us.
# Could also check for a specific material for example
# DOWN.mat == vine
if: isType_empty(SELF) and isType_plant(DOWN)
do: set SELF vine # vine is a material
Types are just a collection of rules, coupled with inheritance.
# Collection of all types, this field name cannot be changed and
# will throw an error if non-existent
types:
# Name of the type
typename:
# OPTIONAL: Type of which to inherit all base_rules
inherits: <some type>
# OPTIONAL: List of rules that will be applied to all materials of that type
# Can be left out if empty
base_rules: []
types:
organism:
base_rules: [
grow
]
plant:
inherits: organism
# Note there are no base_rules here
types:
solid:
movable_solid:
base_rules: [
gravity,
slide_diagonally
]
rules:
fall_slide:
if: DOWN.mat.density < SELF.mat.density
do:
- SWAP SELF DOWN
else:
if: RIGHT.mat.density < SELF.mat.density and DOWNRIGHT.mat.density < SELF.mat.density
do: SWAP SELF DOWNRIGHT
mirrored: true
rise_up:
if: isType_gas(DOWN) and not isType_solid(SELF) and DOWN.mat.density < SELF.mat.density
do: SWAP DOWN SELF
else:
if: isType_gas(DOWN) and not isType_solid(RIGHT) and DOWN.mat.density < RIGHT.mat.density
do: SWAP DOWN RIGHT
precondition: false
# Collection of all materials, this field name cannot be changed and
# will throw an error if non-existent
materials:
# Name of the material
materialname:
# The color of the material, valid options for defining it are:
# color: [255, 0, 255, 255]
# color: [255, 0, 255] # Note, only 3 values with assume alpha is 1
# color: [1.0, 0, 255, 0.3] # Example of mixed types
color: [1.0, 0.0, 1.0, 1.0]
# OPTIONAL: How much light the material should emit
# See the color attribute for possible ways of defining the value
emission: [0.0, 0.0, 0.0, 0.0]
# By specifying a type, this material inherits all rules of the base type
type: <type>
# OPTIONAL: Whether the material can be selectable with NUM_0-9 or the UI
selectable: true
# Density of the material, used for swapping and vertical ordering of materials
density: 2.2
# OPTIONAL: Add some rules here which are unique to this material
# and cannot be added to the type
extra_rules: [
somerule
]
materials:
sand:
type: movable_solid
color: [1.0, 1.0, 0]
density: 1.5
rock:
type: solid
color: [0.2, 0.2, 0.2]
density: 4.0
water:
type: liquid
color: [0.0, 0.0, 1.0, 0.5]
density: 1.5
radioactive:
type: solid
color: [0.196, 0.55, 0.184]
emission: [0.05, 0.7, 0.05, 0.9]
density: 5.0
This binary crate is a collection of library crates related to the sandengine.
Responsible for:
- Running the compute shader which simulates everything
- Rendering the simulation
- (CURRENTLY NOT WORKING) Rendering the UI
Responsible for:
- Reading in the YAML File, defining rules, types and materials
- Parsing that input and producing Rust structs, holding the information included in the the YAML File
- Converting those structs into valid GLSL code (located under shaders/compute/gen)
Holds textures, assets and the YAML File.