Codels
2,4 k tris7,39 × 3,12 × 8,81 m

Utiliser dans le code

Remixer dans l’éditeur
Module ES autonome — ne dépend que de three. Appelez la fonction factory et ajoutez le groupe à votre scène.
/**
 * Helicopter — Codels 3D model library
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
 * Triangles: 2,360 · Size (W × H × D): 7.39 × 3.12 × 8.81 m
 *
 * Usage:
 *   import { createHelicopter } from './helicopter.js';
 *   
 *   const model = createHelicopter();
 *   model.position.set(0, 0, 0);
 *   scene.add(model);
 *   
 *   // Animations: idle
 *   const mixer = new THREE.AnimationMixer(model);
 *   mixer.clipAction(THREE.AnimationClip.findByName(model.animations, 'idle')).play();
 *   // in your render loop: mixer.update(clock.getDelta());
 */
import * as THREE from 'three';

const deg = THREE.MathUtils.degToRad;

// Extrude a 2D outline along Z (centered). bevel = [thickness, size, segments].
function extrude(outline, depth, holes = [], bevel = null) {
  const shape = new THREE.Shape(outline.map(([x, y]) => new THREE.Vector2(x, y)));
  for (const hole of holes) shape.holes.push(new THREE.Path(hole.map(([x, y]) => new THREE.Vector2(x, y))));
  const options = bevel
    ? { depth, bevelEnabled: true, bevelThickness: bevel[0], bevelSize: bevel[1], bevelSegments: bevel[2] }
    : { depth, bevelEnabled: false };
  return new THREE.ExtrudeGeometry(shape, options).translate(0, 0, -depth / 2);
}

// Tube along a smooth curve through the given points.
function tube(path, radius, tubularSegments, radialSegments, closed = false) {
  const curve = new THREE.CatmullRomCurve3(path.map(([x, y, z]) => new THREE.Vector3(x, y, z)), closed);
  return new THREE.TubeGeometry(curve, tubularSegments, radius, radialSegments, closed);
}

