Skip to content

Instantly share code, notes, and snippets.

@chadglory
Created Mar 22, 2019
Embed
What would you like to do?
Interactive 3D Butterflies
<p id="help">Try your mouse ^^</p>
const nbButterflies = 100;
var conf, scene, camera, cameraCtrl, light, renderer;
var whw, whh;
var butterflies;
var bodyTexture, wingTexture1, wingTexture2, wingTexture3, bodyTexture4, wingTexture4;
var destination = new THREE.Vector3();
var mouse = new THREE.Vector2();
var mouseOver = false;
var mousePlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
var mousePosition = new THREE.Vector3();
var raycaster = new THREE.Raycaster();
function init() {
conf = {
attraction: 0.03,
velocityLimit: 1.2,
move: true,
followMouse: true,
shuffle: shuffle
};
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
cameraCtrl = new THREE.OrbitControls(camera);
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
initScene();
const gui = new dat.GUI();
gui.add(conf, 'move');
gui.add(conf, 'followMouse');
gui.add(conf, 'attraction', 0.01, 0.1);
gui.add(conf, 'velocityLimit', 0.1, 2);
gui.add(conf, 'shuffle');
gui.close();
onWindowResize();
window.addEventListener('resize', onWindowResize, false);
document.addEventListener('mousemove', onMouseMove, false);
// document.addEventListener('mouseover', function () { mouseOver = true; }, false);
document.addEventListener('mouseout', function () { mouseOver = false; }, false);
animate();
};
function initScene() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
camera.position.z = 75;
bodyTexture = new THREE.TextureLoader().load('https://klevron.github.io/codepen/butterflies/b1.png');
wingTexture1 = new THREE.TextureLoader().load('https://klevron.github.io/codepen/butterflies/b1w.png');
wingTexture2 = new THREE.TextureLoader().load('https://klevron.github.io/codepen/butterflies/b2w.png');
wingTexture3 = new THREE.TextureLoader().load('https://klevron.github.io/codepen/butterflies/b3w.png');
bodyTexture4 = new THREE.TextureLoader().load('https://klevron.github.io/codepen/butterflies/b4.png');
wingTexture4 = new THREE.TextureLoader().load('https://klevron.github.io/codepen/butterflies/b4w.png');
butterflies = [];
for (var i = 0; i < nbButterflies; i++) {
var b = new Butterfly();
butterflies.push(b);
scene.add(b.o3d);
}
shuffle();
}
function animate() {
requestAnimationFrame(animate);
TWEEN.update();
cameraCtrl.update();
if (conf.move) {
for (var i = 0; i < butterflies.length; i++) {
butterflies[i].move();
}
}
renderer.render(scene, camera);
};
function shuffle() {
for (var i = 0; i < butterflies.length; i++) {
butterflies[i].shuffle();
}
}
function Butterfly() {
this.minWingRotation = -Math.PI / 6;
this.maxWingRotation = Math.PI / 2 - 0.1;
this.wingRotation = 0;
this.velocity = new THREE.Vector3(rnd(1, true), rnd(1, true), rnd(1, true));
this.destination = destination;
var confs = [
{ bodyTexture: bodyTexture, bodyW: 10, bodyH: 15, wingTexture: wingTexture1, wingW: 10, wingH: 15, wingX: 5.5 },
{ bodyTexture: bodyTexture, bodyW: 6, bodyH: 9, wingTexture: wingTexture2, wingW: 15, wingH: 20, wingX: 7.5 },
{ bodyTexture: bodyTexture, bodyW: 8, bodyH: 12, wingTexture: wingTexture3, wingW: 10, wingH: 15, wingX: 5.5 },
{ bodyTexture: bodyTexture4, bodyW: 6, bodyH: 10, bodyY: 2, wingTexture: wingTexture4, wingW: 15, wingH: 20, wingX: 8 },
];
this.init(confs[Math.floor(rnd(4))]);
}
Butterfly.prototype.init = function (bconf) {
var geometry = new THREE.PlaneGeometry(bconf.wingW, bconf.wingH);
var material = new THREE.MeshBasicMaterial({ transparent: true, map: bconf.wingTexture, side: THREE.DoubleSide, depthTest: false });
var lwmesh = new THREE.Mesh(geometry, material);
lwmesh.position.x = -bconf.wingX;
this.lwing = new THREE.Object3D();
this.lwing.add(lwmesh);
var rwmesh = new THREE.Mesh(geometry, material);
rwmesh.rotation.y = Math.PI;
rwmesh.position.x = bconf.wingX;
this.rwing = new THREE.Object3D();
this.rwing.add(rwmesh);
geometry = new THREE.PlaneGeometry(bconf.bodyW, bconf.bodyH);
material = new THREE.MeshBasicMaterial({ transparent: true, map: bconf.bodyTexture, side: THREE.DoubleSide, depthTest: false });
this.body = new THREE.Mesh(geometry, material);
if (bconf.bodyY) this.body.position.y = bconf.bodyY;
// this.body.position.z = -0.1;
this.group = new THREE.Object3D();
this.group.add(this.body);
this.group.add(this.lwing);
this.group.add(this.rwing);
this.group.rotation.x = Math.PI / 2;
this.group.rotation.y = Math.PI;
this.setWingRotation(this.wingRotation);
this.initTween();
this.o3d = new THREE.Object3D();
this.o3d.add(this.group);
};
Butterfly.prototype.initTween = function () {
var duration = limit(conf.velocityLimit - this.velocity.length(), 0.1, 1.5) * 1000;
this.wingRotation = this.minWingRotation;
this.tweenWingRotation = new TWEEN.Tween(this)
.to({ wingRotation: this.maxWingRotation }, duration)
.repeat(1)
.yoyo(true)
// .easing(TWEEN.Easing.Cubic.InOut)
.onComplete(function(object) {
object.initTween();
})
.start();
};
Butterfly.prototype.move = function () {
var destination;
if (mouseOver && conf.followMouse) {
destination = mousePosition;
} else {
destination = this.destination;
}
var dv = destination.clone().sub(this.o3d.position).normalize();
this.velocity.x += conf.attraction * dv.x;
this.velocity.y += conf.attraction * dv.y;
this.velocity.z += conf.attraction * dv.z;
this.limitVelocity();
// update position & rotation
this.setWingRotation(this.wingRotation);
this.o3d.lookAt(this.o3d.position.clone().add(this.velocity));
this.o3d.position.add(this.velocity);
};
Butterfly.prototype.limitVelocity = function (y) {
this.velocity.x = limit(this.velocity.x, -conf.velocityLimit, conf.velocityLimit);
this.velocity.y = limit(this.velocity.y, -conf.velocityLimit, conf.velocityLimit);
this.velocity.z = limit(this.velocity.z, -conf.velocityLimit, conf.velocityLimit);
};
Butterfly.prototype.setWingRotation = function (y) {
this.lwing.rotation.y = y;
this.rwing.rotation.y = -y;
};
Butterfly.prototype.shuffle = function () {
this.velocity = new THREE.Vector3(rnd(1, true), rnd(1, true), rnd(1, true));
var p = new THREE.Vector3(rnd(1, true), rnd(1, true), rnd(1, true)).normalize().multiplyScalar(100);
this.o3d.position.set(p.x, p.y, p.z);
var scale = rnd(0.4) + 0.1;
this.o3d.scale.set(scale, scale, scale);
}
function limit(number, min, max) {
return Math.min(Math.max(number, min), max);
}
function rnd(max, negative) {
return negative ? Math.random() * 2 * max - max : Math.random() * max;
}
function onWindowResize() {
whw = window.innerWidth / 2;
whh = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onMouseMove(event) {
// if (cameraCtrl.getState()!=-1) return;
var v = new THREE.Vector3();
camera.getWorldDirection(v);
v.normalize();
// console.log(v);
mousePlane.normal = v;
mouseOver = true;
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
raycaster.ray.intersectPlane(mousePlane, mousePosition);
}
init();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/100/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.5/dat.gui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/16.7.0/Tween.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
canvas {
position: fixed;
width: 100%;
height: 100%;
z-index: -1;
}
#help {
font-family: Helvetica, Arial, sans-serif;
font-size: 10px;
width: 100%;
position:absolute;
color: #555;
text-align: center;
z-index: 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment