# Fish

> A plump, cartoon low-poly fish with a faceted lathe body, pale belly, forked tail, swept-back dorsal fin, side fins and big googly eyes. Toggle white stripes for a clownfish look. Centered on its origin so it can swim freely; tail and fins are pivoted groups for wiggle animations in ocean, aquarium and fishing games.

![Fish preview](https://codels.download/m/fish.png)

Multi-view sheet (3/4, front, side, top): https://codels.download/m/fish.sheet.png

## Facts

- Category: Characters › Animals
- Size (W × H × D): 0.26 × 0.36 × 0.50 m — units are meters, +Y up, front faces +Z
- Origin: center of the model (it floats — pickups, planets)
- Triangles: 668 · Meshes: 11 · Materials: 5
- License: CC0-1.0 (Codels built-in)
- Tags: fish, sea-life, cartoon, low-poly, stylized, nautical, tropical, underwater, ocean, river, adventure, party-game, animal, collectible, npc, game-ready, mobile-friendly, parametric, color-blue, color-orange, size-small, poly-low
- Page: https://codels.download/models/fish

## Files

| Format | URL |
|---|---|
| three.js ES module (`createFish()`) | https://codels.download/m/fish.js |
| React Three Fiber component (`<Fish />`) | https://codels.download/m/fish.jsx |
| Instancing module (`createFishInstances(count, place)`, one draw call per material) | https://codels.download/m/fish.instanced.js |
| Standalone HTML demo | https://codels.download/m/fish.html |
| glTF binary | https://codels.download/m/fish.glb |
| glTF binary, Meshopt-compressed (2–3× smaller; GLTFLoader needs `setMeshoptDecoder(MeshoptDecoder)`) | https://codels.download/m/fish.glb?compress=meshopt |
| glTF JSON | https://codels.download/m/fish.gltf |
| OBJ (vertex colors) + MTL | https://codels.download/m/fish.obj · https://codels.download/m/fish.mtl |
| STL | https://codels.download/m/fish.stl |
| CodelSpec JSON | https://codels.download/m/fish.json |

## Parameters

Append them as query parameters to any URL above to get a customized variant (e.g. `?body=...`).

| Param | Type | Range / options | Default | Current |
|---|---|---|---|---|
| `body` | color | #rrggbb | #3fa4e0 | #3fa4e0 |
| `fins` | color | #rrggbb | #f2b13c | #f2b13c |
| `stripes` | boolean | true \| false | false | false |
| `size` | number | 0.15 – 1.2 | 0.4 | 0.4 |

## Parts

- fish (group)
  - body (group)
    - bodyShell (lathe · body)
    - bellyShell (lathe · belly)
    - eyeL (sphere · eyeWhite)
    - pupilL (sphere · pupil)
    - eyeR (sphere · eyeWhite)
    - pupilR (sphere · pupil)
  - tail (group)
    - tailFin (extrude · fin)
  - dorsalFin (group)
    - dorsal (extrude · fin)
  - ventralFin (group)
    - ventral (extrude · fin)
  - finL (group)
    - pectoralL (extrude · fin)
  - finR (group)
    - pectoralR (extrude · fin)

## How to use

Save the code below as `fish.js`, then:

```js
import { createFish } from './fish.js';
const model = createFish();
scene.add(model);
```

## Code

```js
/**
 * Fish — Codels 3D model library (https://codels.download/models/fish)
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the model (it floats — pickups, planets)
 * Triangles: 668 · Size (W × H × D): 0.26 × 0.36 × 0.50 m
 *
 * Usage:
 *   import { createFish } from './fish.js';
 *   
 *   const model = createFish();
 *   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);
}

export function createFish() {
  const root = new THREE.Group();
  root.name = 'Fish';

  // Materials
  const bodyMat = new THREE.MeshStandardMaterial({ name: 'body', color: 0x3fa4e0, roughness: 0.55, flatShading: true });
  const bellyMat = new THREE.MeshStandardMaterial({ name: 'belly', color: 0xf3ead6, roughness: 0.6, flatShading: true });
  const finMat = new THREE.MeshStandardMaterial({ name: 'fin', color: 0xf2b13c, roughness: 0.6, flatShading: true });
  const eyeWhiteMat = new THREE.MeshStandardMaterial({ name: 'eyeWhite', color: 0xffffff, roughness: 0.3, flatShading: true });
  const pupilMat = new THREE.MeshStandardMaterial({ name: 'pupil', color: 0x161417, roughness: 0.2, flatShading: true });

  // Shared geometries
  const sphereGeo = new THREE.SphereGeometry(0.036, 8, 6);
  const sphereGeo2 = new THREE.SphereGeometry(0.02, 8, 6);
  const extrudeGeo = extrude([[0, 0.02], [0.07, 0.035], [0.08, -0.01], [0, -0.02]], 0.012);

  // Parts
  const fish = new THREE.Group();
  fish.name = 'fish';
  fish.position.set(0, -0.008, 0.07);
  root.add(fish);
  const body = new THREE.Group();
  body.name = 'body';
  fish.add(body);
  const bodyShell = new THREE.Mesh(lathe([[0, -0.2], [0.04, -0.18], [0.085, -0.11], [0.115, -0.02], [0.118, 0.04], [0.1, 0.11], [0.065, 0.165], [0, 0.195]], 10), bodyMat);
  bodyShell.name = 'bodyShell';
  bodyShell.rotation.set(deg(90), 0, 0);
  bodyShell.scale.set(0.62, 1, 1.12);
  body.add(bodyShell);
  const bellyShell = new THREE.Mesh(lathe([[0, -0.184], [0.0388, -0.1656], [0.0825, -0.1012], [0.1116, -0.0184], [0.1145, 0.0368], [0.0842, 0.1012], [0.0471, 0.1518], [0, 0.1794]], 10), bellyMat);
  bellyShell.name = 'bellyShell';
  bellyShell.position.set(0, -0.026, -0.006);
  bellyShell.rotation.set(deg(90), 0, 0);
  bellyShell.scale.set(0.5952, 1, 1.064);
  body.add(bellyShell);
  const eyeL = new THREE.Mesh(sphereGeo, eyeWhiteMat);
  eyeL.name = 'eyeL';
  eyeL.position.set(0.056, 0.03, 0.11);
  eyeL.scale.set(0.7, 1, 1);
  body.add(eyeL);
  const pupilL = new THREE.Mesh(sphereGeo2, pupilMat);
  pupilL.name = 'pupilL';
  pupilL.position.set(0.076, 0.032, 0.116);
  pupilL.scale.set(0.6, 1, 1);
  body.add(pupilL);
  const eyeR = new THREE.Mesh(sphereGeo, eyeWhiteMat);
  eyeR.name = 'eyeR';
  eyeR.position.set(-0.056, 0.03, 0.11);
  eyeR.scale.set(0.7, 1, 1);
  body.add(eyeR);
  const pupilR = new THREE.Mesh(sphereGeo2, pupilMat);
  pupilR.name = 'pupilR';
  pupilR.position.set(-0.076, 0.032, 0.116);
  pupilR.scale.set(0.6, 1, 1);
  body.add(pupilR);
  const tail = new THREE.Group();
  tail.name = 'tail';
  tail.position.set(0, 0, -0.17);
  fish.add(tail);
  const tailFin = new THREE.Mesh(extrude([[0, 0.03], [0.1, 0.12], [0.135, 0.11], [0.085, 0], [0.135, -0.11], [0.1, -0.12], [0, -0.03]], 0.022), finMat);
  tailFin.name = 'tailFin';
  tailFin.rotation.set(0, deg(90), 0);
  tail.add(tailFin);
  const dorsalFin = new THREE.Group();
  dorsalFin.name = 'dorsalFin';
  dorsalFin.position.set(0, 0.1088, -0.01);
  fish.add(dorsalFin);
  const dorsal = new THREE.Mesh(extrude([[-0.07, 0], [0.1, 0], [0.07, 0.07], [-0.01, 0.1]], 0.018), finMat);
  dorsal.name = 'dorsal';
  dorsal.rotation.set(0, deg(90), 0);
  dorsalFin.add(dorsal);
  const ventralFin = new THREE.Group();
  ventralFin.name = 'ventralFin';
  ventralFin.position.set(0, -0.1032, -0.04);
  fish.add(ventralFin);
  const ventral = new THREE.Mesh(extrude([[-0.03, 0], [0.06, 0], [0.06, -0.05]], 0.014), finMat);
  ventral.name = 'ventral';
  ventral.rotation.set(0, deg(90), 0);
  ventralFin.add(ventral);
  const finL = new THREE.Group();
  finL.name = 'finL';
  finL.position.set(0.0685, -0.035, 0.03);
  finL.rotation.set(0, deg(-40), deg(25));
  fish.add(finL);
  const pectoralL = new THREE.Mesh(extrudeGeo, finMat);
  pectoralL.name = 'pectoralL';
  pectoralL.rotation.set(0, deg(90), 0);
  finL.add(pectoralL);
  const finR = new THREE.Group();
  finR.name = 'finR';
  finR.position.set(-0.0685, -0.035, 0.03);
  finR.rotation.set(0, deg(40), deg(-25));
  fish.add(finR);
  const pectoralR = new THREE.Mesh(extrudeGeo, finMat);
  pectoralR.name = 'pectoralR';
  pectoralR.rotation.set(0, deg(90), 0);
  finR.add(pectoralR);

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

export default createFish;
```

---
Codels — 3D models as code for three.js. Search more: https://codels.download/api/v1/models?q=<query> · Guide: https://codels.download/llms.txt
