‹ construct2

construct2_3d

//
//  construct2_3d.js
//
//  Copyright (c) 2003 Fredrik Olofsson Musikproduktion. All rights reserved.
//
//  2001 original code in java
//  010502 max patch (rev.011004)
//  040920 ported to processing
//  060227 3d version for processing
//  071201 ported to sc (RedConstruct)
//  200326 ported to p5js
//  210517 ported to nannou

// ---------------------------------------------------------------------------------------

use nannou::prelude::*;

const MOV_X: u8 = 95;
const MOV_Y: u8 = 95;
const MOV_Z: u8 = 95;

struct Model {
    c: Rgb8,
    step_x: i32,
    step_y: i32,
    step_z: i32,
    x: i32,
    y: i32,
    z: i32,
    dx: i32,
    dy: i32,
    dz: i32,
    v: u8,
    m: u8,
    col: i32,
    row: i32,
    depth: i32,
    max_x: i32,
    max_y: i32,
    max_z: i32,
    reset: u8,
}

fn main() {
    nannou::app(model).update(update).run();
}

fn model(app: &App) -> Model {
    app.new_window()
    .event(event)
    .size(640, 480)
    .title("Construct2")
    .view(view)
    .build()
    .unwrap();
    let c = rgb(255, 55, 66);
    let step_x = 1;
    let step_y = 1;
    let step_z = 1;
    let x = 10;
    let y = 10;
    let z = 10;
    let dx = 1;
    let dy = 1;
    let dz = 1;
    let v = 1;  // variant
    let m = 1;  // mapping
    let col = 4;
    let row = 3;
    let depth = 5;
    let max_x = app.window_rect().w() as i32 / col;
    let max_y = app.window_rect().h() as i32 / row;
    let max_z = 100 / depth;
    let reset = 1;
    Model {
        c,
        step_x,
        step_y,
        step_z,
        x,
        y,
        z,
        dx,
        dy,
        dz,
        v,
        m,
        col,
        row,
        depth,
        max_x,
        max_y,
        max_z,
        reset,
    }
}

fn event(_app: &App, model: &mut Model, event: WindowEvent) {
    match event {
        KeyPressed(key) => {
            if let Key::Space = key {
                model.reset = 1;
            }
        },
        MousePressed(_button) => {
            model.reset = 1;
        },
        _ => {},
    }
}

fn update(app: &App, model: &mut Model, _update: Update) {
    if random_range(0, 1881) == 0 { // lite då och då
        model.reset = 1;
    }
    
    if model.reset == 1 {
        nytt(model, app); // nya värden
        model.reset = 2;
    } else if model.reset == 2 {
        model.reset = 0;
    }
    
    model.x += model.dx * model.step_x; // flytta position x
    if model.x < 1 { // om vänster kant
        model.dx = random_range(0, 2); // vänd eller stå still i x-led
    } else if model.x > model.max_x / model.col { // om höger kant
        model.dx = 0 - random_range(0, 2); // vänd eller stå still i x-led
    }
    
    model.y += model.dy * model.step_y; // flytta position y
    if model.y < 1 { // om övre kanten
        model.dy = random_range(0, 2) // vänd eller stå still i y-led
    } else if model.y > model.max_y / model.row { // om nedre kanten
        model.dy = 0 - random_range(0, 2) // vänd eller stå still i y-led
    }
    
    model.z += model.dz * model.step_z; // flytta position z
    if model.z < 1 { // om botten
        model.dz = random_range(0, 2) // vänd eller stå still i z-led
    } else if model.z > model.max_z / model.row { // om högst upp
        model.dz = 0 - random_range(0, 2) // vänd eller stå still i z-led
    }
    
    match model.v {
        1 => variant1(model),
        2 => variant2(model),
        3 => variant3(model),
        4 => variant4(model),
        5 => variant5(model),
        6 => variant6(model),
        7 => variant7(model),
        8 => variant8(model),
        9 => variant9(model),
        10 => variant10(model),
        _ => (),
    }
    
    draw_fo_update(model);
}