// Build animation clips from simple descriptions (Codels). Play them with THREE.AnimationMixer.
function codelsClips(THREE, entries) {
  var TAU = Math.PI * 2;
  var DEG = Math.PI / 180;
  var AX = { x: new THREE.Vector3(1, 0, 0), y: new THREE.Vector3(0, 1, 0), z: new THREE.Vector3(0, 0, 1) };
  var periodOf = function (a) { return a.type === 'spin' ? 360 / Math.abs(a.speed) : a.period; };
  var clips = [];

  // loops → 'idle'
  var loops = [];
  entries.forEach(function (e) {
    var l = e.anim.filter(function (a) { return a.type !== 'hinge'; });
    if (l.length) loops.push({ target: e.target, anim: l });
  });
  if (loops.length) {
    var periods = [];
    loops.forEach(function (e) { e.anim.forEach(function (a) { periods.push(periodOf(a)); }); });
    var longest = Math.max.apply(null, periods);
    var D = longest;
    var bestErr = Infinity;
    for (var m = 1; m <= 6 && bestErr > 0.01; m++) {
      var err = 0;
      periods.forEach(function (p) {
        var n = Math.max(1, Math.round((longest * m) / p));
        err = Math.max(err, Math.abs((longest * m) / n - p) / p);
      });
      if (err < bestErr - 1e-9) { bestErr = err; D = longest * m; }
    }
    var cycles = function (a) { return Math.max(1, Math.round(D / periodOf(a))); };
    var steps = 24;
    loops.forEach(function (e) { e.anim.forEach(function (a) { steps = Math.max(steps, cycles(a) * 24); }); });
    steps = Math.min(steps, 720);
    var tracks = [];
    loops.forEach(function (e) {
      var o = e.target;
      var q0 = o.quaternion.clone(), p0 = o.position.clone(), s0 = o.scale.clone();
      var q = new THREE.Quaternion(), turn = new THREE.Quaternion(), p = new THREE.Vector3(), s = new THREE.Vector3();
      var has = function (type) { return e.anim.some(function (a) { return a.type === type; }); };
      var turns = has('spin') || has('swing'), moves = has('bob'), grows = has('pulse');
      var times = [], qv = [], pv = [], sv = [];
      for (var k = 0; k <= steps; k++) {
        var t = (D * k) / steps;
        q.copy(q0); p.copy(p0); s.copy(s0);
        e.anim.forEach(function (a) {
          var n = cycles(a);
          var w = Math.sin(TAU * ((n * t) / D + (a.phase || 0)));
          if (a.type === 'spin') q.multiply(turn.setFromAxisAngle(AX[a.axis], (a.speed < 0 ? -TAU : TAU) * ((n * t) / D)));
          else if (a.type === 'swing') q.multiply(turn.setFromAxisAngle(AX[a.axis], a.angle * DEG * w));
          else if (a.type === 'bob') p.addScaledVector(AX[a.axis], a.distance * w);
          else if (a.type === 'pulse') s.multiplyScalar(1 + a.amount * w);
        });
        times.push(t);
        if (turns) qv.push(q.x, q.y, q.z, q.w);
        if (moves) pv.push(p.x, p.y, p.z);
        if (grows) sv.push(s.x, s.y, s.z);
      }
      if (turns) tracks.push(new THREE.QuaternionKeyframeTrack(o.uuid + '.quaternion', times, qv));
      if (moves) tracks.push(new THREE.VectorKeyframeTrack(o.uuid + '.position', times, pv));
      if (grows) tracks.push(new THREE.VectorKeyframeTrack(o.uuid + '.scale', times, sv));
    });
    clips.push(new THREE.AnimationClip('idle', D, tracks));
  }

  // hinge moves → one-shot clips ('open' …)
  var shots = {};
  entries.forEach(function (e) {
    e.anim.forEach(function (a) {
      if (a.type !== 'hinge') return;
      var name = a.clip || 'open';
      (shots[name] = shots[name] || []).push({ target: e.target, a: a });
    });
  });
  Object.keys(shots).forEach(function (name) {
    var tracks = [];
    var length = 0;
    shots[name].forEach(function (h) {
      var o = h.target, q0 = o.quaternion.clone(), q = new THREE.Quaternion(), turn = new THREE.Quaternion();
      var n = Math.max(4, Math.ceil(Math.abs(h.a.angle) / 10));
      var times = [], qv = [];
      for (var k = 0; k <= n; k++) {
        var f = k / n;
        var eased = f * f * (3 - 2 * f);
        q.copy(q0).multiply(turn.setFromAxisAngle(AX[h.a.axis], h.a.angle * DEG * eased));
        times.push(h.a.duration * f);
        qv.push(q.x, q.y, q.z, q.w);
      }
      length = Math.max(length, h.a.duration);
      tracks.push(new THREE.QuaternionKeyframeTrack(o.uuid + '.quaternion', times, qv));
    });
    clips.push(new THREE.AnimationClip(name, length, tracks));
  });
  return clips;
}

