Codels
1,4 B üçgen9,56 × 2,62 × 7,18 m
Bağımsız bir ES modülü — yalnızca three paketine bağımlıdır. Fabrika fonksiyonunu çağırıp dönen grubu sahnene ekle.
/**
 * Propeller Plane — Codels 3D model library
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
 * Triangles: 1,418 · Size (W × H × D): 9.56 × 2.62 × 7.18 m
 *
 * Usage:
 *   import { createPropellerPlane } from './propeller-plane.js';
 *   
 *   const model = createPropellerPlane();
 *   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;

// Revolve a 2D profile [[radius, y], ...] around the Y axis.
function lathe(points, segments, phiStart = 0, phiLength = Math.PI * 2) {
  return new THREE.LatheGeometry(points.map(([x, y]) => new THREE.Vector2(x, y)), segments, phiStart, phiLength);
}

// 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 createPropellerPlane() {
  const root = new THREE.Group();
  root.name = 'PropellerPlane';

  // Materials
  const paintMat = new THREE.MeshStandardMaterial({ name: 'paint', color: 0xf1ece0, roughness: 0.5, metalness: 0.05, flatShading: true });
  const accentMat = new THREE.MeshStandardMaterial({ name: 'accent', color: 0xd8463a, roughness: 0.45, metalness: 0.05, flatShading: true });
  const glassMat = new THREE.MeshStandardMaterial({ name: 'glass', color: 0x2a3746, roughness: 0.2, metalness: 0.3 });
  const trimMat = new THREE.MeshStandardMaterial({ name: 'trim', color: 0x2b2c30, roughness: 0.8, flatShading: true });
  const metalMat = new THREE.MeshStandardMaterial({ name: 'metal', color: 0xb9bec6, roughness: 0.35, metalness: 0.6, flatShading: true });

  // Shared geometries
  const cylinderGeo = new THREE.CylinderGeometry(0.04, 0.04, 0.3, 6);
  const boxGeo = new THREE.BoxGeometry(1.4, 0.02, 0.2);
  const boxGeo2 = new THREE.BoxGeometry(0.2, 1, 0.045);
  const boxGeo3 = new THREE.BoxGeometry(0.205, 0.14, 0.05);
  const cylinderGeo2 = new THREE.CylinderGeometry(0.28, 0.28, 0.15, 12);
  const cylinderGeo3 = new THREE.CylinderGeometry(0.126, 0.126, 0.17, 8);
  const sphereGeo = new THREE.SphereGeometry(0.32, 10, 6);

  // Parts
  const airframe = new THREE.Group();
  airframe.name = 'airframe';
  airframe.position.set(0, 1.12, 0);
  airframe.rotation.set(deg(-9), 0, 0);
  root.add(airframe);
  const fuselage = new THREE.Mesh(lathe([[0, -3.62], [0.1, -3.55], [0.24, -3.1], [0.42, -2], [0.58, -0.8], [0.64, 0.2], [0.63, 1.4], [0.58, 2.4], [0.52, 2.9], [0, 2.95]], 12), paintMat);
  fuselage.name = 'fuselage';
  fuselage.rotation.set(deg(90), 0, 0);
  airframe.add(fuselage);
  const cowling = new THREE.Mesh(lathe([[0.6095, 2.05], [0.5978, 2.2833], [0.578, 2.5167], [0.55, 2.75]], 12), accentMat);
  cowling.name = 'cowling';
  cowling.rotation.set(deg(90), 0, 0);
  airframe.add(cowling);
  const bandStripe = new THREE.Mesh(lathe([[0.4653, -1.75], [0.4787, -1.65], [0.492, -1.55], [0.5053, -1.45]], 12), accentMat);
  bandStripe.name = 'bandStripe';
  bandStripe.rotation.set(deg(90), 0, 0);
  airframe.add(bandStripe);
  const cowlFront = new THREE.Mesh(new THREE.CylinderGeometry(0.46, 0.5, 0.08, 12), trimMat);
  cowlFront.name = 'cowlFront';
  cowlFront.position.set(0, 0, 2.95);
  cowlFront.rotation.set(deg(90), 0, 0);
  airframe.add(cowlFront);
  const exhaust = new THREE.Mesh(cylinderGeo, metalMat);
  exhaust.name = 'exhaust';
  exhaust.position.set(0.5, -0.28, 2.2);
  exhaust.rotation.set(deg(90), 0, 0);
  airframe.add(exhaust);
  const exhaust2 = new THREE.Mesh(cylinderGeo, metalMat);
  exhaust2.name = 'exhaust';
  exhaust2.position.set(-0.5, -0.28, 2.2);
  exhaust2.rotation.set(deg(90), 0, 0);
  airframe.add(exhaust2);
  const canopy = new THREE.Mesh(new THREE.SphereGeometry(0.55, 12, 5, 0, Math.PI * 2, 0, Math.PI / 2), glassMat);
  canopy.name = 'canopy';
  canopy.position.set(0, 0.3, 0.1);
  canopy.scale.set(0.82, 1, 2);
  airframe.add(canopy);
  const wingL = new THREE.Group();
  wingL.name = 'wingL';
  wingL.position.set(0, -0.4, 0.15);
  wingL.rotation.set(0, 0, deg(5));
  airframe.add(wingL);
  const extrude10 = new THREE.Mesh(extrude([[0, 0.95], [4, 0.68], [4, -0.3], [0, -0.72]], 0.14, [], [0.04, 0.04, 2]), paintMat);
  extrude10.rotation.set(deg(90), 0, 0);
  wingL.add(extrude10);
  const extrude11 = new THREE.Mesh(extrude([[4, 0.68], [4.45, 0.6], [4.75, 0.4], [4.72, -0.12], [4.45, -0.27], [4, -0.3]], 0.14, [], [0.04, 0.04, 2]), accentMat);
  extrude11.rotation.set(deg(90), 0, 0);
  wingL.add(extrude11);
  const box12 = new THREE.Mesh(boxGeo, accentMat);
  box12.position.set(2.4, 0.115, -0.3);
  wingL.add(box12);
  const wingR = new THREE.Group();
  wingR.name = 'wingR';
  wingR.position.set(0, -0.4, 0.15);
  wingR.rotation.set(0, 0, deg(-5));
  airframe.add(wingR);
  const extrude14 = new THREE.Mesh(extrude([[0, 0.95], [-4, 0.68], [-4, -0.3], [0, -0.72]], 0.14, [], [0.04, 0.04, 2]), paintMat);
  extrude14.rotation.set(deg(90), 0, 0);
  wingR.add(extrude14);
  const extrude15 = new THREE.Mesh(extrude([[-4, 0.68], [-4.45, 0.6], [-4.75, 0.4], [-4.72, -0.12], [-4.45, -0.27], [-4, -0.3]], 0.14, [], [0.04, 0.04, 2]), accentMat);
  extrude15.rotation.set(deg(90), 0, 0);
  wingR.add(extrude15);
  const box16 = new THREE.Mesh(boxGeo, accentMat);
  box16.position.set(-2.4, 0.115, -0.3);
  wingR.add(box16);
  const stabilizer = new THREE.Mesh(extrude([[-1.55, 0], [-1.45, 0.3], [-0.2, 0.55], [0.2, 0.55], [1.45, 0.3], [1.55, 0], [1.45, -0.22], [-1.45, -0.22]], 0.06, [], [0.03, 0.03, 1]), paintMat);
  stabilizer.name = 'stabilizer';
  stabilizer.position.set(0, 0.05, -3.15);
  stabilizer.rotation.set(deg(90), 0, 0);
  airframe.add(stabilizer);
  const fin = new THREE.Mesh(extrude([[-2.45, 0.25], [-3.05, 1.42], [-3.5, 1.48], [-3.62, 0.15]], 0.07, [], [0.03, 0.03, 1]), accentMat);
  fin.name = 'fin';
  fin.rotation.set(0, deg(-90), 0);
  airframe.add(fin);
  const propeller = new THREE.Group();
  propeller.name = 'propeller';
  propeller.position.set(0, 0, 3.05);
  airframe.add(propeller);
  const spinner = new THREE.Mesh(new THREE.ConeGeometry(0.27, 0.5, 10), accentMat);
  spinner.name = 'spinner';
  spinner.position.set(0, 0, 0.2);
  spinner.rotation.set(deg(90), 0, 0);
  propeller.add(spinner);
  const blade1 = new THREE.Group();
  blade1.name = 'blade1';
  blade1.rotation.set(0, 0, deg(90));
  propeller.add(blade1);
  const box22 = new THREE.Mesh(boxGeo2, trimMat);
  box22.position.set(0, 0.62, 0);
  box22.rotation.set(0, deg(18), 0);
  blade1.add(box22);
  const box23 = new THREE.Mesh(boxGeo3, accentMat);
  box23.position.set(0, 1.06, 0);
  box23.rotation.set(0, deg(18), 0);
  blade1.add(box23);
  const blade2 = new THREE.Group();
  blade2.name = 'blade2';
  blade2.rotation.set(0, 0, deg(210));
  propeller.add(blade2);
  const box25 = new THREE.Mesh(boxGeo2, trimMat);
  box25.position.set(0, 0.62, 0);
  box25.rotation.set(0, deg(18), 0);
  blade2.add(box25);
  const box26 = new THREE.Mesh(boxGeo3, accentMat);
  box26.position.set(0, 1.06, 0);
  box26.rotation.set(0, deg(18), 0);
  blade2.add(box26);
  const blade3 = new THREE.Group();
  blade3.name = 'blade3';
  blade3.rotation.set(0, 0, deg(330));
  propeller.add(blade3);
  const box28 = new THREE.Mesh(boxGeo2, trimMat);
  box28.position.set(0, 0.62, 0);
  box28.rotation.set(0, deg(18), 0);
  blade3.add(box28);
  const box29 = new THREE.Mesh(boxGeo3, accentMat);
  box29.position.set(0, 1.06, 0);
  box29.rotation.set(0, deg(18), 0);
  blade3.add(box29);
  const landingGear = new THREE.Group();
  landingGear.name = 'landingGear';
  root.add(landingGear);
  const strutL = new THREE.Mesh(tube([[1.5, 0.8912, 0.5931], [1.5, 0.48, 0.75]], 0.045, 1, 6), paintMat);
  strutL.name = 'strutL';
  landingGear.add(strutL);
  const wheelL = new THREE.Group();
  wheelL.name = 'wheelL';
  wheelL.position.set(1.5, 0.28, 0.75);
  landingGear.add(wheelL);
  const tire = new THREE.Mesh(cylinderGeo2, trimMat);
  tire.name = 'tire';
  tire.rotation.set(0, 0, deg(90));
  wheelL.add(tire);
  const hub = new THREE.Mesh(cylinderGeo3, metalMat);
  hub.name = 'hub';
  hub.rotation.set(0, 0, deg(90));
  wheelL.add(hub);
  const wheelPantL = new THREE.Mesh(sphereGeo, accentMat);
  wheelPantL.name = 'wheelPantL';
  wheelPantL.position.set(1.5, 0.36, 0.72);
  wheelPantL.scale.set(0.36, 0.8, 1.55);
  landingGear.add(wheelPantL);
  const strutR = new THREE.Mesh(tube([[-1.5, 0.8912, 0.5931], [-1.5, 0.48, 0.75]], 0.045, 1, 6), paintMat);
  strutR.name = 'strutR';
  landingGear.add(strutR);
  const wheelR = new THREE.Group();
  wheelR.name = 'wheelR';
  wheelR.position.set(-1.5, 0.28, 0.75);
  landingGear.add(wheelR);
  const tire2 = new THREE.Mesh(cylinderGeo2, trimMat);
  tire2.name = 'tire';
  tire2.rotation.set(0, 0, deg(90));
  wheelR.add(tire2);
  const hub2 = new THREE.Mesh(cylinderGeo3, metalMat);
  hub2.name = 'hub';
  hub2.rotation.set(0, 0, deg(90));
  wheelR.add(hub2);
  const wheelPantR = new THREE.Mesh(sphereGeo, accentMat);
  wheelPantR.name = 'wheelPantR';
  wheelPantR.position.set(-1.5, 0.36, 0.72);
  wheelPantR.scale.set(0.36, 0.8, 1.55);
  landingGear.add(wheelPantR);
  const tailStrut = new THREE.Mesh(tube([[0, 0.5009, -3.1418], [0, 0.13, -3.2618]], 0.03, 1, 5), trimMat);
  tailStrut.name = 'tailStrut';
  landingGear.add(tailStrut);
  const tailWheel = new THREE.Group();
  tailWheel.name = 'tailWheel';
  tailWheel.position.set(0, 0.1, -3.2818);
  landingGear.add(tailWheel);
  const cylinder43 = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 0.07, 8), trimMat);
  cylinder43.rotation.set(0, 0, deg(90));
  tailWheel.add(cylinder43);

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

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

export default createPropellerPlane;

Puanlar ve yorumlar

—
Henüz puan yok — ilk puanı sen ver.
5
0
4
0
3
0
2
0
1
0

Puan vermek ve yorum yapmak için giriş yap.

0 yorum

Henüz yorum yok. Bu modeli nasıl kullandığını ya da neyin daha iyi olabileceğini paylaş.

Benzer modeller

Aynı kategori ya da benzer etiketler