‹ 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
}