2 849 Codepen

Колебающиеся градиентные 3d трубки

Объёмные трубки с переливающимся градиентом с определенной амплитудой колыхаются в пространстве. По клику меняется градиент, а на движение курсора реагирует амплитуда трубок


CSS

html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
}

canvas {
  position: fixed;
  width: 100%;
  height: 100%;
}

JS

Подключаемые скрипты
https://cdnjs.cloudflare.com/ajax/libs/three.js/100/three.min.js
https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js
https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.0.3/chroma.min.js
https://threejs.org/examples/js/controls/OrbitControls.js
скрипт
const simplex = new SimplexNoise();

function App(conf) {
  conf = {
    fov: 75,
    cameraZ: 150,
    background: 0x000000,
    tubeRadius: 3,
    resY: 10,
    resX: 4,
    noiseCoef: 50,
    timeCoef: 50,
    mouseCoef: 50,
    heightCoef: 20,
    ambientColor: 0xcccccc,
    lightIntensity: 1,
    light1Color: 0x24f59e,
    light2Color: 0xe15040,
    light3Color: 0x1b859e,
    light4Color: 0x4cb04b,
    ...conf
  };

  let renderer, scene, camera, cameraCtrl;
  let width, height, cx, cy, wWidth, wHeight;
  const TMath = THREE.Math;

  let light1, light2, light3, light4;
  let objects, noiseConf = {};
  let cscale; updateCScale(chroma('#d11f6c'));

  const mouse = new THREE.Vector2();

  init();

  function init() {
    renderer = new THREE.WebGLRenderer({ antialias: true });
    document.body.appendChild(renderer.domElement);
    camera = new THREE.PerspectiveCamera(conf.fov);
    camera.position.z = conf.cameraZ;
    cameraCtrl = new THREE.OrbitControls(camera);

    updateSize();
    window.addEventListener('resize', updateSize, false);

    document.addEventListener('mousemove', e => {
      mouse.x = (e.clientX / width) * 2 - 1;
      mouse.y = -(e.clientY / height) * 2 + 1;
    });

    initScene();
    initGui();
    animate();
  }

  function initGui() {
    // noiseInput.value = 101 - conf.xyCoef;
    // heightInput.value = (conf.zCoef * 100) / 25;

    // noiseInput.addEventListener('input', e => {
    //   conf.noiseCoef = 101 - noiseInput.value;
    // });
    // heightInput.addEventListener('input', e => {
    //   conf.zCoef = (heightInput.value * 25) / 100;
    // });

    document.body.addEventListener('click', e => {
      updateColors();
    });
  }

  function initScene() {
    scene = new THREE.Scene();
    if (conf.background) scene.background = new THREE.Color(conf.background);
    initLights();
    initObjects();

    camera.position.z = 130;
  }

  function initLights() {
    scene.add(new THREE.AmbientLight(conf.ambientColor));

    const z = 50;
    const lightDistance = 500;
    light1 = new THREE.PointLight(conf.light1Color, conf.lightIntensity, lightDistance);
    light1.position.set(0, wHeight / 2, z);
    scene.add(light1);
    light2 = new THREE.PointLight(conf.light2Color, conf.lightIntensity, lightDistance);
    light2.position.set(0, -wHeight / 2, z);
    scene.add(light2);
    light3 = new THREE.PointLight(conf.light3Color, conf.lightIntensity, lightDistance);
    light3.position.set(wWidth / 2, 0, z);
    scene.add(light3);
    light4 = new THREE.PointLight(conf.light4Color, conf.lightIntensity, lightDistance);
    light4.position.set(-wWidth / 2, 0, z);
    scene.add(light4);
  }

  function initObjects() {
    updateNoise();
    const nx = Math.round(wWidth / conf.resX) + 1;
    const ny = Math.round(wHeight / conf.resY) + 1;
    objects = [];
    let tube, color;
    for (let j = 0; j < ny; j++) {
      // color = cscale(j/ny).hex();
      color = cscale(TMath.randFloat(0, 1)).hex();
      // color = chroma.random().hex();
      tube = new Tube(-wWidth / 2, -wHeight / 2 + j * conf.resY, wWidth, nx, conf.tubeRadius, color, noiseConf);
      objects.push(tube);
      scene.add(tube.mesh);
    }
  }

  function updateNoise() {
    noiseConf.coef = conf.noiseCoef * 0.00012;
    noiseConf.height = conf.heightCoef;
    noiseConf.time = Date.now() * conf.timeCoef * 0.000002;
    noiseConf.mouseX = mouse.x / 2;
    noiseConf.mouseY = mouse.y / 2;
    noiseConf.mouse = mouse.x + mouse.y;
  }

  function updateColors() {
    const color = chroma.random();
    updateCScale(color);
    for (let i = 0; i < objects.length; i++) {
      objects[i].material.color = new THREE.Color(cscale(TMath.randFloat(0, 1)).hex());
    }
    light1.color = new THREE.Color(chroma.random().hex());
    light2.color = new THREE.Color(chroma.random().hex());
    light3.color = new THREE.Color(chroma.random().hex());
    light4.color = new THREE.Color(chroma.random().hex());
    console.log(light1.color, light2.color, light3.color, light4.color);
  }

  function updateCScale(color) {
    const colors = [
      color.set('hsl.s', TMath.randFloat(0, 1)).set('hsl.l', TMath.randFloat(0, 0.3)).hex(),
      color.set('hsl.s', TMath.randFloat(0, 1)).set('hsl.l', 0.3 + TMath.randFloat(0, 0.4)).hex(),
      color.set('hsl.s', TMath.randFloat(0, 1)).set('hsl.l', 0.7 + TMath.randFloat(0, 0.3)).hex(),
      0xffffff,
    ];
    console.log(colors);
    cscale = chroma.scale(colors);
  }

  function animate() {
    // if (!animate.counter) animate.counter = 1;
    // if (animate.counter++>10) return;

    requestAnimationFrame(animate);

    animateObjects();
    animateLights();

    if (cameraCtrl) cameraCtrl.update();
    renderer.render(scene, camera);
  }

  function animateObjects() {
    updateNoise();
    for (let i = 0; i < objects.length; i++) {
      objects[i].update();
    }
  }

  function animateLights() {
    const time = Date.now() * 0.001;
    const dx = wWidth / 2;
    const dy = wHeight / 2;
    light1.position.x = Math.sin(time * 0.1) * dx;
    light1.position.y = Math.cos(time * 0.2) * dy;
    light2.position.x = Math.cos(time * 0.3) * dx;
    light2.position.y = Math.sin(time * 0.4) * dy;
    light3.position.x = Math.sin(time * 0.5) * dx;
    light3.position.y = Math.sin(time * 0.6) * dy;
    light4.position.x = Math.sin(time * 0.7) * dx;
    light4.position.y = Math.cos(time * 0.8) * dy;
  }

  function updateSize() {
    width = window.innerWidth;
    cx = width / 2;
    height = window.innerHeight;
    cy = height / 2;
    if (renderer && camera) {
      renderer.setSize(width, height);
      camera.aspect = width / height;
      camera.updateProjectionMatrix();
      const wsize = getRendererSize();
      wWidth = wsize[0];
      wHeight = wsize[1];
    }
  }

  function getRendererSize() {
    const cam = new THREE.PerspectiveCamera(camera.fov, camera.aspect);
    const vFOV = (cam.fov * Math.PI) / 180;
    const height = 2 * Math.tan(vFOV / 2) * Math.abs(conf.cameraZ);
    const width = height * cam.aspect;
    return [width, height];
  }
}

