Codels
594 tris4,00 × 3,29 × 2,00 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.
/**
 * Cliff Edge — Codels 3D model library
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
 * Triangles: 594 · Size (W × H × D): 4.00 × 3.29 × 2.00 m
 *
 * Usage:
 *   import { createCliffEdge } from './cliff-edge.js';
 *   
 *   const model = createCliffEdge();
 *   model.position.set(0, 0, 0);
 *   scene.add(model);
 */
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;
}

// 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);
}

export function createCliffEdge() {
  const root = new THREE.Group();
  root.name = 'CliffEdge';

  // Materials
  const grassMat = new THREE.MeshStandardMaterial({ name: 'grass', color: 0x6aae45, roughness: 0.9, flatShading: true });
  const grassDarkMat = new THREE.MeshStandardMaterial({ name: 'grassDark', color: 0x5b8836, roughness: 0.9, flatShading: true });
  const dirtMat = new THREE.MeshStandardMaterial({ name: 'dirt', color: 0x80553a, roughness: 0.95, flatShading: true });
  const rockMat = new THREE.MeshStandardMaterial({ name: 'rock', color: 0x857868, roughness: 0.92, flatShading: true });
  const rockLightMat = new THREE.MeshStandardMaterial({ name: 'rockLight', color: 0x9b8c79, roughness: 0.9, flatShading: true });
  const rockDarkMat = new THREE.MeshStandardMaterial({ name: 'rockDark', color: 0x6c6053, roughness: 0.95, flatShading: true });
  const rockDeepMat = new THREE.MeshStandardMaterial({ name: 'rockDeep', color: 0x5a5148, roughness: 0.95, flatShading: true });

  // Parts
  const core = new THREE.Mesh(new THREE.BoxGeometry(4, 2.88, 1.25), rockDeepMat);
  core.name = 'core';
  core.position.set(0, 1.44, -0.375);
  root.add(core);
  const plateau = new THREE.Mesh(new THREE.BoxGeometry(4, 0.12, 1.57), grassMat);
  plateau.name = 'plateau';
  plateau.position.set(0, 2.94, -0.215);
  root.add(plateau);
  const topsoil = new THREE.Mesh(new THREE.BoxGeometry(4, 0.22, 1.51), dirtMat);
  topsoil.name = 'topsoil';
  topsoil.position.set(0, 2.77, -0.245);
  root.add(topsoil);
  const strata = new THREE.Group();
  strata.name = 'strata';
  root.add(strata);
  const stratum11 = new THREE.Mesh(extrude([[0.13, 0], [0.708, 0], [0.7683, 0.2528], [0.7381, 0.4346], [0.6477, 0.5892], [0.13, 0.5892]], 1.8163), rockMat);
  stratum11.name = 'stratum1_1';
  stratum11.position.set(-1.0919, 0, 0);
  stratum11.rotation.set(0, deg(-90), 0);
  strata.add(stratum11);
  const stratum12 = new THREE.Mesh(extrude([[0.13, 0], [0.7072, 0], [0.7685, 0.2466], [0.7378, 0.4023], [0.6459, 0.5892], [0.13, 0.5892]], 2.2837), rockMat);
  stratum12.name = 'stratum1_2';
  stratum12.position.set(0.8581, 0, 0);
  stratum12.rotation.set(0, deg(-90), 0);
  strata.add(stratum12);
  const stratum21 = new THREE.Mesh(extrude([[0.13, 0], [0.4235, 0], [0.4932, 0.2287], [0.4583, 0.459], [0.3539, 0.5759], [0.13, 0.5759]], 1.8641), rockDarkMat);
  stratum21.name = 'stratum2_1';
  stratum21.position.set(-1.068, 0.5892, 0);
  stratum21.rotation.set(0, deg(-90), 0);
  strata.add(stratum21);
  const stratum22 = new THREE.Mesh(extrude([[0.13, 0], [0.4674, 0], [0.5337, 0.2513], [0.5005, 0.3908], [0.4012, 0.5759], [0.13, 0.5759]], 2.2359), rockDarkMat);
  stratum22.name = 'stratum2_2';
  stratum22.position.set(0.882, 0.5892, 0);
  stratum22.rotation.set(0, deg(-90), 0);
  strata.add(stratum22);
  const stratum31 = new THREE.Mesh(extrude([[0.13, 0], [0.3474, 0], [0.4387, 0.291], [0.3931, 0.6665], [0.2562, 0.9606], [0.13, 0.9606]], 1.4968), rockLightMat);
  stratum31.name = 'stratum3_1';
  stratum31.position.set(-1.2516, 1.1651, 0);
  stratum31.rotation.set(0, deg(-90), 0);
  strata.add(stratum31);
  const stratum32 = new THREE.Mesh(jitter(extrude([[0.13, 0], [0.4302, 0], [0.5552, 0.4187], [0.4927, 0.7219], [0.3053, 0.9606], [0.13, 0.9606]], 1.5331), 0.06, 415), rockLightMat);
  stratum32.name = 'stratum3_2';
  stratum32.position.set(0.1633, 1.1651, 0);
  stratum32.rotation.set(0, deg(-90), 0);
  strata.add(stratum32);
  const stratum33 = new THREE.Mesh(extrude([[0.13, 0], [0.3729, 0], [0.4825, 0.3337], [0.4277, 0.7007], [0.2633, 0.9606], [0.13, 0.9606]], 1.1701), rockLightMat);
  stratum33.name = 'stratum3_3';
  stratum33.position.set(1.4149, 1.1651, 0);
  stratum33.rotation.set(0, deg(-90), 0);
  strata.add(stratum33);
  const stratum41 = new THREE.Mesh(extrude([[0.13, 0], [0.2554, 0], [0.3359, 0.2179], [0.2957, 0.3773], [0.175, 0.5344], [0.13, 0.5344]], 2.3355), rockMat);
  stratum41.name = 'stratum4_1';
  stratum41.position.set(-0.8323, 2.1256, 0);
  stratum41.rotation.set(0, deg(-90), 0);
  strata.add(stratum41);
  const stratum42 = new THREE.Mesh(extrude([[0.13, 0], [0.3218, 0], [0.3946, 0.2258], [0.3582, 0.4191], [0.2491, 0.5344], [0.13, 0.5344]], 1.7645), rockMat);
  stratum42.name = 'stratum4_2';
  stratum42.position.set(1.1177, 2.1256, 0);
  stratum42.rotation.set(0, deg(-90), 0);
  strata.add(stratum42);
  const fringe = new THREE.Group();
  fringe.name = 'fringe';
  root.add(fringe);
  const box15 = new THREE.Mesh(new THREE.BoxGeometry(0.1565, 0.1658, 0.05), grassMat);
  box15.position.set(-1.8038, 2.8171, 0.54);
  fringe.add(box15);
  const box16 = new THREE.Mesh(new THREE.BoxGeometry(0.1628, 0.084, 0.05), grassMat);
  box16.position.set(-1.4727, 2.858, 0.54);
  fringe.add(box16);
  const box17 = new THREE.Mesh(new THREE.BoxGeometry(0.1942, 0.2119, 0.05), grassMat);
  box17.position.set(-0.927, 2.794, 0.54);
  fringe.add(box17);
  const box18 = new THREE.Mesh(new THREE.BoxGeometry(0.1548, 0.1792, 0.05), grassMat);
  box18.position.set(-0.3003, 2.8104, 0.54);
  fringe.add(box18);
  const box19 = new THREE.Mesh(new THREE.BoxGeometry(0.2094, 0.2181, 0.05), grassMat);
  box19.position.set(0.0172, 2.7909, 0.54);
  fringe.add(box19);
  const box20 = new THREE.Mesh(new THREE.BoxGeometry(0.2223, 0.1732, 0.05), grassMat);
  box20.position.set(0.3142, 2.8134, 0.54);
  fringe.add(box20);
  const box21 = new THREE.Mesh(new THREE.BoxGeometry(0.1365, 0.1218, 0.05), grassMat);
  box21.position.set(0.6037, 2.8391, 0.54);
  fringe.add(box21);
  const box22 = new THREE.Mesh(new THREE.BoxGeometry(0.1391, 0.0938, 0.05), grassMat);
  box22.position.set(0.8973, 2.8531, 0.54);
  fringe.add(box22);
  const box23 = new THREE.Mesh(new THREE.BoxGeometry(0.1558, 0.2026, 0.05), grassMat);
  box23.position.set(1.2014, 2.7987, 0.54);
  fringe.add(box23);
  const box24 = new THREE.Mesh(new THREE.BoxGeometry(0.1889, 0.1758, 0.05), grassMat);
  box24.position.set(1.4993, 2.8121, 0.54);
  fringe.add(box24);
  const box25 = new THREE.Mesh(new THREE.BoxGeometry(0.1592, 0.1462, 0.05), grassMat);
  box25.position.set(1.795, 2.8269, 0.54);
  fringe.add(box25);
  const dressing = new THREE.Group();
  dressing.name = 'dressing';
  root.add(dressing);
  const tuft1 = new THREE.Group();
  tuft1.name = 'tuft1';
  tuft1.position.set(-1.4907, 3, -0.4765);
  dressing.add(tuft1);
  const cone28 = new THREE.Mesh(new THREE.ConeGeometry(0.0531, 0.287, 3), grassDarkMat);
  cone28.position.set(0.045, 0.1235, 0.0161);
  cone28.rotation.set(deg(53.0639), deg(73.2989), deg(-49.6629));
  cone28.scale.set(1, 1, 0.45);
  tuft1.add(cone28);
  const cone29 = new THREE.Mesh(new THREE.ConeGeometry(0.0531, 0.2438, 3), grassMat);
  cone29.position.set(-0.0188, 0.1019, 0.0439);
  cone29.rotation.set(deg(11.2191), deg(41.2433), deg(9.9815));
  cone29.scale.set(1, 1, 0.45);
  tuft1.add(cone29);
  const cone30 = new THREE.Mesh(new THREE.ConeGeometry(0.0531, 0.2314, 3), grassDarkMat);
  cone30.position.set(-0.0042, 0.0957, -0.0476);
  cone30.rotation.set(deg(-11.8205), deg(9.9528), deg(1.0306));
  cone30.scale.set(1, 1, 0.45);
  tuft1.add(cone30);
  const tuft2 = new THREE.Group();
  tuft2.name = 'tuft2';
  tuft2.position.set(-0.6024, 3, -0.2692);
  dressing.add(tuft2);
  const cone32 = new THREE.Mesh(new THREE.ConeGeometry(0.0522, 0.2576, 3), grassDarkMat);
  cone32.position.set(0.0439, 0.1088, -0.0167);
  cone32.rotation.set(deg(11.6276), deg(53.7417), deg(-19.614));
  cone32.scale.set(1, 1, 0.45);
  tuft2.add(cone32);
  const cone33 = new THREE.Mesh(new THREE.ConeGeometry(0.0522, 0.2717, 3), grassMat);
  cone33.position.set(-0.0077, 0.1158, 0.0464);
  cone33.rotation.set(deg(-140.9677), deg(78.4083), deg(162.3012));
  cone33.scale.set(1, 1, 0.45);
  tuft2.add(cone33);
  const cone34 = new THREE.Mesh(new THREE.ConeGeometry(0.0522, 0.2488, 3), grassDarkMat);
  cone34.position.set(-0.0399, 0.1044, -0.0249);
  cone34.rotation.set(deg(-13.5156), deg(42.5169), deg(11.8136));
  cone34.scale.set(1, 1, 0.45);
  tuft2.add(cone34);
  const tuft3 = new THREE.Group();
  tuft3.name = 'tuft3';
  tuft3.position.set(-0.186, 3, -0.4267);
  dressing.add(tuft3);
  const cone36 = new THREE.Mesh(new THREE.ConeGeometry(0.0576, 0.2964, 3), grassDarkMat);
  cone36.position.set(0.0498, 0.1282, 0.0144);
  cone36.rotation.set(deg(20.1915), deg(49.7001), deg(-20.9154));
  cone36.scale.set(1, 1, 0.45);
  tuft3.add(cone36);
  const cone37 = new THREE.Mesh(new THREE.ConeGeometry(0.0576, 0.3143, 3), grassMat);
  cone37.position.set(-0.0227, 0.1372, 0.0466);
  cone37.rotation.set(deg(9.1987), deg(30.6688), deg(7.2019));
  cone37.scale.set(1, 1, 0.45);
  tuft3.add(cone37);
  const cone38 = new THREE.Mesh(new THREE.ConeGeometry(0.0576, 0.1887, 3), grassDarkMat);
  cone38.position.set(-0.0486, 0.0743, -0.018);
  cone38.rotation.set(deg(-102.5909), deg(69.8728), deg(94.5204));
  cone38.scale.set(1, 1, 0.45);
  tuft3.add(cone38);
  const cone39 = new THREE.Mesh(new THREE.ConeGeometry(0.0576, 0.2767, 3), grassMat);
  cone39.position.set(0.0215, 0.1184, -0.0471);
  cone39.rotation.set(deg(-6.2405), deg(48.4319), deg(-8.7241));
  cone39.scale.set(1, 1, 0.45);
  tuft3.add(cone39);
  const tuft4 = new THREE.Group();
  tuft4.name = 'tuft4';
  tuft4.position.set(0.7428, 3, 0.4109);
  dressing.add(tuft4);
  const cone41 = new THREE.Mesh(new THREE.ConeGeometry(0.0421, 0.2208, 3), grassDarkMat);
  cone41.position.set(0.0377, 0.0904, 0.0039);
  cone41.rotation.set(deg(93.5272), deg(69.2586), deg(-91.1856));
  cone41.scale.set(1, 1, 0.45);
  tuft4.add(cone41);
  const cone42 = new THREE.Mesh(new THREE.ConeGeometry(0.0421, 0.2213, 3), grassMat);
  cone42.position.set(-0.0306, 0.0906, 0.0225);
  cone42.rotation.set(deg(1.5608), deg(31.6594), deg(15.1364));
  cone42.scale.set(1, 1, 0.45);
  tuft4.add(cone42);
  const cone43 = new THREE.Mesh(new THREE.ConeGeometry(0.0421, 0.1546, 3), grassDarkMat);
  cone43.position.set(-0.0207, 0.0573, -0.0318);
  cone43.rotation.set(deg(-28.7223), deg(59.2322), deg(17.3406));
  cone43.scale.set(1, 1, 0.45);
  tuft4.add(cone43);
  const tuft5 = new THREE.Group();
  tuft5.name = 'tuft5';
  tuft5.position.set(1.4155, 3, -0.1511);
  dressing.add(tuft5);
  const cone45 = new THREE.Mesh(new THREE.ConeGeometry(0.0616, 0.2328, 3), grassDarkMat);
  cone45.position.set(0.0542, 0.0964, -0.0115);
  cone45.rotation.set(deg(141.5617), deg(57.4303), deg(-141.228));
  cone45.scale.set(1, 1, 0.45);
  tuft5.add(cone45);
  const cone46 = new THREE.Mesh(new THREE.ConeGeometry(0.0616, 0.3024, 3), grassMat);
  cone46.position.set(-0.0062, 0.1312, 0.0551);
  cone46.rotation.set(deg(13.745), deg(14.843), deg(1.6272));
  cone46.scale.set(1, 1, 0.45);
  tuft5.add(cone46);
  const cone47 = new THREE.Mesh(new THREE.ConeGeometry(0.0616, 0.2041, 3), grassDarkMat);
  cone47.position.set(-0.0486, 0.0821, 0.0265);
  cone47.rotation.set(deg(-151.1066), deg(60.8452), deg(154.8763));
  cone47.scale.set(1, 1, 0.45);
  tuft5.add(cone47);
  const cone48 = new THREE.Mesh(new THREE.ConeGeometry(0.0616, 0.2827, 3), grassMat);
  cone48.position.set(0.0187, 0.1214, -0.0522);
  cone48.rotation.set(deg(-17.411), deg(6.8747), deg(-6.4255));
  cone48.scale.set(1, 1, 0.45);
  tuft5.add(cone48);
  const boulder1 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.1319, 0), 0.0185, 124), rockMat);
  boulder1.name = 'boulder1';
  boulder1.position.set(-1.2935, 0.1061, 0.7764);
  boulder1.rotation.set(0, deg(57.4221), 0);
  boulder1.scale.set(1.2, 0.7757, 1);
  dressing.add(boulder1);
  const boulder2 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.1804, 0), 0.0253, 125), rockDarkMat);
  boulder2.name = 'boulder2';
  boulder2.position.set(-0.3454, 0.1201, 0.7545);
  boulder2.rotation.set(0, deg(54.2635), 0);
  boulder2.scale.set(1.2, 0.6921, 1);
  dressing.add(boulder2);
  const boulder3 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.1423, 0), 0.0199, 126), rockMat);
  boulder3.name = 'boulder3';
  boulder3.position.set(0.466, 0.1127, 0.7607);
  boulder3.rotation.set(0, deg(23.3979), 0);
  boulder3.scale.set(1.2, 0.7761, 1);
  dressing.add(boulder3);
  const boulder4 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.2971, 0), 0.0416, 127), rockDarkMat);
  boulder4.name = 'boulder4';
  boulder4.position.set(1.4587, 0.1733, 0.6886);
  boulder4.rotation.set(0, deg(3.6045), 0);
  boulder4.scale.set(1.2, 0.6689, 1);
  dressing.add(boulder4);

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

export default createCliffEdge;

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