uix-kit
Version:
A free web kits for fast web design and development, compatible with Bootstrap v5.
679 lines (471 loc) • 24.9 kB
JavaScript
/*
*************************************
* <!-- 3D Particle Effect -->
*************************************
*/
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
After threejs 151+, the spot light effect will be different, please refer to the documentation.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
/**
* module.THREE_PARTICLE
*
* @requires ./examples/assets/js/min/three.min.js
* @requires ./src/plugins/THREE
*/
import {
UixBrowser,
UixModuleInstance,
} from '@uixkit/core/_global/js';
export const THREE_PARTICLE = ( ( module, $, window, document ) => {
if ( window.THREE_PARTICLE === null ) return false;
// Make THREE available globally
window.THREE = THREE;
module.THREE_PARTICLE = module.THREE_PARTICLE || {};
module.THREE_PARTICLE.version = '1.1.0';
module.THREE_PARTICLE.documentReady = function( $ ) {
//Prevent this module from loading in other pages
if ( $( '#3D-particle-effect-canvas' ).length == 0 || ! Modernizr.webgl ) return false;
let sceneSubjects = []; // Import objects and animations dynamically
const MainStage = function() {
let windowWidth = window.innerWidth,
windowHeight = window.innerHeight;
const rendererCanvasID = '3D-particle-effect-canvas';
let renderer,
texture,
scene,
camera,
particles,
imagedata,
clock = new THREE.Clock(),
mouseX = 0,
mouseY = 0,
isMouseDown = true,
lastMousePos = {x: 0, y: 0},
windowHalfX = windowWidth / 2,
windowHalfY = windowHeight / 2,
animStartStatus = false,
animCompleted = false;
//background
const backgroundBg = new THREE.Color('#CE3A3E');
const backgroundPlane = new THREE.Color('#DE510E');;
const backgroundPlaneDecay = new THREE.Color('#FFF7BA');;
// Light from scene ready
let sceneForLightPlane, sceneForSpotLight, sceneForAmbientLight;
// camera data
let fieldOfView, aspectRatio, nearPlane, farPlane;
let dist, vFOV, visibleHeight, visibleWidth;
let xLimit, yLimit;
const maxTargetZ = 200;
//particle rotation
let particleRotation;
const centerVector = new THREE.Vector3(0, 0, 0);
let previousTime = 0;
function init() {
//==================================
//==================================
//camera
fieldOfView = 60;
aspectRatio = windowWidth / windowHeight;
nearPlane = 1; // the camera won't "see" any object placed in front of this plane
farPlane = 10000; // the camera wont't see any object placed further than this plane
camera = new THREE.PerspectiveCamera( fieldOfView, aspectRatio, nearPlane, farPlane );
camera.position.set(0, 65, -500);
camera.lookAt( centerVector );
// convert the field of view to radians
const ang = (fieldOfView / 2) * Math.PI / 180;
// calculate the max y position seen by the camera related to the maxTargetZ position, I start by calculating the y limit because fielOfView is a vertical field of view. I then calculate the x Limit
yLimit = (camera.position.z + maxTargetZ) * Math.tan(ang); // this is a formula I found, don't ask me why it works, it just does :)
// Calculate the max x position seen by the camera related to the y Limit position
xLimit = yLimit * camera.aspect;
// Fit plane to screen
dist = 1000;
vFOV = THREE.MathUtils.degToRad( camera.fov ); // convert vertical fov to radians
visibleHeight = 2 * Math.tan( vFOV / 2 ) * dist; // visible height
visibleWidth = visibleHeight * camera.aspect; // visible width
//console.log( 'visibleWidth:' + visibleWidth + ', visibleHeight: ' + visibleHeight + ', xLimit: ' + xLimit + ', yLimit: ' + yLimit );
//==================================
//==================================
//Scene
scene = new THREE.Scene();
scene.fog = new THREE.Fog(backgroundBg, 0.0025, 650); // Used to cover the light plane
/*
const axesHelper = new THREE.AxesHelper(1000);
scene.add(axesHelper);
*/
//==================================
//==================================
//Light from scene ready
// Light plane
sceneForLightPlane = new THREE.Mesh(new THREE.CircleGeometry(1000, 32), new THREE.MeshPhongMaterial({
color: backgroundPlaneDecay, // Add a base color
emissive: backgroundPlane, // Maintain the glowing color
emissiveIntensity: 1, // Add luminous intensity
side: THREE.DoubleSide,
}));
sceneForLightPlane.receiveShadow = true; // !!!Important
sceneForLightPlane.position.set(0, -101, 5);
sceneForLightPlane.rotation.x = getRadian( -95 );
scene.add(sceneForLightPlane);
/*
const boxHelper = new THREE.BoxHelper(sceneForLightPlane, 0xffff00);
scene.add(boxHelper);
const spotLightHelper = new THREE.SpotLightHelper(sceneForSpotLight, 0xffffff);
scene.add(spotLightHelper);
const gridHelper = new THREE.GridHelper(2000, 20, 0x888888, 0x444444);
scene.add(gridHelper);
*/
// Spot Light
const spotLightColor = 0xffffff,
spotLightIntensity = 8, // !!!Important
spotLightDistance = 1200,
spotLightAngle = getRadian( 45 ),
spotLightPenumbra = 0.6, // Reduce soft edges appropriately
spotLightDecay = 0.002; // !!!Important
sceneForSpotLight = new THREE.SpotLight(spotLightColor, spotLightIntensity, spotLightDistance, spotLightAngle, spotLightPenumbra, spotLightDecay);
sceneForSpotLight.position.set(5, 320, 5); // Setting the y-axis bond angle is critical
sceneForSpotLight.castShadow = true; // !!!Important
sceneForSpotLight.shadow.mapSize.width = 1024;
sceneForSpotLight.shadow.mapSize.height = 1024;
sceneForSpotLight.shadow.camera.near = 0.5;
sceneForSpotLight.shadow.camera.far = 31;
sceneForSpotLight.shadow.bias = -0.001; // Reduces shadow blemishes
sceneForSpotLight.shadow.normalBias = 0.1; // Improved shadow quality
scene.add(sceneForSpotLight);
//console.log( sceneForSpotLight );
/*
const spotLightHelper = new THREE.SpotLightHelper( sceneForSpotLight );
scene.add( spotLightHelper );
*/
// Ambient Light
sceneForAmbientLight = new THREE.AmbientLight(0xffffff, 0.08);
scene.add(sceneForAmbientLight);
//==================================
//==================================
//WebGL Renderer
renderer = new THREE.WebGLRenderer( {
canvas : document.getElementById( rendererCanvasID ), //canvas
alpha : true,
antialias: true
} );
renderer.setSize( windowWidth, windowHeight );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// instantiate a loader
const loader = new THREE.TextureLoader();
// load a resource
loader.load(
// resource URL
$( '#' + rendererCanvasID ).data( 'img-src' ),
// onLoad callback
function ( texture ) {
// in this example we create the material when the texture is loaded
// Get data from an image
imagedata = getImageData( texture.image );
// Immediately use the texture for material creation
const geometry = new THREE.BufferGeometry();
const vertices = [];
const verticesDest = [];
for (let y = 0, y2 = imagedata.height; y < y2; y += 2) {
for (let x = 0, x2 = imagedata.width; x < x2; x += 2) {
if (imagedata.data[(x * 4 + y * 4 * imagedata.width) + 3] > 128) {
// The array of vertices holds the position of every vertex in the model.
const vertex = new THREE.Vector3();
vertex.x = Math.random() * 1000 - 500;
vertex.y = Math.random() * 1000 - 500;
vertex.z = -Math.random() * 500 + 1500;
vertex.destination = {
x: x - imagedata.width / 2,
y: -y + imagedata.height / 2,
z: 0
};
vertices.push(vertex.x, vertex.y, vertex.z);
verticesDest.push(vertex.destination.x, vertex.destination.y, vertex.destination.z);
}
}
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
geometry.setAttribute('position_destination', new THREE.Float32BufferAttribute(verticesDest, 3));
geometry.computeBoundingSphere();
//
const material = new THREE.PointsMaterial({
size: 3,
color: 0xffffff,
sizeAttenuation: false,
vertexColors: false,
fog: false //Excluding objects from fog
});
particles = new THREE.Points( geometry, material );
scene.add( particles );
particles.scale.setScalar( 0.7 );
particles.position.y = 50;
particles.position.z = 70;
particles.rotation.y = getRadian( 180 );
},
// onProgress callback currently not supported
undefined,
// onError callback
function ( err ) {
console.error( 'An error happened.' );
}
);
// add particle rotation
particleRotation = new THREE.Object3D();
scene.add( particleRotation );
const geometryPR = new THREE.TetrahedronGeometry(2, 0),
materialPR = new THREE.MeshPhongMaterial({
color: 0xffffff,
emissive: 0xffffff,
shininess: 80,
flatShading: true,
});
for (let i = 0; i < 750; i++) {
const mesh = new THREE.Mesh( geometryPR, materialPR );
mesh.position.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize();
mesh.position.multiplyScalar(90 + (Math.random() * 700));
mesh.rotation.set(Math.random() * 2, Math.random() * 2, Math.random() * 2);
particleRotation.add(mesh);
// set castShadow to object
mesh.castShadow = true;
}
//----
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'touchstart', onDocumentTouchStart, UixBrowser.supportsPassive ? { passive: true } : false );
document.addEventListener( 'touchmove', onDocumentTouchMove, UixBrowser.supportsPassive ? { passive: true } : false );
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
document.addEventListener( 'mouseup', onDocumentMouseUp, false );
// Fires when the window changes
window.addEventListener( 'resize', onWindowResize, false );
}
function animStart() {
if (typeof particles === 'undefined') return;
animStartStatus = true;
const points = particles.geometry.attributes.position.array;
const targetPoints = particles.geometry.attributes.position_destination.array;
// use target array as the tween object to store tween properties... HACKY I KNOW!
// targetPoints.repeat = -1;
// targetPoints.repeatDelay = 1;
targetPoints.ease = Power2.easeOut;
TweenMax.to(points, 4, targetPoints);
targetPoints.onUpdate = function() {
particles.geometry.attributes.position.needsUpdate = true;
}
targetPoints.onComplete = function() {
animCompleted = true;
}
/*
gsap.to(particles.geometry.attributes.position.array, {
endArray: particles.geometry.attributes.position_destination.array,
duration: 2,
ease: 'power3.out',
// Make sure to tell it to update
onUpdate: () => {
particles.geometry.attributes.position.needsUpdate = true;
},
onComplete: () => {
animCompleted = true;
}
});
*/
}
function render() {
requestAnimationFrame( render );
const delta = clock.getDelta(),
thickness = 40;
//---
//
// To set a background color.
renderer.setClearColor( backgroundBg );
//---
//
// Animation start
if (!animStartStatus) {
animStart();
}
//---
//
if( ! isMouseDown ) {
camera.position.x += (0-camera.position.x)*0.06;
camera.position.y += (0-camera.position.y)*0.06;
}
if ( animCompleted ) {
camera.position.x += ( mouseX - camera.position.x ) * 0.09;
camera.position.y += ( - mouseY - camera.position.y ) * 0.09;
}
if ( camera.position.y < -60 ) camera.position.y = -60;
camera.lookAt( centerVector );
//particle rotation
particleRotation.rotation.x += 0.0000;
particleRotation.rotation.y -= 0.0040;
//---
//
//push objects
/*
@Usage:
function CustomObj( scene ) {
const elements = new THREE...;
scene.add( elements );
this.update = function( time ) {
elements.rotation.y = time*0.003;
}
}
sceneSubjects.push( new CustomObj( MainStage.getScene() ) );
*/
for( let i = 0; i < sceneSubjects.length; i++ ) {
sceneSubjects[i].update( clock.getElapsedTime()*1 );
}
//---
//
//render the scene to display our scene through the camera's eye.
renderer.render( scene, camera );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseMove( event ) {
mouseX = event.clientX - windowHalfX;
mouseY = event.clientY - windowHalfY;
if( isMouseDown ) {
camera.position.x += (event.clientX-lastMousePos.x)/100;
camera.position.y -= (event.clientY-lastMousePos.y)/100;
camera.lookAt( centerVector );
lastMousePos = {x: event.clientX, y: event.clientY};
}
}
function onDocumentTouchStart( event ) {
if ( event.touches.length == 1 ) {
event.preventDefault();
mouseX = event.touches[ 0 ].pageX - windowHalfX;
mouseY = event.touches[ 0 ].pageY - windowHalfY;
}
}
function onDocumentTouchMove( event ) {
if ( event.touches.length == 1 ) {
event.preventDefault();
mouseX = event.touches[ 0 ].pageX - windowHalfX;
mouseY = event.touches[ 0 ].pageY - windowHalfY;
}
}
function onDocumentMouseUp() {
isMouseDown = false;
}
function onDocumentMouseDown( event ) {
isMouseDown = true;
lastMousePos = {x: event.clientX, y: event.clientY};
}
/*
* Get Image Data when Draw Image To Canvas
*
* @param {!Element} image - Overridden with a record type holding data, width and height.
* @return {Object} - The image data via JSON.
*/
function getImageData( image ) {
const canvas = document.createElement( 'canvas' );
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext( '2d' );
ctx.drawImage(image, 0, 0);
return ctx.getImageData(0, 0, image.width, image.height);
}
/*
* Get Object Coordinate, Width and Height From Screen
* Note: No data may be acquired without delay !!
*
* @param {THREE.Mesh} obj - Mesh object.
* @param {THREE.PerspectiveCamera} camera - Mesh object.
* @param {Number} rendererWidth - Width of renderer.
* @param {Number} rendererHeight - Height of renderer.
* @param {String} type - Build type.
* @return {JSON}
*/
/* @usage:
const screenPos = nestedObjectToScreenXYZAndWH( displacementSprite , camera, renderer.domElement.width, renderer.domElement.height );
*/
function nestedObjectToScreenXYZAndWH( obj, camera, rendererWidth, rendererHeight ) {
const vector = new THREE.Vector3();
vector.setFromMatrixPosition( obj.matrixWorld );
const widthHalf = rendererWidth/2;
const heightHalf = rendererHeight/2;
const aspect = rendererHeight/rendererWidth;
vector.project( camera );
vector.x = ( vector.x * widthHalf ) + widthHalf;
vector.y = - ( vector.y * heightHalf ) + heightHalf;
//compute bounding box after
const boxInfo = new THREE.Box3().setFromObject( obj ).getSize( new THREE.Vector3() );
//Change it to fit the width and height of the stage based on the current value
const ratioFixedNum = 7;
//correction
return {
position: vector,
width: ( boxInfo.x * ratioFixedNum * aspect ).toFixed( 2 ),
height: ( boxInfo.y * ratioFixedNum * aspect ).toFixed( 2 )
};
}
/*
* Generate random number between two numbers
*
* @return {Number}
*/
function getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}
/*
* Returns the degree from radian.
*
* @return {Number} rad - Value of radian.
* @return {Number}
* @usage:
angle = rad / ( Math.PI / 180 ) = rad * ( 180/Math.PI );
*/
function getDegree( rad ) {
return rad / Math.PI * 180;
}
/*
* Returns the radian degree .
*
* @return {Number} deg - Value of degree.
* @return {Number}
* @usage:
rad = Math.PI / 180 * 30 ;
*/
function getRadian( deg ) {
return deg * Math.PI / 180;
}
/*
* Convert three.js scene rotation to polar coordinates
*
* @return {Number} deg - Value of degree.
* @return {Number}
* @usage:
x = r * cos(θ)
y = r * sin(θ)
*/
function getPolarCoord(x, y, z) {
const nx = Math.cos(x) * Math.cos(y) * z,
nz = Math.cos(x) * Math.sin(y) * z,
ny = Math.sin(x) * z;
return new THREE.Vector3(nx, ny, nz);
}
//
//-------------------------------------
return {
init : init,
render : render,
getRendererCanvasID : function () { return rendererCanvasID; },
getScene : function () { return scene; },
getCamera : function () { return camera; }
};
}();
MainStage.init();
MainStage.render();
};
module.components.documentReady.push( module.THREE_PARTICLE.documentReady );
return class THREE_PARTICLE {
constructor() {
this.module = module;
}
};
})( UixModuleInstance, jQuery, window, document );