/**
 * Custom curve
 */
function CustomCurve(x, y, l, noise) {
  THREE.Curve.call(this);
  this.x = x; this.y = y; this.l = l;
  this.noise = noise;
  this.yn = this.y * this.noise.coef;
}
CustomCurve.prototype = Object.create(THREE.Curve.prototype);
CustomCurve.prototype.constructor = CustomCurve;
CustomCurve.prototype.getPoint = function (t) {
  let x = this.x + t * this.l;
  let xn = x * this.noise.coef;
  let noise1 = simplex.noise2D(xn + this.noise.time + this.noise.mouseX/2, this.yn - this.noise.time + this.noise.mouseY/2);
  let noise2 = simplex.noise2D(this.yn + this.noise.time, xn - this.noise.time);
  let z = noise2 * this.noise.height;
  let y = this.y + noise1 * this.noise.height;
  return new THREE.Vector3(x, y, z);
};

/**
 * Tube class
 */
class Tube {
  constructor(x, y, l, segments, radius, color, noise) {
    this.segments = segments;
    this.radialSegments = 8;
    this.radius = radius;

    this.curve = new CustomCurve(x, y, l, noise);
    this.geometry = new THREE.TubeBufferGeometry(this.curve, segments, radius, this.radialSegments, false);
    // this.material = new THREE.MeshBasicMaterial({ color });
    // this.material = new THREE.MeshLambertMaterial({ color });
    this.material = new THREE.MeshStandardMaterial({ color, metalness: 1 });
    this.mesh = new THREE.Mesh(this.geometry, this.material);
  }
  update() {
    this.frames = this.curve.computeFrenetFrames(this.segments, false);
    this.geometry.tangents = frames.tangents;
    this.geometry.normals = frames.normals;
    this.geometry.binormals = frames.binormals;

    this.pArray = this.geometry.attributes.position.array;
    this.nArray = this.geometry.attributes.normal.array;
    this.P = new THREE.Vector3();
    this.normal = new THREE.Vector3();
    for (let i = 0; i < this.segments; i++) {
      this.updateSegment(i);
    }
    this.updateSegment(this.segments);

    this.geometry.attributes.position.needsUpdate = true;
    this.geometry.attributes.normal.needsUpdate = true;
  }
  updateSegment(i) {
    this.P = this.curve.getPointAt(i / this.segments, this.P);
    const N = this.frames.normals[i];
    const B = this.frames.binormals[i];
    for (let j = 0; j <= this.radialSegments; j++) {
      let v = j / this.radialSegments * Math.PI * 2;
      let sin = Math.sin(v);
      let cos = - Math.cos(v);
      this.normal.x = (cos * N.x + sin * B.x);
      this.normal.y = (cos * N.y + sin * B.y);
      this.normal.z = (cos * N.z + sin * B.z);
      this.normal.normalize();
      let index = (i * (this.radialSegments + 1) + j) * 3;
      this.nArray[index] = this.normal.x;
      this.nArray[index + 1] = this.normal.y;
      this.nArray[index + 2] = this.normal.z;
      this.pArray[index] = this.P.x + this.radius * this.normal.x;
      this.pArray[index + 1] = this.P.y + this.radius * this.normal.y;
      this.pArray[index + 2] = this.P.z + this.radius * this.normal.z;
    }
  }
}

App();

Комментарии

  • Facebook
  • Вконтакте

Похожие статьи