Monday 5 October 2020

Trying to simulate a 3D effect via Three.js

I'm trying to achieve something akin to this amazing effect : https://www.cobosrl.co/

Here's what I have so far : https://codepen.io/routsou/pen/ZEGWJgR?editors=0010

/*--------------------
Setup
--------------------*/
console.clear();
const canvas = document.querySelector('#bubble');

//wobble
let mouseDown = false;
let howMuch = 0;
let howMuchLimit = 0.25;

//ripple
let rippleAmount = 0;
let rippleRatio = 5;
let step = 0;
let sphereVerticesArray = [];
let sphereVerticesNormArray = [];

//raycaster
let raycaster;
let INTERSECTED = null;

let width = canvas.offsetWidth,
    height = canvas.offsetHeight;
const renderer = new THREE.WebGLRenderer({
  canvas: canvas,
  antialias: true,
  alpha: true
});
const scene = new THREE.Scene();

const setup = () => {
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize(width, height);
  renderer.setClearColor(0xebebeb, 0);
  renderer.shadowMap.enabled = true;
  renderer.shadowMapSoft = true;

  scene.fog = new THREE.Fog(0x000000, 10, 950);

  const aspectRatio = width / height;
  const fieldOfView = 100;
  const nearPlane = 0.1;
  const farPlane = 10000;
  camera = new THREE.PerspectiveCamera(
    fieldOfView,
    aspectRatio,
    nearPlane,
    farPlane
  );
  
  raycaster = new THREE.Raycaster();
   
  camera.position.x = 0;
  camera.position.y = 0;
  camera.position.z = 300;
}
setup();


/*--------------------
Lights
--------------------*/
let hemispshereLight, shadowLight, light2;
const createLights = () => {
    hemisphereLight = new THREE.HemisphereLight(0xffffff,0x000000, .5)
  
    shadowLight = new THREE.DirectionalLight(0x666666, .4);
    shadowLight.position.set(0, 450, 350);
    shadowLight.castShadow = true;

    shadowLight.shadow.camera.left = -650;
    shadowLight.shadow.camera.right = 650;
    shadowLight.shadow.camera.top = 650;
    shadowLight.shadow.camera.bottom = -650;
    shadowLight.shadow.camera.near = 1;
    shadowLight.shadow.camera.far = 1000;

    shadowLight.shadow.mapSize.width = 4096;
    shadowLight.shadow.mapSize.height = 4096;
  
  light2 = new THREE.DirectionalLight(0x666666, .25);
    light2.position.set(-600, 350, 350);
  
  light3 = new THREE.DirectionalLight(0x666666, .15);
    light3.position.set(0, -250, 300);

    scene.add(hemisphereLight);  
    scene.add(shadowLight);
  scene.add(light2);
  scene.add(light3);
}
createLights();


/*--------------------
Bubble
--------------------*/
const vertex = width > 575 ? 80 : 40;
const bubbleGeometry = new THREE.SphereGeometry( 150, vertex, vertex );

let bubble;
const createBubble = () => {
  for(let i = 0; i < bubbleGeometry.vertices.length; i++) {
    let vector = bubbleGeometry.vertices[i];
    vector.original = vector.clone();  
  }
  
  const bubbleMaterial = new THREE.MeshStandardMaterial({
    emissive: 0x91176b,
    emissiveIntensity: 0.85,
    roughness: 0.55,
    metalness: 0.51,
    side: THREE.FrontSide,
  });
  
  // save points for later calculation
  for (var i = 0; i < bubbleGeometry.vertices.length; i += 1) {
    var vertex = bubbleGeometry.vertices[i];
    var vec = new THREE.Vector3(vertex.x, vertex.y, vertex.z);
    sphereVerticesArray.push(vec);
    var mag = vec.x * vec.x + vec.y * vec.y + vec.z * vec.z;
    mag = Math.sqrt(mag);
    var norm = new THREE.Vector3(vertex.x / mag, vertex.y / mag, vertex.z / mag);
    sphereVerticesNormArray.push(norm);
  }
  
  bubble = new THREE.Mesh(bubbleGeometry, bubbleMaterial);
  bubble.castShadow = true;
  bubble.receiveShadow = false;
  bubble.rotation.y = -90;
  
  scene.add(bubble);
}
createBubble();


/*--------------------
Plane
--------------------*/
const createPlane = () => {
  const planeGeometry = new THREE.PlaneBufferGeometry( 2000, 2000 );
  const planeMaterial = new THREE.ShadowMaterial({
    opacity: 0.15
  });
  const plane = new THREE.Mesh( planeGeometry, planeMaterial );
  plane.position.y = -150;
  plane.position.x = 0;
  plane.position.z = 0;
  plane.rotation.x = Math.PI / 180 * -90;
  plane.receiveShadow = true;
  scene.add(plane);
}
createPlane();


/*--------------------
Map
--------------------*/
const map = (num, in_min, in_max, out_min, out_max) => {
  return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}


/*--------------------
Distance
--------------------*/
const distance = (a, b) => {
  const dx = a.x - b.x;
  const dy = a.y - b.y;
  const d = Math.sqrt( dx * dx + dy * dy );
  return d;
}


