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);
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.