npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ovium/models

v0.5.1

Published

Code-generated, customizable, animatable low-poly 3D models for the web.

Downloads

955

Readme

@ovium/models

Low-poly 3D models for Three.js, built entirely in code. No .glb, no .fbx, no Blender exports sitting in your repo — every model here is a function that builds a THREE.Group at runtime, made of separately editable parts.

Right now there's one model: a car. More are coming, but the car is a good reference for how everything in this package works, so this doc walks through it in full.

Install

npm install @ovium/models three

three is a peer dependency — you bring your own version (anything >=0.150.0 works), Ovium doesn't ship its own copy or force a specific one on you.

The basic idea

Instead of importing a static asset, you call a function that returns the model plus a small controller object for interacting with it:

import { createCar } from "@ovium/models";

const car = createCar();
scene.add(car.object3D);

car.object3D is a normal THREE.Group. Add it to your scene, position it, scale it, whatever — it behaves like anything else you'd put in a Three.js scene, because that's exactly what it is.

Customizing on creation

Pass options into the factory function to set the starting state:

const car = createCar({
  body: { color: "#00ff88", roughness: 0.3, metalness: 0.7 },
  wheels: { color: "#1a1a1a" },
  doors: { color: "#00ff88" },
  lights: { on: true, color: "#fff4d6" },
  interior: { seatColor: "#222", dashColor: "#111" },
});

Every field is optional. Anything you don't specify falls back to a sensible default (currently a red paint job with black wheels, if you're curious).

Changing things after the fact

car.set() patches any subset of those same options on a model that's already in your scene — no need to remove and recreate it:

button.addEventListener("click", () => {
  car.set({ body: { color: "#ff00aa" } });
});

Only the fields you pass get touched. Everything else on the model stays as it was.

Getting at individual parts

car.parts gives you direct references to the five sub-groups that make up the car, in case you want to do something the API doesn't cover yet — attach something to the roof, hide the interior, whatever:

car.parts.doors.visible = false;

The five parts are body, wheels, doors, lights, and interior.

Animations

car.animate has a handful of named behaviors built in:

car.animate.openDoor("left");        // animates over 0.6s by default
car.animate.openDoor("left", 1.2);   // or pass your own duration in seconds
car.animate.closeDoor("right");
car.animate.toggleLights();

Making it drivable

This is the one that takes a bit more wiring, because driving needs a per-frame update. Call car.animate.drive() inside your own render loop with a throttle/steer input and the frame's delta time:

const input = { throttle: 0, steer: 0, brake: false };

window.addEventListener("keydown", (e) => {
  if (e.key === "ArrowUp") input.throttle = 1;
  if (e.key === "ArrowDown") input.throttle = -1;
  if (e.key === "ArrowLeft") input.steer = -1;
  if (e.key === "ArrowRight") input.steer = 1;
});

window.addEventListener("keyup", (e) => {
  if (e.key === "ArrowUp" || e.key === "ArrowDown") input.throttle = 0;
  if (e.key === "ArrowLeft" || e.key === "ArrowRight") input.steer = 0;
});

const clock = new THREE.Clock();

function tick() {
  requestAnimationFrame(tick);
  const delta = clock.getDelta();
  car.animate.drive(input, delta);
  renderer.render(scene, camera);
}
tick();

This gives you throttle-based acceleration, braking, and steering that scales with speed. It's intentionally lightweight — there's no rigid-body physics under the hood, no collision with other objects. If you need real physics (colliding with obstacles, jumps, that kind of thing), wire the car into Cannon-es or Rapier yourself and use car.object3D.position / .rotation as the thing your physics engine drives instead.

Terrain-following suspension

If your car needs to sit correctly on uneven ground instead of a flat plane, hand it your terrain mesh once:

car.animate.enableTerrainFollow(terrainMesh);

Then call car.update(delta) every frame (alongside drive(), not instead of it):

