<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@v0.171.0/build/three.webgpu.js",
"three/webgpu": "https://cdn.jsdelivr.net/npm/three@v0.171.0/build/three.webgpu.js",
"three/tsl": "https://cdn.jsdelivr.net/npm/three@v0.171.0/build/three.tsl.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@v0.171.0/examples/jsm/"
}
}
</script>
<div id="courses"><a href="https://niklever.com/courses" target="_blank">niklever.com/courses</a></div>
body {
padding: 0;
margin: 0;
}
#courses {
font: bold 30px "Arial";
position: fixed;
left: 20px;
top: 20px;
color: #ffffff;
text-decoration: none;
}
a:link {
color: white;
text-decoration: none;
}
a:hover {
color: #dddd33;
text-decoration: underline;
}
a:visited {
color: white;
text-decoration: none;
}
import * as THREE from "three";
import {
positionLocal,
normalLocal,
normalize,
modelWorldMatrix,
cameraProjectionMatrix,
cameraViewMatrix,
mix,
attributeArray,
clamp,
time,
sin,
cos,
mod,
floor,
mx_noise_float,
Fn,
uint,
float,
cross,
If,
Continue,
distance,
length,
attribute,
max,
exp,
mat3,
vec3,
select,
Loop,
instanceIndex,
uniform
} from "three/tsl";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
class FlockGeometry extends THREE.BufferGeometry {
constructor(geo) {
super();
const geometry = geo.toNonIndexed();
const srcPosAttr = geometry.getAttribute( "position" );
const srcNormAttr = geometry.getAttribute( "normal" );
const count = srcPosAttr.count;
const total = count * BOIDS;
const posAttr = new THREE.BufferAttribute(new Float32Array(total * 3), 3);
const normAttr = new THREE.BufferAttribute(new Float32Array(total * 3), 3);
const instanceIDAttr = new THREE.BufferAttribute(new Uint32Array(total), 1);
this.setAttribute("instanceID", instanceIDAttr);
this.setAttribute("position", posAttr);
this.setAttribute("normal", normAttr);
for (let b = 0; b < BOIDS; b++) {
let offset = b * count * 3;
for (let i = 0; i < count * 3; i++) {
posAttr.array[offset + i] = srcPosAttr.array[i];
normAttr.array[offset + i] = srcNormAttr.array[i];
}
offset = b * count;
for (let i = 0; i < count; i++) {
instanceIDAttr.array[offset + i] = b;
}
}
}
}
let container;
let camera,
scene,
renderer,
options,
material,
assetPath,
clock,
boid,
flock,
deltaTime,
computeVelocity,
computePosition,
computeTest;
const BOIDS = 9;
init();
function init() {
container = document.createElement("div");
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(
40,
window.innerWidth / window.innerHeight,
1,
100
);
camera.position.set(0.0, 1, 2);
//
scene = new THREE.Scene();
scene.background = new THREE.Color(0x444488);
//
renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(render);
container.appendChild(renderer.domElement);
//
//content
const ambient = new THREE.HemisphereLight(0xaaaaaa, 0x333333);
const light = new THREE.DirectionalLight(0xffffff, 3);
light.position.set(3, 3, 1);
scene.add(ambient);
scene.add(light);
clock = new THREE.Clock();
const controls = new OrbitControls(camera, renderer.domElement);
assetPath = "https://assets.codepen.io/2666677/";
loadGLB("boid");
window.addEventListener("resize", onWindowResize);
}
function loadGLB(name) {
const loader = new GLTFLoader().setPath(assetPath);
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath(
"https://cdn.jsdelivr.net/npm/three@v0.170.0/examples/jsm/libs/draco/gltf/"
);
loader.setDRACOLoader(dracoLoader);
loader.load(`${name}.glb`, (gltf) => {
boid = gltf.scene.children[0];
const scale = 0.2;
boid.geometry.scale( scale, scale, scale );
tsl();
//scene.add(boid);
});
}
function initStorage() {
const positionArray = new Float32Array((BOIDS+1) * 3);
const cellSize = 0.5;
for (let i = 0; i < BOIDS; i++) {
const offset = i * 3;
const row = (i % 3) - 1;
const col = (~~(i / 3)) - 1;
positionArray[offset + 0] = col * cellSize;
positionArray[offset + 1] = row * cellSize;
}
const positionStorage = attributeArray(positionArray, "vec3").label(
"positionStorage"
);
// The Pixel Buffer Object (PBO) is required to get the GPU computed data in the WebGL2 fallback.
positionStorage.setPBO(true);
return positionStorage;
}
function tsl() {
const positionStorage = initStorage();
const flockVertexTSL = Fn(() => {
const instanceID = attribute("instanceID");
const finalVert = modelWorldMatrix.mul(positionLocal).add(positionStorage.element(instanceID)).toVar();
return cameraProjectionMatrix.mul(cameraViewMatrix).mul(finalVert);
});
const geometry = new FlockGeometry(boid.geometry);
const material = new THREE.MeshStandardNodeMaterial();
const radius = uniform( float(0.7) );
const delta = uniform( float() );
computePosition = Fn( () => {
const PI2 = float( 6.2832 );
const theta = PI2.div( BOIDS ).toVar();
const posx = cos(time.add(theta.mul(instanceIndex))).mul( radius ).toVar();
const posy = sin(time.add(theta.mul(instanceIndex))).mul( radius ).toVar();
const cellSize = float(0.5).toVar();
const row = float(instanceIndex).mod(3.0).sub(1.0);
const col = floor(float(instanceIndex).div(3.0)).sub(1.0);
const position = positionStorage.element( instanceIndex );
const v1 = vec3( posx, posy, 0).toVar();
const v2 = vec3( col.mul(cellSize), row.mul(cellSize), 0).toVar();
position.assign( mix( v1, v2, delta ) );
})().compute(BOIDS);
flock = new THREE.Mesh(geometry, material);
scene.add(flock);
material.vertexNode = flockVertexTSL();
const gui = new GUI();
gui.add( radius, 'value', 0.2, 2).name('radius');
gui.add(delta, 'value', 0, 1).name('delta');
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
//
function render() {
if (computePosition) renderer.compute(computePosition);
renderer.render(scene, camera);
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.