Codels
736 Tris1,00 × 0,69 × 1,01 m

Im Code verwenden

Im Editor remixen
Eigenständiges ES-Modul — hängt nur von three ab. Ruf die Factory auf und füge die Gruppe deiner Szene hinzu.
/**
 * Campfire — Codels 3D model library
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
 * Triangles: 736 · Size (W × H × D): 1.00 × 0.69 × 1.01 m
 *
 * Usage:
 *   import { createCampfire } from './campfire.js';
 *   
 *   const model = createCampfire();
 *   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;

// Deterministic vertex displacement (organic low-poly look).
function jitter(geometry, amount, seed) {
  const pos = geometry.attributes.position;
  const h = (a, b, c, s) => {
    let x = Math.imul(a, 73856093) ^ Math.imul(b, 19349663) ^ Math.imul(c, 83492791) ^ Math.imul(s, 2654435761);
    x = Math.imul(x ^ (x >>> 16), 2246822507);
    x = Math.imul(x ^ (x >>> 13), 3266489909);
    return ((x ^ (x >>> 16)) >>> 0) / 4294967295 - 0.5;
  };
  for (let i = 0; i < pos.count; i++) {
    const x = pos.getX(i), y = pos.getY(i), z = pos.getZ(i);
    const qx = Math.round(x * 1000), qy = Math.round(y * 1000), qz = Math.round(z * 1000);
    pos.setXYZ(i, x + h(qx, qy, qz, seed) * 2 * amount, y + h(qx, qy, qz, seed + 1) * 2 * amount, z + h(qx, qy, qz, seed + 2) * 2 * amount);
  }
  pos.needsUpdate = true;
  geometry.computeVertexNormals();
  return geometry;
}

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

  // Materials
  const stoneMat = new THREE.MeshStandardMaterial({ name: 'stone', color: 0x7b766f, roughness: 0.95, flatShading: true });
  const stoneDarkMat = new THREE.MeshStandardMaterial({ name: 'stoneDark', color: 0x5d5953, roughness: 0.95, flatShading: true });
  const ashMat = new THREE.MeshStandardMaterial({ name: 'ash', color: 0x3b3734, roughness: 1, flatShading: true });
  const barkMat = new THREE.MeshStandardMaterial({ name: 'bark', color: 0x6a4529, roughness: 0.95, flatShading: true });
  const cutMat = new THREE.MeshStandardMaterial({ name: 'cut', color: 0xd6a565, roughness: 0.9, flatShading: true });
  const charMat = new THREE.MeshStandardMaterial({ name: 'char', color: 0x1f1b19, roughness: 1, flatShading: true });
  const redMat = new THREE.MeshStandardMaterial({ name: 'red', color: 0xe8401c, roughness: 1, emissive: 0xd42c08, emissiveIntensity: 1.1, flatShading: true });
  const orangeMat = new THREE.MeshStandardMaterial({ name: 'orange', color: 0xff7a1a, roughness: 1, emissive: 0xf05a00, emissiveIntensity: 1.25, flatShading: true });
  const yellowMat = new THREE.MeshStandardMaterial({ name: 'yellow', color: 0xffd760, roughness: 1, emissive: 0xffb830, emissiveIntensity: 1.5, flatShading: true });
  const emberMat = new THREE.MeshStandardMaterial({ name: 'ember', color: 0xff6a1a, roughness: 1, emissive: 0xff5000, emissiveIntensity: 1.4, flatShading: true });

  // Shared geometries
  const cylinderGeo = new THREE.CylinderGeometry(0.048, 0.0518, 0.56, 7);
  const cylinderGeo2 = new THREE.CylinderGeometry(0.0432, 0.0432, 0.008, 7);
  const tetrahedronGeo = new THREE.TetrahedronGeometry(0.0211, 0);

  // Parts
  const ashBed = new THREE.Mesh(new THREE.CylinderGeometry(0.3, 0.33, 0.02, 10), ashMat);
  ashBed.name = 'ashBed';
  ashBed.position.set(0, 0.01, 0);
  root.add(ashBed);
  const stone = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0908, 0), 0.0091, 1), stoneMat);
  stone.name = 'stone';
  stone.position.set(0.4085, 0.0556, -0.0349);
  stone.rotation.set(0, deg(264.0706), 0);
  stone.scale.set(1.15, 0.6448, 1);
  root.add(stone);
  const stone2 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.1085, 0), 0.0108, 2), stoneDarkMat);
  stone2.name = 'stone';
  stone2.position.set(0.3084, 0.0628, 0.2701);
  stone2.rotation.set(0, deg(1.635), 0);
  stone2.scale.set(1.15, 0.6092, 1);
  root.add(stone2);
  const stone3 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0962, 0), 0.0096, 3), stoneMat);
  stone3.name = 'stone';
  stone3.position.set(0.0662, 0.0549, 0.4046);
  stone3.rotation.set(0, deg(6.1921), 0);
  stone3.scale.set(1.15, 0.6005, 1);
  root.add(stone3);
  const stone4 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.103, 0), 0.0103, 4), stoneMat);
  stone4.name = 'stone';
  stone4.position.set(-0.2434, 0.0656, 0.3299);
  stone4.rotation.set(0, deg(307.8811), 0);
  stone4.scale.set(1.15, 0.671, 1);
  root.add(stone4);
  const stone5 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0984, 0), 0.0098, 5), stoneDarkMat);
  stone5.name = 'stone';
  stone5.position.set(-0.3988, 0.0682, 0.0951);
  stone5.rotation.set(0, deg(330.992), 0);
  stone5.scale.set(1.15, 0.7293, 1);
  root.add(stone5);
  const stone6 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0939, 0), 0.0094, 6), stoneMat);
  stone6.name = 'stone';
  stone6.position.set(-0.3952, 0.0663, -0.1093);
  stone6.rotation.set(0, deg(279.8125), 0);
  stone6.scale.set(1.15, 0.7438, 1);
  root.add(stone6);
  const stone7 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0945, 0), 0.0094, 7), stoneMat);
  stone7.name = 'stone';
  stone7.position.set(-0.2313, 0.0547, -0.3385);
  stone7.rotation.set(0, deg(168.7615), 0);
  stone7.scale.set(1.15, 0.6101, 1);
  root.add(stone7);
  const stone8 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0997, 0), 0.01, 8), stoneDarkMat);
  stone8.name = 'stone';
  stone8.position.set(0.0494, 0.0681, -0.407);
  stone8.rotation.set(0, deg(65.8), 0);
  stone8.scale.set(1.15, 0.7194, 1);
  root.add(stone8);
  const stone9 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0951, 0), 0.0095, 9), stoneMat);
  stone9.name = 'stone';
  stone9.position.set(0.2979, 0.0563, -0.2817);
  stone9.rotation.set(0, deg(107.8394), 0);
  stone9.scale.set(1.15, 0.6225, 1);
  root.add(stone9);
  const log1 = new THREE.Group();
  log1.name = 'log1';
  log1.rotation.set(0, deg(81.2895), 0);
  root.add(log1);
  const log = new THREE.Mesh(cylinderGeo, barkMat);
  log.name = 'log';
  log.position.set(0.2, 0.1614, 0);
  log.rotation.set(0, 0, deg(68));
  log1.add(log);
  const logEnd = new THREE.Mesh(cylinderGeo2, cutMat);
  logEnd.name = 'logEnd';
  logEnd.position.set(0.4624, 0.0554, 0);
  logEnd.rotation.set(0, 0, deg(68));
  log1.add(logEnd);
  const log2 = new THREE.Group();
  log2.name = 'log2';
  log2.rotation.set(0, deg(177.9705), 0);
  root.add(log2);
  const log3 = new THREE.Mesh(cylinderGeo, barkMat);
  log3.name = 'log';
  log3.position.set(0.2, 0.1614, 0);
  log3.rotation.set(0, 0, deg(68));
  log2.add(log3);
  const logEnd2 = new THREE.Mesh(cylinderGeo2, cutMat);
  logEnd2.name = 'logEnd';
  logEnd2.position.set(0.4624, 0.0554, 0);
  logEnd2.rotation.set(0, 0, deg(68));
  log2.add(logEnd2);
  const log32 = new THREE.Group();
  log32.name = 'log3';
  log32.rotation.set(0, deg(262.7465), 0);
  root.add(log32);
  const log4 = new THREE.Mesh(cylinderGeo, barkMat);
  log4.name = 'log';
  log4.position.set(0.2, 0.1614, 0);
  log4.rotation.set(0, 0, deg(68));
  log32.add(log4);
  const logEnd3 = new THREE.Mesh(cylinderGeo2, cutMat);
  logEnd3.name = 'logEnd';
  logEnd3.position.set(0.4624, 0.0554, 0);
  logEnd3.rotation.set(0, 0, deg(68));
  log32.add(logEnd3);
  const log42 = new THREE.Group();
  log42.name = 'log4';
  log42.rotation.set(0, deg(357.6807), 0);
  root.add(log42);
  const log5 = new THREE.Mesh(cylinderGeo, barkMat);
  log5.name = 'log';
  log5.position.set(0.2, 0.1614, 0);
  log5.rotation.set(0, 0, deg(68));
  log42.add(log5);
  const logEnd4 = new THREE.Mesh(cylinderGeo2, cutMat);
  logEnd4.name = 'logEnd';
  logEnd4.position.set(0.4624, 0.0554, 0);
  logEnd4.rotation.set(0, 0, deg(68));
  log42.add(logEnd4);
  const flames = new THREE.Group();
  flames.name = 'flames';
  flames.position.set(0, 0.05, 0);
  flames.scale.setScalar(1.1);
  root.add(flames);
  const glow = new THREE.Mesh(new THREE.IcosahedronGeometry(0.11, 0), yellowMat);
  glow.name = 'glow';
  glow.position.set(0, 0.06, 0);
  glow.scale.set(1.2, 0.7, 1.2);
  flames.add(glow);
  const flameMainPivot = new THREE.Group();
  flameMainPivot.name = 'flameMainPivot';
  flameMainPivot.position.set(0, 0.03, -0.01);
  flames.add(flameMainPivot);
  const flameMain = new THREE.Mesh(jitter(new THREE.ConeGeometry(0.12, 0.54, 6), 0.014, 3), orangeMat);
  flameMain.name = 'flameMain';
  flameMain.position.set(0, 0.27, 0);
  flameMain.rotation.set(0, deg(15), deg(3));
  flameMainPivot.add(flameMain);
  const flameBackLPivot = new THREE.Group();
  flameBackLPivot.name = 'flameBackLPivot';
  flameBackLPivot.position.set(-0.075, 0.03, -0.07);
  flames.add(flameBackLPivot);
  const flameBackL = new THREE.Mesh(jitter(new THREE.ConeGeometry(0.1, 0.44, 6), 0.01, 4), redMat);
  flameBackL.name = 'flameBackL';
  flameBackL.position.set(0, 0.22, 0);
  flameBackL.rotation.set(deg(-8), 0, deg(12));
  flameBackLPivot.add(flameBackL);
  const flameBackRPivot = new THREE.Group();
  flameBackRPivot.name = 'flameBackRPivot';
  flameBackRPivot.position.set(0.085, 0.03, -0.05);
  flames.add(flameBackRPivot);
  const flameBackR = new THREE.Mesh(jitter(new THREE.ConeGeometry(0.09, 0.37, 6), 0.01, 5), redMat);
  flameBackR.name = 'flameBackR';
  flameBackR.position.set(0, 0.185, 0);
  flameBackR.rotation.set(deg(-6), 0, deg(-16));
  flameBackRPivot.add(flameBackR);
  const flameSideLPivot = new THREE.Group();
  flameSideLPivot.name = 'flameSideLPivot';
  flameSideLPivot.position.set(-0.12, 0.03, 0.04);
  flames.add(flameSideLPivot);
  const flameSideL = new THREE.Mesh(jitter(new THREE.ConeGeometry(0.08, 0.3, 6), 0.008, 7), orangeMat);
  flameSideL.name = 'flameSideL';
  flameSideL.position.set(0, 0.15, 0);
  flameSideL.rotation.set(deg(10), 0, deg(24));
  flameSideLPivot.add(flameSideL);
  const flameSideRPivot = new THREE.Group();
  flameSideRPivot.name = 'flameSideRPivot';
  flameSideRPivot.position.set(0.125, 0.03, 0.03);
  flames.add(flameSideRPivot);
  const flameSideR = new THREE.Mesh(new THREE.ConeGeometry(0.075, 0.27, 6), redMat);
  flameSideR.name = 'flameSideR';
  flameSideR.position.set(0, 0.135, 0);
  flameSideR.rotation.set(deg(8), 0, deg(-26));
  flameSideRPivot.add(flameSideR);
  const flameFrontPivot = new THREE.Group();
  flameFrontPivot.name = 'flameFrontPivot';
  flameFrontPivot.position.set(0.01, 0.03, 0.09);
  flames.add(flameFrontPivot);
  const flameFront = new THREE.Mesh(jitter(new THREE.ConeGeometry(0.085, 0.31, 6), 0.008, 6), yellowMat);
  flameFront.name = 'flameFront';
  flameFront.position.set(0, 0.155, 0);
  flameFront.rotation.set(deg(14), 0, deg(-4));
  flameFrontPivot.add(flameFront);
  const flameFrontRPivot = new THREE.Group();
  flameFrontRPivot.name = 'flameFrontRPivot';
  flameFrontRPivot.position.set(0.085, 0.03, 0.1);
  flames.add(flameFrontRPivot);
  const flameFrontR = new THREE.Mesh(new THREE.ConeGeometry(0.05, 0.18, 5), yellowMat);
  flameFrontR.name = 'flameFrontR';
  flameFrontR.position.set(0, 0.09, 0);
  flameFrontR.rotation.set(deg(18), 0, deg(-20));
  flameFrontRPivot.add(flameFrontR);
  const flameFrontLPivot = new THREE.Group();
  flameFrontLPivot.name = 'flameFrontLPivot';
  flameFrontLPivot.position.set(-0.075, 0.03, 0.11);
  flames.add(flameFrontLPivot);
  const flameFrontL = new THREE.Mesh(new THREE.ConeGeometry(0.045, 0.16, 5), orangeMat);
  flameFrontL.name = 'flameFrontL';
  flameFrontL.position.set(0, 0.08, 0);
  flameFrontL.rotation.set(deg(20), 0, deg(18));
  flameFrontLPivot.add(flameFrontL);
  const ember = new THREE.Mesh(new THREE.TetrahedronGeometry(0.0176, 0), charMat);
  ember.name = 'ember';
  ember.position.set(0.0166, 0.026, -0.1957);
  ember.rotation.set(deg(65.5131), deg(50.6012), 0);
  root.add(ember);
  const ember2 = new THREE.Mesh(new THREE.TetrahedronGeometry(0.0202, 0), emberMat);
  ember2.name = 'ember';
  ember2.position.set(-0.1212, 0.026, 0.2021);
  ember2.rotation.set(deg(42.4342), deg(34.4371), 0);
  root.add(ember2);
  const ember3 = new THREE.Mesh(new THREE.TetrahedronGeometry(0.0158, 0), emberMat);
  ember3.name = 'ember';
  ember3.position.set(-0.0049, 0.026, 0.2073);
  ember3.rotation.set(deg(77.4927), deg(5.3695), 0);
  root.add(ember3);
  const ember4 = new THREE.Mesh(new THREE.TetrahedronGeometry(0.0204, 0), charMat);
  ember4.name = 'ember';
  ember4.position.set(-0.1574, 0.026, 0.1278);
  ember4.rotation.set(deg(83.8229), deg(75.4053), 0);
  root.add(ember4);
  const ember5 = new THREE.Mesh(new THREE.TetrahedronGeometry(0.0206, 0), emberMat);
  ember5.name = 'ember';
  ember5.position.set(-0.185, 0.026, 0.1243);
  ember5.rotation.set(deg(55.6616), deg(31.4516), 0);
  root.add(ember5);
  const ember6 = new THREE.Mesh(tetrahedronGeo, emberMat);
  ember6.name = 'ember';
  ember6.position.set(-0.2087, 0.026, 0.0995);
  ember6.rotation.set(deg(13.74), deg(43.3169), 0);
  root.add(ember6);
  const ember7 = new THREE.Mesh(new THREE.TetrahedronGeometry(0.0124, 0), charMat);
  ember7.name = 'ember';
  ember7.position.set(0.0021, 0.026, 0.2088);
  ember7.rotation.set(deg(2.5402), deg(63.6992), 0);
  root.add(ember7);
  const ember8 = new THREE.Mesh(new THREE.TetrahedronGeometry(0.0143, 0), emberMat);
  ember8.name = 'ember';
  ember8.position.set(0.1672, 0.026, 0.1213);
  ember8.rotation.set(deg(8.4287), deg(39.0649), 0);
  root.add(ember8);
  const ember9 = new THREE.Mesh(tetrahedronGeo, emberMat);
  ember9.name = 'ember';
  ember9.position.set(-0.0674, 0.026, -0.2685);
  ember9.rotation.set(deg(51.703), deg(51.6534), 0);
  root.add(ember9);

  // Animations ('idle') — play them with THREE.AnimationMixer
  root.animations = codelsClips(THREE, [
    { target: glow, anim: [{ type: 'pulse', amount: 0.08, period: 1.5 }] },
    { target: flameMainPivot, anim: [{ type: 'pulse', amount: 0.12, period: 1.5 }] },
    { target: flameBackLPivot, anim: [{ type: 'pulse', amount: 0.12, period: 1, phase: 0.3 }] },
    { target: flameBackRPivot, anim: [{ type: 'pulse', amount: 0.12, period: 0.75, phase: 0.6 }] },
    { target: flameSideLPivot, anim: [{ type: 'pulse', amount: 0.12, period: 1, phase: 0.75 }] },
    { target: flameSideRPivot, anim: [{ type: 'pulse', amount: 0.12, period: 0.75, phase: 0.2 }] },
    { target: flameFrontPivot, anim: [{ type: 'pulse', amount: 0.12, period: 1.5, phase: 0.5 }] },
    { target: flameFrontRPivot, anim: [{ type: 'pulse', amount: 0.12, period: 0.75, phase: 0.45 }] },
    { target: flameFrontLPivot, anim: [{ type: 'pulse', amount: 0.12, period: 1, phase: 0.1 }] },
  ]);

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

export default createCampfire;

Bewertungen & Kommentare

—
Noch keine Bewertungen — gib die erste ab.
5
0
4
0
3
0
2
0
1
0

Melde dich an, um zu bewerten und zu kommentieren.

0 Kommentare

Noch keine Kommentare. Erzähl, wie du dieses Modell genutzt hast oder was besser sein könnte.

Ähnliche Modelle

Gleiche Kategorie oder ähnliche Tags