|
7 | 7 | <link type="text/css" rel="stylesheet" href="main.css"> |
8 | 8 | </head> |
9 | 9 | <body> |
| 10 | + <div id="info"> |
| 11 | + <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - instancing - dynamic<br/> |
| 12 | + Based on <a href="https://web.archive.org/web/20210101053442/http://oos.moxiecode.com/js_webgl/cubescape/" target="_blank" rel="noopener">Cubescape</a> |
| 13 | + by <a href="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/oosmoxiecode" target="_blank" rel="noopener">oosmoxiecode</a> |
| 14 | + </div> |
| 15 | + |
10 | 16 | <script type="importmap"> |
11 | 17 | { |
12 | 18 | "imports": { |
|
20 | 26 |
|
21 | 27 | import * as THREE from 'three'; |
22 | 28 |
|
23 | | - import Stats from 'three/addons/libs/stats.module.js'; |
24 | | - import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; |
| 29 | + import { Timer } from 'three/addons/misc/Timer.js'; |
| 30 | + import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'; |
| 31 | + import TWEEN from 'three/addons/libs/tween.module.js'; |
| 32 | + |
| 33 | + let camera, scene, renderer, timer, mesh; |
25 | 34 |
|
26 | | - let camera, scene, renderer, stats; |
| 35 | + const amount = 100; |
27 | 36 |
|
28 | | - let mesh; |
29 | | - const amount = parseInt( window.location.search.slice( 1 ) ) || 10; |
30 | | - const count = Math.pow( amount, 3 ); |
| 37 | + const count = Math.pow( amount, 2 ); |
31 | 38 | const dummy = new THREE.Object3D(); |
32 | 39 |
|
| 40 | + const seeds = []; |
| 41 | + const baseColors = []; |
| 42 | + |
| 43 | + const color = new THREE.Color(); |
| 44 | + const colors = [ new THREE.Color( 0x00ffff ), new THREE.Color( 0xffff00 ), new THREE.Color( 0xff00ff ) ]; |
| 45 | + const animation = { t: 0 }; |
| 46 | + let currentColorIndex = 0; |
| 47 | + let nextColorIndex = 1; |
| 48 | + |
| 49 | + const maxDistance = 75; |
| 50 | + const cameraTarget = new THREE.Vector3(); |
| 51 | + |
33 | 52 | init(); |
34 | 53 |
|
35 | 54 | function init() { |
36 | 55 |
|
| 56 | + renderer = new THREE.WebGLRenderer( { antialias: true } ); |
| 57 | + renderer.setPixelRatio( window.devicePixelRatio ); |
| 58 | + renderer.setSize( window.innerWidth, window.innerHeight ); |
| 59 | + renderer.toneMapping = THREE.NeutralToneMapping; |
| 60 | + renderer.setAnimationLoop( animate ); |
| 61 | + document.body.appendChild( renderer.domElement ); |
| 62 | + |
37 | 63 | camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 ); |
38 | | - camera.position.set( amount * 0.9, amount * 0.9, amount * 0.9 ); |
| 64 | + camera.position.set( 10, 10, 10 ); |
39 | 65 | camera.lookAt( 0, 0, 0 ); |
40 | 66 |
|
| 67 | + const pmremGenerator = new THREE.PMREMGenerator( renderer ); |
| 68 | + |
41 | 69 | scene = new THREE.Scene(); |
| 70 | + scene.background = new THREE.Color( 0xadd8e6 ); |
| 71 | + scene.environment = pmremGenerator.fromScene( new RoomEnvironment(), 0.04 ).texture; |
42 | 72 |
|
43 | | - const loader = new THREE.BufferGeometryLoader(); |
44 | | - loader.load( 'models/json/suzanne_buffergeometry.json', function ( geometry ) { |
| 73 | + timer = new Timer(); |
| 74 | + timer.connect( document ); |
45 | 75 |
|
46 | | - geometry.computeVertexNormals(); |
47 | | - geometry.scale( 0.5, 0.5, 0.5 ); |
| 76 | + const loader = new THREE.TextureLoader(); |
| 77 | + const texture = loader.load( 'textures/edge3.jpg' ); |
| 78 | + texture.colorSpace = THREE.SRGBColorSpace; |
48 | 79 |
|
49 | | - const material = new THREE.MeshNormalMaterial(); |
50 | | - // check overdraw |
51 | | - // let material = new THREE.MeshBasicMaterial( { color: 0xff0000, opacity: 0.1, transparent: true } ); |
| 80 | + const geometry = new THREE.BoxGeometry(); |
| 81 | + const material = new THREE.MeshStandardMaterial( { map: texture } ); |
52 | 82 |
|
53 | | - mesh = new THREE.InstancedMesh( geometry, material, count ); |
54 | | - mesh.instanceMatrix.setUsage( THREE.DynamicDrawUsage ); // will be updated every frame |
55 | | - scene.add( mesh ); |
| 83 | + mesh = new THREE.InstancedMesh( geometry, material, count ); |
| 84 | + mesh.instanceMatrix.setUsage( THREE.DynamicDrawUsage ); // will be updated every frame |
| 85 | + scene.add( mesh ); |
56 | 86 |
|
57 | | - // |
| 87 | + let i = 0; |
| 88 | + const offset = ( amount - 1 ) / 2; |
58 | 89 |
|
59 | | - const gui = new GUI(); |
60 | | - gui.add( mesh, 'count', 0, count ); |
| 90 | + for ( let x = 0; x < amount; x ++ ) { |
61 | 91 |
|
62 | | - } ); |
| 92 | + for ( let z = 0; z < amount; z ++ ) { |
63 | 93 |
|
64 | | - // |
| 94 | + dummy.position.set( offset - x, 0, offset - z ); |
| 95 | + dummy.scale.set( 1, 2, 1 ); |
65 | 96 |
|
66 | | - renderer = new THREE.WebGLRenderer( { antialias: true } ); |
67 | | - renderer.setPixelRatio( window.devicePixelRatio ); |
68 | | - renderer.setSize( window.innerWidth, window.innerHeight ); |
69 | | - renderer.setAnimationLoop( animate ); |
70 | | - document.body.appendChild( renderer.domElement ); |
| 97 | + dummy.updateMatrix(); |
71 | 98 |
|
72 | | - // |
| 99 | + color.setHSL( 1, 0.5 + ( Math.random() * 0.5 ), 0.5 + ( Math.random() * 0.5 ) ); |
| 100 | + baseColors.push( color.getHex() ); |
| 101 | + |
| 102 | + mesh.setMatrixAt( i, dummy.matrix ); |
| 103 | + mesh.setColorAt( i, color.multiply( colors[ 0 ] ) ); |
| 104 | + |
| 105 | + i ++; |
| 106 | + |
| 107 | + seeds.push( Math.random() ); |
73 | 108 |
|
74 | | - stats = new Stats(); |
75 | | - document.body.appendChild( stats.dom ); |
| 109 | + } |
| 110 | + |
| 111 | + } |
76 | 112 |
|
77 | 113 | // |
78 | 114 |
|
79 | 115 | window.addEventListener( 'resize', onWindowResize ); |
80 | 116 |
|
| 117 | + setInterval( startTween, 3000 ); |
| 118 | + |
| 119 | + } |
| 120 | + |
| 121 | + function startTween() { |
| 122 | + |
| 123 | + // tween for animating color transition |
| 124 | + |
| 125 | + new TWEEN.Tween( animation ) |
| 126 | + .to( { |
| 127 | + t: 1 |
| 128 | + }, 2000 ) |
| 129 | + .easing( TWEEN.Easing.Sinusoidal.In ) |
| 130 | + .onComplete( () => { |
| 131 | + |
| 132 | + animation.t = 0; |
| 133 | + |
| 134 | + currentColorIndex = nextColorIndex; |
| 135 | + nextColorIndex ++; |
| 136 | + |
| 137 | + if ( nextColorIndex >= colors.length ) nextColorIndex = 0; |
| 138 | + |
| 139 | + } ) |
| 140 | + .start(); |
| 141 | + |
81 | 142 | } |
82 | 143 |
|
83 | 144 | function onWindowResize() { |
|
93 | 154 |
|
94 | 155 | function animate() { |
95 | 156 |
|
96 | | - render(); |
| 157 | + timer.update(); |
97 | 158 |
|
98 | | - stats.update(); |
| 159 | + const time = timer.getElapsed(); |
99 | 160 |
|
100 | | - } |
| 161 | + TWEEN.update(); |
| 162 | + |
| 163 | + // animate camera |
| 164 | + |
| 165 | + camera.position.x = Math.sin( time / 4 ) * 10; |
| 166 | + camera.position.z = Math.cos( time / 4 ) * 10; |
| 167 | + camera.position.y = 8 + Math.cos( time / 2 ) * 2; |
| 168 | + |
| 169 | + cameraTarget.x = Math.sin( time / 4 ) * - 8; |
| 170 | + cameraTarget.z = Math.cos( time / 2 ) * - 8; |
| 171 | + |
| 172 | + camera.lookAt( cameraTarget ); |
101 | 173 |
|
102 | | - function render() { |
| 174 | + camera.up.x = Math.sin( time / 400 ); |
103 | 175 |
|
104 | | - if ( mesh ) { |
| 176 | + // animate instance positions and colors |
105 | 177 |
|
106 | | - const time = Date.now() * 0.001; |
| 178 | + for ( let i = 0; i < mesh.count; i ++ ) { |
107 | 179 |
|
108 | | - mesh.rotation.x = Math.sin( time / 4 ); |
109 | | - mesh.rotation.y = Math.sin( time / 2 ); |
| 180 | + mesh.getMatrixAt( i, dummy.matrix ); |
| 181 | + dummy.matrix.decompose( dummy.position, dummy.quaternion, dummy.scale ); |
110 | 182 |
|
111 | | - let i = 0; |
112 | | - const offset = ( amount - 1 ) / 2; |
| 183 | + dummy.position.y = Math.abs( Math.sin( ( time + seeds[ i ] ) * 2 + seeds[ i ] ) ); |
113 | 184 |
|
114 | | - for ( let x = 0; x < amount; x ++ ) { |
| 185 | + dummy.updateMatrix(); |
115 | 186 |
|
116 | | - for ( let y = 0; y < amount; y ++ ) { |
| 187 | + mesh.setMatrixAt( i, dummy.matrix ); |
117 | 188 |
|
118 | | - for ( let z = 0; z < amount; z ++ ) { |
| 189 | + // colors |
119 | 190 |
|
120 | | - dummy.position.set( offset - x, offset - y, offset - z ); |
121 | | - dummy.rotation.y = ( Math.sin( x / 4 + time ) + Math.sin( y / 4 + time ) + Math.sin( z / 4 + time ) ); |
122 | | - dummy.rotation.z = dummy.rotation.y * 2; |
| 191 | + if ( animation.t > 0 ) { |
123 | 192 |
|
124 | | - dummy.updateMatrix(); |
| 193 | + const currentColor = colors[ currentColorIndex ]; |
| 194 | + const nextColor = colors[ nextColorIndex ]; |
| 195 | + |
| 196 | + const f = dummy.position.length() / maxDistance; |
125 | 197 |
|
126 | | - mesh.setMatrixAt( i ++, dummy.matrix ); |
| 198 | + if ( f <= animation.t ) { |
127 | 199 |
|
128 | | - } |
| 200 | + color.set( baseColors[ i ] ).multiply( nextColor ); |
| 201 | + |
| 202 | + } else { |
| 203 | + |
| 204 | + color.set( baseColors[ i ] ).multiply( currentColor ); |
129 | 205 |
|
130 | 206 | } |
131 | 207 |
|
132 | | - } |
| 208 | + mesh.setColorAt( i, color ); |
133 | 209 |
|
134 | | - mesh.instanceMatrix.needsUpdate = true; |
135 | | - mesh.computeBoundingSphere(); |
| 210 | + } |
136 | 211 |
|
137 | 212 | } |
138 | 213 |
|
| 214 | + mesh.instanceMatrix.needsUpdate = true; |
| 215 | + if ( animation.t > 0 ) mesh.instanceColor.needsUpdate = true; |
| 216 | + |
| 217 | + mesh.computeBoundingSphere(); |
| 218 | + |
139 | 219 | renderer.render( scene, camera ); |
140 | 220 |
|
141 | 221 | } |
|
0 commit comments