1,1 k tris0,93 × 0,61 × 4,92 m
Utiliser dans le code
Remixer dans l’éditeurcanoe.js · 216 lignes
Module ES autonome — ne dépend que de three. Appelez la fonction factory et ajoutez le groupe à votre scène.
/**
* 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;
Notes et commentaires
0 commentaire
Aucun commentaire pour l’instant. Dites comment vous avez utilisé ce modèle ou ce qui pourrait être amélioré.
Modèles similaires
Même catégorie ou étiquettes proches









