~shockham/Physo

8784ab5fc94e8fc9194ec35e302400d5a13e80ca — shockham 3 years ago
Init commit
8 files changed, 376 insertions(+), 0 deletions(-)

A .gitignore
A Cargo.toml
A Makefile
A README.md
A index.html
A src/lib.rs
A src/shaders/frag.glsl
A src/shaders/vert.glsl
A  => .gitignore +11 -0
@@ 1,11 @@
target
**/*.rs.bk
Cargo.lock
.cargo-ok
node_modules
yarn.lock
yarn-error.log
dist
wasm-pack.log
pkg
.cache

A  => Cargo.toml +19 -0
@@ 1,19 @@
[package]
name = "physo"
version = "0.1.0"
authors = ["shockham <samuel.hockham@gmail.com>"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
infuse = "0.5"
wasm-bindgen = "0.2"
instant = { version = "0.1", features = [ "wasm-bindgen", "now" ] }

[dependencies.web-sys]
version = "0.3"
features = [
  'Window',
]

A  => Makefile +20 -0
@@ 1,20 @@
dev:
	git ls-files | entr make build

serve:
	python3 -m http.server

build:
	wasm-pack build --release --target web

clean:
	cargo clean
	rm -r pkg
	rm -r dist

rebuild: clean serve

dist: clean build
	mkdir dist
	cp index.html ./dist
	cp -r pkg ./dist

A  => README.md +25 -0
@@ 1,25 @@
# infuse-template

A template to use with [cargo-generate](https://github.com/ashleygwilliams/cargo-generate) to create [infuse](https://github.com/shockham/infuse) projects.

Usage:
- Install cargo-generate with:
```
cargo install cargo-generate
```
- Create a new project with:
```
cargo generate --git https://github.com/shockham/infuse-template.git
```

Run:
- Requires [`make`](https://www.gnu.org/software/make/) and [`entr`](http://eradman.com/entrproject/) for hot reloading
- Install wasm-pack:
```
cargo install wasm-pack
```
- Serve the app:
```
make build
make serve
```

A  => index.html +24 -0
@@ 1,24 @@
<html>
  <head>
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
    <style type="text/css">
    canvas {
        display: block;
        margin: auto;
        height: 100%;
    }
    </style>
  </head>
  <body>
    <canvas id="canvas" height="1000" width="1000" />
    <script type="module">
      import init from './pkg/physo.js';

      async function run() {
        let res = await init();
      }

      run();
    </script>
  </body>
</html>

A  => src/lib.rs +44 -0
@@ 1,44 @@
use std::collections::HashMap;

use infuse::{request_animation_frame, RenderItem, Renderer, Uniform};
use instant;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;

const VERT: &str = include_str!("./shaders/vert.glsl");
const FRAG: &str = include_str!("./shaders/frag.glsl");

#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
    let mut renderer = Renderer::new()?;

    let start_time = instant::now();

    // add a shader that will use the uniform
    renderer.add_shader("colour".into(), VERT.into(), FRAG.into())?;

    // create the uniforms for the render item
    let mut uniforms = HashMap::new();
    uniforms.insert("time".to_string(), Uniform::Float(start_time as f32));

    let render_item = RenderItem::new(
        vec![
            -1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0,
            1.0, 0.0,
        ],
        "colour".into(),
        Some(uniforms),
    );
    let mut render_items = vec![render_item];

    request_animation_frame!({
        let tick_time = instant::now();
        render_items[0].set_uniform(
            "time".to_string(),
            Uniform::Float((tick_time / 500f64) as f32),
        );
        renderer.draw(&render_items).unwrap();
    });

    Ok(())
}

A  => src/shaders/frag.glsl +222 -0
@@ 1,222 @@
precision mediump float;

const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float EPSILON = 0.0001;
const int MAX_ITERS = 8;
const float HALF_PI =  1.5707964;

const float distance = 20.0;
const float noise = 0.15;
const float displ = 0.0;
const float light = 0.5;
const float ncolor = 0.0;
const float round = 0.6;
const float size = 1.0;
const vec2 dimensions = vec2(1000.0, 1000.0);

uniform float time;

varying vec3 vposition;
varying vec3 vcolor;

int id = 0;

float sphere(vec3 p, float s) {
    return length(p) - s;
}

float box(vec3 p, vec3 b) {
  vec3 d = abs(p) - b;
  return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));
}

float disp(vec3 p, float amt) {
    return sin(amt*p.x)*sin(amt*p.y)*sin(amt*p.z);
}

mat3 rotateY(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(c, 0, s),
        vec3(0, 1, 0),
        vec3(-s, 0, c)
    );
}


float onion(float sdf, float thickness) {
    return abs(sdf)-thickness;
}

float octa(vec3 p, in float s) {
    p = abs(p);
    return (p.x+p.y+p.z-s)*0.57735027;
}


vec3 rep(in vec3 p, in vec3 c) {
    vec3 q = mod(p,c)-0.5*c;
    return q;
}

float scene(vec3 p) {
    vec3 q = rep(p, vec3(2.0, 0.0, 2.0));
    vec3 rp = rotateY(time / 4.0 + (sin(time) * 0.1)) * q;

	float o_sphere = sphere(rp, 1.0);

    return o_sphere;
}

float shortest_dist(vec3 eye, vec3 dir, float start, float end) {
    float depth = start;
    for (int i = 0; i < MAX_MARCHING_STEPS; i++) {
        float dist = scene(eye + depth * dir);
        if (dist < EPSILON || depth >=  end) break;
        depth += dist / (1.0 + displ);
    }
    return depth;
}

vec3 estimate_normal(vec3 p) {
    vec2 e = vec2(1.0,-1.0)*0.5773*0.0005;
    return normalize( e.xyy * scene(p + e.xyy) +
                      e.yyx * scene(p + e.yyx) +
                      e.yxy * scene(p + e.yxy) +
                      e.xxx * scene(p + e.xxx) );
}

vec3 phong_contrib(vec3 k_d, vec3 k_s, float alpha, vec3 p, vec3 eye,
                          vec3 light_pos, vec3 light_intensity) {
    vec3 N = estimate_normal(p);
    vec3 L = normalize(light_pos - p);
    vec3 V = normalize(eye - p);
    vec3 R = normalize(reflect(-L, N));

    float dotLN = dot(L, N);
    float dotRV = dot(R, V);

    if (dotLN < 0.0) {
        return vec3(0.0, 0.0, 0.0);
    }

    if (dotRV < 0.0) {
        return light_intensity * (k_d * dotLN);
    }
    return light_intensity * (k_d * dotLN + k_s * pow(dotRV, alpha));
}


float softshadow(vec3 eye, vec3 dir, float mint, float tmax ) {
    float res = 1.0;
    float t = mint;
    for(int i = 0; i < 16; i++) {
        float h = scene(eye + dir * t);
        res = min(res, 8.0 * h / t);
        t += clamp(h, 0.02, 0.10);
        if(h < 0.001 || t > tmax) break;
    }
    return clamp(res, 0.0, 1.0);
}


float calc_AO(vec3 pos, vec3 nor) {
    float occ = 0.0;
    float sca = 1.0;
    for(int i=0; i<5; i++) {
        float hr = 0.01 + 0.12*float(i)/4.0;
        vec3 aopos =  nor * hr + pos;
        float dd = scene(aopos);
        occ += -(dd-hr)*sca;
        sca *= 0.95;
    }
    return clamp( 1.0 - 3.0*occ, 0.0, 1.0 );
}

float rand(vec2 co) {
    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

vec3 lighting(vec3 k_a, vec3 k_d, vec3 k_s, float alpha, vec3 p, vec3 eye) {
    const vec3 ambient_light = vec3(0.6);
    vec3 color = ambient_light * k_a;
    vec3 normal = estimate_normal(p);

    float occ = calc_AO(p, normal);

    vec3 light_pos = vec3(4.0 * sin(time),
                          5.0,
                          4.0 * cos(time));
    vec3 light_intensity = vec3(light);


	color = mix(color, normal, ncolor);
	if(id == 1) {
		color = mix(color, vec3(1.0), 0.9);
	} else {
		color = mix(color, vec3(1.0), 0.1);
	}

    color += phong_contrib(k_d, k_s, alpha, p, eye,
                                  light_pos,
                                  light_intensity);
    color = mix(
        color,
        color * occ * softshadow(p, normalize(light_pos), 0.02, 5.0),
        light + tan(sin(time / 7.2)) * 4.0
    );

    color = mix(color, vec3(rand(vposition.xy * time)), noise);

    return color;
}


vec4 render(vec3 cam_pos, vec3 v_dir) {
    float dist = shortest_dist(cam_pos, v_dir, MIN_DIST, MAX_DIST);

    if (dist > MAX_DIST - EPSILON) {
        return vec4(0.0, 0.0, 0.0, 0.0);
    }

    vec3 p = cam_pos + dist * v_dir;
    vec3 color = lighting(vec3(0.2), vec3(0.2), vec3(1.0), 20.0, p, cam_pos);
    return vec4(color, 1.0);
}

mat4 view_matrix(vec3 eye, vec3 center, vec3 up) {
    vec3 f = normalize(center - eye);
    vec3 s = normalize(cross(f, up));
    vec3 u = cross(s, f);
    return mat4(
        vec4(s, 0.0),
        vec4(u, 0.0),
        vec4(-f, 0.0),
        vec4(0.0, 0.0, 0.0, 1)
    );
}

vec3 ray_dir(float fieldOfView, vec2 size, vec2 fragCoord) {
    vec2 xy = fragCoord - size / 2.0;
    float z = size.y / tan(radians(fieldOfView) / 2.0);
    return normalize(vec3(xy, -z));
}

void main() {
    vec3 dir = ray_dir(45.0, dimensions, vposition.xy * dimensions + (dimensions / 2.0));

    vec2 input_cam_pos = vec2(-2.4, 1.0);// * cos(time / 25.0);
    vec3 cam_pos = vec3(
        cos(input_cam_pos.x) * cos(input_cam_pos.y),
        sin(input_cam_pos.y),
        sin(input_cam_pos.x) * cos(input_cam_pos.y)
    ) * distance;

    mat4 view_mat = view_matrix(cam_pos, vec3(5.0, -10.0, 5.0), vec3(0.0, 1.0, 0.0));
    vec3 v_dir = (view_mat * vec4(dir, 0.0)).xyz;

    gl_FragColor = render(cam_pos, v_dir);
}

A  => src/shaders/vert.glsl +11 -0
@@ 1,11 @@
attribute vec4 position;
attribute vec3 color;

varying vec3 vposition;
varying vec3 vcolor;

void main () {
    gl_Position = position;
    vcolor = color;
    vposition = position.xyz;
}