• 01 - Critter Models 02
  • 02 - VantaJS Shaders
  • 03 - App Bundle 02
  • 04 - Cathedrals
  • 05 - App Bundle 01
  • 06 - Retroscopic
  • 07 - Games
  • 08 - Voronoi
  • 09 - Terrain
  • 10 - Critter Models 01
  • 11 - Chromatophores
  • 12 - Conformal Maps
  • 13 - Echo Logix
  • 14 - Music
  • 15 - Colour Theory
MIKE'S NOTEBOOK
Notes
Number:
Project:
01Critter Models 02
└ Locust└ Butterfly└ Cockroach└ Spider└ Scorpion└ Dragon└ Brazilian Treehopper└ Tardigrade└ Diatoms└ Bumblebee└ Wasp└ Hammerhead Worm└ Giraffe Weevil└ Trilobite Beetle
02VantaJS Shaders
└ Clouds└ Waves└ Cells└ Rings└ Halo└ Globe└ Net└ Dots└ Birds└ Trunk└ Topology└ About VantaJS
03App Bundle 02
└ Gutindex└ Insect Builder└ OpenLayers
04Cathedrals
└ Lattice└ Lost└ Lucent└ Lumen└ Glass└ Disco└ Hyperion└ Latent
05App Bundle 01
└ Robohash└ PrismJS└ Horoscope└ Binary Jazz└ Morse Code└ URL Extractor└ Entity Escaper└ Phonetic Alphabet└ Toki Pona└ Semaphore└ Cut Up Machine└ Text Mixer└ Currency Freaks└ Host IO└ CrUX API└ Logo Dev└ Web Speech API└ GEO DB
06Retroscopic
└ Galaxy└ Ocean└ Slime└ 24-Cell└ Unicorn└ Polyp└ Oneiric 1└ Oneiric 2└ Node└ Torus Knot└ GLSL Grid└ Liquid Crystal└ Silky└ Butterfly└ Ocean 2└ Icosahedron└ Luma└ Swarm└ Broken Solid└ Hypersphere└ Constraints└ Psycho Killer
01 / 03

GIRAFFE WEEVIL - THREEJS PROCEDURAL ANIMATION SOURCE CODE

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ThreeJS: Giraffe-Necked Weevil</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  html, body { background: #000; overflow: hidden; height: 100%; }
  #bgCanvas { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 1; }
  #animationCanvas { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 2; cursor: grab; touch-action: none; }
  #animationCanvas:active { cursor: grabbing; }
</style>
</head>
<body>
<canvas id="bgCanvas"></canvas>
<canvas id="animationCanvas"></canvas>

<script>
const bgCanvas = document.getElementById('bgCanvas');
const bgCtx = bgCanvas.getContext('2d');
function initBackground(){
  bgCanvas.width = innerWidth; bgCanvas.height = innerHeight;
  bgCtx.fillStyle = '#000';
  bgCtx.fillRect(0, 0, bgCanvas.width, bgCanvas.height);
}

const renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('animationCanvas'), antialias:true, alpha:true });
let scene, camera, weevil;
const legParts = [];
const antennaParts = [];
let neck = null;      // { lower, upper, head } articulated groups
let elytra = null;    // for the occasional wing-case flick
let abdomenMesh = null;

function updateSize(){
  renderer.setSize(innerWidth, innerHeight);
  renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
}

const cam = { dist: 4.0, yaw: 0.55, pitch: 0.22, targetYaw: 0.55, targetPitch: 0.22, targetDist: 4.0, autoYaw: 0 };
function updateCamera(){
  const w = innerWidth;
  if (w < 480){ cam.targetDist = Math.min(Math.max(cam.targetDist, 8), 13); camera.fov = 58; }
  else if (w < 768){ camera.fov = 52; }
  else { camera.fov = 46; }
  camera.updateProjectionMatrix();
}
function applyCamera(){
  cam.yaw   += (cam.targetYaw   - cam.yaw)   * 0.08;
  cam.pitch += (cam.targetPitch - cam.pitch) * 0.08;
  cam.dist  += (cam.targetDist  - cam.dist)  * 0.08;
  const total = cam.yaw + cam.autoYaw;
  const p = Math.max(-1.35, Math.min(1.35, cam.pitch));
  camera.position.set(
    Math.sin(total)*Math.cos(p)*cam.dist,
    Math.sin(p)*cam.dist + 0.35,
    Math.cos(total)*Math.cos(p)*cam.dist
  );
  camera.lookAt(0, 0.15, 0.0);
}

function matte(color, opts={}){
  return new THREE.MeshStandardMaterial(Object.assign({
    color, roughness:0.55, metalness:0.12, side:THREE.DoubleSide
  }, opts));
}

function fuzz(geo, amp, freq){
  const pos = geo.attributes.position, v = new THREE.Vector3();
  for (let i=0;i<pos.count;i++){
    v.fromBufferAttribute(pos, i);
    const n = (Math.sin(v.x*freq)*Math.cos(v.y*freq*1.3) + Math.sin(v.z*freq*1.7))*0.5;
    const d = 1 + amp*n + amp*0.4*(Math.random()-0.5);
    v.multiplyScalar(d);
    pos.setXYZ(i, v.x, v.y, v.z);
  }
  pos.needsUpdate = true;
  geo.computeVertexNormals();
}

function segmentBetween(p0, p1, r0, r1, material, radialSeg=10){
  const dir = new THREE.Vector3().subVectors(p1, p0);
  const len = dir.length();
  const geo = new THREE.CylinderGeometry(r1, r0, len, radialSeg, 1, false);
  const mesh = new THREE.Mesh(geo, material);
  mesh.position.copy(p0).add(p1).multiplyScalar(0.5);
  mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0), dir.clone().normalize());
  return mesh;
}

// ---- Giraffe-necked weevil (Trachelophorus giraffa) ----
// Madagascar's crane-necked beetle: glossy jet-black body and an absurdly
// long, hinged neck, with brilliant scarlet elytra (wing cases) over the
// abdomen. The neck articulates like a lever arm - it nods and cranes,
// which is exactly what the animation leans into.

const inkMat    = matte(0x0a0a0c, { roughness:0.3,  metalness:0.35 });  // polished black chitin
const inkMat2   = matte(0x16161a, { roughness:0.38, metalness:0.3  });
const redMat    = matte(0xc41e10, { roughness:0.25, metalness:0.2  });  // scarlet elytra
const redDark   = matte(0x8f130a, { roughness:0.3,  metalness:0.18 });

function makeLeg(side, cfg){
  const s = side;
  const root = new THREE.Group();
  root.position.set(s*cfg.x, cfg.y, cfg.z);
  root.rotation.z = s*cfg.out;
  root.rotation.x = cfg.back || 0;

  const coxaEnd = new THREE.Vector3(s*0.02, -0.05, 0.005);
  root.add(segmentBetween(new THREE.Vector3(0,0,0), coxaEnd, 0.032, 0.022, inkMat, 8));

  const swing = new THREE.Group();
  swing.position.copy(coxaEnd);
  root.add(swing);

  const A = new THREE.Vector3(s*0.05, -cfg.len1, -0.01);
  swing.add(segmentBetween(new THREE.Vector3(0,0,0), A, 0.022, 0.014, inkMat2, 8));
  const kneeJoint = new THREE.Mesh(new THREE.SphereGeometry(0.015, 8, 6), inkMat);
  kneeJoint.position.copy(A);
  swing.add(kneeJoint);

  const mid = new THREE.Group();
  mid.position.copy(A);
  mid.rotation.x = cfg.baseMid;
  swing.add(mid);

  const B = new THREE.Vector3(s*0.035, -cfg.len2, 0.02);
  mid.add(segmentBetween(new THREE.Vector3(0,0,0), B, 0.013, 0.007, inkMat, 8));

  const tarsus = new THREE.Group();
  tarsus.position.copy(B);
  tarsus.rotation.x = -0.25;
  mid.add(tarsus);

  let prev = new THREE.Vector3(0,0,0);
  for (let i=0;i<3;i++){
    const segLen = cfg.len3 * (i===0 ? 0.45 : 0.28);
    const next = new THREE.Vector3(s*0.004, -segLen, 0.008*i);
    tarsus.add(segmentBetween(prev, next, 0.007 - i*0.0015, 0.0055 - i*0.0015, i%2 ? inkMat : inkMat2, 6));
    prev = next.clone();
  }
  for (let c=-1; c<=1; c+=2){
    const dir = new THREE.Vector3(c*0.3*s, -1, 0.1).normalize();
    tarsus.add(segmentBetween(prev, prev.clone().add(dir.multiplyScalar(0.025)), 0.004, 0.001, inkMat, 4));
  }

  legParts.push({ swing, mid, phase:cfg.phase, swingAmp:cfg.swingAmp, baseMid:cfg.baseMid });
  return root;
}

function makeAntenna(side){
  // Short elbowed antennae near the eyes, typical of weevils
  const grp = new THREE.Group();
  grp.position.set(side*0.035, 0.01, 0.06);
  const A = new THREE.Vector3(side*0.05, 0.015, 0.05);
  grp.add(segmentBetween(new THREE.Vector3(0,0,0), A, 0.008, 0.006, inkMat, 6));
  const flagellum = new THREE.Group();
  flagellum.position.copy(A);
  grp.add(flagellum);
  const B = new THREE.Vector3(side*0.05, -0.03, 0.055);
  flagellum.add(segmentBetween(new THREE.Vector3(0,0,0), B, 0.006, 0.004, inkMat, 5));
  const club = new THREE.Mesh(new THREE.SphereGeometry(0.011, 6, 6), inkMat2);
  club.position.copy(B);
  flagellum.add(club);
  antennaParts.push({ flagellum, phase: side*0.7 });
  return grp;
}

function createWeevil(){
  const g = new THREE.Group();

  // --- Abdomen with scarlet elytra ---
  const abdomen = new THREE.Group();
  abdomen.position.set(0, 0.02, -0.52);
  g.add(abdomen);

  const belly = new THREE.Mesh(new THREE.SphereGeometry(1, 24, 20), inkMat2);
  belly.scale.set(0.45, 0.38, 0.60);
  abdomen.add(belly);
  abdomenMesh = belly;

  // Wing cases: a scarlet dome raised over the black underside, with the
  // rear tips pinched. Hinged at the front so it can flick slightly.
  elytra = new THREE.Group();
  elytra.position.set(0, 0.06, 0.28);   // hinge at the front edge
  abdomen.add(elytra);
  const caseGeo = new THREE.SphereGeometry(1, 26, 20);
  fuzz(caseGeo, 0.008, 9);
  const cases = new THREE.Mesh(caseGeo, redMat);
  cases.scale.set(0.47, 0.34, 0.62);
  cases.position.set(0, 0.02, -0.28);
  elytra.add(cases);

  // Seam where the two wing cases meet: a thin black blade down the midline
  const seam = new THREE.Mesh(new THREE.SphereGeometry(1, 8, 12), inkMat);
  seam.scale.set(0.008, 0.345, 0.625);
  seam.position.set(0, 0.02, -0.28);
  elytra.add(seam);

  // Punctation: rows of tiny darker-red pits along each wing case
  for (let row=-3; row<=3; row++){
    if (row===0) continue;
    for (let k=0;k<9;k++){
      const fz = -0.62 + (k/8)*0.58;
      const fx = row*0.11;
      // project roughly onto the ellipsoid surface
      const nx = fx/0.47, nz = (fz+0.28)/0.62;
      const nySq = 1 - nx*nx - nz*nz;
      if (nySq < 0.05) continue;
      const fy = Math.sqrt(nySq)*0.34;
      const pit = new THREE.Mesh(new THREE.SphereGeometry(0.011, 5, 5), redDark);
      pit.position.set(fx, 0.02 + fy, fz);
      elytra.add(pit);
    }
  }

  // --- Thorax ---
  const thorax = new THREE.Mesh(new THREE.SphereGeometry(1, 20, 16), inkMat);
  thorax.scale.set(0.22, 0.20, 0.30);
  thorax.position.set(0, 0.06, 0.12);
  g.add(thorax);

  // --- The neck: the whole point of this beetle ---
  // Two long hinged segments, thick at the base and tapering, ending in a
  // small head. Each hinge is a group so the crane action reads clearly.
  const lower = new THREE.Group();
  lower.position.set(0, 0.14, 0.32);
  lower.rotation.x = -0.55;             // base pose: raked up and forward
  g.add(lower);

  const L1 = new THREE.Vector3(0, 0.55, 0.10);
  lower.add(segmentBetween(new THREE.Vector3(0,0,0), L1, 0.075, 0.048, inkMat, 12));
  // collar flare at the base, like the pronotum extending into the neck
  const collar = new THREE.Mesh(new THREE.SphereGeometry(0.085, 12, 10), inkMat);
  collar.scale.set(1, 0.7, 1.1);
  lower.add(collar);
  const hinge1 = new THREE.Mesh(new THREE.SphereGeometry(0.05, 10, 8), inkMat2);
  hinge1.position.copy(L1);
  lower.add(hinge1);

  const upper = new THREE.Group();
  upper.position.copy(L1);
  upper.rotation.x = 0.85;              // fold forward at the elbow
  lower.add(upper);

  const L2 = new THREE.Vector3(0, 0.44, 0.06);
  upper.add(segmentBetween(new THREE.Vector3(0,0,0), L2, 0.042, 0.028, inkMat2, 10));
  const hinge2 = new THREE.Mesh(new THREE.SphereGeometry(0.032, 10, 8), inkMat);
  hinge2.position.copy(L2);
  upper.add(hinge2);

  // --- Head at the end of the crane ---
  const head = new THREE.Group();
  head.position.copy(L2);
  head.rotation.x = -0.35;              // level the head against the fold
  upper.add(head);

  const skull = new THREE.Mesh(new THREE.SphereGeometry(1, 16, 12), inkMat);
  skull.scale.set(0.065, 0.06, 0.095);
  skull.position.set(0, 0.01, 0.04);
  head.add(skull);

  // Bulging eyes on the sides
  [-1,1].forEach(sx=>{
    const eye = new THREE.Mesh(new THREE.SphereGeometry(0.026, 10, 8), matte(0x000000, { roughness:0.15, metalness:0.5 }));
    eye.position.set(sx*0.055, 0.015, 0.05);
    head.add(eye);
    const hi = new THREE.Mesh(new THREE.SphereGeometry(0.007, 6, 6), matte(0x8a8a90, { roughness:0.2 }));
    hi.position.set(sx*0.066, 0.028, 0.062);
    head.add(hi);
  });

  // Short weevil rostrum (snout) with tiny mandibles at the tip
  const rostrum = segmentBetween(new THREE.Vector3(0,0,0.10), new THREE.Vector3(0,-0.015,0.20), 0.022, 0.012, inkMat2, 8);
  head.add(rostrum);
  [-1,1].forEach(sx=>{
    const mand = new THREE.Mesh(new THREE.ConeGeometry(0.007, 0.02, 5), inkMat);
    mand.position.set(sx*0.008, -0.018, 0.21);
    mand.rotation.x = Math.PI/2 - 0.3;
    mand.rotation.z = sx*0.4;
    head.add(mand);
  });

  head.add(makeAntenna(-1));
  head.add(makeAntenna(1));

  neck = { lower, upper, head, baseLower:-0.55, baseUpper:0.85, baseHead:-0.35 };

  // --- Legs: six slender black legs ---
  const legDefs = [
    { x:0.18, y:-0.04, z:0.22,  out:0.65, back:-0.2, len1:0.24, len2:0.26, len3:0.13, swingAmp:0.35, baseMid:0.5 },
    { x:0.22, y:-0.06, z:-0.10, out:0.75, back:0.1,  len1:0.28, len2:0.30, len3:0.14, swingAmp:0.30, baseMid:0.45 },
    { x:0.26, y:-0.10, z:-0.48, out:0.70, back:0.45, len1:0.30, len2:0.34, len3:0.15, swingAmp:0.26, baseMid:0.45 }
  ];
  legDefs.forEach((d,p)=>{
    g.add(makeLeg( 1, Object.assign({}, d, { phase: p*2.1 })));
    g.add(makeLeg(-1, Object.assign({}, d, { phase: p*2.1 + Math.PI })));
  });

  g.scale.setScalar(1.5);
  g.position.y = -0.15;
  return g;
}

function init(){
  initBackground(); updateSize();

  scene = new THREE.Scene();
  scene.background = null;
  scene.fog = new THREE.FogExp2(0x000000, 0.026);

  camera = new THREE.PerspectiveCamera(46, innerWidth/innerHeight, 0.1, 1000);
  updateCamera();

  weevil = createWeevil();
  scene.add(weevil);

  scene.add(new THREE.HemisphereLight(0xffffff, 0x141020, 0.6));
  const key  = new THREE.DirectionalLight(0xffffff, 1.4); key.position.set(4,7,6); scene.add(key);
  const rim  = new THREE.PointLight(0x33ccff, 1.0, 60); rim.position.set(-6,3,-4); scene.add(rim);
  const rim2 = new THREE.PointLight(0xff7733, 0.9, 60); rim2.position.set(6,-2,-3); scene.add(rim2);
  const glint= new THREE.PointLight(0xffffff, 1.0, 40); glint.position.set(2,4,8); scene.add(glint);

  addPointerControls();
  animate();
}

// Behavior: alternates between a slow deliberate walk and pausing to crane
// and bob the neck - the signature display of this species. The head
// counter-rotates against both hinges so it stays roughly level, the way
// real animals stabilize their gaze.
let gaitSpeed = 0.5, gaitTarget = 0.5, nextChange = 3;
let elytraFlick = 0, nextFlick = 6;
function animate(){
  requestAnimationFrame(animate);
  const t = performance.now() * 0.001;

  if (t > nextChange){
    gaitTarget = Math.random() < 0.4 ? 0.06 : 0.45 + Math.random()*0.5;
    nextChange = t + 2.5 + Math.random()*4.0;
  }
  gaitSpeed += (gaitTarget - gaitSpeed) * 0.02;
  const walk = (animate.wt = (animate.wt||0) + gaitSpeed * 0.05);

  // Slow six-legged stepping
  legParts.forEach(L=>{
    const ph = walk + L.phase;
    L.swing.rotation.x = L.swingAmp * Math.sin(ph) * Math.min(1, gaitSpeed*2);
    const recover = Math.max(0, Math.cos(ph));
    const ease = recover*recover*(3 - 2*recover);
    L.mid.rotation.x = L.baseMid + 0.4 * ease * Math.min(1, gaitSpeed*2);
  });

  // Neck crane: bobbing grows when the weevil pauses (display behavior)
  if (neck){
    const display = Math.max(0, 1.0 - gaitSpeed*1.4);
    const slowBob = Math.sin(t*0.7);
    const nod     = Math.sin(t*1.9)*0.10*display;
    neck.lower.rotation.x = neck.baseLower + slowBob*0.10 + nod + Math.sin(walk*2)*0.02*gaitSpeed;
    neck.upper.rotation.x = neck.baseUpper - slowBob*0.14 - nod*1.4;
    // Head counter-rotates to stay level, plus a curious side-to-side scan
    neck.head.rotation.x  = neck.baseHead + slowBob*0.05 + nod*0.5;
    neck.head.rotation.y  = Math.sin(t*0.5)*0.35*display + Math.sin(t*1.3)*0.05;
    // The whole neck sways gently with the walk
    neck.lower.rotation.z = Math.sin(walk)*0.03*gaitSpeed + Math.sin(t*0.4)*0.02;
  }

  antennaParts.forEach(A=>{
    A.flagellum.rotation.x = Math.sin(t*3.1 + A.phase)*0.25;
    A.flagellum.rotation.z = Math.cos(t*2.4 + A.phase)*0.15;
  });

  // Occasional quick elytra flick - beetles resettle their wing cases
  if (t > nextFlick){ elytraFlick = 1; nextFlick = t + 7 + Math.random()*9; }
  elytraFlick = Math.max(0, elytraFlick - 0.03);
  if (elytra){
    const pop = Math.sin(Math.min(1, (1-elytraFlick))*Math.PI);
    elytra.rotation.x = -pop*0.12*(elytraFlick>0?1:0);
  }

  // Breathing pulse on the abdomen
  if (abdomenMesh){
    const b = 1 + Math.sin(t*1.1)*0.02;
    abdomenMesh.scale.set(0.45*b, 0.38*(2-b), 0.60);
  }

  // Body: gentle sway and settle with the gait, no floating bob
  const busy = Math.min(1, gaitSpeed*1.6);
  weevil.rotation.z = Math.sin(walk)*0.03*busy;
  weevil.rotation.x = Math.sin(walk*2)*0.012*busy + Math.sin(t*0.6)*0.008;
  weevil.position.y = -0.15 + Math.abs(Math.sin(walk))*0.015*busy;
  weevil.position.x = Math.sin(t*0.09)*0.15*gaitSpeed;

  if (!dragging) cam.autoYaw += 0.0012 + gaitSpeed*0.0004;
  applyCamera();

  renderer.render(scene, camera);
}

