example: add a pendulum simulation (#9992)
parent
4ac751d773
commit
2c4a59f367
|
@ -0,0 +1,366 @@
|
||||||
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
// sim.v * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
// created by: jordan bonecutter * * * * * * * * * * * * * * * * * * *
|
||||||
|
// jpbonecutter@gmail.com * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
//
|
||||||
|
// I wrote the pendulum simulator to learn V, I think it could be a
|
||||||
|
// good addition to the examples directory.
|
||||||
|
// Essentially, the pendulum sim runs a simulation of a pendulum with
|
||||||
|
// a metallic tip swinging over three magnets.
|
||||||
|
// I run this simulation with the initial position at each pixel in an
|
||||||
|
// image and color the pixel according to the magnet over which it
|
||||||
|
// finally rests.
|
||||||
|
// I used some fun features in V like coroutines, channels,
|
||||||
|
// struct embedding, mutability, methods, and the like.
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import term
|
||||||
|
import runtime
|
||||||
|
|
||||||
|
// customisable through setting VJOBS
|
||||||
|
const parallel_workers = runtime.nr_jobs()
|
||||||
|
|
||||||
|
const width = 800
|
||||||
|
|
||||||
|
const height = 600
|
||||||
|
|
||||||
|
struct Vec3D {
|
||||||
|
x f64
|
||||||
|
y f64
|
||||||
|
z f64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (v Vec3D) add(v2 Vec3D) Vec3D {
|
||||||
|
return Vec3D{
|
||||||
|
x: v.x + v2.x
|
||||||
|
y: v.y + v2.y
|
||||||
|
z: v.z + v2.z
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (v Vec3D) dot(v2 Vec3D) f64 {
|
||||||
|
return (v.x * v2.x) + (v.y * v2.y) + (v.z * v2.z)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (v Vec3D) scale(scalar f64) Vec3D {
|
||||||
|
return Vec3D{
|
||||||
|
x: v.x * scalar
|
||||||
|
y: v.y * scalar
|
||||||
|
z: v.z * scalar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (v Vec3D) norm_squared() f64 {
|
||||||
|
return v.dot(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (v Vec3D) norm() f64 {
|
||||||
|
return math.sqrt(v.norm_squared())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SimState {
|
||||||
|
mut:
|
||||||
|
position Vec3D
|
||||||
|
velocity Vec3D
|
||||||
|
accel Vec3D
|
||||||
|
}
|
||||||
|
|
||||||
|
// magnets lie at [
|
||||||
|
// math.cos(index * 2 * math.pi / 3) * magnet_spacing
|
||||||
|
// math.sin(index * 2 * math.pi / 3) * magnet_spacing
|
||||||
|
// -magnet_height
|
||||||
|
// ]
|
||||||
|
struct SimParams {
|
||||||
|
rope_length f64
|
||||||
|
bearing_mass f64
|
||||||
|
magnet_spacing f64
|
||||||
|
magnet_height f64
|
||||||
|
magnet_strength f64
|
||||||
|
gravity f64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (params SimParams) get_rope_vector(state SimState) Vec3D {
|
||||||
|
rope_origin := Vec3D{
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
z: params.rope_length
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.position.add(rope_origin.scale(-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut state SimState) satisfy_rope_constraint(params SimParams) {
|
||||||
|
mut rope_vector := params.get_rope_vector(state)
|
||||||
|
rope_vector = rope_vector.scale(params.rope_length / rope_vector.norm())
|
||||||
|
state.position = Vec3D{
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
z: params.rope_length
|
||||||
|
}.add(rope_vector)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (params SimParams) get_grav_force(state SimState) Vec3D {
|
||||||
|
return Vec3D{
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
z: -params.bearing_mass * params.gravity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (params SimParams) get_magnet_position(theta f64) Vec3D {
|
||||||
|
return Vec3D{
|
||||||
|
x: math.cos(theta) * params.magnet_spacing
|
||||||
|
y: math.sin(theta) * params.magnet_spacing
|
||||||
|
z: -params.magnet_height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (params SimParams) get_magnet_force(theta f64, state SimState) Vec3D {
|
||||||
|
magnet_position := params.get_magnet_position(theta)
|
||||||
|
mut diff := magnet_position.add(state.position.scale(-1))
|
||||||
|
distance_squared := diff.norm_squared()
|
||||||
|
diff = diff.scale(1.0 / math.sqrt(distance_squared))
|
||||||
|
return diff.scale(params.magnet_strength / distance_squared)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (params SimParams) get_magnet_dist(theta f64, state SimState) f64 {
|
||||||
|
return params.get_magnet_position(theta).add(state.position.scale(-1)).norm()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (params SimParams) get_magnet1_force(state SimState) Vec3D {
|
||||||
|
return params.get_magnet_force(0.0 * math.pi / 3.0, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (params SimParams) get_magnet2_force(state SimState) Vec3D {
|
||||||
|
return params.get_magnet_force(2.0 * math.pi / 3.0, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (params SimParams) get_magnet3_force(state SimState) Vec3D {
|
||||||
|
return params.get_magnet_force(4.0 * math.pi / 3.0, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (params SimParams) get_tension_force(state SimState, f_passive Vec3D) Vec3D {
|
||||||
|
rope_vector := params.get_rope_vector(state)
|
||||||
|
rope_vector_norm := rope_vector.scale(1.0 / rope_vector.norm())
|
||||||
|
return rope_vector_norm.scale(-1.0 * rope_vector_norm.dot(f_passive))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut state SimState) increment(delta_t f64, params SimParams) {
|
||||||
|
// basically just add up all forces =>
|
||||||
|
// get an accelleration =>
|
||||||
|
// add to velocity =>
|
||||||
|
// ensure rope constraint is satisfied
|
||||||
|
|
||||||
|
// force due to gravity
|
||||||
|
f_gravity := params.get_grav_force(state)
|
||||||
|
|
||||||
|
// force due to each magnet
|
||||||
|
f_magnet1 := params.get_magnet1_force(state)
|
||||||
|
|
||||||
|
// force due to each magnet
|
||||||
|
f_magnet2 := params.get_magnet2_force(state)
|
||||||
|
|
||||||
|
// force due to each magnet
|
||||||
|
f_magnet3 := params.get_magnet3_force(state)
|
||||||
|
|
||||||
|
// passive forces
|
||||||
|
f_passive := f_gravity.add(f_magnet1.add(f_magnet2.add(f_magnet3)))
|
||||||
|
|
||||||
|
// force due to tension of the rope
|
||||||
|
f_tension := params.get_tension_force(state, f_passive)
|
||||||
|
|
||||||
|
// sum up all the fores
|
||||||
|
f_sum := f_tension.add(f_passive)
|
||||||
|
|
||||||
|
// get the acceleration
|
||||||
|
accel := f_sum.scale(1.0 / params.bearing_mass)
|
||||||
|
state.accel = accel
|
||||||
|
|
||||||
|
// update the velocity
|
||||||
|
state.velocity = state.velocity.add(accel.scale(delta_t))
|
||||||
|
|
||||||
|
// update the position
|
||||||
|
state.position = state.position.add(state.velocity.scale(delta_t))
|
||||||
|
|
||||||
|
// ensure the position satisfies rope constraint
|
||||||
|
state.satisfy_rope_constraint(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (state SimState) done() bool {
|
||||||
|
return state.velocity.norm() < 0.05 && state.accel.norm() < 0.01
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PPMWriter {
|
||||||
|
mut:
|
||||||
|
file os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ImageSettings {
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Pixel {
|
||||||
|
r byte
|
||||||
|
g byte
|
||||||
|
b byte
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut writer PPMWriter) start_for_file(fname string, settings ImageSettings) {
|
||||||
|
writer.file = os.create(fname) or { panic("can't create file $fname") }
|
||||||
|
writer.file.writeln('P6 $settings.width $settings.height 255') or {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut writer PPMWriter) next_pixel(p Pixel) {
|
||||||
|
writer.file.write([p.r, p.g, p.b]) or {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut writer PPMWriter) finish() {
|
||||||
|
writer.file.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_runner(mut state SimState, params SimParams) Pixel {
|
||||||
|
// do the simulation!
|
||||||
|
for _ in 0 .. 1000 {
|
||||||
|
state.increment(0.0005, params)
|
||||||
|
if state.done() {
|
||||||
|
println('done!')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the closest magnet
|
||||||
|
m1_dist := params.get_magnet_dist(0, state)
|
||||||
|
m2_dist := params.get_magnet_dist(2.0 * math.pi / 3.0, state)
|
||||||
|
m3_dist := params.get_magnet_dist(4.0 * math.pi / 3.0, state)
|
||||||
|
|
||||||
|
if m1_dist < m2_dist && m1_dist < m3_dist {
|
||||||
|
return Pixel{
|
||||||
|
r: 255
|
||||||
|
g: 0
|
||||||
|
b: 0
|
||||||
|
}
|
||||||
|
} else if m2_dist < m1_dist && m2_dist < m3_dist {
|
||||||
|
return Pixel{
|
||||||
|
r: 0
|
||||||
|
g: 255
|
||||||
|
b: 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Pixel{
|
||||||
|
r: 0
|
||||||
|
g: 0
|
||||||
|
b: 255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SimResult {
|
||||||
|
id u64
|
||||||
|
p Pixel
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SimRequest {
|
||||||
|
id u64
|
||||||
|
params SimParams
|
||||||
|
mut:
|
||||||
|
initial SimState
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_worker(request_chan chan SimRequest, result_chan chan SimResult) {
|
||||||
|
// serve sim requests as they come in
|
||||||
|
for {
|
||||||
|
mut request := <-request_chan or { break }
|
||||||
|
|
||||||
|
result_chan <- SimResult{
|
||||||
|
id: request.id
|
||||||
|
p: sim_runner(mut request.initial, request.params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ValidPixel {
|
||||||
|
Pixel
|
||||||
|
mut:
|
||||||
|
valid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
fn image_worker(mut writer PPMWriter, result_chan chan SimResult, total_pixels u64) {
|
||||||
|
// as new pixels come in, write them to the image file
|
||||||
|
mut current_index := u64(0)
|
||||||
|
mut pixel_buf := []ValidPixel{len: int(total_pixels), init: ValidPixel{
|
||||||
|
valid: false
|
||||||
|
}}
|
||||||
|
for {
|
||||||
|
result := <-result_chan or { break }
|
||||||
|
pixel_buf[result.id].Pixel = result.p
|
||||||
|
pixel_buf[result.id].valid = true
|
||||||
|
|
||||||
|
for current_index < total_pixels && pixel_buf[current_index].valid {
|
||||||
|
writer.next_pixel(pixel_buf[current_index].Pixel)
|
||||||
|
current_index++
|
||||||
|
}
|
||||||
|
|
||||||
|
if current_index >= total_pixels {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
params := SimParams{
|
||||||
|
rope_length: 0.25
|
||||||
|
bearing_mass: 0.03
|
||||||
|
magnet_spacing: 0.05
|
||||||
|
magnet_height: 0.03
|
||||||
|
magnet_strength: 10.0
|
||||||
|
gravity: 4.9
|
||||||
|
}
|
||||||
|
|
||||||
|
mut writer := PPMWriter{}
|
||||||
|
writer.start_for_file('test.ppm', ImageSettings{
|
||||||
|
width: width
|
||||||
|
height: height
|
||||||
|
})
|
||||||
|
defer {
|
||||||
|
writer.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
result_chan := chan SimResult{}
|
||||||
|
request_chan := chan SimRequest{}
|
||||||
|
|
||||||
|
// start a worker on each core
|
||||||
|
for _ in 0 .. parallel_workers {
|
||||||
|
go sim_worker(request_chan, result_chan)
|
||||||
|
}
|
||||||
|
|
||||||
|
go fn (request_chan chan SimRequest, params SimParams) {
|
||||||
|
mut index := u64(0)
|
||||||
|
println('')
|
||||||
|
for y in 0 .. height {
|
||||||
|
term.clear_previous_line()
|
||||||
|
println('Line: $y')
|
||||||
|
for x in 0 .. width {
|
||||||
|
// setup initial conditions
|
||||||
|
mut state := SimState{}
|
||||||
|
state.position = Vec3D{
|
||||||
|
x: 0.1 * ((f64(x) - 0.5 * f64(width - 1)) / f64(width - 1))
|
||||||
|
y: 0.1 * ((f64(y) - 0.5 * f64(height - 1)) / f64(height - 1))
|
||||||
|
z: 0.0
|
||||||
|
}
|
||||||
|
state.velocity = Vec3D{}
|
||||||
|
state.satisfy_rope_constraint(params)
|
||||||
|
request_chan <- SimRequest{
|
||||||
|
id: index
|
||||||
|
initial: state
|
||||||
|
params: params
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request_chan.close()
|
||||||
|
}(request_chan, params)
|
||||||
|
|
||||||
|
image_worker(mut writer, result_chan, width * height)
|
||||||
|
}
|
Loading…
Reference in New Issue