UNPKG

uix-kit

Version:

A free web kits for fast web design and development, compatible with Bootstrap v5.

564 lines (384 loc) 17.3 kB
/* ************************************* * <!-- 3D Mouse Interaction with three.js --> ************************************* */ import { UixBrowser, UixModuleInstance, } from '@uixkit/core/_global/js'; export const THREE_MOUSE_INTERACTION2 = ( ( module, $, window, document ) => { if ( window.THREE_MOUSE_INTERACTION2 === null ) return false; module.THREE_MOUSE_INTERACTION2 = module.THREE_MOUSE_INTERACTION2 || {}; module.THREE_MOUSE_INTERACTION2.version = '0.0.8'; module.THREE_MOUSE_INTERACTION2.documentReady = function( $ ) { //Prevent this module from loading in other pages if ( $( '#3D-mouseinteraction2-three-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-mouseinteraction2-three-canvas'; // Generate one plane geometries mesh to scene //------------------------------------- let camera, scene, light, renderer, displacementSprite, theta = 0, clickEnabled = false; // controls const scroller = new CameraScroller({direction: "y"}); // mouse const mouseVector = new THREE.Vector2(); let raycaster, intersects, INTERSECTED, nucleus, atoms = []; function init() { //camera camera = new THREE.PerspectiveCamera( 70, windowWidth / windowHeight, 1, 50000 ); camera.position.set( 0, 0, 20000 ); //Scene scene = new THREE.Scene(); //HemisphereLight scene.add( new THREE.AmbientLight( 0x555555 ) ); light = new THREE.SpotLight( 0xffffff, 1.5 ); light.position.set( 0, 500, 2000 ); light.decay = 0; // !!!Important scene.add( light ); //WebGL Renderer renderer = new THREE.WebGLRenderer( { canvas : document.getElementById( rendererCanvasID ), //canvas alpha : true, antialias: true } ); renderer.setSize( windowWidth, windowHeight ); scroller.init( renderer.domElement ); // Immediately use the texture for material creation generateGeometry( 500 ); // Fires when the window changes window.addEventListener( 'resize', onWindowResize, false ); // When the mouse moves, call the given function raycaster = new THREE.Raycaster(); document.addEventListener( 'mousemove', onDocumentMouseMove, false ); document.getElementById( rendererCanvasID ).addEventListener( 'click', onDocumentMouseDown, false ); document.addEventListener( 'mouseup', onDocumentMouseUp, false ); } function render() { requestAnimationFrame( render ); //To set a background color. renderer.setClearColor( 0x000000 ); //update controls camera.position.y = scroller.getScrollPosY()*10000; //Mouse interactions raycaster.setFromCamera( mouseVector, camera ); intersects = raycaster.intersectObjects( atoms ); //intersects = raycaster.intersectObjects( scene.children ); if ( intersects.length > 0 ) { if ( INTERSECTED != intersects[ 0 ].object ) { // restore previous intersection object (if it exists) to its original color if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex ); INTERSECTED = intersects[ 0 ].object; INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex(); INTERSECTED.material.emissive.setHex( 0xff0000 ); } } else { // restore previous intersection object (if it exists) to its original color if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex ); //by setting current intersection object to "nothing" INTERSECTED = null; } //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 ) { event.preventDefault(); mouseVector.x = ( event.clientX / window.innerWidth ) * 2 - 1; mouseVector.y = - ( event.clientY / window.innerHeight ) * 2 + 1; } function onDocumentMouseDown( event ) { event.preventDefault(); mouseVector.x = ( event.clientX / window.innerWidth ) * 2 - 1; mouseVector.y = - ( event.clientY / window.innerHeight ) * 2 + 1; clickEnabled = true; //Mouse interactions raycaster.setFromCamera( mouseVector, camera ); intersects = raycaster.intersectObjects( atoms ); //intersects = raycaster.intersectObjects( scene.children ); if ( intersects.length > 0 && intersects[0].object.name.indexOf( 'nucleus' ) >= 0 ) { const _obj = intersects[0].object; const targetAtomPos = _obj.position; console.log(targetAtomPos); // targetAtomPos.tween.pause(); const destinationPos = targetAtomPos.clone(); // jump to new position // y movement via scroller object // x and z movement via TWEEN scroller.targetPosition = _obj.position.y/10000; const targetPos = { x: _obj.position.x, y:_obj.position.y, z:_obj.position.z+1100}; TweenMax.to( targetPos, 2,{ x:destinationPos.x, y:destinationPos.y, z:destinationPos.z}); TweenMax.to( camera.position, 2,{ x:destinationPos.x, y:destinationPos.y, z:destinationPos.z+1000, onUpdate:function(){ camera.up.set(0,1,0); camera.updateProjectionMatrix(); }, onComplete: function() { // get object new coordinates const screenData = nestedObjectToScreenXYZAndWH( _obj, camera, renderer.domElement.width, renderer.domElement.height ); console.log( `Current object coordinates: {x: ${screenData.position.x}, y: ${screenData.position.y}, z: ${screenData.position.z} }` ); } }); } else { //restore scroller position scroller.targetPosition = 0; //restore camera position TweenMax.to( camera.position, 2,{ x:0, y:0, z:20000, onComplete:function(){ TweenMax.resumeAll(); } }); } } function onDocumentMouseUp( event ) { event.preventDefault(); mouseVector.x = ( event.clientX / window.innerWidth ) * 2 - 1; mouseVector.y = - ( event.clientY / window.innerHeight ) * 2 + 1; theta = 0; clickEnabled = false; } /* * Batch generation of geometry * * @param {Number} numObjects - The total number of generated objects. * @return {Void} */ function generateGeometry( numObjects ) { const applyVertexColors = function(geometry, color) { // Creates an array of vertex colors const positions = geometry.attributes.position; const colors = new Float32Array(positions.count * 3); for (let i = 0; i < positions.count; i++) { colors[i * 3] = color.r; colors[i * 3 + 1] = color.g; colors[i * 3 + 2] = color.b; } geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); }; for ( let i = 0; i < numObjects; i ++ ) { let geom; const color = new THREE.Color(); const position = new THREE.Vector3(); position.x = -9000 + (i % 10) * 2000; position.y = -9000 + Math.floor((i % 100) / 10) * 2000; position.z = -1000 + Math.floor(i / 100) * 2000; const rotation = new THREE.Euler(); rotation.x = 0; rotation.y = 0; rotation.z = 0; const scale = new THREE.Vector3(); scale.x = 1200; scale.y = 600; scale.z = 200; geom = new THREE.BoxGeometry( 1, 1, 1 ); color.setRGB( 0, 0, Math.random() + 0.1 ); // give the geom's vertices a random color, to be displayed applyVertexColors( geom, color ); // Immediately use the texture for material creation const defaultMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff, flatShading: true, vertexColors: true } ); displacementSprite = new THREE.Mesh( geom, defaultMaterial ); scene.add( displacementSprite ); const object = new THREE.Mesh( geom ); displacementSprite.name = 'nucleus-' + i; displacementSprite.position.copy( position ); displacementSprite.rotation.copy( rotation ); displacementSprite.scale.copy( scale ); displacementSprite.updateMatrix(); scene.add( displacementSprite ); atoms.push( displacementSprite ); } } /* * CameraSroller * Scrolls the camera vertically (up/down) by mouse, scrollwhell and touch * including a velocity based animation */ function CameraScroller(options) { this.targetPosition = 0; this.targetPositionOnMouseDown = 0; this.mouseY = 0; this.mouseYOnMouseDown = 0; this.scrollPosY = 0; this.domElem = undefined; this.init = function(domEl) { this.domElem = domEl; this.domElem.addEventListener('mousedown', this.onDocumentMouseDown, false); this.domElem.addEventListener('touchstart', this.onDocumentTouchStart, UixBrowser.supportsPassive ? { passive: true } : false); this.domElem.addEventListener('touchmove', this.onDocumentTouchMove, UixBrowser.supportsPassive ? { passive: true } : false); this.domElem.addEventListener('wheel', this.onDocumentMousewheel, UixBrowser.supportsPassive ? { passive: true } : false); }; this.onDocumentMouseDown = function(event) { event.preventDefault(); this.domElem.addEventListener('mousemove', this.onDocumentMouseMove, false); this.domElem.addEventListener('mouseup', this.onDocumentMouseUp, false); this.domElem.addEventListener('mouseout', this.onDocumentMouseOut, false); this.mouseYOnMouseDown = event.clientY; this.targetPositionOnMouseDown = this.targetPosition; }.bind(this); this.onDocumentMouseMove = function(event) { this.mouseY = event.clientY; this.targetPosition = this.targetPositionOnMouseDown + (this.mouseY - this.mouseYOnMouseDown) * 0.02; }.bind(this); this.onDocumentMouseUp = function(event) { this.domElem.removeEventListener('mousemove', this.onDocumentMouseMove, false); this.domElem.removeEventListener('mouseup', this.onDocumentMouseUp, false); this.domElem.removeEventListener('mouseout', this.onDocumentMouseOut, false); }.bind(this); this.onDocumentMouseOut = function(event) { this.domElem.removeEventListener('mousemove', this.onDocumentMouseMove, false); this.domElem.removeEventListener('mouseup', this.onDocumentMouseUp, false); this.domElem.removeEventListener('mouseout', this.onDocumentMouseOut, false); }.bind(this); this.onDocumentTouchStart = function(event) { if (event.touches.length == 1) { event.preventDefault(); this.mouseYOnMouseDown = event.touches[0].pageY; this.targetPositionOnMouseDown = this.targetPosition; } }.bind(this); this.onDocumentTouchMove = function(event) { if (event.touches.length == 1) { event.preventDefault(); this.mouseY = event.touches[0].pageY; this.targetPosition = this.targetPositionOnMouseDown + (this.mouseY - this.mouseYOnMouseDown) * 0.02; } }.bind(this); this.onDocumentMousewheel = function(event) { this.targetPosition = this.targetPosition + event.wheelDeltaY * 0.005; }.bind(this); this.getScrollPosY = function() { this.scrollPosY = this.scrollPosY + (this.targetPosition - this.scrollPosY) * 0.05; // 0.05=long scroll delay, 0.15=short delay return this.scrollPosY }.bind(this); } /* * 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_MOUSE_INTERACTION2.documentReady ); return class THREE_MOUSE_INTERACTION2 { constructor() { this.module = module; } }; })( UixModuleInstance, jQuery, window, document );