fn draw_fo_update(model: &mut Model) {
    let mx = model.m % 3;
    let my = model.m / 3 % 3;
    let mz = model.m / 6 % 3;
    for i in 0..model.col {
        if (mx == 1 && i % 2 == 0) || (mx ==2 && i % 2 == 1) {
            model.x = mirror_x(model);
        }
        for j in 0..model.row {
            if (my == 1 && j % 2 == 0) || (my == 2 && j % 2 == 1) {
                model.y = mirror_y(model);
            }
            for k in 0..model.depth {
                if (mz == 1 && k % 2 == 0) || (mz == 2 && k % 2 == 1) {
                    model.z = mirror_z(model);
                }
            }
        }
    }
}

// ---------------------------------------------------------------------------------------
fn variant1(model: &mut Model) { // 1. slumpa riktningar individuellt
    if random_range(0, 100) >= MOV_X {
        slumpa_riktning_x(model);
    }
    if random_range(0, 100) >= MOV_Y {
        slumpa_riktning_y(model);
    }
    if random_range(0, 100) >= MOV_Z {
        slumpa_riktning_z(model);
    }
}
fn variant2(model: &mut Model) { // 2. slumpa alltid riktningar tillsammans
    slumpa_riktning_xyz(model);
}
fn variant3(model: &mut Model) { // 3. slumpa riktningar individuellt
    if random_range(0, 100) >= MOV_X {
        dra_vanster(model);
    }
    if random_range(0, 100) >= MOV_Y {
        dra_uppat(model);
    }
    if random_range(0, 100) >= MOV_Z {
        dra_hogre(model);
    }
}
fn variant4(model: &mut Model) { // 4. slumpa riktningar individuellt
    if random_range(0, 100) >= MOV_X {
        dra_hoger(model);
    }
    if random_range(0, 100) >= MOV_Y {
        dra_uppat(model);
    }
    if random_range(0, 100) >= MOV_Z {
        dra_hogre(model);
    }
}
fn variant5(model: &mut Model) { // 5. slumpa riktningar individuellt
    if random_range(0, 100) >= MOV_X {
        dra_vanster(model);
    }
    if random_range(0, 100) >= MOV_Y {
        dra_nedat(model);
    }
    if random_range(0, 100) >= MOV_Z {
        dra_hogre(model);
    }
}
fn variant6(model: &mut Model) { // 6. slumpa riktningar individuellt
    if random_range(0, 100) >= MOV_X {
        dra_hoger(model);
    }
    if random_range(0, 100) >= MOV_Y {
        dra_nedat(model);
    }
    if random_range(0, 100) >= MOV_Z {
        dra_hogre(model);
    }
}
fn variant7(model: &mut Model) { // 7. slumpa riktningar individuellt
    if random_range(0, 100) >= MOV_X {
        dra_vanster(model);
    }
    if random_range(0, 100) >= MOV_Y {
        dra_uppat(model);
    }
    if random_range(0, 100) >= MOV_Z {
        dra_lagre(model);
    }
}
fn variant8(model: &mut Model) { // 8. slumpa riktningar individuellt
    if random_range(0, 100) >= MOV_X {
        dra_hoger(model);
    }
    if random_range(0, 100) >= MOV_Y {
        dra_uppat(model);
    }
    if random_range(0, 100) >= MOV_Z {
        dra_lagre(model);
    }
}
fn variant9(model: &mut Model) { // 9. slumpa riktningar individuellt
    if random_range(0, 100) >= MOV_X {
        dra_vanster(model);
    }
    if random_range(0, 100) >= MOV_Y {
        dra_nedat(model);
    }
    if random_range(0, 100) >= MOV_Z {
        dra_lagre(model);
    }
}
fn variant10(model: &mut Model) { // 10. slumpa riktningar individuellt
    if random_range(0, 100) >= MOV_X {
        dra_hoger(model);
    }
    if random_range(0, 100) >= MOV_Y {
        dra_nedat(model);
    }
    if random_range(0, 100) >= MOV_Z {
        dra_lagre(model);
    }
}

