Skip to content

Commit

Permalink
Add a demo showcasing a procedurally generated flame
Browse files Browse the repository at this point in the history
  • Loading branch information
kjagiello committed Jan 18, 2023
1 parent 5c8de90 commit a3b8427
Show file tree
Hide file tree
Showing 8 changed files with 441 additions and 0 deletions.
22 changes: 22 additions & 0 deletions examples/flame/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "probe-run --chip RP2040"

rustflags = [
"-C", "linker=flip-link",
"-C", "link-arg=--nmagic",
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",

# Code-size optimizations.
# trap unreachable can save a lot of space, but requires nightly compiler.
# uncomment the next line if you wish to enable it
# "-Z", "trap-unreachable=no",
"-C", "inline-threshold=5",
"-C", "no-vectorize-loops",
]

[build]
target = "thumbv6m-none-eabi"

[env]
DEFMT_LOG = "debug"
74 changes: 74 additions & 0 deletions examples/flame/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
[package]
edition = "2021"
name = "dashy"
version = "0.1.0"

[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = { version = "0.2.5", features = ["unproven"] }
embedded-time = "0.12"

defmt = "0.3"
defmt-rtt = "0.3"
panic-probe = { version = "0.3", features = ["print-defmt"] }

rp-pico = "0.6.0"

embedded-graphics = "0.7.1"
hub75-pio = { path = "../../" }

nalgebra = { version = "0.31.4", default-features = false, features= ["libm"] }
libm = "0.2.6"

# cargo build/run
[profile.dev]
codegen-units = 1
debug = 2
debug-assertions = true
incremental = false
opt-level = 3
overflow-checks = true

# cargo build/run --release
[profile.release]
codegen-units = 1
debug = 2
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 3
overflow-checks = false

# do not optimize proc-macro crates = faster builds from scratch
[profile.dev.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false

[profile.release.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false

# cargo test
[profile.test]
codegen-units = 1
debug = 2
debug-assertions = true
incremental = false
opt-level = 3
overflow-checks = true

# cargo test --release
[profile.bench]
codegen-units = 1
debug = 2
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 3
39 changes: 39 additions & 0 deletions examples/flame/Embed.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[default.probe]
protocol = "Swd"
speed = 20000
# If you only have one probe cargo embed will pick automatically
# Otherwise: add your probe's VID/PID/serial to filter

## rust-dap
# usb_vid = "6666"
# usb_pid = "4444"
# serial = "test"


[default.flashing]
enabled = true

[default.reset]
enabled = true
halt_afterwards = false

[default.general]
chip = "RP2040"
log_level = "WARN"
# RP2040 does not support connect_under_reset
connect_under_reset = false

[default.rtt]
enabled = true
up_mode = "NoBlockSkip"
channels = [
{ up = 0, down = 0, name = "name", up_mode = "NoBlockSkip", format = "Defmt" },
]
timeout = 3000
show_timestamps = true
log_enabled = false
log_path = "./logs"

[default.gdb]
enabled = false
gdb_connection_string = "127.0.0.1:2345"
31 changes: 31 additions & 0 deletions examples/flame/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());

// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
}
36 changes: 36 additions & 0 deletions examples/flame/debug_probes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Compatible CMSIS-DAP debug probes

## Raspberry Pi Pico

You can use a second Pico as your debugger.

- Download this file: https://github.com/majbthrd/DapperMime/releases/download/20210225/raspberry_pi_pico-DapperMime.uf2
- Boot the Pico in bootloader mode by holding the bootset button while plugging it in
- Open the drive RPI-RP2 when prompted
- Copy raspberry_pi_pico-DapperMime.uf2 from Downloads into RPI-RP2
- Connect the debug pins of your CMSIS-DAP Pico to the target one
- Connect GP2 on the Probe to SWCLK on the Target
- Connect GP3 on the Probe to SWDIO on the Target
- Connect a ground line from the CMSIS-DAP Probe to the Target too

## WeAct MiniF4
https://therealprof.github.io/blog/usb-c-pill-part1/

## HS-Probe
https://github.com/probe-rs/hs-probe

## ST-LINK v2 clone
It's getting harder to source these with stm32f103's as time goes on, so you might be better off choosing a stm32f103 dev board

Firmware: https://github.com/devanlai/dap42

## LPC-Link2
https://www.nxp.com/design/microcontrollers-developer-resources/lpc-link2:OM13054

## MCU-Link
https://www.nxp.com/part/MCU-LINK#/

## DAPLink
You can use DAPLink firmware with any of it's supported chips (LPC4322, LPC11U35, K20, K22, KL26). You'll need to use the 'develop' branch to use GCC to build it. You'll need to find a chip with the correct

Firmware source: https://github.com/ARMmbed/DAPLink/tree/develop
15 changes: 15 additions & 0 deletions examples/flame/memory.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
MEMORY {
BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
RAM : ORIGIN = 0x20000000, LENGTH = 256K
}

EXTERN(BOOT2_FIRMWARE)

SECTIONS {
/* ### Boot loader */
.boot2 ORIGIN(BOOT2) :
{
KEEP(*(.boot2));
} > BOOT2
} INSERT BEFORE .text;
109 changes: 109 additions & 0 deletions examples/flame/src/flame.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use libm::{floorf, modff};
use nalgebra::{clamp, SMatrix, Vector2, Vector3};

type FlameCanvas = SMatrix<Vector3<u8>, 32, 32>;

fn fractf(x: f32) -> f32 {
return modff(x).0;
}

fn hash(x: Vector2<f32>) -> Vector2<f32> {
let k: Vector2<f32> = Vector2::new(0.3183099, 0.3678794);
let x = x.component_mul(&k) + k.yx();
2.0 * (16.0 * k * fractf(x.x * x.y * (x.x + x.y))).map(fractf) - Vector2::from_element(1.0)
}

fn noised(p: Vector2<f32>) -> Vector3<f32> {
let i = p.map(floorf);
let f = p.map(fractf);

let u1 = f * 6.0 - Vector2::from_element(15.0);
let u2 = f.component_mul(&u1) + Vector2::from_element(10.0);
let u = f
.component_mul(&f)
.component_mul(&f)
.component_mul(&f)
.component_mul(&u2);

let du1 = f - Vector2::from_element(2.0);
let du2 = f.component_mul(&du1) + Vector2::from_element(1.0);
let du = 30.0 * f.component_mul(&f).component_mul(&du2);

let ga = hash(i + Vector2::new(0.0, 0.0));
let gb = hash(i + Vector2::new(1.0, 0.0));
let gc = hash(i + Vector2::new(0.0, 1.0));
let gd = hash(i + Vector2::new(1.0, 1.0));

let fa = f - Vector2::new(0.0, 0.0);
let fb = f - Vector2::new(1.0, 0.0);
let fc = f - Vector2::new(0.0, 1.0);
let fd = f - Vector2::new(1.0, 1.0);

let va = ga.dot(&fa);
let vb = ga.dot(&fb);
let vc = ga.dot(&fc);
let vd = ga.dot(&fd);

let r1 = va + u.x * (vb - va) + u.y * (vc - va) + u.x * u.y * (va - vb - vc + vd);
let r22 = u.yx() * (va - vb - vc + vd) + Vector2::new(vb, vc) - Vector2::from_element(va);
let r2 = ga
+ u.x * (gb - ga)
+ u.y * (gc - ga)
+ u.x * u.y * (ga - gb - gc + gd)
+ du.component_mul(&r22);

Vector3::new(r1, r2.x, r2.y)
}

fn stepf(x: f32, l: f32) -> f32 {
if x < l {
1.0
} else {
0.0
}
}

fn sdf_circle(p: Vector2<f32>, r: f32) -> f32 {
p.norm() - r
}

fn shader(t: usize, x: f32, y: f32, _color: Vector3<u8>) -> Vector3<f32> {
let uv = Vector2::new(x as f32, y as f32) / 31f32;
let octaves = 7f32;
let noise_amount = noised(octaves * uv + Vector2::new(0.0, t as f32)).x;
let y_gradient = clamp(0.7 - uv.y, 0.0, 1.0) * 0.6;
let sdf_noise = Vector2::new(0.1, 2.5 * y_gradient) * noise_amount;

let p1 = uv - Vector2::new(0.6, 0.7) + sdf_noise;
let p2 = uv - Vector2::new(0.6, 0.775) + sdf_noise;
let p3 = uv - Vector2::new(0.6, 0.80) + sdf_noise;

let amount_outer = stepf(sdf_circle(p1, 0.25), 0.0);
let amount_inner = stepf(sdf_circle(p2, 0.175), 0.0);
let amount_center = stepf(sdf_circle(p3, 0.075), 0.0);

let couter: Vector3<f32> = Vector3::new(214.0, 10.0, 3.0);
let cinner: Vector3<f32> = Vector3::new(214.0, 63.0, 2.0);
let center: Vector3<f32> = Vector3::new(255.0, 204.0, 23.0);

if amount_center > 0.0 {
center
} else if amount_inner > 0.0 {
cinner
} else if amount_outer > 0.0 {
couter
} else {
Vector3::from_element(0f32)
}
}

fn into_color_vec(color: Vector3<f32>) -> Vector3<u8> {
color.map(|v| u8::try_from(floorf(v) as u32).unwrap_or(0))
}

pub fn tick(t: usize) -> FlameCanvas {
let canvas = FlameCanvas::from_element(Vector3::repeat(0));
canvas
.map_with_location(|x, y, c| shader(t, x as f32, y as f32, c))
.map(into_color_vec)
}
Loading

0 comments on commit a3b8427

Please sign in to comment.