1.5K tris2.45 × 1.88 × 7.22 m
Use it in code
Remix in the editorspeedboat.js · 317 lines
Self-contained ES module — only depends on three. Call the factory and add the group to your scene.
/**
* Speedboat — Codels 3D model library
* License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
* Triangles: 1,512 · Size (W × H × D): 2.45 × 1.88 × 7.22 m
*
* Usage:
* import { createSpeedboat } from './speedboat.js';
*
* const model = createSpeedboat();
* 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 createSpeedboat() {
const root = new THREE.Group();
root.name = 'Speedboat';
// Materials
const hullMat = new THREE.MeshStandardMaterial({ name: 'hull', color: 0xeef1f3, roughness: 0.35, metalness: 0.1, flatShading: true });
const accentMat = new THREE.MeshStandardMaterial({ name: 'accent', color: 0xd63f30, roughness: 0.4, metalness: 0.1, flatShading: true });
const glassMat = new THREE.MeshStandardMaterial({ name: 'glass', color: 0x25313e, roughness: 0.2, metalness: 0.35 });
const seatMat = new THREE.MeshStandardMaterial({ name: 'seat', color: 0xe5d6b8, roughness: 0.7, flatShading: true });
const deckMat = new THREE.MeshStandardMaterial({ name: 'deck', color: 0xb98a5a, roughness: 0.85, flatShading: true });
const darkMat = new THREE.MeshStandardMaterial({ name: 'dark', color: 0x2a2d33, roughness: 0.5, metalness: 0.35, flatShading: true });
const metalMat = new THREE.MeshStandardMaterial({ name: 'metal', color: 0xc3c9d0, roughness: 0.3, metalness: 0.75, flatShading: true });
const navRedMat = new THREE.MeshStandardMaterial({ name: 'navRed', color: 0xff4a3a, roughness: 0.4, emissive: 0xff2a1a, emissiveIntensity: 1.3, flatShading: true });
const navGreenMat = new THREE.MeshStandardMaterial({ name: 'navGreen', color: 0x4dff7a, roughness: 0.4, emissive: 0x1fe05a, emissiveIntensity: 1.3, flatShading: true });
// Shared geometries
const boxGeo = new THREE.BoxGeometry(0.05, 0.05, 0.22);
const boxGeo2 = new THREE.BoxGeometry(0.07, 0.06, 0.1);
const boxGeo3 = new THREE.BoxGeometry(0.035, 0.36, 0.62);
const boxGeo4 = new THREE.BoxGeometry(0.52, 0.32, 0.5);
const boxGeo5 = new THREE.BoxGeometry(0.52, 0.55, 0.14);
const boxGeo6 = new THREE.BoxGeometry(0.5, 0.08, 0.48);
const cylinderGeo = new THREE.CylinderGeometry(0.018, 0.018, 0.26, 4);
const boxGeo7 = new THREE.BoxGeometry(0.1, 0.17, 0.02);
// Parts
const hull = new THREE.Group();
hull.name = 'hull';
root.add(hull);
const bottomHull = new THREE.Mesh(lathe([[0, -3], [1.12, -3], [1.2, -2], [1.2, -0.5], [1.12, 0.8], [0.92, 1.8], [0.6, 2.6], [0.25, 3.15], [0, 3.4]], 4, -Math.PI / 2, Math.PI), hullMat);
bottomHull.name = 'bottomHull';
bottomHull.position.set(0, 0.72, 0);
bottomHull.rotation.set(deg(90), 0, 0);
bottomHull.scale.set(1, 1, 0.6);
hull.add(bottomHull);
const topsides = new THREE.Mesh(extrude([[-1.12, -3], [-1.2, -2], [-1.2, -0.5], [-1.12, 0.8], [-0.92, 1.8], [-0.6, 2.6], [-0.25, 3.15], [0, 3.4], [0.25, 3.15], [0.6, 2.6], [0.92, 1.8], [1.12, 0.8], [1.2, -0.5], [1.2, -2], [1.12, -3]], 0.5, [[[-1.028, -2.9], [-1.1, -2], [-1.1, -0.5], [-1.02, 0.8], [-0.82, 1.8], [-0.5, 2.6], [-0.15, 3.15], [-0.08, 3.22], [0.08, 3.22], [0.15, 3.15], [0.5, 2.6], [0.82, 1.8], [1.02, 0.8], [1.1, -0.5], [1.1, -2], [1.028, -2.9]]]), hullMat);
topsides.name = 'topsides';
topsides.position.set(0, 0.97, 0);
topsides.rotation.set(deg(90), 0, 0);
hull.add(topsides);
const cockpitFloor = new THREE.Mesh(extrude([[-1.028, -2.9], [-1.1, -2], [-1.1, -0.5], [-1.02, 0.8], [-0.82, 1.8], [-0.5, 2.6], [-0.15, 3.15], [-0.08, 3.22], [0.08, 3.22], [0.15, 3.15], [0.5, 2.6], [0.82, 1.8], [1.02, 0.8], [1.1, -0.5], [1.1, -2], [1.028, -2.9]], 0.06), deckMat);
cockpitFloor.name = 'cockpitFloor';
cockpitFloor.position.set(0, 0.75, 0);
cockpitFloor.rotation.set(deg(90), 0, 0);
hull.add(cockpitFloor);
const bowDeck = new THREE.Mesh(extrude([[-1.1262, 0.7], [-1.12, 0.8], [-0.92, 1.8], [-0.6, 2.6], [-0.25, 3.15], [0, 3.4], [0.25, 3.15], [0.6, 2.6], [0.92, 1.8], [1.12, 0.8], [1.1262, 0.7]], 0.56), hullMat);
bowDeck.name = 'bowDeck';
bowDeck.position.set(0, 1, 0);
bowDeck.rotation.set(deg(90), 0, 0);
hull.add(bowDeck);
const sunDeck = new THREE.Mesh(extrude([[-1.12, -3], [-1.164, -2.45], [1.164, -2.45], [1.12, -3]], 0.52), hullMat);
sunDeck.name = 'sunDeck';
sunDeck.position.set(0, 0.98, 0);
sunDeck.rotation.set(deg(90), 0, 0);
hull.add(sunDeck);
const sunPad = new THREE.Mesh(extrude([[-1.0096, -2.88], [-1.0344, -2.57], [1.0344, -2.57], [1.0096, -2.88]], 0.08), seatMat);
sunPad.name = 'sunPad';
sunPad.position.set(0, 1.28, 0);
sunPad.rotation.set(deg(90), 0, 0);
hull.add(sunPad);
const stripe = new THREE.Mesh(extrude([[-1.12, -3], [-1.2, -2], [-1.2, -0.5], [-1.12, 0.8], [-0.92, 1.8], [-0.6, 2.6], [-0.25, 3.15], [0, 3.4], [0.25, 3.15], [0.6, 2.6], [0.92, 1.8], [1.12, 0.8], [1.2, -0.5], [1.2, -2], [1.12, -3]], 0.2, [[[-1.028, -2.9], [-1.1, -2], [-1.1, -0.5], [-1.02, 0.8], [-0.82, 1.8], [-0.5, 2.6], [-0.15, 3.15], [-0.08, 3.22], [0.08, 3.22], [0.15, 3.15], [0.5, 2.6], [0.82, 1.8], [1.02, 0.8], [1.1, -0.5], [1.1, -2], [1.028, -2.9]]]), accentMat);
stripe.name = 'stripe';
stripe.position.set(0, 0.92, 0);
stripe.rotation.set(deg(90), 0, 0);
stripe.scale.set(1.02, 1.003, 1);
hull.add(stripe);
const pinstripe = new THREE.Mesh(extrude([[-1.12, -3], [-1.2, -2], [-1.2, -0.5], [-1.12, 0.8], [-0.92, 1.8], [-0.6, 2.6], [-0.25, 3.15], [0, 3.4], [0.25, 3.15], [0.6, 2.6], [0.92, 1.8], [1.12, 0.8], [1.2, -0.5], [1.2, -2], [1.12, -3]], 0.04, [[[-1.028, -2.9], [-1.1, -2], [-1.1, -0.5], [-1.02, 0.8], [-0.82, 1.8], [-0.5, 2.6], [-0.15, 3.15], [-0.08, 3.22], [0.08, 3.22], [0.15, 3.15], [0.5, 2.6], [0.82, 1.8], [1.02, 0.8], [1.1, -0.5], [1.1, -2], [1.028, -2.9]]]), darkMat);
pinstripe.name = 'pinstripe';
pinstripe.position.set(0, 1.09, 0);
pinstripe.rotation.set(deg(90), 0, 0);
pinstripe.scale.set(1.02, 1.003, 1);
hull.add(pinstripe);
const rubRail = new THREE.Mesh(extrude([[-1.12, -3], [-1.2, -2], [-1.2, -0.5], [-1.12, 0.8], [-0.92, 1.8], [-0.6, 2.6], [-0.25, 3.15], [0, 3.4], [0.25, 3.15], [0.6, 2.6], [0.92, 1.8], [1.12, 0.8], [1.2, -0.5], [1.2, -2], [1.12, -3]], 0.05, [[[-1.028, -2.9], [-1.1, -2], [-1.1, -0.5], [-1.02, 0.8], [-0.82, 1.8], [-0.5, 2.6], [-0.15, 3.15], [-0.08, 3.22], [0.08, 3.22], [0.15, 3.15], [0.5, 2.6], [0.82, 1.8], [1.02, 0.8], [1.1, -0.5], [1.1, -2], [1.028, -2.9]]]), darkMat);
rubRail.name = 'rubRail';
rubRail.position.set(0, 1.225, 0);
rubRail.rotation.set(deg(90), 0, 0);
rubRail.scale.set(1.012, 1.003, 1);
hull.add(rubRail);
const deckStripe = new THREE.Mesh(new THREE.BoxGeometry(0.22, 0.02, 2.1), accentMat);
deckStripe.name = 'deckStripe';
deckStripe.position.set(0, 1.29, 2);
hull.add(deckStripe);
const cleat = new THREE.Mesh(boxGeo, metalMat);
cleat.name = 'cleat';
cleat.position.set(0.85, 1.26, -2.75);
hull.add(cleat);
const cleat2 = new THREE.Mesh(boxGeo, metalMat);
cleat2.name = 'cleat';
cleat2.position.set(-0.85, 1.26, -2.75);
hull.add(cleat2);
const bowLightHousing = new THREE.Mesh(new THREE.BoxGeometry(0.16, 0.08, 0.14), darkMat);
bowLightHousing.name = 'bowLightHousing';
bowLightHousing.position.set(0, 1.32, 2.72);
hull.add(bowLightHousing);
const navLightPort = new THREE.Mesh(boxGeo2, navRedMat);
navLightPort.name = 'navLightPort';
navLightPort.position.set(0.045, 1.39, 2.72);
hull.add(navLightPort);
const navLightStarboard = new THREE.Mesh(boxGeo2, navGreenMat);
navLightStarboard.name = 'navLightStarboard';
navLightStarboard.position.set(-0.045, 1.39, 2.72);
hull.add(navLightStarboard);
const windshield = new THREE.Group();
windshield.name = 'windshield';
root.add(windshield);
const frontGlass = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.42, 0.035), glassMat);
frontGlass.name = 'frontGlass';
frontGlass.position.set(0, 1.44, 0.6);
frontGlass.rotation.set(deg(-32), 0, 0);
windshield.add(frontGlass);
const sideGlass = new THREE.Mesh(boxGeo3, glassMat);
sideGlass.name = 'sideGlass';
sideGlass.position.set(0.97, 1.42, 0.33);
sideGlass.rotation.set(0, deg(24), 0);
windshield.add(sideGlass);
const sideGlass2 = new THREE.Mesh(boxGeo3, glassMat);
sideGlass2.name = 'sideGlass';
sideGlass2.position.set(-0.97, 1.42, 0.33);
sideGlass2.rotation.set(0, deg(-24), 0);
windshield.add(sideGlass2);
const frame = new THREE.Mesh(new THREE.BoxGeometry(1.52, 0.04, 0.05), darkMat);
frame.name = 'frame';
frame.position.set(0, 1.62, 0.49);
windshield.add(frame);
const cockpit = new THREE.Group();
cockpit.name = 'cockpit';
root.add(cockpit);
const console = new THREE.Mesh(new THREE.BoxGeometry(0.62, 0.55, 0.42), hullMat);
console.name = 'console';
console.position.set(-0.5, 1.055, 0.35);
cockpit.add(console);
const dash = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.03, 0.3), darkMat);
dash.name = 'dash';
dash.position.set(-0.5, 1.36, 0.32);
dash.rotation.set(deg(-25), 0, 0);
cockpit.add(dash);
const steeringWheel = new THREE.Group();
steeringWheel.name = 'steeringWheel';
steeringWheel.position.set(-0.5, 1.4, 0.12);
steeringWheel.rotation.set(deg(-35), 0, 0);
cockpit.add(steeringWheel);
const rim = new THREE.Mesh(new THREE.TorusGeometry(0.17, 0.025, 4, 12), darkMat);
rim.name = 'rim';
steeringWheel.add(rim);
const box27 = new THREE.Mesh(new THREE.BoxGeometry(0.32, 0.03, 0.03), darkMat);
steeringWheel.add(box27);
const seatDriver = new THREE.Mesh(boxGeo4, seatMat);
seatDriver.name = 'seatDriver';
seatDriver.position.set(-0.5, 0.94, -0.45);
cockpit.add(seatDriver);
const backrestDriver = new THREE.Mesh(boxGeo5, seatMat);
backrestDriver.name = 'backrestDriver';
backrestDriver.position.set(-0.5, 1.33, -0.72);
backrestDriver.rotation.set(deg(-12), 0, 0);
cockpit.add(backrestDriver);
const box30 = new THREE.Mesh(boxGeo6, accentMat);
box30.position.set(-0.5, 1.14, -0.45);
cockpit.add(box30);
const seatPassenger = new THREE.Mesh(boxGeo4, seatMat);
seatPassenger.name = 'seatPassenger';
seatPassenger.position.set(0.5, 0.94, -0.45);
cockpit.add(seatPassenger);
const backrestPassenger = new THREE.Mesh(boxGeo5, seatMat);
backrestPassenger.name = 'backrestPassenger';
backrestPassenger.position.set(0.5, 1.33, -0.72);
backrestPassenger.rotation.set(deg(-12), 0, 0);
cockpit.add(backrestPassenger);
const box33 = new THREE.Mesh(boxGeo6, accentMat);
box33.position.set(0.5, 1.14, -0.45);
cockpit.add(box33);
const rearBench = new THREE.Mesh(new THREE.BoxGeometry(2, 0.36, 0.52), seatMat);
rearBench.name = 'rearBench';
rearBench.position.set(0, 0.96, -2.1);
cockpit.add(rearBench);
const box35 = new THREE.Mesh(new THREE.BoxGeometry(2, 0.08, 0.5), accentMat);
box35.position.set(0, 1.18, -2.1);
cockpit.add(box35);
const bowRail = new THREE.Group();
bowRail.name = 'bowRail';
root.add(bowRail);
const rail = new THREE.Mesh(tube([[-0.82, 1.52, 1], [-0.55, 1.52, 2.2], [-0.2, 1.52, 2.95], [0, 1.52, 3.1], [0.2, 1.52, 2.95], [0.55, 1.52, 2.2], [0.82, 1.52, 1]], 0.022, 16, 4), metalMat);
rail.name = 'rail';
bowRail.add(rail);
const cylinder38 = new THREE.Mesh(cylinderGeo, metalMat);
cylinder38.position.set(-0.82, 1.4, 1);
bowRail.add(cylinder38);
const cylinder39 = new THREE.Mesh(cylinderGeo, metalMat);
cylinder39.position.set(-0.55, 1.4, 2.2);
bowRail.add(cylinder39);
const cylinder40 = new THREE.Mesh(cylinderGeo, metalMat);
cylinder40.position.set(0, 1.4, 3.1);
bowRail.add(cylinder40);
const cylinder41 = new THREE.Mesh(cylinderGeo, metalMat);
cylinder41.position.set(0.55, 1.4, 2.2);
bowRail.add(cylinder41);
const cylinder42 = new THREE.Mesh(cylinderGeo, metalMat);
cylinder42.position.set(0.82, 1.4, 1);
bowRail.add(cylinder42);
const motor = new THREE.Group();
motor.name = 'motor';
motor.position.set(0, 1.24, -3.08);
root.add(motor);
const clampBracket = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.24, 0.18), darkMat);
clampBracket.name = 'clampBracket';
clampBracket.position.set(0, -0.06, 0.05);
motor.add(clampBracket);
const cowling = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.5, 0.58), darkMat);
cowling.name = 'cowling';
cowling.position.set(0, 0.25, -0.34);
motor.add(cowling);
const cowlTop = new THREE.Mesh(new THREE.SphereGeometry(0.3, 8, 3, 0, Math.PI * 2, 0, Math.PI / 2), darkMat);
cowlTop.name = 'cowlTop';
cowlTop.position.set(0, 0.5, -0.34);
cowlTop.scale.set(0.66, 0.45, 0.96);
motor.add(cowlTop);
const cowlStripe = new THREE.Mesh(new THREE.BoxGeometry(0.41, 0.07, 0.59), accentMat);
cowlStripe.name = 'cowlStripe';
cowlStripe.position.set(0, 0.36, -0.34);
motor.add(cowlStripe);
const leg = new THREE.Mesh(new THREE.BoxGeometry(0.16, 0.97, 0.26), darkMat);
leg.name = 'leg';
leg.position.set(0, -0.485, -0.3);
motor.add(leg);
const cavitationPlate = new THREE.Mesh(new THREE.BoxGeometry(0.38, 0.03, 0.5), darkMat);
cavitationPlate.name = 'cavitationPlate';
cavitationPlate.position.set(0, -0.77, -0.35);
motor.add(cavitationPlate);
const gearHousing = new THREE.Mesh(new THREE.CapsuleGeometry(0.09, 0.3, 3, 8), darkMat);
gearHousing.name = 'gearHousing';
gearHousing.position.set(0, -0.97, -0.33);
gearHousing.rotation.set(deg(90), 0, 0);
motor.add(gearHousing);
const skeg = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.2, 0.2), darkMat);
skeg.name = 'skeg';
skeg.position.set(0, -1.14, -0.36);
motor.add(skeg);
const propeller = new THREE.Group();
propeller.name = 'propeller';
propeller.position.set(0, -0.97, -0.62);
motor.add(propeller);
const propCap = new THREE.Mesh(new THREE.ConeGeometry(0.06, 0.12, 6), metalMat);
propCap.name = 'propCap';
propCap.position.set(0, 0, -0.05);
propCap.rotation.set(deg(-90), 0, 0);
propeller.add(propCap);
const blade1 = new THREE.Group();
blade1.name = 'blade1';
propeller.add(blade1);
const box55 = new THREE.Mesh(boxGeo7, metalMat);
box55.position.set(0, 0.11, 0);
box55.rotation.set(0, deg(25), 0);
blade1.add(box55);
const blade2 = new THREE.Group();
blade2.name = 'blade2';
blade2.rotation.set(0, 0, deg(120));
propeller.add(blade2);
const box57 = new THREE.Mesh(boxGeo7, metalMat);
box57.position.set(0, 0.11, 0);
box57.rotation.set(0, deg(25), 0);
blade2.add(box57);
const blade3 = new THREE.Group();
blade3.name = 'blade3';
blade3.rotation.set(0, 0, deg(240));
propeller.add(blade3);
const box59 = new THREE.Mesh(boxGeo7, metalMat);
box59.position.set(0, 0.11, 0);
box59.rotation.set(0, deg(25), 0);
blade3.add(box59);
root.traverse((o) => {
if (o.isMesh) {
o.castShadow = true;
o.receiveShadow = true;
}
});
return root;
}
export default createSpeedboat;
Ratings & comments
0 comments
No comments yet. Share how you used this model or what could be better.
Related models
Same category or similar tags