/*--------------------
Mouse
--------------------*/
let mouse = new THREE.Vector2(0, 0);
const onMouseMove = (e) => {
  TweenMax.to(mouse, 0.8, {
    x : ( e.clientX / window.innerWidth ) * 2 - 1,
    y: - ( e.clientY / window.innerHeight ) * 2 + 1,
    ease: Power2.easeOut
  });
  
  raycaster.setFromCamera( mouse, camera );
  let intersects = raycaster.intersectObjects( scene.children );

  try{
    if ( intersects.length > 0 ) {
      if ( INTERSECTED != intersects[ 0 ].object ) {

        if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );

        INTERSECTED = intersects[ 0 ].object;
        INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
        INTERSECTED.material.emissive.setHex( 0x000000 );
        document.body.style.cursor = 'pointer';
      }
    } else {
      if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
      INTERSECTED = null;
      document.body.style.cursor = 'auto';
    }
  }catch(e){
    
  }
};
['mousemove', 'touchmove'].forEach(event => {
  window.addEventListener(event, onMouseMove);  
});


/*--------------------
Spring
--------------------*/
let spring = {
  scale: 1
};
const clicking = {
  down: () => {
    mouseDown = true;
  },
  up: () => {
    mouseDown = false;
  }
};
['mousedown', 'touchstart'].forEach(event => {
  window.addEventListener(event, clicking.down);
});
['mouseup', 'touchend'].forEach(event => {
  window.addEventListener(event, clicking.up);
});


/*--------------------
Resize
--------------------*/
const onResize = () => {
  canvas.style.width = '';
  canvas.style.height = '';
  width = canvas.offsetWidth;
  height = canvas.offsetHeight;
  camera.aspect = width / height;
  camera.updateProjectionMatrix(); 
  maxDist = distance(mouse, {x: width / 2, y: height / 2});
  renderer.setSize(width, height);
}
let resizeTm;
window.addEventListener('resize', function(){
  resizeTm = clearTimeout(resizeTm);
  resizeTm = setTimeout(onResize, 200);
});


/*--------------------
Noise
--------------------*/
let dist = new THREE.Vector2(0, 0);
let maxDist = distance(mouse, {x: width / 2, y: height / 2});
const updateVertices = (time) => {
  dist = distance(mouse, {x: width / 2, y: height / 2});
  dist /= maxDist;
  dist = map(dist, 1, 0, 0, 1);
  for(let i = 0; i < bubbleGeometry.vertices.length; i++) {
    let vector = bubbleGeometry.vertices[i];
    vector.copy(vector.original);
    let perlin = noise.simplex3(
      (vector.x * 0.006) + (time * 0.0005),
      (vector.y * 0.006) + (time * 0.0005),
      (vector.z * 0.006)
    );
    
    let ratio = ((perlin * 0.3 * (howMuch + 0.1)) + 0.9);
    vector.multiplyScalar(ratio);
  }
  bubbleGeometry.verticesNeedUpdate = true;
}

/*--------------------
Animate
--------------------*/
const render = (a) => {
  step +=1;
  requestAnimationFrame(render);
  
  //bubble.scale.set(spring.scale, spring.scale, spring.scale);
  updateVertices(a);
  renderer.clear();
  renderer.render(scene, camera);
  
  //Activate on mouse down
  if(mouseDown && howMuch < howMuchLimit)
    howMuch += 0.01;
  else if (howMuch > 0)
    howMuch -= 0.01;
  
  if(INTERSECTED){
    if(rippleAmount < 10)
      rippleAmount += 0.05;
  }else if(rippleAmount > 0)
      rippleAmount -= 0.05;
  
  doRipple();

}
requestAnimationFrame(render);
renderer.render(scene, camera);

/*--------------------
Helpers
--------------------*/

function fbm(p) {
  var result = noise.simplex3(p._x, p._y, p._z);
  return result;
}

function addPoint(arr) {
  var r = new Point(0, 0, 0);
  var len = arr.length;
  for (var i = 0; i < len; i += 1) {
    r._x += arr[i]._x;
    r._y += arr[i]._y;
    r._z += arr[i]._z;
  }

  return r;
}

function Point(_x=0, _y=0, _z=0) {
  this._x = _x;
  this._y = _y;
  this._z = _z;
}

function ripple(p) {
  var q = new Point(fbm(addPoint([p, new Point(0, 0, 0)])), 
                    fbm(addPoint([p, new Point(0, 1, 0)])),
                    fbm(addPoint([p, new Point(0, 0, 1)])));

  return fbm(addPoint([p, new Point(0.5 * q._x, 0.5 * q._y, 0.5 * q._z)]));
}

function doRipple(){
  //ripple
  for (var i = 0; i < bubbleGeometry.vertices.length; i += 1) {
    var vertex = bubbleGeometry.vertices[i];

    // var value = pn.noise((vertex.x + step)/ 10, vertex.y / 10, vertex.z / 10);

    var value = ripple(new Point((vertex.x + step) / 100.0), vertex.y / 100.0, vertex.z / 100.0);

    vertex.x = sphereVerticesArray[i].x + sphereVerticesNormArray[i].x * value * rippleAmount;
    vertex.y = sphereVerticesArray[i].y + sphereVerticesNormArray[i].y * value * rippleAmount;
    vertex.z = sphereVerticesArray[i].z + sphereVerticesNormArray[i].z * value * rippleAmount;
  }
  bubbleGeometry.computeFaceNormals();
  bubbleGeometry.computeVertexNormals();

  bubbleGeometry.verticesNeedUpdate = true;
  bubbleGeometry.normalsNeedUpdate = true;
}

Any help, particularly about the mouse pointer "sculpting the geometry", and the waves being more natural and from the pointer?

Thank you very much in advance



from Trying to simulate a 3D effect via Three.js

No comments:

Post a Comment