Skip to content

Repository files navigation

SkinViewerJS

Three.js-based Minecraft skin viewer for LeiCraftClient, with support for LeiCraft cosmetics (Blockbench models attached to the player's body parts). It is a fork of skinview3d, kept in sync with upstream (currently v3.4.2, three.js r156), and keeps skinview3d's API.

Stack

  • Bun runtime, package manager and test runner
  • TypeScript (strict, ESM), Biome formatter/linter
  • tsc for the ES modules, Rollup + SWC for the browser bundle
  • AGPL-3.0, with MIT-licensed upstream code (see THIRD_PARTY_NOTICES.md)

Usage

Browser bundle

bun run build writes bundles/skinview3d.bundle.js, a single file with three.js included that defines the global skinview3d (a drop-in replacement for the V1 bundle). Pipelines on main also keep it as an artifact.

<canvas id="skin_container"></canvas>
<script src="skinview3d.bundle.js"></script>
<script>
	const skinViewer = new skinview3d.SkinViewer({
		canvas: document.getElementById("skin_container"),
		width: 300,
		height: 400,
		skin: "https://example.com/skin.png",
	});
	skinViewer.loadCape("https://example.com/cape.png");
	skinViewer.loadCosmetics("https://example.com/item/texture.png", "https://example.com/item/model.cfg");
	skinViewer.animation = new skinview3d.WalkingAnimation();
</script>

ES modules

The package @leicraftmc/skinviewerjs (libs/ with type declarations, plus bundles/, assets/ and the vendored skinview-utils/) depends on three ^0.156.0. It is published to the GitLab npm registry of git.leicraftmc.de. Point the @leicraftmc scope at the instance-level endpoint in your project's .npmrc (the project is public, reading needs no token):

@leicraftmc:registry=https://git.leicraftmc.de/api/v4/packages/npm/
bun add @leicraftmc/skinviewerjs
import { SkinViewer, WalkingAnimation } from "@leicraftmc/skinviewerjs";

bun pm pack builds a local tarball (leicraftmc-skinviewerjs-<version>.tgz) instead; pipelines on main keep it as an artifact.

For everything that is not LeiCraft-specific (animations, name tags, ears, panoramas, camera, offscreen rendering) see the skinview3d README.

Cosmetics

API Description
loadCosmetics(texture, config) Adds a cosmetic. texture is an image URL or source, config the model config. Can be called several times to stack cosmetics.
loadCosmetics(null) / resetCosmetics() Removes the cosmetics.
cosmetics option [{ source, config }, …] loaded by the SkinViewer constructor.
playerObject.cosmetics The loaded CosmeticsObjects.

config is fetched when it is an http(s):// URL, an absolute path (/…) or starts with ./. Anything else, including a relative path like img/model.cfg, is parsed as the JSON content itself.

Model config

Cosmetics use Blockbench's "PlayerItem" JSON export (.cfg). The parser reads:

  • textureSize: [width, height] of the texture
  • models[]: attachTo (head, body, rightArm, leftArm, rightLeg, leftLeg), translate, rotate (degrees), boxes, and nested submodels with the same fields
  • boxes[]: coordinates ([x, y, z, width, height, depth]), optional sizeAdd, and either textureOffset (Minecraft box UV layout) or per-face uvNorth, uvEast, uvSouth, uvWest, uvUp, uvDown

invertAxis is ignored: the parser always mirrors the x and z axes, and the existing models are built for that. Models attached to body are moved up by 6 units. examples/img/ has three example models.

Known limitations

These behaviors are unchanged from the V1 production viewer:

  • resetCosmetics() removes one cosmetic group per body part, so a part with several cosmetic models keeps all but one.
  • Cosmetic textures are not disposed when cosmetics are reset.
  • resetSkin() / loadSkin(null) shows a transparent skin instead of hiding the player (upstream hides it), so cosmetics stay visible.

Development

bun install
bun run dev    # rebuild the bundle on change and serve http://localhost:12540/examples/
Script Description
bun run build ES modules (libs/) and browser bundle (bundles/)
bun pm pack Clean build, then the package tarball
bun publish Done by CI: a v<version> tag publishes the package (.gitlab/ci/release.yml)
bun run dev Bundle watch mode plus the examples server (scripts/serve.ts, port 12540)
bun test Cosmetics parser and geometry snapshot tests
bun run typecheck TypeScript check
bun run check / format / lint Biome

The examples page (examples/index.html) lets you switch the example cosmetics and animations. examples/offscreen-render.html renders skins to images.

Releasing

  1. Bump "version" in package.json (semver; the bundle API is the compatibility contract).
  2. Commit, then tag v<version> and push the tag.
  3. The publish:package job publishes @leicraftmc/skinviewerjs@<version> to the GitLab npm registry. The website picks it up with bun update @leicraftmc/skinviewerjs.

Syncing with upstream

  1. Diff the upstream tags, for example git diff v3.4.2 <new tag> -- src/.
  2. src/animation.ts, src/nametag.ts and src/skinview3d.ts have no LeiCraft changes. Copy upstream's files over them unchanged; Biome ignores them, so they stay byte-identical.
  3. Merge src/model.ts and src/viewer.ts by hand, keeping the cosmetics code listed in THIRD_PARTY_NOTICES.md, then run bun run format. Upstream imports skinview-utils from npm; here it is the vendored ../skinview-utils/index.js.
  4. Keep three on the version upstream uses, and update its pin in .gitlab/renovate.json.
  5. Run bun test. A changed geometry snapshot means the cosmetics render differently; compare the result against the previous bundle in a browser before updating the snapshot.

License

AGPL-3.0, see LICENSE. Code from skinview3d, skinview-utils and three.js stays under the MIT license, see THIRD_PARTY_NOTICES.md.

Releases

Packages

Contributors

Languages