Three.js 炫酷立方体之舞

文化   2024-09-11 07:01   河南  

使用 Three.js 的 WebGL 小实验。炫酷立方体之舞。

实现代码

HTML:

<html lang="en">

<head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Cubes Dance</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/TweenMax.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r123/three.min.js"></script> <script src="https://unpkg.com/three@0.145.0/examples/js/controls/OrbitControls.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.js"></script></head>

<body></body>

</html>
CSS:
body {  background:#333d6a;  color: #fff;  width: 100%;  height: 100vh;  margin: 0;  padding: 0;  box-sizing: border-box;  overflow: hidden;  cursor: -webkit-grab;  cursor: -moz-grab;}

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

JAVASCRIPT:

function RoundedBoxGeometry ( width , height , depth , radius, radiusSegments) {

THREE.BufferGeometry.call( this );

this.type = 'RoundedBoxGeometry';



//validate params ===================================

radiusSegments = !isNaN( radiusSegments ) ? Math.max( 1, Math.floor( radiusSegments ) ) : 1 ;

width = !isNaN(width) ? width : 1;height = !isNaN(height) ? height : 1;depth = !isNaN(depth) ? depth : 1;

radius = !isNaN(radius) ? radius : .15;

radius = Math.min( radius , Math.min( width , Math.min( height , Math.min( depth ) ) ) / 2 );

var edgeHalfWidth = width / 2 - radius;var edgeHalfHeight = height / 2 - radius;var edgeHalfDepth = depth / 2 - radius;



//not sure why this is needed, for querying? ========

this.parameters = {width: width , height: height , depth: depth , radius: radius, radiusSegments: radiusSegments};



//calculate vertices count ==========================

var rs1 = radiusSegments + 1; //radius segments + 1

var totalVertexCount = ( rs1 * radiusSegments + 1 ) << 3;



//make buffers ======================================

var positions = new THREE.BufferAttribute( new Float32Array( totalVertexCount * 3 ), 3 );
var normals = new THREE.BufferAttribute( new Float32Array( totalVertexCount * 3 ), 3 );


//some vars =========================================

var cornerVerts = [], cornerNormals = [],normal = new THREE.Vector3(),vertex = new THREE.Vector3(),vertexPool = [],normalPool = [],indices = [];

varlastVertex = rs1 * radiusSegments,cornerVertNumber = rs1 * radiusSegments + 1;

doVertices();doFaces();doCorners();doHeightEdges();doWidthEdges();doDepthEdges()

// calculate vert positions =========================

function doVertices(){

//corner offsetsvar cornerLayout = [new THREE.Vector3( 1 , 1 , 1 ),new THREE.Vector3( 1 , 1 , -1 ),new THREE.Vector3( -1 , 1 , -1 ),new THREE.Vector3( -1 , 1 , 1 ),new THREE.Vector3( 1 , -1 , 1 ),new THREE.Vector3( 1 , -1 , -1 ),new THREE.Vector3( -1 , -1 , -1 ),new THREE.Vector3( -1 , -1 , 1 )];

//corner holder for ( var j = 0 ; j < 8 ; j ++ ){

cornerVerts.push([]); cornerNormals.push([]);

}

//construct 1/8 sphere ==============================

var PIhalf = Math.PI / 2;

var cornerOffset = new THREE.Vector3( edgeHalfWidth , edgeHalfHeight , edgeHalfDepth );

for ( var y = 0; y <= radiusSegments; y ++ ) {

var v = y / radiusSegments;

var va = v * PIhalf; //arrange in 90 deg

var cosVa = Math.cos( va ); //scale of vertical angle

var sinVa = Math.sin( va );

if( y == radiusSegments ){

vertex.set( 0 , 1 , 0 );

var vert = vertex.clone().multiplyScalar( radius ).add( cornerOffset );
cornerVerts[0].push( vert );

vertexPool.push( vert );
var norm = vertex.clone();

cornerNormals[0].push( norm );

normalPool.push( norm );

continue; //skip row loop
}

for ( var x = 0; x <= radiusSegments; x ++ ) {

var u = x / radiusSegments;

var ha = u * PIhalf;

//make 1/8 sphere pointsvertex.x = cosVa * Math.cos( ha );vertex.y = sinVa;vertex.z = cosVa * Math.sin( ha );

//copy sphere point, scale by radius, offset by half whdvar vert = vertex.clone().multiplyScalar( radius ).add( cornerOffset );

cornerVerts[0].push( vert );

vertexPool.push( vert );
//sphere already normalized, just clone

var norm = vertex.clone().normalize();cornerNormals[0].push( norm );normalPool.push( norm );

}

}

//distribute corner verts ===========================

for ( var i = 1 ; i < 8 ; i ++ ){

for( var j = 0 ; j < cornerVerts[0].length ; j ++ ){

var vert = cornerVerts[0][j].clone().multiply( cornerLayout[i] );

cornerVerts[i].push( vert );
vertexPool.push( vert );

var norm = cornerNormals[0][j].clone().multiply( cornerLayout[i] );

cornerNormals[i].push( norm );

normalPool.push( norm );

}

}

}


// weave corners ====================================

function doCorners(){

var indexInd = 0;



var flips = [true,false,true,false,false,true,false,true];

var lastRowOffset = rs1 * ( radiusSegments - 1 );

for ( var i = 0 ; i < 8 ; i ++ ){

var cornerOffset = cornerVertNumber * i;

for ( var v = 0 ; v < radiusSegments - 1 ; v ++ ){

var r1 = v * rs1; //row offsetvar r2 = (v + 1) * rs1; //next row

for ( var u = 0 ; u < radiusSegments ; u ++ ){

var u1 = u + 1;var a = cornerOffset + r1 + u;var b = cornerOffset + r1 + u1;var c = cornerOffset + r2 + u;var d = cornerOffset + r2 + u1;

if( !flips[i] ){

indices.push( a );indices.push( b );indices.push( c );

indices.push( b );indices.push( d );indices.push( c );

} else {

indices.push( a );indices.push( c );indices.push( b );

indices.push( b );indices.push( c );indices.push( d );

}

}

}
for ( var u = 0 ; u < radiusSegments ; u ++ ){

var a = cornerOffset + lastRowOffset + u;var b = cornerOffset + lastRowOffset + u + 1;var c = cornerOffset + lastVertex;

if( !flips[i] ){

indices.push( a );indices.push( b );indices.push( c );

} else {

indices.push( a );indices.push( c );indices.push( b );

}

}

}

}


//plates ============================================//fix this loop matrices find pattern something

function doFaces(){

//topvar a = lastVertex;// + cornerVertNumber * 0;var b = lastVertex + cornerVertNumber;// * 1;var c = lastVertex + cornerVertNumber * 2;var d = lastVertex + cornerVertNumber * 3;

indices.push( a );indices.push( b );indices.push( c );indices.push( a );indices.push( c );indices.push( d );

//bottoma = lastVertex + cornerVertNumber * 4;// + cornerVertNumber * 0;b = lastVertex + cornerVertNumber * 5;// * 1;c = lastVertex + cornerVertNumber * 6;d = lastVertex + cornerVertNumber * 7;

indices.push( a );indices.push( c );indices.push( b );indices.push( a );indices.push( d );indices.push( c );

//left a = 0;b = cornerVertNumber;c = cornerVertNumber * 4;d = cornerVertNumber * 5;

indices.push( a );indices.push( c );indices.push( b );indices.push( b );indices.push( c );indices.push( d );

//right a = cornerVertNumber * 2;b = cornerVertNumber * 3;c = cornerVertNumber * 6;d = cornerVertNumber * 7;

indices.push( a );indices.push( c );indices.push( b );indices.push( b );indices.push( c );indices.push( d );

//front a = radiusSegments;b = radiusSegments + cornerVertNumber * 3;c = radiusSegments + cornerVertNumber * 4;d = radiusSegments + cornerVertNumber * 7;

indices.push( a );indices.push( b );indices.push( c );indices.push( b );indices.push( d );indices.push( c );

//back a = radiusSegments + cornerVertNumber;b = radiusSegments + cornerVertNumber * 2;c = radiusSegments + cornerVertNumber * 5;d = radiusSegments + cornerVertNumber * 6;

indices.push( a );indices.push( c );indices.push( b );indices.push( b );indices.push( c );indices.push( d );

}



// weave edges ======================================

function doHeightEdges(){

for ( var i = 0 ; i < 4 ; i ++ ){

var cOffset = i * cornerVertNumber;var cRowOffset = 4 * cornerVertNumber + cOffset;var needsFlip = i & 1 === 1;for ( var u = 0 ; u < radiusSegments ; u ++ ){

var u1 = u + 1;var a = cOffset + u;var b = cOffset + u1;var c = cRowOffset + u;var d = cRowOffset + u1;

if( !needsFlip ){

indices.push( a );indices.push( b );indices.push( c );indices.push( b );indices.push( d );indices.push( c );

} else {

indices.push( a );indices.push( c );indices.push( b );indices.push( b );indices.push( c );indices.push( d );

}

}

}

}
function doDepthEdges(){

var cStarts = [ 0 , 2 , 4 , 6 ];var cEnds = [ 1 , 3 , 5 , 7 ];
for ( var i = 0 ; i < 4 ; i ++ ){

var cStart = cornerVertNumber * cStarts[ i ];var cEnd = cornerVertNumber * cEnds[ i ];

var needsFlip = 1 >= i;

for ( var u = 0 ; u < radiusSegments ; u ++ ){

var urs1 = u * rs1;var u1rs1 = (u+1) * rs1;

var a = cStart + urs1;var b = cStart + u1rs1;var c = cEnd + urs1;var d = cEnd + u1rs1

if( needsFlip ){

indices.push( a );indices.push( c );indices.push( b );indices.push( b );indices.push( c );indices.push( d );

} else {

indices.push( a );indices.push( b );indices.push( c );indices.push( b );indices.push( d );indices.push( c );

}

}

}

}

function doWidthEdges(){

var end = radiusSegments - 1;

var cStarts = [ 0 , 1 , 4 , 5 ];var cEnds = [ 3 , 2 , 7 , 6 ];var needsFlip = [0,1,1,0];

for ( var i = 0 ; i < 4 ; i ++ ){

var cStart = cStarts[i] * cornerVertNumber;var cEnd = cEnds[i] * cornerVertNumber;


for ( var u = 0 ; u <= end ; u ++ ){

// var dInd = u != end ? radiusSegments + u * rs1 : cornerVertNumber - 1;

var a = cStart + radiusSegments + u * rs1;var b = cStart + (u != end ? radiusSegments + (u + 1) * rs1 : cornerVertNumber - 1);

var c = cEnd + radiusSegments + u * rs1;var d = cEnd + (u != end ? radiusSegments + (u + 1) * rs1 : cornerVertNumber - 1);

if( !needsFlip[i] ){

indices.push( a );indices.push( b );indices.push( c );indices.push( b );indices.push( d );indices.push( c );

} else {

indices.push( a );indices.push( c );indices.push( b );indices.push( b );indices.push( c );indices.push( d );

}

}

}

}



//fill buffers ======================================

var index = 0;

for ( var i = 0 ; i < vertexPool.length ; i ++ ){

positions.setXYZ( index , vertexPool[i].x , vertexPool[i].y , vertexPool[i].z );

normals.setXYZ( index , normalPool[i].x , normalPool[i].y , normalPool[i].z );

index++;

}

this.setIndex( new THREE.BufferAttribute( new Uint16Array( indices ) , 1 ) );
this.addAttribute( 'position', positions );
this.addAttribute( 'normal', normals );
};

RoundedBoxGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );RoundedBoxGeometry.constructor = RoundedBoxGeometry;

class App { init() { this.ambientLightColor = '#ffffff'; this.backgroundColor = '#43fa8e'; this.spotLightColor = 0xffffff; this.gridSize = 40; this.col = this.gridSize this.row = this.gridSize; this.boxSize = 1; this.easing = Sine.easeInOut; this.gui = new dat.GUI();

this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera(20, window.innerWidth / window.innerHeight, 1, 1000); this.camera.position.set(-20, 20, -20); this.odds = []; this.evens = [];

this.addRenderer();

this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement); this.controls.enableDamping = true; this.controls.dampingFactor = 0.04; document.body.style.cursor = "-moz-grabg"; document.body.style.cursor = "-webkit-grab";

this.controls.addEventListener("start", () => { requestAnimationFrame(() => { document.body.style.cursor = "-moz-grabbing"; document.body.style.cursor = "-webkit-grabbing"; }); });

this.controls.addEventListener("end", () => { requestAnimationFrame(() => { document.body.style.cursor = "-moz-grab"; document.body.style.cursor = "-webkit-grab"; }); });

this.addAmbientLight();

this.addSpotLight();

this.animate();

this.lightMaterialProps = { color: '#7d7d7d', emissive: '#0f3855', };

this.darkMaterialProps = { color: '#25222f', emissive: '#031e31', };

const roundedGeometry = new RoundedBoxGeometry(this.boxSize, this.boxSize, this.boxSize, .04, .4);

this.darkMaterial = new THREE.MeshPhongMaterial(this.darkMaterialProps); this.lightMaterial = new THREE.MeshPhongMaterial(this.lightMaterialProps);

const light = new THREE.PointLight(0xffffff, 1, 1000); light.position.set(0, 20, 0); light.castShadow = true; this.scene.add(light);

const light1 = new THREE.PointLight(0x00ff00, 1, 100); light1.position.set(0, 20, 0); light1.castShadow = true; this.scene.add(light1);

const light2 = new THREE.PointLight(0xff00ff, 1, 1000); light2.position.set(-50, 50, -20); this.scene.add(light2);

this.addBoxes(roundedGeometry);

this.animateOdds();

TweenMax.delayedCall(1.5, this.animateEvens.bind(this));

this.addGUIControls();

window.addEventListener('resize', this.onResize.bind(this)); }

