Codels
344 треуг.0,19 × 0,49 × 0,19 m

Использовать в коде

Ремикс в редакторе
Самодостаточный ES-модуль — зависит только от three. Вызовите фабрику и добавьте группу в сцену.
/**
 * Lantern — Codels 3D model library
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
 * Triangles: 344 · Size (W × H × D): 0.19 × 0.49 × 0.19 m
 *
 * Usage:
 *   import { createLantern } from './lantern.js';
 *   
 *   const model = createLantern();
 *   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;

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

  // Materials
  const frameMat = new THREE.MeshStandardMaterial({ name: 'frame', color: 0x3c3f46, roughness: 0.45, metalness: 0.5, flatShading: true });
  const trimMat = new THREE.MeshStandardMaterial({ name: 'trim', color: 0xb8893c, roughness: 0.35, metalness: 0.5, flatShading: true });
  const glassMat = new THREE.MeshStandardMaterial({ name: 'glass', color: 0xffbb59, roughness: 0.2, emissive: 0xffb347, emissiveIntensity: 1.05, flatShading: true });

  // Shared geometries
  const boxGeo = new THREE.BoxGeometry(0.016, 0.204, 0.016);
  const boxGeo2 = new THREE.BoxGeometry(0.008, 0.2, 0.006);

  // Parts
  const base = new THREE.Mesh(new THREE.CylinderGeometry(0.112, 0.118, 0.03, 4), frameMat);
  base.name = 'base';
  base.position.set(0, 0.015, 0);
  base.rotation.set(0, deg(45), 0);
  root.add(base);
  const baseTrim = new THREE.Mesh(new THREE.CylinderGeometry(0.116, 0.116, 0.008, 4), trimMat);
  baseTrim.name = 'baseTrim';
  baseTrim.position.set(0, 0.022, 0);
  baseTrim.rotation.set(0, deg(45), 0);
  root.add(baseTrim);
  const glass = new THREE.Mesh(new THREE.CylinderGeometry(0.092, 0.092, 0.2, 4), glassMat);
  glass.name = 'glass';
  glass.position.set(0, 0.13, 0);
  glass.rotation.set(0, deg(45), 0);
  root.add(glass);
  const topFrame = new THREE.Mesh(new THREE.CylinderGeometry(0.11, 0.11, 0.022, 4), frameMat);
  topFrame.name = 'topFrame';
  topFrame.position.set(0, 0.241, 0);
  topFrame.rotation.set(0, deg(45), 0);
  root.add(topFrame);
  const post = new THREE.Mesh(boxGeo, frameMat);
  post.name = 'post';
  post.position.set(0.0679, 0.13, 0.0679);
  post.rotation.set(0, deg(45), 0);
  root.add(post);
  const bar = new THREE.Mesh(boxGeo2, frameMat);
  bar.name = 'bar';
  bar.position.set(0.0667, 0.13, 0);
  bar.rotation.set(0, deg(90), 0);
  root.add(bar);
  const post2 = new THREE.Mesh(boxGeo, frameMat);
  post2.name = 'post';
  post2.position.set(0.0679, 0.13, -0.0679);
  post2.rotation.set(0, deg(135), 0);
  root.add(post2);
  const bar2 = new THREE.Mesh(boxGeo2, frameMat);
  bar2.name = 'bar';
  bar2.position.set(0, 0.13, -0.0667);
  bar2.rotation.set(0, deg(180), 0);
  root.add(bar2);
  const post3 = new THREE.Mesh(boxGeo, frameMat);
  post3.name = 'post';
  post3.position.set(-0.0679, 0.13, -0.0679);
  post3.rotation.set(0, deg(225), 0);
  root.add(post3);
  const bar3 = new THREE.Mesh(boxGeo2, frameMat);
  bar3.name = 'bar';
  bar3.position.set(-0.0667, 0.13, 0);
  bar3.rotation.set(0, deg(270), 0);
  root.add(bar3);
  const post4 = new THREE.Mesh(boxGeo, frameMat);
  post4.name = 'post';
  post4.position.set(-0.0679, 0.13, 0.0679);
  post4.rotation.set(0, deg(315), 0);
  root.add(post4);
  const bar4 = new THREE.Mesh(boxGeo2, frameMat);
  bar4.name = 'bar';
  bar4.position.set(0, 0.13, 0.0667);
  bar4.rotation.set(0, deg(360), 0);
  root.add(bar4);
  const midBand = new THREE.Mesh(new THREE.CylinderGeometry(0.097, 0.097, 0.012, 4), frameMat);
  midBand.name = 'midBand';
  midBand.position.set(0, 0.16, 0);
  midBand.rotation.set(0, deg(45), 0);
  root.add(midBand);
  const roof = new THREE.Mesh(new THREE.ConeGeometry(0.135, 0.11, 4), frameMat);
  roof.name = 'roof';
  roof.position.set(0, 0.307, 0);
  roof.rotation.set(0, deg(45), 0);
  root.add(roof);
  const roofTrim = new THREE.Mesh(new THREE.CylinderGeometry(0.136, 0.136, 0.008, 4), trimMat);
  roofTrim.name = 'roofTrim';
  roofTrim.position.set(0, 0.254, 0);
  roofTrim.rotation.set(0, deg(45), 0);
  root.add(roofTrim);
  const chimney = new THREE.Mesh(new THREE.CylinderGeometry(0.022, 0.028, 0.05, 6), frameMat);
  chimney.name = 'chimney';
  chimney.position.set(0, 0.367, 0);
  root.add(chimney);
  const chimneyCap = new THREE.Mesh(new THREE.CylinderGeometry(0.032, 0.032, 0.008, 6), trimMat);
  chimneyCap.name = 'chimneyCap';
  chimneyCap.position.set(0, 0.396, 0);
  root.add(chimneyCap);
  const ring = new THREE.Mesh(new THREE.TorusGeometry(0.042, 0.008, 4, 12), frameMat);
  ring.name = 'ring';
  ring.position.set(0, 0.442, 0);
  root.add(ring);

  // Animations ('idle') — play them with THREE.AnimationMixer
  root.animations = codelsClips(THREE, [
    { target: glass, anim: [{ type: 'pulse', amount: 0.04, period: 1.2 }] },
  ]);

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

export default createLantern;

Оценки и комментарии

—
Оценок пока нет — будьте первым.
5
0
4
0
3
0
2
0
1
0

Войдите, чтобы ставить оценки и оставлять комментарии.

0 комментариев

Комментариев пока нет. Расскажите, как вы использовали эту модель или что можно улучшить.

Похожие модели

Та же категория или похожие теги