Codels
1516 三角面0.18 × 0.20 × 0.16 m

在代码中使用

在编辑器中改编
自包含的 ES 模块,仅依赖 three。调用工厂函数,然后把返回的 group 添加到场景中。
/**
 * Magic Orb — Codels 3D model library
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the model (it floats — pickups, planets)
 * Triangles: 1,516 · Size (W × H × D): 0.18 × 0.20 × 0.16 m
 *
 * Usage:
 *   import { createMagicOrb } from './magic-orb.js';
 *   
 *   const model = createMagicOrb();
 *   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);
}

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

  // Materials
  const glassMat = new THREE.MeshStandardMaterial({ name: 'glass', color: 0xa17dff, roughness: 0.05, metalness: 0.1, emissive: 0x8a5cff, emissiveIntensity: 0.6, transparent: true, opacity: 0.6 });
  const coreMat = new THREE.MeshStandardMaterial({ name: 'core', color: 0x9c74ff, roughness: 0.3, emissive: 0x8a5cff, emissiveIntensity: 1.4 });
  const swirlMat = new THREE.MeshStandardMaterial({ name: 'swirl', color: 0xe2d6ff, roughness: 0.3, emissive: 0xc5aeff, flatShading: true });
  const standMat = new THREE.MeshStandardMaterial({ name: 'stand', color: 0x4a3b33, roughness: 0.55, metalness: 0.45, flatShading: true });
  const trimMat = new THREE.MeshStandardMaterial({ name: 'trim', color: 0xc9a04a, roughness: 0.35, metalness: 0.8, flatShading: true });
  const talonMat = new THREE.MeshStandardMaterial({ name: 'talon', color: 0xe9e1cf, roughness: 0.5, flatShading: true });

  // Shared geometries
  const sphereGeo = new THREE.SphereGeometry(0.011, 5, 4);
  const coneGeo = new THREE.ConeGeometry(0.0085, 0.026, 6);

  // Parts
  const magicOrb = new THREE.Group();
  magicOrb.name = 'magicOrb';
  magicOrb.position.set(0, -0.0997, 0);
  root.add(magicOrb);
  const pedestal = new THREE.Mesh(lathe([[0, 0], [0.072, 0], [0.078, 0.01], [0.07, 0.02], [0.04, 0.03], [0.026, 0.045], [0.03, 0.058], [0.05, 0.068], [0.056, 0.075], [0.04, 0.079], [0, 0.079]], 12), standMat);
  pedestal.name = 'pedestal';
  magicOrb.add(pedestal);
  const baseRing = new THREE.Mesh(new THREE.TorusGeometry(0.073, 0.005, 4, 14), trimMat);
  baseRing.name = 'baseRing';
  baseRing.position.set(0, 0.012, 0);
  baseRing.rotation.set(deg(90), 0, 0);
  magicOrb.add(baseRing);
  const cupRing = new THREE.Mesh(new THREE.TorusGeometry(0.053, 0.004, 3, 14), trimMat);
  cupRing.name = 'cupRing';
  cupRing.position.set(0, 0.073, 0);
  cupRing.rotation.set(deg(90), 0, 0);
  magicOrb.add(cupRing);
  const claw1 = new THREE.Mesh(tube([[0.0297, 0.071, 0.0297], [0.0467, 0.087, 0.0467], [0.058, 0.1114, 0.058], [0.0526, 0.152, 0.0526], [0.0363, 0.1772, 0.0363]], 0.0085, 10, 5), standMat);
  claw1.name = 'claw1';
  magicOrb.add(claw1);
  const knuckle1 = new THREE.Mesh(sphereGeo, standMat);
  knuckle1.name = 'knuckle1';
  knuckle1.position.set(0.058, 0.1114, 0.058);
  magicOrb.add(knuckle1);
  const talon1 = new THREE.Mesh(coneGeo, talonMat);
  talon1.name = 'talon1';
  talon1.position.set(0.0311, 0.1853, 0.0311);
  talon1.rotation.set(deg(-39.0207), deg(-14.3114), deg(39.0207));
  magicOrb.add(talon1);
  const claw2 = new THREE.Mesh(tube([[0.0297, 0.071, -0.0297], [0.0467, 0.087, -0.0467], [0.058, 0.1114, -0.058], [0.0526, 0.152, -0.0526], [0.0363, 0.1772, -0.0363]], 0.0085, 10, 5), standMat);
  claw2.name = 'claw2';
  magicOrb.add(claw2);
  const knuckle2 = new THREE.Mesh(sphereGeo, standMat);
  knuckle2.name = 'knuckle2';
  knuckle2.position.set(0.058, 0.1114, -0.058);
  magicOrb.add(knuckle2);
  const talon2 = new THREE.Mesh(coneGeo, talonMat);
  talon2.name = 'talon2';
  talon2.position.set(0.0311, 0.1853, -0.0311);
  talon2.rotation.set(deg(39.0207), deg(14.3114), deg(39.0207));
  magicOrb.add(talon2);
  const claw3 = new THREE.Mesh(tube([[-0.0297, 0.071, -0.0297], [-0.0467, 0.087, -0.0467], [-0.058, 0.1114, -0.058], [-0.0526, 0.152, -0.0526], [-0.0363, 0.1772, -0.0363]], 0.0085, 10, 5), standMat);
  claw3.name = 'claw3';
  magicOrb.add(claw3);
  const knuckle3 = new THREE.Mesh(sphereGeo, standMat);
  knuckle3.name = 'knuckle3';
  knuckle3.position.set(-0.058, 0.1114, -0.058);
  magicOrb.add(knuckle3);
  const talon3 = new THREE.Mesh(coneGeo, talonMat);
  talon3.name = 'talon3';
  talon3.position.set(-0.0311, 0.1853, -0.0311);
  talon3.rotation.set(deg(39.0207), deg(-14.3114), deg(-39.0207));
  magicOrb.add(talon3);
  const claw4 = new THREE.Mesh(tube([[-0.0297, 0.071, 0.0297], [-0.0467, 0.087, 0.0467], [-0.058, 0.1114, 0.058], [-0.0526, 0.152, 0.0526], [-0.0363, 0.1772, 0.0363]], 0.0085, 10, 5), standMat);
  claw4.name = 'claw4';
  magicOrb.add(claw4);
  const knuckle4 = new THREE.Mesh(sphereGeo, standMat);
  knuckle4.name = 'knuckle4';
  knuckle4.position.set(-0.058, 0.1114, 0.058);
  magicOrb.add(knuckle4);
  const talon4 = new THREE.Mesh(coneGeo, talonMat);
  talon4.name = 'talon4';
  talon4.position.set(-0.0311, 0.1853, 0.0311);
  talon4.rotation.set(deg(-39.0207), deg(14.3114), deg(-39.0207));
  magicOrb.add(talon4);
  const orb = new THREE.Mesh(new THREE.SphereGeometry(0.07, 14, 10), glassMat);
  orb.name = 'orb';
  orb.position.set(0, 0.1254, 0);
  magicOrb.add(orb);
  const core = new THREE.Mesh(new THREE.SphereGeometry(0.0294, 8, 6), coreMat);
  core.name = 'core';
  core.position.set(0, 0.1254, 0);
  magicOrb.add(core);
  const swirl1 = new THREE.Mesh(new THREE.TorusGeometry(0.0434, 0.005, 3, 14, 4.3982), swirlMat);
  swirl1.name = 'swirl1';
  swirl1.position.set(0, 0.1254, 0);
  swirl1.rotation.set(deg(70), deg(20), 0);
  magicOrb.add(swirl1);
  const swirl2 = new THREE.Mesh(new THREE.TorusGeometry(0.0385, 0.004, 3, 12, 3.7699), swirlMat);
  swirl2.name = 'swirl2';
  swirl2.position.set(0, 0.1254, 0);
  swirl2.rotation.set(deg(-50), deg(60), deg(120));
  magicOrb.add(swirl2);
  const sparkle1 = new THREE.Mesh(new THREE.OctahedronGeometry(0.008, 0), swirlMat);
  sparkle1.name = 'sparkle1';
  sparkle1.position.set(0.0875, 0.1744, 0.021);
  sparkle1.scale.set(0.6, 1.4, 0.6);
  magicOrb.add(sparkle1);
  const sparkle2 = new THREE.Mesh(new THREE.OctahedronGeometry(0.01, 0), swirlMat);
  sparkle2.name = 'sparkle2';
  sparkle2.position.set(-0.084, 0.1499, 0.0385);
  sparkle2.rotation.set(0, deg(30), 0);
  sparkle2.scale.set(0.6, 1.4, 0.6);
  magicOrb.add(sparkle2);
  const sparkle3 = new THREE.Mesh(new THREE.OctahedronGeometry(0.012, 0), swirlMat);
  sparkle3.name = 'sparkle3';
  sparkle3.position.set(0.014, 0.1849, -0.0735);
  sparkle3.rotation.set(0, deg(60), 0);
  sparkle3.scale.set(0.6, 1.4, 0.6);
  magicOrb.add(sparkle3);

  // Animations ('idle') — play them with THREE.AnimationMixer
  root.animations = codelsClips(THREE, [
    { target: magicOrb, anim: [{ type: 'spin', axis: 'y', speed: 90 }, { type: 'bob', axis: 'y', distance: 0.05, period: 2 }] },
    { target: core, anim: [{ type: 'pulse', amount: 0.12, period: 1 }] },
    { target: swirl1, anim: [{ type: 'spin', axis: 'z', speed: 180 }] },
    { target: swirl2, anim: [{ type: 'spin', axis: 'z', speed: -270 }] },
  ]);

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

export default createMagicOrb;

评分与评论

—
暂无评分,快来抢先评分吧。
5
0
4
0
3
0
2
0
1
0

登录后即可评分和评论。

0 条评论

暂无评论。分享一下你是如何使用这个模型的,或者有哪些可以改进的地方。

相关模型

同一分类或相似标签