addRenderer() { this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); this.renderer.shadowMap.enabled = true; this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; this.renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(this.renderer.domElement); }

addAmbientLight() { const light = new THREE.AmbientLight(this.ambientLightColor, 1); this.scene.add(light); }

addSpotLight() { this.spotLight = new THREE.SpotLight(this.spotLightColor); this.spotLight.position.set(0, 500, 0); this.scene.add(this.spotLight); }

addGUIControls() { const backgroundGUI = this.gui.addFolder('Background'); backgroundGUI.addColor(this, 'backgroundColor').onChange((color) => { document.body.style.backgroundColor = color; });

const tileGUI = this.gui.addFolder('Light Material'); tileGUI.addColor(this.lightMaterialProps, 'color').onChange((color) => { this.lightMaterial.color = this.hexToRgbTreeJs(color); });

tileGUI.addColor(this.lightMaterialProps, 'emissive').onChange((emissive) => { this.lightMaterial.emissive = this.hexToRgbTreeJs(emissive); });

const tileGUIDark = this.gui.addFolder('Dark Material'); tileGUIDark.addColor(this.darkMaterialProps, 'color').onChange((color) => { this.darkMaterial.color = this.hexToRgbTreeJs(color); });

tileGUIDark.addColor(this.darkMaterialProps, 'emissive').onChange((emissive) => { this.darkMaterial.emissive = this.hexToRgbTreeJs(emissive); }); }