function tick() {
  requestAnimationFrame(tick);
  const delta = clock.getDelta();
  car.animate.drive(input, delta);
  car.update(delta);
  renderer.render(scene, camera);
}

Each wheel raycasts down onto your terrain mesh and the body rides a spring-damped average of all four contact points, so it doesn't just clip through hills or float above dips. One thing worth knowing: this assumes your terrain is a PlaneGeometry rotated -90° on the X axis, which is the normal way to lay flat ground in Three.js. If your terrain is built some other way, the height sampling might not line up correctly.

Cleaning up

If you're removing the car from the scene for good (not just hiding it), call dispose() so the geometries and materials actually get freed instead of sitting around in GPU memory:

scene.remove(car.object3D);
car.dispose();

Full options reference

| Path | Type | Default | |---|---|---| | body.color | string (hex) | #c81e3a | | body.roughness | number 0–1 | 0.4 | | body.metalness | number 0–1 | 0.6 | | body.length | number (meters) | 4.2 | | body.width | number (meters) | 1.8 | | body.height | number (meters) | 1.1 | | wheels.radius | number (meters) | 0.38 | | wheels.width | number (meters) | 0.28 | | wheels.color | string (hex) | #111111 | | doors.open | boolean | false | | doors.color | string (hex) | #c81e3a | | lights.on | boolean | true | | lights.color | string (hex) | #fff4d6 | | lights.intensity | number | 1.2 | | interior.seatColor | string (hex) | #1a1a1a | | interior.dashColor | string (hex) | #222222 |

A note on React

This package has no React dependency and doesn't assume any framework — it's plain Three.js, so it works the same whether you're in a Vite app, a plain <script type="module"> tag, or anything else that can run Three.js. There's no official React/R3F wrapper published yet. If that changes, it'll show up as a separate @ovium/react package rather than baked into this one, so this package will stay dependency-light either way.

Status

This is early — 0.0.x versions, one model, API might still shift a bit as more models get added and patterns get ironed out. If something breaks or feels off, that's useful to know about.

License

Copyrighted, all rights reserved — see the main project for details. Not open source (yet, at least).

The vehicle catalog (Kenney Car Kit)

Alongside the procedural car above, this package includes 25 real 3D vehicle models — sedans, SUVs, trucks, an ambulance, a fire truck, go-karts, tractors, and more — sourced from Kenney's Car Kit (CC0 licensed, free for commercial use).

These work differently from the procedural car: instead of building geometry from primitives at runtime, each one loads a real .glb file. That means these are async — call the function and await it, or use .then():

import { createSedan } from "@ovium/models";

const sedan = await createSedan({ bodyColor: "#2e5fd9", wheelColor: "#111111" });
scene.add(sedan.object3D);

Every vehicle in this batch shares the same two options — bodyColor and wheelColor — and the same drive(input, delta) animation as the procedural car, called the same way in your render loop:

function tick() {
  requestAnimationFrame(tick);
  const delta = clock.getDelta();
  sedan.animate.drive(input, delta);
  renderer.render(scene, camera);
}

Full list: createSedan, createSportsSedan, createSportsHatchback, createSuv, createLuxurySuv, createTaxi, createVan, createTruck, createFlatbedTruck, createDeliveryTruck, createFlatDeliveryTruck, createAmbulance, createFireTruck, createPoliceCar, createGarbageTruck, createTractor, createPoliceTractor, createShovelTractor, createRaceCar, createFutureRaceCar, createKartOobi, createKartOodi, createKartOoli, createKartOopi, createKartOozi.

Note: the go-karts don't have a separate body node in their source file, so bodyColor won't do anything on those — wheelColor still works.

Credit

Vehicle models in this package are built on Kenney's Car Kit, licensed CC0 (public domain). Credit isn't required by the license, but Kenney does great work funding an entire studio on donations/Patreon for assets like this — worth a look if you use these a lot: kenney.nl/donate.