Codels
삼각형 896개2.00 × 0.76 × 2.00 m

코드에서 사용하기

에디터에서 리믹스
독립형 ES 모듈이에요 — three에만 의존해요. 팩토리 함수를 호출하고 그룹을 씬에 추가하세요.
/**
 * Grass Tile — Codels 3D model library
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
 * Triangles: 896 · Size (W × H × D): 2.00 × 0.76 × 2.00 m
 *
 * Usage:
 *   import { createGrassTile } from './grass-tile.js';
 *   
 *   const model = createGrassTile();
 *   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 createGrassTile() {
  const root = new THREE.Group();
  root.name = 'GrassTile';

  // Materials
  const grassMat = new THREE.MeshStandardMaterial({ name: 'grass', color: 0x6aae45, roughness: 0.9, flatShading: true });
  const grassLightMat = new THREE.MeshStandardMaterial({ name: 'grassLight', color: 0x72ba59, 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: 0x8b5d3b, roughness: 0.95, flatShading: true });
  const dirtDarkMat = new THREE.MeshStandardMaterial({ name: 'dirtDark', color: 0x6a4630, roughness: 0.95, flatShading: true });
  const stoneMat = new THREE.MeshStandardMaterial({ name: 'stone', color: 0x9a958c, roughness: 0.9, flatShading: true });
  const petalMat = new THREE.MeshStandardMaterial({ name: 'petal', color: 0xfff3c4, roughness: 0.7, flatShading: true });
  const petalPinkMat = new THREE.MeshStandardMaterial({ name: 'petalPink', color: 0xf58fb0, roughness: 0.7, flatShading: true });

  // Parts
  const grassTop = new THREE.Mesh(new THREE.BoxGeometry(2, 0.12, 2), grassMat);
  grassTop.name = 'grassTop';
  grassTop.position.set(0, 0.44, 0);
  root.add(grassTop);
  const dirt = new THREE.Mesh(new THREE.BoxGeometry(1.92, 0.209, 1.92), dirtMat);
  dirt.name = 'dirt';
  dirt.position.set(0, 0.2755, 0);
  root.add(dirt);
  const dirtDeep = new THREE.Mesh(new THREE.BoxGeometry(1.92, 0.171, 1.92), dirtDarkMat);
  dirtDeep.name = 'dirtDeep';
  dirtDeep.position.set(0, 0.0855, 0);
  root.add(dirtDeep);
  const wallStones = new THREE.Group();
  wallStones.name = 'wallStones';
  root.add(wallStones);
  const stone1 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0997, 0), 0.0149, 40), dirtDarkMat);
  stone1.name = 'stone1';
  stone1.position.set(-0.5161, 0.2514, 0.952);
  stone1.scale.set(1.25, 0.8, 0.42);
  wallStones.add(stone1);
  const stone2 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0804, 0), 0.0121, 41), stoneMat);
  stone2.name = 'stone2';
  stone2.position.set(0.3268, 0.1189, 0.9536);
  stone2.scale.set(1.25, 0.8, 0.42);
  wallStones.add(stone2);
  const stone3 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.084, 0), 0.0126, 42), stoneMat);
  stone3.name = 'stone3';
  stone3.position.set(-0.4879, 0.2323, -0.9533);
  stone3.scale.set(1.25, 0.8, 0.42);
  wallStones.add(stone3);
  const stone4 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.098, 0), 0.0147, 43), dirtDarkMat);
  stone4.name = 'stone4';
  stone4.position.set(0.3557, 0.2278, -0.9522);
  stone4.scale.set(1.25, 0.8, 0.42);
  wallStones.add(stone4);
  const stone5 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0796, 0), 0.0119, 44), stoneMat);
  stone5.name = 'stone5';
  stone5.position.set(0.9536, 0.1065, -0.3716);
  stone5.scale.set(0.42, 0.8, 1.25);
  wallStones.add(stone5);
  const stone6 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0747, 0), 0.0112, 45), stoneMat);
  stone6.name = 'stone6';
  stone6.position.set(0.954, 0.1727, 0.39);
  stone6.scale.set(0.42, 0.8, 1.25);
  wallStones.add(stone6);
  const stone7 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.099, 0), 0.0149, 46), dirtDarkMat);
  stone7.name = 'stone7';
  stone7.position.set(-0.9521, 0.1584, -0.4864);
  stone7.scale.set(0.42, 0.8, 1.25);
  wallStones.add(stone7);
  const stone8 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0724, 0), 0.0109, 47), stoneMat);
  stone8.name = 'stone8';
  stone8.position.set(-0.9542, 0.1847, 0.353);
  stone8.scale.set(0.42, 0.8, 1.25);
  wallStones.add(stone8);
  const fringe = new THREE.Group();
  fringe.name = 'fringe';
  root.add(fringe);
  const box14 = new THREE.Mesh(new THREE.BoxGeometry(0.2035, 0.1149, 0.04), grassMat);
  box14.position.set(-0.7826, 0.3425, 0.976);
  fringe.add(box14);
  const box15 = new THREE.Mesh(new THREE.BoxGeometry(0.1765, 0.0855, 0.04), grassMat);
  box15.position.set(-0.443, 0.3572, 0.976);
  fringe.add(box15);
  const box16 = new THREE.Mesh(new THREE.BoxGeometry(0.2104, 0.1628, 0.04), grassMat);
  box16.position.set(-0.1877, 0.3186, 0.976);
  fringe.add(box16);
  const box17 = new THREE.Mesh(new THREE.BoxGeometry(0.231, 0.174, 0.04), grassMat);
  box17.position.set(0.1736, 0.313, 0.976);
  fringe.add(box17);
  const box18 = new THREE.Mesh(new THREE.BoxGeometry(0.202, 0.147, 0.04), grassMat);
  box18.position.set(0.4436, 0.3265, 0.976);
  fringe.add(box18);
  const box19 = new THREE.Mesh(new THREE.BoxGeometry(0.1743, 0.152, 0.04), grassMat);
  box19.position.set(0.781, 0.324, 0.976);
  fringe.add(box19);
  const box20 = new THREE.Mesh(new THREE.BoxGeometry(0.2031, 0.1751, 0.04), grassMat);
  box20.position.set(-0.7844, 0.3124, -0.976);
  fringe.add(box20);
  const box21 = new THREE.Mesh(new THREE.BoxGeometry(0.1774, 0.1158, 0.04), grassMat);
  box21.position.set(-0.4439, 0.3421, -0.976);
  fringe.add(box21);
  const box22 = new THREE.Mesh(new THREE.BoxGeometry(0.2321, 0.1043, 0.04), grassMat);
  box22.position.set(-0.1278, 0.3479, -0.976);
  fringe.add(box22);
  const box23 = new THREE.Mesh(new THREE.BoxGeometry(0.2058, 0.1143, 0.04), grassMat);
  box23.position.set(0.1863, 0.3429, -0.976);
  fringe.add(box23);
  const box24 = new THREE.Mesh(new THREE.BoxGeometry(0.2067, 0.153, 0.04), grassMat);
  box24.position.set(0.4558, 0.3235, -0.976);
  fringe.add(box24);
  const box25 = new THREE.Mesh(new THREE.BoxGeometry(0.1767, 0.1211, 0.04), grassMat);
  box25.position.set(0.7831, 0.3394, -0.976);
  fringe.add(box25);
  const box26 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.1332, 0.2277), grassMat);
  box26.position.set(0.976, 0.3334, -0.7713);
  fringe.add(box26);
  const box27 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.1603, 0.208), grassMat);
  box27.position.set(0.976, 0.3198, -0.1356);
  fringe.add(box27);
  const box28 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.1257, 0.1676), grassMat);
  box28.position.set(0.976, 0.3371, 0.1689);
  fringe.add(box28);
  const box29 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.0836, 0.1534), grassMat);
  box29.position.set(0.976, 0.3582, 0.4582);
  fringe.add(box29);
  const box30 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.1461, 0.2218), grassMat);
  box30.position.set(-0.976, 0.327, -0.4982);
  fringe.add(box30);
  const box31 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.147, 0.2213), grassMat);
  box31.position.set(-0.976, 0.3265, 0.1472);
  fringe.add(box31);
  const box32 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.0868, 0.1733), grassMat);
  box32.position.set(-0.976, 0.3566, 0.801);
  fringe.add(box32);
  const dressing = new THREE.Group();
  dressing.name = 'dressing';
  root.add(dressing);
  const patch1 = new THREE.Mesh(extrude([[0.2699, 0.2022], [0.1197, -0.0441], [-0.0914, -0.0588], [-0.2827, 0.0607], [-0.2908, 0.3137], [-0.1093, 0.4924], [0.2452, 0.4421]], 0.01), grassDarkMat);
  patch1.name = 'patch1';
  patch1.position.set(0, 0.505, 0);
  patch1.rotation.set(deg(-90), 0, 0);
  dressing.add(patch1);
  const patch2 = new THREE.Mesh(extrude([[-0.0492, 0.1285], [-0.1696, -0.0828], [-0.3953, -0.1221], [-0.5793, -0.0887], [-0.538, 0.2105], [-0.3324, 0.3297], [-0.1519, 0.2815]], 0.01), grassLightMat);
  patch2.name = 'patch2';
  patch2.position.set(0, 0.505, 0);
  patch2.rotation.set(deg(-90), 0, 0);
  dressing.add(patch2);
  const patch3 = new THREE.Mesh(extrude([[0.891, 0.4463], [0.7064, 0.2439], [0.4918, 0.2463], [0.2658, 0.347], [0.2862, 0.5391], [0.4859, 0.6334], [0.7019, 0.6226]], 0.01), grassDarkMat);
  patch3.name = 'patch3';
  patch3.position.set(0, 0.505, 0);
  patch3.rotation.set(deg(-90), 0, 0);
  dressing.add(patch3);
  const tuft1 = new THREE.Group();
  tuft1.name = 'tuft1';
  tuft1.position.set(-0.4418, 0.5, -0.5656);
  dressing.add(tuft1);
  const cone38 = new THREE.Mesh(new THREE.ConeGeometry(0.0513, 0.2765, 3), grassDarkMat);
  cone38.position.set(0.0428, 0.1183, 0.0172);
  cone38.rotation.set(deg(156.3576), deg(70.0612), deg(-151.021));
  cone38.scale.set(1, 1, 0.45);
  tuft1.add(cone38);
  const cone39 = new THREE.Mesh(new THREE.ConeGeometry(0.0513, 0.2698, 3), grassMat);
  cone39.position.set(-0.0146, 0.1149, 0.0438);
  cone39.rotation.set(deg(11.1371), deg(35.9658), deg(5.9415));
  cone39.scale.set(1, 1, 0.45);
  tuft1.add(cone39);
  const cone40 = new THREE.Mesh(new THREE.ConeGeometry(0.0513, 0.24, 3), grassLightMat);
  cone40.position.set(-0.0224, 0.1, -0.0404);
  cone40.rotation.set(deg(-172.0323), deg(69.8879), deg(155.8518));
  cone40.scale.set(1, 1, 0.45);
  tuft1.add(cone40);
  const tuft2 = new THREE.Group();
  tuft2.name = 'tuft2';
  tuft2.position.set(0.4093, 0.5, -0.249);
  dressing.add(tuft2);
  const cone42 = new THREE.Mesh(new THREE.ConeGeometry(0.0467, 0.2121, 3), grassDarkMat);
  cone42.position.set(0.038, 0.086, 0.0181);
  cone42.rotation.set(deg(8.2785), deg(0.8935), deg(-16.3633));
  cone42.scale.set(1, 1, 0.45);
  tuft2.add(cone42);
  const cone43 = new THREE.Mesh(new THREE.ConeGeometry(0.0467, 0.2228, 3), grassMat);
  cone43.position.set(0.004, 0.0914, 0.0419);
  cone43.rotation.set(deg(21.691), deg(14.3474), deg(-2.0401));
  cone43.scale.set(1, 1, 0.45);
  tuft2.add(cone43);
  const cone44 = new THREE.Mesh(new THREE.ConeGeometry(0.0467, 0.2533, 3), grassLightMat);
  cone44.position.set(-0.042, 0.1066, -0.0015);
  cone44.rotation.set(deg(-57.4396), deg(77.0843), deg(57.717));
  cone44.scale.set(1, 1, 0.45);
  tuft2.add(cone44);
  const cone45 = new THREE.Mesh(new THREE.ConeGeometry(0.0467, 0.2007, 3), grassDarkMat);
  cone45.position.set(-0.0058, 0.0803, -0.0417);
  cone45.rotation.set(deg(-26.1155), deg(57.1997), deg(5.4368));
  cone45.scale.set(1, 1, 0.45);
  tuft2.add(cone45);
  const tuft3 = new THREE.Group();
  tuft3.name = 'tuft3';
  tuft3.position.set(0.5506, 0.5, 0.2147);
  dressing.add(tuft3);
  const cone47 = new THREE.Mesh(new THREE.ConeGeometry(0.0599, 0.2416, 3), grassDarkMat);
  cone47.position.set(0.0478, 0.1008, 0.025);
  cone47.rotation.set(deg(25.9645), deg(39.2885), deg(-24.2156));
  cone47.scale.set(1, 1, 0.45);
  tuft3.add(cone47);
  const cone48 = new THREE.Mesh(new THREE.ConeGeometry(0.0599, 0.2442, 3), grassMat);
  cone48.position.set(0.0224, 0.1021, 0.0491);
  cone48.rotation.set(deg(20.6663), deg(32.7584), deg(-8.54));
  cone48.scale.set(1, 1, 0.45);
  tuft3.add(cone48);
  const cone49 = new THREE.Mesh(new THREE.ConeGeometry(0.0599, 0.2708, 3), grassLightMat);
  cone49.position.set(-0.048, 0.1154, 0.0246);
  cone49.rotation.set(deg(5.4579), deg(9.4433), deg(15.5592));
  cone49.scale.set(1, 1, 0.45);
  tuft3.add(cone49);
  const cone50 = new THREE.Mesh(new THREE.ConeGeometry(0.0599, 0.2824, 3), grassDarkMat);
  cone50.position.set(0.0204, 0.1212, -0.0499);
  cone50.rotation.set(deg(-10.2713), deg(21.9003), deg(-5.3453));
  cone50.scale.set(1, 1, 0.45);
  tuft3.add(cone50);
  const tuft4 = new THREE.Group();
  tuft4.name = 'tuft4';
  tuft4.position.set(0.3254, 0.5, -0.3063);
  dressing.add(tuft4);
  const cone52 = new THREE.Mesh(new THREE.ConeGeometry(0.0504, 0.19, 3), grassDarkMat);
  cone52.position.set(0.0417, 0.075, 0.0179);
  cone52.rotation.set(deg(21.5798), deg(34.6345), deg(-22.654));
  cone52.scale.set(1, 1, 0.45);
  tuft4.add(cone52);
  const cone53 = new THREE.Mesh(new THREE.ConeGeometry(0.0504, 0.2133, 3), grassMat);
  cone53.position.set(-0.0253, 0.0867, 0.0377);
  cone53.rotation.set(deg(-120.4876), deg(73.0393), deg(136.8698));
  cone53.scale.set(1, 1, 0.45);
  tuft4.add(cone53);
  const cone54 = new THREE.Mesh(new THREE.ConeGeometry(0.0504, 0.2116, 3), grassLightMat);
  cone54.position.set(-0.0149, 0.0858, -0.0428);
  cone54.rotation.set(deg(175.9501), deg(62.1274), deg(171.3132));
  cone54.scale.set(1, 1, 0.45);
  tuft4.add(cone54);
  const flower1 = new THREE.Group();
  flower1.name = 'flower1';
  flower1.position.set(-0.053, 0.5, 0.3938);
  dressing.add(flower1);
  const cylinder56 = new THREE.Mesh(new THREE.CylinderGeometry(0.012, 0.016, 0.1615, 4), grassDarkMat);
  cylinder56.position.set(0, 0.0608, 0);
  flower1.add(cylinder56);
  const dodecahedron57 = new THREE.Mesh(new THREE.DodecahedronGeometry(0.042, 0), petalMat);
  dodecahedron57.position.set(0, 0.1515, 0);
  dodecahedron57.scale.set(1, 0.6, 1);
  flower1.add(dodecahedron57);
  const cone58 = new THREE.Mesh(new THREE.ConeGeometry(0.0258, 0.0646, 3), grassDarkMat);
  cone58.position.set(0.0129, 0.0226, 0);
  cone58.rotation.set(0, deg(180.0627), deg(-40));
  cone58.scale.set(1, 1, 0.4);
  flower1.add(cone58);
  const flower2 = new THREE.Group();
  flower2.name = 'flower2';
  flower2.position.set(0.2323, 0.5, 0.729);
  dressing.add(flower2);
  const cylinder60 = new THREE.Mesh(new THREE.CylinderGeometry(0.012, 0.016, 0.2186, 4), grassDarkMat);
  cylinder60.position.set(0, 0.0893, 0);
  flower2.add(cylinder60);
  const dodecahedron61 = new THREE.Mesh(new THREE.DodecahedronGeometry(0.0568, 0), petalPinkMat);
  dodecahedron61.position.set(0, 0.2086, 0);
  dodecahedron61.scale.set(1, 0.6, 1);
  flower2.add(dodecahedron61);
  const cone62 = new THREE.Mesh(new THREE.ConeGeometry(0.035, 0.0875, 3), grassDarkMat);
  cone62.position.set(0.0175, 0.0306, 0);
  cone62.rotation.set(0, deg(16.7453), deg(-40));
  cone62.scale.set(1, 1, 0.4);
  flower2.add(cone62);
  const pebble1 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0549, 0), 0.0099, 165), stoneMat);
  pebble1.name = 'pebble1';
  pebble1.position.set(-0.2674, 0.5291, 0.6302);
  pebble1.rotation.set(deg(-6.4408), deg(281.2683), deg(1.4088));
  pebble1.scale.set(1.0367, 0.4965, 1);
  dressing.add(pebble1);
  const pebble2 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.077, 0), 0.0139, 200), stoneMat);
  pebble2.name = 'pebble2';
  pebble2.position.set(-0.781, 0.5342, 0.7674);
  pebble2.rotation.set(deg(-2.0315), deg(349.122), deg(-6.6074));
  pebble2.scale.set(1.3451, 0.4923, 1);
  dressing.add(pebble2);

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

export default createGrassTile;

평점 및 댓글

—
아직 평점이 없어요 — 첫 평점을 남겨 보세요.
5
0
4
0
3
0
2
0
1
0

평점과 댓글을 남기려면 로그인하세요.

댓글 0개

아직 댓글이 없어요. 이 모델을 어떻게 사용했는지, 어떤 점이 더 나아질 수 있을지 공유해 주세요.

관련 모델

같은 카테고리 또는 비슷한 태그