animateOdds() { this.animateBoxes(this.odds, this.animateOdds.bind(this)); }

animateEvens() { this.animateBoxes(this.evens, this.animateEvens.bind(this)); }

animateBoxes(list, callback) { for (let i = 0; i < list.length; i++) { const element = list[i];

TweenMax.to(element.position, 1.5, { y: 3, ease: this.easing, yoyo: true, yoyoEase: this.easing, repeat: -1 }); TweenMax.to(element.rotation, .6, { z: '-=' + this.radians(180), delay: 1 + (i/1000) , ease: this.easing}); }

TweenMax.delayedCall(3, callback); }

hexToRgbTreeJs(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16) / 255, g: parseInt(result[2], 16) / 255, b: parseInt(result[3], 16) / 255 } : null; }

radians(degrees) { return degrees * Math.PI / 180; }

getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); }

getMaterial() { const number = this.getRandomInt(0, this.gridSize*2);

return number%2===0 ? this.lightMaterial : this.darkMaterial; }

addBoxes(geometry) { for (let i = 0; i < this.col; i++) { for (let j = 0; j < this.row; j++) { const box = new THREE.Mesh(geometry, this.getMaterial());

box.position.set((i * this.boxSize) + this.gridSize * -.5, 2, (j * this.boxSize) + this.gridSize * -.5); box.castShadow = true; box.receiveShadow = true;

if ((i + j) % 2 === 0) { this.evens[this.evens.length] = box; } else { this.odds[this.odds.length] = box; }

this.scene.add(box); } } }

animate() { this.controls.update();

this.renderer.render(this.scene, this.camera); requestAnimationFrame(this.animate.bind(this)); }

onResize() { const ww = window.innerWidth; const wh = window.innerHeight;

this.camera.aspect = ww / wh; this.camera.updateProjectionMatrix(); this.renderer.setSize(ww, wh); }}

new App().init();



源码:

https://codepen.io/iondrimba/pen/BqQoMj


体验:

https://codepen.io/iondrimba/full/BqQoMj





感谢您的阅读      

在看点赞 好文不断  

初识Threejs
初识 Three.js 的奇妙世界,走进三维空间,与小编一起拥抱前端技术,涉及WebGL、WebGPU、Threejs、Shader、GIS、VR/AR、数字孪生、3D图形学等。
 最新文章