Codels
1,1 mil tris0,93 × 0,61 × 4,92 m
Módulo ES autocontido — depende apenas do three. Chame a factory e adicione o grupo à sua cena.
/**
 * Canoe — Codels 3D model library
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
 * Triangles: 1,128 · Size (W × H × D): 0.93 × 0.61 × 4.92 m
 *
 * Usage:
 *   import { createCanoe } from './canoe.js';
 *   
 *   const model = createCanoe();
 *   model.position.set(0, 0, 0);
 *   scene.add(model);
 */
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);
}

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

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

export function createCanoe() {
  const root = new THREE.Group();
  root.name = 'Canoe';

  // Materials
  const paintMat = new THREE.MeshStandardMaterial({ name: 'paint', color: 0xc4412f, roughness: 0.35, metalness: 0.05, flatShading: true });
  const woodMat = new THREE.MeshStandardMaterial({ name: 'wood', color: 0xc99660, roughness: 0.75, flatShading: true });
  const darkWoodMat = new THREE.MeshStandardMaterial({ name: 'darkWood', color: 0x7a5233, roughness: 0.8, flatShading: true });
  const caneMat = new THREE.MeshStandardMaterial({ name: 'cane', color: 0xdcc28e, roughness: 0.9, flatShading: true });

  // Shared geometries
  const extrudeGeo = extrude([[0, -0.03], [0.7326, -0.03], [0.7326, 0.082], [0, 0.01]], 0.018);
  const extrudeGeo2 = extrude([[0, -0.03], [0.4982, -0.03], [0.4982, 0.1982], [0, 0.082]], 0.018);
  const cylinderGeo = new THREE.CylinderGeometry(0.018, 0.018, 1, 6);
  const extrudeGeo3 = extrude([[0, 0], [0.1, 0.06], [0.11, 0.42], [0.06, 0.52], [-0.06, 0.52], [-0.11, 0.42], [-0.1, 0.06]], 0.02);
  const boxGeo = new THREE.BoxGeometry(0.11, 0.035, 0.04);

  // Parts
  const hull = new THREE.Group();
  hull.name = 'hull';
  root.add(hull);
  const hullShell = new THREE.Mesh(lathe([[0, -2.4], [0.16, -2.208], [0.32, -1.728], [0.43, -0.912], [0.45, 0], [0.43, 0.912], [0.32, 1.728], [0.16, 2.208], [0, 2.4]], 10, -Math.PI / 2, Math.PI), paintMat);
  hullShell.name = 'hullShell';
  hullShell.position.set(0, 0.3375, 0);
  hullShell.rotation.set(deg(90), 0, 0);
  hullShell.scale.set(1, 1, 0.75);
  hull.add(hullShell);
  const hullInterior = new THREE.Mesh(lathe([[0, 2.4], [0.135, 2.208], [0.295, 1.728], [0.405, 0.912], [0.425, 0], [0.405, -0.912], [0.295, -1.728], [0.135, -2.208], [0, -2.4]], 10, -Math.PI / 2, Math.PI), woodMat);
  hullInterior.name = 'hullInterior';
  hullInterior.position.set(0, 0.3395, 0);
  hullInterior.rotation.set(deg(90), 0, 0);
  hullInterior.scale.set(1, 1, 0.7275);
  hull.add(hullInterior);
  const gunwaleL = new THREE.Mesh(tube([[0.025, 0.5377, -2.364], [0.171, 0.4775, -2.16], [0.251, 0.4215, -1.92], [0.3376, 0.3675, -1.56], [0.4185, 0.3495, -0.96], [0.445, 0.3495, 0], [0.4185, 0.3495, 0.96], [0.3376, 0.3675, 1.56], [0.251, 0.4215, 1.92], [0.171, 0.4775, 2.16], [0.025, 0.5377, 2.364]], 0.022, 28, 4), darkWoodMat);
  gunwaleL.name = 'gunwaleL';
  hull.add(gunwaleL);
  const sheerPanel = new THREE.Mesh(extrudeGeo, paintMat);
  sheerPanel.name = 'sheerPanel';
  sheerPanel.position.set(0.3912, 0.3375, 1.2);
  sheerPanel.rotation.set(0, deg(-100.6332), 0);
  hull.add(sheerPanel);
  const sheerPanel2 = new THREE.Mesh(extrudeGeo2, paintMat);
  sheerPanel2.name = 'sheerPanel';
  sheerPanel2.position.set(0.256, 0.3375, 1.92);
  sheerPanel2.rotation.set(0, deg(-116.9765), 0);
  hull.add(sheerPanel2);
  const sheerPanel3 = new THREE.Mesh(extrudeGeo, paintMat);
  sheerPanel3.name = 'sheerPanel';
  sheerPanel3.position.set(0.3912, 0.3375, -1.2);
  sheerPanel3.rotation.set(0, deg(100.6332), 0);
  hull.add(sheerPanel3);
  const sheerPanel4 = new THREE.Mesh(extrudeGeo2, paintMat);
  sheerPanel4.name = 'sheerPanel';
  sheerPanel4.position.set(0.256, 0.3375, -1.92);
  sheerPanel4.rotation.set(0, deg(116.9765), 0);
  hull.add(sheerPanel4);
  const gunwaleR = new THREE.Mesh(tube([[-0.025, 0.5377, -2.364], [-0.171, 0.4775, -2.16], [-0.251, 0.4215, -1.92], [-0.3376, 0.3675, -1.56], [-0.4185, 0.3495, -0.96], [-0.445, 0.3495, 0], [-0.4185, 0.3495, 0.96], [-0.3376, 0.3675, 1.56], [-0.251, 0.4215, 1.92], [-0.171, 0.4775, 2.16], [-0.025, 0.5377, 2.364]], 0.022, 28, 4), darkWoodMat);
  gunwaleR.name = 'gunwaleR';
  hull.add(gunwaleR);
  const sheerPanel5 = new THREE.Mesh(extrudeGeo, paintMat);
  sheerPanel5.name = 'sheerPanel';
  sheerPanel5.position.set(-0.3912, 0.3375, 1.2);
  sheerPanel5.rotation.set(0, deg(-79.3668), 0);
  hull.add(sheerPanel5);
  const sheerPanel6 = new THREE.Mesh(extrudeGeo2, paintMat);
  sheerPanel6.name = 'sheerPanel';
  sheerPanel6.position.set(-0.256, 0.3375, 1.92);
  sheerPanel6.rotation.set(0, deg(-63.0235), 0);
  hull.add(sheerPanel6);
  const sheerPanel7 = new THREE.Mesh(extrudeGeo, paintMat);
  sheerPanel7.name = 'sheerPanel';
  sheerPanel7.position.set(-0.3912, 0.3375, -1.2);
  sheerPanel7.rotation.set(0, deg(79.3668), 0);
  hull.add(sheerPanel7);
  const sheerPanel8 = new THREE.Mesh(extrudeGeo2, paintMat);
  sheerPanel8.name = 'sheerPanel';
  sheerPanel8.position.set(-0.256, 0.3375, -1.92);
  sheerPanel8.rotation.set(0, deg(63.0235), 0);
  hull.add(sheerPanel8);
  const bowStem = new THREE.Mesh(extrude([[1.85, 0.3175], [2.36, 0.2375], [2.46, 0.4575], [2.39, 0.6075], [2.26, 0.5775]], 0.05), paintMat);
  bowStem.name = 'bowStem';
  bowStem.rotation.set(0, deg(-90), 0);
  hull.add(bowStem);
  const bowDeck = new THREE.Mesh(extrude([[-0.208, 2.064], [0.208, 2.064], [0, 2.364]], 0.03), woodMat);
  bowDeck.name = 'bowDeck';
  bowDeck.position.set(0, 0.4854, 0);
  bowDeck.rotation.set(deg(90), 0, 0);
  hull.add(bowDeck);
  const sternStem = new THREE.Mesh(extrude([[-1.85, 0.3175], [-2.36, 0.2375], [-2.46, 0.4575], [-2.39, 0.6075], [-2.26, 0.5775]], 0.05), paintMat);
  sternStem.name = 'sternStem';
  sternStem.rotation.set(0, deg(-90), 0);
  hull.add(sternStem);
  const sternDeck = new THREE.Mesh(extrude([[-0.208, -2.064], [0.208, -2.064], [0, -2.364]], 0.03), woodMat);
  sternDeck.name = 'sternDeck';
  sternDeck.position.set(0, 0.4854, 0);
  sternDeck.rotation.set(deg(90), 0, 0);
  hull.add(sternDeck);
  const bowSeatFront = new THREE.Mesh(new THREE.BoxGeometry(0.6121, 0.04, 0.06), darkWoodMat);
  bowSeatFront.name = 'bowSeatFront';
  bowSeatFront.position.set(0, 0.2775, 1.512);
  hull.add(bowSeatFront);
  const bowSeatBack = new THREE.Mesh(new THREE.BoxGeometry(0.5329, 0.04, 0.06), darkWoodMat);
  bowSeatBack.name = 'bowSeatBack';
  bowSeatBack.position.set(0, 0.2775, 1.752);
  hull.add(bowSeatBack);
  const bowSeatCane = new THREE.Mesh(new THREE.BoxGeometry(0.4712, 0.02, 0.22), caneMat);
  bowSeatCane.name = 'bowSeatCane';
  bowSeatCane.position.set(0, 0.2825, 1.632);
  hull.add(bowSeatCane);
  const sternSeatFront = new THREE.Mesh(new THREE.BoxGeometry(0.4632, 0.04, 0.06), darkWoodMat);
  sternSeatFront.name = 'sternSeatFront';
  sternSeatFront.position.set(0, 0.2775, -1.848);
  hull.add(sternSeatFront);
  const sternSeatBack = new THREE.Mesh(new THREE.BoxGeometry(0.5846, 0.04, 0.06), darkWoodMat);
  sternSeatBack.name = 'sternSeatBack';
  sternSeatBack.position.set(0, 0.2775, -1.608);
  hull.add(sternSeatBack);
  const sternSeatCane = new THREE.Mesh(new THREE.BoxGeometry(0.4043, 0.02, 0.22), caneMat);
  sternSeatCane.name = 'sternSeatCane';
  sternSeatCane.position.set(0, 0.2825, -1.728);
  hull.add(sternSeatCane);
  const yoke = new THREE.Mesh(new THREE.BoxGeometry(0.88, 0.035, 0.08), darkWoodMat);
  yoke.name = 'yoke';
  yoke.position.set(0, 0.3225, 0.05);
  hull.add(yoke);
  const yokePad = new THREE.Mesh(new THREE.BoxGeometry(0.32, 0.05, 0.1), darkWoodMat);
  yokePad.name = 'yokePad';
  yokePad.position.set(0, 0.3475, 0.05);
  hull.add(yokePad);
  const paddle1 = new THREE.Group();
  paddle1.name = 'paddle1';
  paddle1.position.set(0.09, 0.085, 0.15);
  paddle1.rotation.set(0, deg(3), 0);
  root.add(paddle1);
  const shaft = new THREE.Mesh(cylinderGeo, woodMat);
  shaft.name = 'shaft';
  shaft.position.set(0, 0, 0.1);
  shaft.rotation.set(deg(90), 0, 0);
  paddle1.add(shaft);
  const blade = new THREE.Mesh(extrudeGeo3, woodMat);
  blade.name = 'blade';
  blade.position.set(0, 0, -0.4);
  blade.rotation.set(deg(-90), 0, 0);
  paddle1.add(blade);
  const grip = new THREE.Mesh(boxGeo, darkWoodMat);
  grip.name = 'grip';
  grip.position.set(0, 0, 0.62);
  paddle1.add(grip);
  const paddle2 = new THREE.Group();
  paddle2.name = 'paddle2';
  paddle2.position.set(-0.09, 0.09, -0.15);
  paddle2.rotation.set(0, deg(183), 0);
  root.add(paddle2);
  const shaft2 = new THREE.Mesh(cylinderGeo, woodMat);
  shaft2.name = 'shaft';
  shaft2.position.set(0, 0, 0.1);
  shaft2.rotation.set(deg(90), 0, 0);
  paddle2.add(shaft2);
  const blade2 = new THREE.Mesh(extrudeGeo3, woodMat);
  blade2.name = 'blade';
  blade2.position.set(0, 0, -0.4);
  blade2.rotation.set(deg(-90), 0, 0);
  paddle2.add(blade2);
  const grip2 = new THREE.Mesh(boxGeo, darkWoodMat);
  grip2.name = 'grip';
  grip2.position.set(0, 0, 0.62);
  paddle2.add(grip2);

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

export default createCanoe;

Avaliações e comentários

—
Nenhuma avaliação ainda — seja o primeiro.
5
0
4
0
3
0
2
0
1
0

Entre para avaliar e comentar.

0 comentário

Nenhum comentário ainda. Conte como você usou este modelo ou o que poderia melhorar.

Modelos relacionados

Mesma categoria ou tags parecidas