export function createHelicopter() {
  const root = new THREE.Group();
  root.name = 'Helicopter';

  // Materials
  const paintMat = new THREE.MeshStandardMaterial({ name: 'paint', color: 0xe8a93a, roughness: 0.45, metalness: 0.1, flatShading: true });
  const accentMat = new THREE.MeshStandardMaterial({ name: 'accent', color: 0x2f5d8c, roughness: 0.5, metalness: 0.1, flatShading: true });
  const glassMat = new THREE.MeshStandardMaterial({ name: 'glass', color: 0x26323f, roughness: 0.2, metalness: 0.3 });
  const trimMat = new THREE.MeshStandardMaterial({ name: 'trim', color: 0x2b2d32, roughness: 0.7, metalness: 0.2, flatShading: true });
  const beaconMat = new THREE.MeshStandardMaterial({ name: 'beacon', color: 0xff4040, roughness: 0.3, emissive: 0xff2a2a, emissiveIntensity: 1.2, flatShading: true });

  // Shared geometries
  const boxGeo = new THREE.BoxGeometry(0.05, 0.3, 0.32);
  const boxGeo2 = new THREE.BoxGeometry(0.26, 0.045, 3.55);
  const boxGeo3 = new THREE.BoxGeometry(0.265, 0.05, 0.3);
  const boxGeo4 = new THREE.BoxGeometry(0.04, 0.62, 0.13);
  const boxGeo5 = new THREE.BoxGeometry(0.045, 0.1, 0.135);

  // Parts
  const body = new THREE.Group();
  body.name = 'body';
  root.add(body);
  const cabin = new THREE.Mesh(new THREE.SphereGeometry(1, 14, 9), paintMat);
  cabin.name = 'cabin';
  cabin.position.set(0, 1.32, 0.35);
  cabin.scale.set(0.92, 0.95, 1.38);
  body.add(cabin);
  const windshield = new THREE.Mesh(new THREE.SphereGeometry(1.012, 14, 9, 0.7908, 1.56, 0.42, 1.12), glassMat);
  windshield.name = 'windshield';
  windshield.position.set(0, 1.32, 0.35);
  windshield.scale.set(0.92, 0.95, 1.38);
  body.add(windshield);
  const doorWindowL = new THREE.Mesh(new THREE.SphereGeometry(1.012, 14, 9, 2.7816, 0.5, 0.62, 0.62), glassMat);
  doorWindowL.name = 'doorWindowL';
  doorWindowL.position.set(0, 1.32, 0.35);
  doorWindowL.scale.set(0.92, 0.95, 1.38);
  body.add(doorWindowL);
  const doorWindowR = new THREE.Mesh(new THREE.SphereGeometry(1.012, 14, 9, -0.36, 0.5, 0.62, 0.62), glassMat);
  doorWindowR.name = 'doorWindowR';
  doorWindowR.position.set(0, 1.32, 0.35);
  doorWindowR.scale.set(0.92, 0.95, 1.38);
  body.add(doorWindowR);
  const bellyStripe = new THREE.Mesh(new THREE.SphereGeometry(1.01, 14, 9, 0, Math.PI * 2, 1.95, 0.32), accentMat);
  bellyStripe.name = 'bellyStripe';
  bellyStripe.position.set(0, 1.32, 0.35);
  bellyStripe.scale.set(0.92, 0.95, 1.38);
  body.add(bellyStripe);
  const engineCover = new THREE.Mesh(new THREE.CapsuleGeometry(0.42, 0.9, 3, 10), accentMat);
  engineCover.name = 'engineCover';
  engineCover.position.set(0, 2.2, -0.35);
  engineCover.rotation.set(deg(90), 0, 0);
  engineCover.scale.set(1, 1, 0.75);
  body.add(engineCover);
  const exhaustGrille = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.06, 0.5), trimMat);
  exhaustGrille.name = 'exhaustGrille';
  exhaustGrille.position.set(0, 2.52, -0.45);
  body.add(exhaustGrille);
  const mast = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.1, 0.4, 8), trimMat);
  mast.name = 'mast';
  mast.position.set(0, 2.62, 0);
  body.add(mast);
  const tailBoom = new THREE.Mesh(new THREE.CylinderGeometry(0.13, 0.32, 4.3, 10), paintMat);
  tailBoom.name = 'tailBoom';
  tailBoom.position.set(0, 1.57, -2.55);
  tailBoom.rotation.set(deg(-90), 0, 0);
  body.add(tailBoom);
  const tailFin = new THREE.Mesh(extrude([[-4.25, 0], [-4.55, 1.05], [-4.95, 1.12], [-4.92, -0.35], [-4.65, -0.42]], 0.08, [], [0.03, 0.03, 1]), accentMat);
  tailFin.name = 'tailFin';
  tailFin.position.set(0, 1.57, 0);
  tailFin.rotation.set(0, deg(-90), 0);
  body.add(tailFin);
  const stabilizer = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.06, 0.42), accentMat);
  stabilizer.name = 'stabilizer';
  stabilizer.position.set(0, 1.57, -3.85);
  body.add(stabilizer);
  const box13 = new THREE.Mesh(boxGeo, accentMat);
  box13.position.set(0.76, 1.64, -3.88);
  body.add(box13);
  const box14 = new THREE.Mesh(boxGeo, accentMat);
  box14.position.set(-0.76, 1.64, -3.88);
  body.add(box14);
  const beacon = new THREE.Mesh(new THREE.SphereGeometry(0.07, 8, 6), beaconMat);
  beacon.name = 'beacon';
  beacon.position.set(0, 2.74, -4.8);
  body.add(beacon);
  const bellyBeacon = new THREE.Mesh(new THREE.SphereGeometry(0.06, 8, 6), beaconMat);
  bellyBeacon.name = 'bellyBeacon';
  bellyBeacon.position.set(0, 0.44, 0.1);
  body.add(bellyBeacon);
  const skidL = new THREE.Mesh(tube([[0.92, 0.06, -1.25], [0.92, 0.06, 0.4], [0.92, 0.06, 1.25], [0.92, 0.2, 1.62]], 0.06, 10, 6), trimMat);
  skidL.name = 'skidL';
  body.add(skidL);
  const tube18 = new THREE.Mesh(tube([[0.92, 0.06, -0.3], [0.874, 0.4169, -0.3], [0.506, 0.6948, -0.3]], 0.045, 5, 5), trimMat);
  body.add(tube18);
  const tube19 = new THREE.Mesh(tube([[0.92, 0.06, 0.9], [0.874, 0.3917, 0.9], [0.506, 0.6528, 0.9]], 0.045, 5, 5), trimMat);
  body.add(tube19);
  const skidR = new THREE.Mesh(tube([[-0.92, 0.06, -1.25], [-0.92, 0.06, 0.4], [-0.92, 0.06, 1.25], [-0.92, 0.2, 1.62]], 0.06, 10, 6), trimMat);
  skidR.name = 'skidR';
  body.add(skidR);
  const tube21 = new THREE.Mesh(tube([[-0.92, 0.06, -0.3], [-0.874, 0.4169, -0.3], [-0.506, 0.6948, -0.3]], 0.045, 5, 5), trimMat);
  body.add(tube21);
  const tube22 = new THREE.Mesh(tube([[-0.92, 0.06, 0.9], [-0.874, 0.3917, 0.9], [-0.506, 0.6528, 0.9]], 0.045, 5, 5), trimMat);
  body.add(tube22);
  const mainRotor = new THREE.Group();
  mainRotor.name = 'mainRotor';
  mainRotor.position.set(0, 2.87, 0);
  root.add(mainRotor);
  const rotorHub = new THREE.Mesh(new THREE.CylinderGeometry(0.24, 0.24, 0.14, 10), trimMat);
  rotorHub.name = 'rotorHub';
  mainRotor.add(rotorHub);
  const hubCap = new THREE.Mesh(new THREE.ConeGeometry(0.12, 0.16, 8), accentMat);
  hubCap.name = 'hubCap';
  hubCap.position.set(0, 0.15, 0);
  mainRotor.add(hubCap);
  const blade1 = new THREE.Group();
  blade1.name = 'blade1';
  blade1.rotation.set(0, deg(80), 0);
  mainRotor.add(blade1);
  const box27 = new THREE.Mesh(boxGeo2, trimMat);
  box27.position.set(0, 0, 1.95);
  box27.rotation.set(0, 0, deg(6));
  blade1.add(box27);
  const box28 = new THREE.Mesh(boxGeo3, paintMat);
  box28.position.set(0, 0, 3.58);
  box28.rotation.set(0, 0, deg(6));
  blade1.add(box28);
  const blade2 = new THREE.Group();
  blade2.name = 'blade2';
  blade2.rotation.set(0, deg(170), 0);
  mainRotor.add(blade2);
  const box30 = new THREE.Mesh(boxGeo2, trimMat);
  box30.position.set(0, 0, 1.95);
  box30.rotation.set(0, 0, deg(6));
  blade2.add(box30);
  const box31 = new THREE.Mesh(boxGeo3, paintMat);
  box31.position.set(0, 0, 3.58);
  box31.rotation.set(0, 0, deg(6));
  blade2.add(box31);
  const blade3 = new THREE.Group();
  blade3.name = 'blade3';
  blade3.rotation.set(0, deg(260), 0);
  mainRotor.add(blade3);
  const box33 = new THREE.Mesh(boxGeo2, trimMat);
  box33.position.set(0, 0, 1.95);
  box33.rotation.set(0, 0, deg(6));
  blade3.add(box33);
  const box34 = new THREE.Mesh(boxGeo3, paintMat);
  box34.position.set(0, 0, 3.58);
  box34.rotation.set(0, 0, deg(6));
  blade3.add(box34);
  const blade4 = new THREE.Group();
  blade4.name = 'blade4';
  blade4.rotation.set(0, deg(350), 0);
  mainRotor.add(blade4);
  const box36 = new THREE.Mesh(boxGeo2, trimMat);
  box36.position.set(0, 0, 1.95);
  box36.rotation.set(0, 0, deg(6));
  blade4.add(box36);
  const box37 = new THREE.Mesh(boxGeo3, paintMat);
  box37.position.set(0, 0, 3.58);
  box37.rotation.set(0, 0, deg(6));
  blade4.add(box37);
  const tailRotor = new THREE.Group();
  tailRotor.name = 'tailRotor';
  tailRotor.position.set(0.14, 1.87, -4.72);
  root.add(tailRotor);
  const tailHub = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.08, 0.14, 8), trimMat);
  tailHub.name = 'tailHub';
  tailHub.rotation.set(0, 0, deg(90));
  tailRotor.add(tailHub);
  const tailBlade1 = new THREE.Group();
  tailBlade1.name = 'tailBlade1';
  tailBlade1.rotation.set(deg(30), 0, 0);
  tailRotor.add(tailBlade1);
  const box41 = new THREE.Mesh(boxGeo4, trimMat);
  box41.position.set(0, 0.36, 0);
  box41.rotation.set(0, deg(12), 0);
  tailBlade1.add(box41);
  const box42 = new THREE.Mesh(boxGeo5, paintMat);
  box42.position.set(0, 0.62, 0);
  box42.rotation.set(0, deg(12), 0);
  tailBlade1.add(box42);
  const tailBlade2 = new THREE.Group();
  tailBlade2.name = 'tailBlade2';
  tailBlade2.rotation.set(deg(210), 0, 0);
  tailRotor.add(tailBlade2);
  const box44 = new THREE.Mesh(boxGeo4, trimMat);
  box44.position.set(0, 0.36, 0);
  box44.rotation.set(0, deg(12), 0);
  tailBlade2.add(box44);
  const box45 = new THREE.Mesh(boxGeo5, paintMat);
  box45.position.set(0, 0.62, 0);
  box45.rotation.set(0, deg(12), 0);
  tailBlade2.add(box45);

  // Animations ('idle') — play them with THREE.AnimationMixer
  root.animations = codelsClips(THREE, [
    { target: mainRotor, anim: [{ type: 'spin', axis: 'y', speed: 900 }] },
    { target: tailRotor, anim: [{ type: 'spin', axis: 'x', speed: 1080 }] },
  ]);

  root.traverse((o) => {
    if (o.isMesh) {
      o.castShadow = true;
      o.receiveShadow = true;
    }
  });
  return root;
}

export default createHelicopter;

Notes et commentaires

—
Aucune note pour l’instant — soyez le premier.
5
0
4
0
3
0
2
0
1
0

Connectez-vous pour noter et commenter.

0 commentaire

Aucun commentaire pour l’instant. Dites comment vous avez utilisé ce modèle ou ce qui pourrait être amélioré.

Modèles similaires

Même catégorie ou étiquettes proches