let dragging=false, lastX=0, lastY=0, pinchD=0;
function addPointerControls(){
  const c = renderer.domElement;
  c.addEventListener('pointerdown', e=>{ dragging=true; lastX=e.clientX; lastY=e.clientY; c.setPointerCapture(e.pointerId); });
  c.addEventListener('pointermove', e=>{
    if(!dragging) return;
    cam.targetYaw   += (e.clientX-lastX)*0.006;
    cam.targetPitch += (e.clientY-lastY)*0.006;
    lastX=e.clientX; lastY=e.clientY;
  });
  const stop=()=>dragging=false;
  c.addEventListener('pointerup', stop);
  c.addEventListener('pointercancel', stop);
  c.addEventListener('pointerleave', stop);
  c.addEventListener('wheel', e=>{ e.preventDefault(); cam.targetDist = Math.max(4, Math.min(15, cam.targetDist + e.deltaY*0.01)); }, {passive:false});
  c.addEventListener('touchmove', e=>{
    if(e.touches.length===2){
      const dx=e.touches[0].clientX-e.touches[1].clientX, dy=e.touches[0].clientY-e.touches[1].clientY;
      const d=Math.hypot(dx,dy);
      if(pinchD) cam.targetDist = Math.max(4, Math.min(15, cam.targetDist + (pinchD-d)*0.02));
      pinchD=d;
    }
  }, {passive:true});
  c.addEventListener('touchend', ()=>pinchD=0);
}

addEventListener('resize', ()=>{
  updateSize();
  initBackground();
  if(camera){ camera.aspect = innerWidth/innerHeight; updateCamera(); }
});
addEventListener('load', init);
</script>
</body>
</html>
MikeHiggins.me.uk LinkedIn GitHub Bluesky Contra Upwork
© 2026 Mike Higgins. All rights reserved.