body{
  overflow: hidden;
  margin: 0;
}
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

console.clear();

class TubeStuff extends THREE.Group{
  constructor(curve){
    super();
    this.curve = curve;
    this.baseObjects = [
      new THREE.BoxGeometry(0.3, 0.3, 0.3),
      new THREE.IcosahedronGeometry(0.22, 1)
    ];
    this.objMaterial = new THREE.MeshLambertMaterial({color: new THREE.Color("maroon").multiplyScalar(0.25)});
    
    this.init();
  }
  
  init(){
    for(let i = 0; i < 20; i++){
      let obj = new THREE.Mesh(this.baseObjects[Math.random() < 0.5 ? 0 : 1], this.objMaterial);
      obj.userData = {
        initCurvePos: Math.random(),
        initRotation: new THREE.Vector3().randomDirection().multiplyScalar(Math.PI),
        rotationSpeed: new THREE.Vector3().randomDirection().multiplyScalar((0.5 + Math.random() * 0.5) * Math.PI),
        localShift: new THREE.Vector3().randomDirection().multiplyScalar(Math.random() * 0.75 + 0.01)
      }
      this.add(obj);
    }
    console.log(this.children);
  }
  
  update(t){
    this.children.forEach(o => {
      let ud = o.userData;
      let curvePos = (ud.initCurvePos + t * 0.1) % 1;
      this.curve.getPointAt(curvePos, o.position);
      o.position.add(ud.localShift);
      let ir = ud.initRotation;
      let rs = ud.rotationSpeed;
      o.rotation.set(
        ir.x + rs.x * t, 
        ir.y + rs.y * t, 
        ir.z + rs.z * t
      );
      let sc = 1. - THREE.MathUtils.smoothstep(Math.abs(curvePos - 0.5), 0.4, 0.5);
      o.scale.setScalar(0.01 + 0.99 * sc);
    })
  }
}

let gu = {
  time: { value: 0 }
};

let scene = new THREE.Scene();
scene.background = new THREE.Color(0xface8d);
let camera = new THREE.PerspectiveCamera(30, innerWidth / innerHeight, 1, 100);
camera.position.set(0.25, 0.5, 1).setLength(30);

let renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(devicePixelRatio);
document.body.appendChild(renderer.domElement);

window.addEventListener("resize", (event) => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});

let controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

let light = new THREE.DirectionalLight(0xffffff, Math.PI);
light.position.setScalar(1);
scene.add(light, new THREE.AmbientLight(0xffffff, 0.5 * Math.PI));

let curve = new THREE.CatmullRomCurve3( new Array(5).fill().map((_, idx) => {
  let v = new THREE.Vector3((Math.random() + 2) * Math.sign(Math.random() - 0.5), 0, (-2 + idx) * 5);
  if (idx == 2) v.set(0, 0, 0);
  return v;
}));

let gTube = new THREE.TubeGeometry(curve, 25, 1, 8);
let gTubeWire = gTube.clone();
ToQuads(gTubeWire);


let tu = {
  point: {value: new THREE.Vector3(0, 0, 0)},
  radius: {value: 7},
  visible: {value: 1}
}
let tube = new THREE.Mesh(
  gTube, 
  new THREE.MeshLambertMaterial({
    color: new THREE.Color(0xface8d),
    side: THREE.DoubleSide,
    onBeforeCompile: shader => {
      shader.uniforms.point = tu.point;
      shader.uniforms.radius = tu.radius;
      shader.uniforms.visible = tu.visible;
      shader.vertexShader = `
        varying vec3 vPos;
        ${shader.vertexShader}
      `.replace(
        `#include <begin_vertex>`,
        `#include <begin_vertex>
          vPos = position;
        `
      );
      console.log(shader.vertexShader);
      shader.fragmentShader = `
        uniform vec3 point;
        uniform float radius;
        uniform float visible;
        varying vec3 vPos;
        ${shader.fragmentShader}
      `.replace(
        `#include <clipping_planes_fragment>`,
        `#include <clipping_planes_fragment>
        
        if (visible > 0.5) {
          if (gl_FrontFacing == true) {
            vec3 cell = fract(vPos * 10.) - 0.5;
            float cellDist = length(cell);
            float cellRadius = sqrt(3.) * 0.5;
            float dist = distance(point.xyz, vPos);
            float visibleRatio = smoothstep(radius, radius - 5., dist);
            if (cellDist < cellRadius * visibleRatio) discard;
          }
        }
        
        `
      ).replace(
        `vec4 diffuseColor = vec4( diffuse, opacity );`,
        `vec4 diffuseColor = vec4( diffuse, opacity );
          if(gl_FrontFacing == false){diffuseColor.rgb *= 0.5;}
        `
      );
      console.log(shader.fragmentShader);
    }
  })
)
let tubeWire = new THREE.LineSegments(
  gTubeWire,
  new THREE.LineBasicMaterial({
    color: new THREE.Color("maroon")
  })
)
scene.add(tube, tubeWire);

let tubeStuff = new TubeStuff(curve);
scene.add(tubeStuff);

let raycaster = new THREE.Raycaster();
let pointer = new THREE.Vector2();
let intersects;
window.addEventListener("pointermove", event => {
  pointer.x = ( event.clientX / window.innerWidth ) * 2 - 1;
  pointer.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
})

window.addEventListener("pointerdown", event => {
  raycaster.setFromCamera(pointer, camera);
  intersects = raycaster.intersectObject(tube);
  if(intersects.length > 0){
    tu.point.value.copy(intersects[0].point);
    //tu.visible.value = 1;
  }else{
    //tu.visible.value = 0;
  }
})

let clock = new THREE.Clock();
let t = 0;

renderer.setAnimationLoop(() => {
  let dt = clock.getDelta();
  t += dt;
  controls.update();
  tubeStuff.update(t);
  gu.time.value = t;
  
  renderer.render(scene, camera);
});

function ToQuads(g) {
  let p = g.parameters;
  let segmentsX = (g.type == "TorusBufferGeometry" ? p.tubularSegments : p.radialSegments) || p.widthSegments || p.thetaSegments || (p.points.length - 1) || 1;
  let segmentsY = (g.type == "TorusBufferGeometry" ? p.radialSegments : p.tubularSegments) || p.heightSegments || p.phiSegments || p.segments || 1;
  let indices = [];
  for (let i = 0; i < segmentsY + 1; i++) {
    let index11 = 0;
    let index12 = 0;
    for (let j = 0; j < segmentsX; j++) {
      index11 = (segmentsX + 1) * i + j;
      index12 = index11 + 1;
      let index21 = index11;
      let index22 = index11 + (segmentsX + 1);
      indices.push(index11, index12);
      if (index22 < ((segmentsX + 1) * (segmentsY + 1) - 1)) {
        indices.push(index21, index22);
      }
    }
    if ((index12 + segmentsX + 1) <= ((segmentsX + 1) * (segmentsY + 1) - 1)) {
      indices.push(index12, index12 + segmentsX + 1);
    }
  }
  g.setIndex(indices);
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.