//ported from threejs examples
//https://threejs.org/examples/#webgl_instancing_scatter
//load css
AB.loadCSS ( '/uploads/threeport/main.css' );
AB.newDiv ('container');
import * as THREE from '/api/threemodule/libs/three.module.js';
import Stats from '/uploads/threeport/stats.module.js';
import { GUI } from '/uploads/threejs/dat.gui.module.js';
import { MeshSurfaceSampler } from '/uploads/threejs/MeshSurfaceSampler.js';
import { GLTFLoader } from '/uploads/threejs/GLTFLoader.js';
let camera, scene, renderer, stats;
const api = {
count: 2000,
distribution: 'random',
resample: resample,
surfaceColor: 0xFFF784,
backgroundColor: 0xE39469,
};
let stemMesh, blossomMesh;
let stemGeometry, blossomGeometry;
let stemMaterial, blossomMaterial;
let sampler;
const count = api.count;
const ages = new Float32Array( count );
const scales = new Float32Array( count );
const dummy = new THREE.Object3D();
const _position = new THREE.Vector3();
const _normal = new THREE.Vector3();
const _scale = new THREE.Vector3();
// let surfaceGeometry = new THREE.BoxGeometry( 10, 10, 10 ).toNonIndexed();
const surfaceGeometry = new THREE.TorusKnotGeometry( 10, 3, 100, 16 ).toNonIndexed();
const surfaceMaterial = new THREE.MeshLambertMaterial( { color: api.surfaceColor, wireframe: false } );
const surface = new THREE.Mesh( surfaceGeometry, surfaceMaterial );
// Source: https://gist.github.com/gre/1650294
const easeOutCubic = function ( t ) {
return ( -- t ) * t * t + 1;
};
// Scaling curve causes particles to grow quickly, ease gradually into full scale, then
// disappear quickly. More of the particle's lifetime is spent around full scale.
const scaleCurve = function ( t ) {
return Math.abs( easeOutCubic( ( t > 0.5 ? 1 - t : t ) * 2 ) );
};
const loader = new GLTFLoader();
loader.load( '/uploads/threejs/Flower.glb', function ( gltf ) {
const _stemMesh = gltf.scene.getObjectByName( 'Stem' );
const _blossomMesh = gltf.scene.getObjectByName( 'Blossom' );
stemGeometry = _stemMesh.geometry.clone();
blossomGeometry = _blossomMesh.geometry.clone();
const defaultTransform = new THREE.Matrix4()
.makeRotationX( Math.PI )
.multiply( new THREE.Matrix4().makeScale( 7, 7, 7 ) );
stemGeometry.applyMatrix4( defaultTransform );
blossomGeometry.applyMatrix4( defaultTransform );
stemMaterial = _stemMesh.material;
blossomMaterial = _blossomMesh.material;
stemMesh = new THREE.InstancedMesh( stemGeometry, stemMaterial, count );
blossomMesh = new THREE.InstancedMesh( blossomGeometry, blossomMaterial, count );
// Assign random colors to the blossoms.
const color = new THREE.Color();
const blossomPalette = [ 0xF20587, 0xF2D479, 0xF2C879, 0xF2B077, 0xF24405 ];
for ( let i = 0; i < count; i ++ ) {
color.setHex( blossomPalette[ Math.floor( Math.random() * blossomPalette.length ) ] );
blossomMesh.setColorAt( i, color );
}
// Instance matrices will be updated every frame.
stemMesh.instanceMatrix.setUsage( THREE.DynamicDrawUsage );
blossomMesh.instanceMatrix.setUsage( THREE.DynamicDrawUsage );
resample();
init();
animate();
} );
function init() {
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.set( 25, 25, 25 );
camera.lookAt( 0, 0, 0 );
//
scene = new THREE.Scene();
scene.background = new THREE.Color( api.backgroundColor );
const pointLight = new THREE.PointLight( 0xAA8899, 0.75 );
pointLight.position.set( 50, - 25, 75 );
scene.add( pointLight );
scene.add( new THREE.HemisphereLight() );
//
scene.add( stemMesh );
scene.add( blossomMesh );
scene.add( surface );
//
const gui = new GUI();
gui.add( api, 'count', 0, count ).onChange( function () {
stemMesh.count = api.count;
blossomMesh.count = api.count;
} );
// gui.addColor( api, 'backgroundColor' ).onChange( function () {
// scene.background.setHex( api.backgroundColor );
// } );
// gui.addColor( api, 'surfaceColor' ).onChange( function () {
// surfaceMaterial.color.setHex( api.surfaceColor );
// } );
gui.add( api, 'distribution' ).options( [ 'random', 'weighted' ] ).onChange( resample );
gui.add( api, 'resample' );
//
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
//
stats = new Stats();
document.body.appendChild( stats.dom );
//
window.addEventListener( 'resize', onWindowResize );
}
function resample() {
const vertexCount = surface.geometry.getAttribute( 'position' ).count;
console.info( 'Sampling ' + count + ' points from a surface with ' + vertexCount + ' vertices...' );
//
console.time( '.build()' );
sampler = new MeshSurfaceSampler( surface )
.setWeightAttribute( api.distribution === 'weighted' ? 'uv' : null )
.build();
console.timeEnd( '.build()' );
//
console.time( '.sample()' );
for ( let i = 0; i < count; i ++ ) {
ages[ i ] = Math.random();
scales[ i ] = scaleCurve( ages[ i ] );
resampleParticle( i );
}
console.timeEnd( '.sample()' );
stemMesh.instanceMatrix.needsUpdate = true;
blossomMesh.instanceMatrix.needsUpdate = true;
}
function resampleParticle( i ) {
sampler.sample( _position, _normal );
_normal.add( _position );
dummy.position.copy( _position );
dummy.scale.set( scales[ i ], scales[ i ], scales[ i ] );
dummy.lookAt( _normal );
dummy.updateMatrix();
stemMesh.setMatrixAt( i, dummy.matrix );
blossomMesh.setMatrixAt( i, dummy.matrix );
}
function updateParticle( i ) {
// Update lifecycle.
ages[ i ] += 0.005;
if ( ages[ i ] >= 1 ) {
ages[ i ] = 0.001;
scales[ i ] = scaleCurve( ages[ i ] );
resampleParticle( i );
return;
}
// Update scale.
const prevScale = scales[ i ];
scales[ i ] = scaleCurve( ages[ i ] );
_scale.set( scales[ i ] / prevScale, scales[ i ] / prevScale, scales[ i ] / prevScale );
// Update transform.
stemMesh.getMatrixAt( i, dummy.matrix );
dummy.matrix.scale( _scale );
stemMesh.setMatrixAt( i, dummy.matrix );
blossomMesh.setMatrixAt( i, dummy.matrix );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
//
function animate() {
requestAnimationFrame( animate );
render();
stats.update();
}
function render() {
if ( stemMesh && blossomMesh ) {
const time = Date.now() * 0.001;
scene.rotation.x = Math.sin( time / 4 );
scene.rotation.y = Math.sin( time / 2 );
for ( let i = 0; i < api.count; i ++ ) {
updateParticle( i );
}
stemMesh.instanceMatrix.needsUpdate = true;
blossomMesh.instanceMatrix.needsUpdate = true;
}
renderer.render( scene, camera );
}