// ---------------------------------------------------------------------------------------
fn nytt(model: &mut Model, app: &App) {
    model.v = random_range(0, 10) + 1;   // variant
    model.m = random_range(0, 36);  // mapping
    model.col = random_range(0, 15) + 1;
    model.row = random_range(0, 13) + 1;
    model.depth = random_range(0, 11) + 1;
    model.max_x = app.window_rect().w() as i32 / model.col;
    model.max_y = app.window_rect().h() as i32 / model.row;
    model.max_z = (random_range(0, 600) + 20) / model.depth;
    model.step_x = 1;
    // model.step_x = random_range(0, 2) + 1;
    model.step_y = 1;
    // model.step_y = random_range(0, 2) + 1;
    model.step_z = 1;
    // model.step_z = random_range(0, 2) + 1;
    slumpa_riktning_xyz(model);
    // println!("v:{} m:{} col:{} row:{} depth:{} max_x:{} max_y:{} max_z:{}", model.v, model.m, model.col, model.row, model.depth, model.max_x, model.max_y, model.max_z);
    model.c.red = random_range(0, 255);
}

fn slumpa_riktning_x(model: &mut Model) {
    model.dx = random_range(0, 2);
}
fn slumpa_riktning_y(model: &mut Model) {
    model.dy = random_range(0, 2);
}
fn slumpa_riktning_z(model: &mut Model) {
    model.dz = random_range(0, 2);
}
fn slumpa_riktning_xyz(model: &mut Model) {
    while model.dx == 0 && model.dy == 0 && model.dz == 0 { // kolla att inte alla riktningar blir 0
        model.dx = random_range(0, 2);
        model.dy = random_range(0, 2);
        model.dz = random_range(0, 2);
    }
}

fn dra_vanster(model: &mut Model) {
    if model.dx == 0 && model.x >= 1 {
        model.dx = -1;
    } else {
        model.dx = 0;
    }   // dra åt vänster
}
fn dra_hoger(model: &mut Model) {
    if model.dx == 0 && model.x <= model.max_x {
        model.dx = 1;
    } else {
        model.dx = 0;
    }   // dra åt höger
}
fn dra_nedat(model: &mut Model) {
    if model.dy == 0 && model.y <= model.max_y {
        model.dy = 1;
    } else {
        model.dy = 0;
    }   // dra nedåt
}
fn dra_uppat(model: &mut Model) {
    if model.dy == 0 && model.y >= 1 {
        model.dy = -1;
    } else {
        model.dy = 0;
    }   // dra uppåt
}
fn dra_hogre(model: &mut Model) {
    if model.dz == 0 && model.z >= 1 {
        model.dz = -1;
    } else {
        model.dz = 0;
    }   // dra högre
}
fn dra_lagre(model: &mut Model) {
    if model.dz == 0 && model.z <= model.max_z {
        model.dz = 1;
    } else {
        model.dz = 0;
    }   // dra lägre
}

// ---------------------------------------------------------------------------------------
fn draw_fo(model: &Model, draw: &Draw, win: Rect) {
    for i in 0..model.col {
        let i_col = model.max_x * i;
        for j in 0..model.row {
            let j_row = model.max_y * j;
            for k in 0..model.depth {
                let k_depth = model.max_z * k;
                let point = vec3((model.x + i_col) as f32, (model.y + j_row) as f32, (model.z + k_depth) as f32);
                let (xy, size) = to_2d(point, win, 600.0, 1.0, 0.5);
                draw.rect().xy(xy).w_h(size, size).color(model.c);
            }
        }
    }
}

fn view(app: &App, model: &Model, frame: Frame) {
    let win = app.main_window().rect();
    let draw = app.draw().x_y(win.w() * -0.5, win.h() * -0.5);
    if model.reset > 0 {
        frame.clear(WHITE); // rensa skärmen
    }
    draw_fo(model, &draw, win);
    draw.to_frame(app, &frame).unwrap();
}

fn to_2d(pnt: Vec3, win: Rect, depth: f32, s: f32, f: f32) -> (Vec2, f32) {
    let z = (depth - pnt.z) / (depth * s) * (1.0 - f) + f;
    let x= pnt.x * z;
    let y= pnt.y * z;
    let ox = (1.0 - z) * win.w() * 0.5 + x;
    let oy = (1.0 - z) * win.h() * 0.5 + y;
    (vec2(ox, oy), z)
}

// ---------------------------------------------------------------------------------------
fn mirror_x(model: &Model) -> i32 {
    model.max_x - model.x
}
fn mirror_y(model: &Model) -> i32 {
    model.max_y - model.y
}
fn mirror_z(model: &Model) -> i32 {
    model.max_z - model.z
}