|
|
@@ -38,16 +38,17 @@
|
|
|
<script type="module">
|
|
|
|
|
|
import * as THREE from 'three';
|
|
|
- import { Fn, uniform, texture, instancedArray, instanceIndex, float, hash, vec3, If } from 'three/tsl';
|
|
|
+ import { Fn, If, uniform, float, uv, vec2, vec3, hash,
|
|
|
+ instancedArray, instanceIndex } from 'three/tsl';
|
|
|
|
|
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
|
import Stats from 'three/addons/libs/stats.module.js';
|
|
|
|
|
|
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
|
|
|
|
|
- const particleCount = 500000;
|
|
|
+ const particleCount = 200_000;
|
|
|
|
|
|
- const gravity = uniform( - .0098 );
|
|
|
+ const gravity = uniform( - .00098 );
|
|
|
const bounce = uniform( .8 );
|
|
|
const friction = uniform( .99 );
|
|
|
const size = uniform( .12 );
|
|
|
@@ -58,6 +59,8 @@
|
|
|
let controls, stats;
|
|
|
let computeParticles;
|
|
|
|
|
|
+ let isOrbitControlsActive;
|
|
|
+
|
|
|
const timestamps = document.getElementById( 'timestamps' );
|
|
|
|
|
|
init();
|
|
|
@@ -67,37 +70,35 @@
|
|
|
const { innerWidth, innerHeight } = window;
|
|
|
|
|
|
camera = new THREE.PerspectiveCamera( 50, innerWidth / innerHeight, .1, 1000 );
|
|
|
- camera.position.set( 15, 30, 15 );
|
|
|
+ camera.position.set( 0, 5, 20 );
|
|
|
|
|
|
scene = new THREE.Scene();
|
|
|
|
|
|
- // textures
|
|
|
-
|
|
|
- const textureLoader = new THREE.TextureLoader();
|
|
|
- const map = textureLoader.load( 'textures/sprite1.png' );
|
|
|
-
|
|
|
//
|
|
|
|
|
|
- const positionBuffer = instancedArray( particleCount, 'vec3' );
|
|
|
- const velocityBuffer = instancedArray( particleCount, 'vec3' );
|
|
|
- const colorBuffer = instancedArray( particleCount, 'vec3' );
|
|
|
+ const positions = instancedArray( particleCount, 'vec3' );
|
|
|
+ const velocities = instancedArray( particleCount, 'vec3' );
|
|
|
+ const colors = instancedArray( particleCount, 'vec3' );
|
|
|
|
|
|
// compute
|
|
|
+
|
|
|
+ const separation = 0.2;
|
|
|
+ const amount = Math.sqrt( particleCount );
|
|
|
+ const offset = float( amount / 2 );
|
|
|
|
|
|
const computeInit = Fn( () => {
|
|
|
|
|
|
- const position = positionBuffer.element( instanceIndex );
|
|
|
- const color = colorBuffer.element( instanceIndex );
|
|
|
-
|
|
|
- const randX = hash( instanceIndex );
|
|
|
- const randY = hash( instanceIndex.add( 2 ) );
|
|
|
- const randZ = hash( instanceIndex.add( 3 ) );
|
|
|
+ const position = positions.element( instanceIndex );
|
|
|
+ const color = colors.element( instanceIndex );
|
|
|
+
|
|
|
+ const x = instanceIndex.mod( amount );
|
|
|
+ const z = instanceIndex.div( amount );
|
|
|
+
|
|
|
+ position.x = offset.sub( x ).mul( separation );
|
|
|
+ position.z = offset.sub( z ).mul( separation );
|
|
|
|
|
|
- position.x = randX.mul( 100 ).add( - 50 );
|
|
|
- position.y = 0; // randY.mul( 10 );
|
|
|
- position.z = randZ.mul( 100 ).add( - 50 );
|
|
|
-
|
|
|
- color.assign( vec3( randX, randY, randZ ) );
|
|
|
+ color.x = hash( instanceIndex );
|
|
|
+ color.y = hash( instanceIndex.add( 2 ) );
|
|
|
|
|
|
} )().compute( particleCount );
|
|
|
|
|
|
@@ -105,8 +106,8 @@
|
|
|
|
|
|
const computeUpdate = Fn( () => {
|
|
|
|
|
|
- const position = positionBuffer.element( instanceIndex );
|
|
|
- const velocity = velocityBuffer.element( instanceIndex );
|
|
|
+ const position = positions.element( instanceIndex );
|
|
|
+ const velocity = velocities.element( instanceIndex );
|
|
|
|
|
|
velocity.addAssign( vec3( 0.00, gravity, 0.00 ) );
|
|
|
position.addAssign( velocity );
|
|
|
@@ -131,31 +132,26 @@
|
|
|
|
|
|
computeParticles = computeUpdate().compute( particleCount );
|
|
|
|
|
|
- // create nodes
|
|
|
-
|
|
|
- const textureNode = texture( map );
|
|
|
-
|
|
|
// create particles
|
|
|
-
|
|
|
- const particleMaterial = new THREE.SpriteNodeMaterial();
|
|
|
- particleMaterial.colorNode = textureNode.mul( colorBuffer.element( instanceIndex ) );
|
|
|
- particleMaterial.positionNode = positionBuffer.toAttribute();
|
|
|
- particleMaterial.scaleNode = size;
|
|
|
- particleMaterial.depthWrite = false;
|
|
|
- particleMaterial.depthTest = true;
|
|
|
- particleMaterial.transparent = true;
|
|
|
-
|
|
|
- const particles = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), particleMaterial );
|
|
|
+
|
|
|
+ const material = new THREE.SpriteNodeMaterial();
|
|
|
+ material.colorNode = uv().mul( colors.element( instanceIndex ) );
|
|
|
+ material.positionNode = positions.toAttribute();
|
|
|
+ material.scaleNode = size;
|
|
|
+ material.alphaTestNode = uv().mul( 2 ).distance( vec2( 1 ) );
|
|
|
+ material.transparent = false;
|
|
|
+
|
|
|
+ const particles = new THREE.Sprite( material );
|
|
|
particles.count = particleCount;
|
|
|
particles.frustumCulled = false;
|
|
|
scene.add( particles );
|
|
|
|
|
|
//
|
|
|
|
|
|
- const helper = new THREE.GridHelper( 60, 40, 0x303030, 0x303030 );
|
|
|
+ const helper = new THREE.GridHelper( 90, 45, 0x303030, 0x303030 );
|
|
|
scene.add( helper );
|
|
|
|
|
|
- const geometry = new THREE.PlaneGeometry( 1000, 1000 );
|
|
|
+ const geometry = new THREE.PlaneGeometry( 200, 200 );
|
|
|
geometry.rotateX( - Math.PI / 2 );
|
|
|
|
|
|
const plane = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { visible: false } ) );
|
|
|
@@ -179,19 +175,19 @@
|
|
|
|
|
|
renderer.computeAsync( computeInit );
|
|
|
|
|
|
- // click event
|
|
|
+ // Hit
|
|
|
|
|
|
const computeHit = Fn( () => {
|
|
|
|
|
|
- const position = positionBuffer.element( instanceIndex );
|
|
|
- const velocity = velocityBuffer.element( instanceIndex );
|
|
|
+ const position = positions.element( instanceIndex );
|
|
|
+ const velocity = velocities.element( instanceIndex );
|
|
|
|
|
|
const dist = position.distance( clickPosition );
|
|
|
const direction = position.sub( clickPosition ).normalize();
|
|
|
- const distArea = float( 6 ).sub( dist ).max( 0 );
|
|
|
+ const distArea = float( 3 ).sub( dist ).max( 0 );
|
|
|
|
|
|
const power = distArea.mul( .01 );
|
|
|
- const relativePower = power.mul( hash( instanceIndex ).mul( .5 ).add( .5 ) );
|
|
|
+ const relativePower = power.mul( hash( instanceIndex ).mul( 1.5 ).add( .5 ) );
|
|
|
|
|
|
velocity.assign( velocity.add( direction.mul( relativePower ) ) );
|
|
|
|
|
|
@@ -200,12 +196,14 @@
|
|
|
//
|
|
|
|
|
|
function onMove( event ) {
|
|
|
+
|
|
|
+ if ( isOrbitControlsActive ) return;
|
|
|
|
|
|
pointer.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1 );
|
|
|
|
|
|
raycaster.setFromCamera( pointer, camera );
|
|
|
|
|
|
- const intersects = raycaster.intersectObjects( [ plane ], false );
|
|
|
+ const intersects = raycaster.intersectObject( plane, false );
|
|
|
|
|
|
if ( intersects.length > 0 ) {
|
|
|
|
|
|
@@ -224,17 +222,24 @@
|
|
|
|
|
|
}
|
|
|
|
|
|
- // events
|
|
|
-
|
|
|
renderer.domElement.addEventListener( 'pointermove', onMove );
|
|
|
|
|
|
- //
|
|
|
+ // controls
|
|
|
|
|
|
controls = new OrbitControls( camera, renderer.domElement );
|
|
|
+ controls.enableDamping = true;
|
|
|
controls.minDistance = 5;
|
|
|
controls.maxDistance = 200;
|
|
|
- controls.target.set( 0, 0, 0 );
|
|
|
+ controls.target.set( 0, -8, 0 );
|
|
|
controls.update();
|
|
|
+
|
|
|
+ controls.addEventListener( 'start', () => { isOrbitControlsActive = true; } );
|
|
|
+ controls.addEventListener( 'end', () => { isOrbitControlsActive = false; } );
|
|
|
+
|
|
|
+ controls.touches = {
|
|
|
+ ONE: null,
|
|
|
+ TWO: THREE.TOUCH.DOLLY_PAN
|
|
|
+ };
|
|
|
|
|
|
//
|
|
|
|
|
|
@@ -266,6 +271,8 @@
|
|
|
|
|
|
stats.update();
|
|
|
|
|
|
+ controls.update();
|
|
|
+
|
|
|
await renderer.computeAsync( computeParticles );
|
|
|
renderer.resolveTimestampsAsync( THREE.TimestampQuery.COMPUTE );
|
|
|
|