Codels
416 tris1,08 × 2,24 × 0,68 m

Úsalo en código

Hacer remix en el editor
Módulo ES autónomo — solo depende de three. Llama a la función factory y añade el grupo a tu escena.
/**
 * Door with Frame — Codels 3D model library
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
 * Triangles: 416 · Size (W × H × D): 1.08 × 2.24 × 0.68 m
 *
 * Usage:
 *   import { createDoor } from './door.js';
 *   
 *   const model = createDoor();
 *   model.position.set(0, 0, 0);
 *   scene.add(model);
 *   
 *   // Animations: open
 *   const mixer = new THREE.AnimationMixer(model);
 *   const open = mixer.clipAction(THREE.AnimationClip.findByName(model.animations, 'open'));
 *   open.setLoop(THREE.LoopOnce, 1);
 *   open.clampWhenFinished = true;
 *   open.play(); // back: open.timeScale = -1; open.paused = false;
 *   // 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 createDoor() {
  const root = new THREE.Group();
  root.name = 'Door';

  // Materials
  const leafMat = new THREE.MeshStandardMaterial({ name: 'leaf', color: 0x9a6436, roughness: 0.9, flatShading: true });
  const leaf2Mat = new THREE.MeshStandardMaterial({ name: 'leaf2', color: 0x84562e, roughness: 0.9, flatShading: true });
  const frameMat = new THREE.MeshStandardMaterial({ name: 'frame', color: 0x5c3b22, roughness: 0.9, flatShading: true });
  const stoneMat = new THREE.MeshStandardMaterial({ name: 'stone', color: 0x8f887d, roughness: 0.95, flatShading: true });
  const ironMat = new THREE.MeshStandardMaterial({ name: 'iron', color: 0x2f3134, roughness: 0.55, metalness: 0.7, flatShading: true });
  const darkMat = new THREE.MeshStandardMaterial({ name: 'dark', color: 0x2a1d14, roughness: 1, flatShading: true });

  // Shared geometries
  const boxGeo = new THREE.BoxGeometry(0.1, 2.22, 0.2);
  const boxGeo2 = new THREE.BoxGeometry(0.708, 0.13, 0.03);
  const boxGeo3 = new THREE.BoxGeometry(0.5358, 0.055, 0.012);
  const cylinderGeo = new THREE.CylinderGeometry(0.045, 0.045, 0.012, 8);
  const cylinderGeo2 = new THREE.CylinderGeometry(0.022, 0.022, 0.2, 6);

  // Parts
  const threshold = new THREE.Mesh(jitter(new THREE.BoxGeometry(1, 0.04, 0.6), 0.006, 1), stoneMat);
  threshold.name = 'threshold';
  threshold.position.set(0, 0.02, 0);
  root.add(threshold);
  const jamb = new THREE.Mesh(boxGeo, frameMat);
  jamb.name = 'jamb';
  jamb.position.set(-0.45, 1.11, 0);
  root.add(jamb);
  const jamb2 = new THREE.Mesh(boxGeo, frameMat);
  jamb2.name = 'jamb';
  jamb2.position.set(0.45, 1.11, 0);
  root.add(jamb2);
  const lintel = new THREE.Mesh(new THREE.BoxGeometry(1.08, 0.14, 0.24), frameMat);
  lintel.name = 'lintel';
  lintel.position.set(0, 2.17, 0);
  root.add(lintel);
  const lintelGroove = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.025, 0.02), darkMat);
  lintelGroove.name = 'lintelGroove';
  lintelGroove.position.set(0, 2.17, 0.12);
  root.add(lintelGroove);
  const leaf = new THREE.Group();
  leaf.name = 'leaf';
  leaf.position.set(-0.394, 0, 0.05);
  leaf.rotation.set(0, deg(-25), 0);
  root.add(leaf);
  const plank = new THREE.Mesh(new THREE.BoxGeometry(0.1496, 2.0377, 0.05), leafMat);
  plank.name = 'plank';
  plank.position.set(0.0788, 1.0649, -0.025);
  leaf.add(plank);
  const plank2 = new THREE.Mesh(new THREE.BoxGeometry(0.1496, 2.0447, 0.05), leafMat);
  plank2.name = 'plank';
  plank2.position.set(0.2364, 1.0684, -0.025);
  leaf.add(plank2);
  const plank3 = new THREE.Mesh(new THREE.BoxGeometry(0.1496, 2.0467, 0.05), leafMat);
  plank3.name = 'plank';
  plank3.position.set(0.394, 1.0694, -0.025);
  leaf.add(plank3);
  const plank4 = new THREE.Mesh(new THREE.BoxGeometry(0.1496, 2.0372, 0.05), leafMat);
  plank4.name = 'plank';
  plank4.position.set(0.5516, 1.0646, -0.025);
  leaf.add(plank4);
  const plank5 = new THREE.Mesh(new THREE.BoxGeometry(0.1496, 2.0344, 0.05), leaf2Mat);
  plank5.name = 'plank';
  plank5.position.set(0.7092, 1.0632, -0.025);
  leaf.add(plank5);
  const ledge = new THREE.Mesh(boxGeo2, leaf2Mat);
  ledge.name = 'ledge';
  ledge.position.set(0.394, 0.396, -0.065);
  leaf.add(ledge);
  const ledge2 = new THREE.Mesh(boxGeo2, leaf2Mat);
  ledge2.name = 'ledge';
  ledge2.position.set(0.394, 1.744, -0.065);
  leaf.add(ledge2);
  const brace = new THREE.Mesh(new THREE.BoxGeometry(0.11, 1.3615, 0.03), leaf2Mat);
  brace.name = 'brace';
  brace.position.set(0.394, 1.07, -0.065);
  brace.rotation.set(0, 0, deg(27.4685));
  leaf.add(brace);
  const strapHinge = new THREE.Mesh(boxGeo3, ironMat);
  strapHinge.name = 'strapHinge';
  strapHinge.position.set(0.2779, 0.426, 0.006);
  leaf.add(strapHinge);
  const strapEnd = new THREE.Mesh(cylinderGeo, ironMat);
  strapEnd.name = 'strapEnd';
  strapEnd.position.set(0.5458, 0.426, 0.006);
  strapEnd.rotation.set(deg(90), 0, 0);
  leaf.add(strapEnd);
  const pintle = new THREE.Mesh(cylinderGeo2, ironMat);
  pintle.name = 'pintle';
  pintle.position.set(-0.005, 0.426, 0);
  leaf.add(pintle);
  const strapHinge2 = new THREE.Mesh(boxGeo3, ironMat);
  strapHinge2.name = 'strapHinge';
  strapHinge2.position.set(0.2779, 1.714, 0.006);
  leaf.add(strapHinge2);
  const strapEnd2 = new THREE.Mesh(cylinderGeo, ironMat);
  strapEnd2.name = 'strapEnd';
  strapEnd2.position.set(0.5458, 1.714, 0.006);
  strapEnd2.rotation.set(deg(90), 0, 0);
  leaf.add(strapEnd2);
  const pintle2 = new THREE.Mesh(cylinderGeo2, ironMat);
  pintle2.name = 'pintle';
  pintle2.position.set(-0.005, 1.714, 0);
  leaf.add(pintle2);
  const ringPlate = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 0.012, 8), ironMat);
  ringPlate.name = 'ringPlate';
  ringPlate.position.set(0.658, 1.026, 0.006);
  ringPlate.rotation.set(deg(90), 0, 0);
  leaf.add(ringPlate);
  const ringPull = new THREE.Mesh(new THREE.TorusGeometry(0.06, 0.011, 4, 10), ironMat);
  ringPull.name = 'ringPull';
  ringPull.position.set(0.658, 0.976, 0.02);
  leaf.add(ringPull);
  const keyhole = new THREE.Mesh(new THREE.BoxGeometry(0.02, 0.05, 0.01), darkMat);
  keyhole.name = 'keyhole';
  keyhole.position.set(0.658, 0.866, 0.004);
  leaf.add(keyhole);

  // Animations ('open') — play them with THREE.AnimationMixer
  root.animations = codelsClips(THREE, [
    { target: leaf, anim: [{ type: 'hinge', axis: 'y', angle: -75, duration: 1.15 }] },
  ]);

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

export default createDoor;

Valoraciones y comentarios

—
Aún no hay valoraciones — sé el primero.
5
0
4
0
3
0
2
0
1
0

Inicia sesión para valorar y comentar.

0 comentarios

Aún no hay comentarios. Cuenta cómo usaste este modelo o qué se podría mejorar.

Modelos relacionados

Misma categoría o etiquetas similares