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

@gaming.tools/gtmap

v0.32.0

Published

[![npm](https://img.shields.io/npm/v/@gaming.tools/gtmap)](https://www.npmjs.com/package/@gaming.tools/gtmap) [![license](https://img.shields.io/npm/l/@gaming.tools/gtmap)](https://github.com/gamingtools/gt-map/blob/main/LICENSE) [![bundle size](https://i

Downloads

238

Readme

GTMap

npm license bundle size

High-performance, pixel-CRS WebGL map renderer with a small, typed API. Optimized for image/scan maps (no geodetic lat-lng). Ships a thin facade (GTMap) over a fast WebGL core: input, rendering, and caches.

Status: early-stage, pre-release -- everything is subject to change before the initial release (including public APIs and behavior). No heavy runtime deps; pure TypeScript and Web APIs.

Install

npm install @gaming.tools/gtmap

Quick Start

HTML

<div id="map"></div>
<style>
  #map {
    width: 100%;
    height: 480px;
  }
  /* Ensure the container has a concrete size */
</style>

TypeScript

import { GTMap, ImageVisual, type MapOptions } from '@gaming.tools/gtmap';

const container = document.getElementById('map') as HTMLDivElement;

const map = new GTMap(container, {
  mapSize: { width: 8192, height: 8192 },
  wrapX: false,
  minZoom: 0,
  maxZoom: 5,
  center: { x: 4096, y: 4096 },
  zoom: 3,
  backgroundColor: '#0a0a0a',
} satisfies MapOptions);

Architecture

GTMap uses a facade pattern. The root GTMap class exposes four sub-objects:

| Facade | Access | Purpose | |--------|--------|---------| | view | map.view | Center, zoom, setView, bounds, coordinate transforms, icon scaling | | layers | map.layers | Layer creation, attachment, removal, per-layer display | | display | map.display | Grid overlay, upscale filter, FPS cap, background color, zoom snap | | input | map.input | Wheel speed, inertia options |

Events are accessed via map.events.

Layers

Content is organized into typed layers. Layers must be created via map.layers, then attached with a z-order:

Tile Layer

Backed by a .gtpk tile pyramid (binary pack containing the full tile set).

const tiles = map.layers.createTileLayer({
  packUrl: 'https://cdn.example.com/tiles/map.gtpk',
  tileSize: 256,
  sourceMinZoom: 0,
  sourceMaxZoom: 5,
});
map.layers.addLayer(tiles, { z: 0 });

Interactive Layer

Owns markers with WebGL hit-testing (click, hover, drag).

const layer = map.layers.createInteractiveLayer();
map.layers.addLayer(layer, { z: 1 });

// Add markers using Visual templates
const mk = layer.addMarker(2048, 2048, {
  visual: new ImageVisual('/icons/pin.png', 32),
  scale: 1.25,
  data: { name: 'POI A', category: 'shop' },
});

Static Layer

Owns vector shapes (polylines, polygons, circles).

const vectors = map.layers.createStaticLayer();
map.layers.addLayer(vectors, { z: 2 });

vectors.addPolygon(
  [{ x: 100, y: 100 }, { x: 200, y: 100 }, { x: 150, y: 200 }],
  { color: '#00ff00', fill: true, fillColor: 'rgba(0,255,0,0.2)' },
);
vectors.addPolyline(
  [{ x: 0, y: 0 }, { x: 300, y: 150 }],
  { color: '#ff0000', weight: 2 },
);
vectors.addCircle({ x: 500, y: 500 }, 80, { color: '#0000ff' });

Clustered Layer

Groups nearby markers into clusters with optional boundary polygons.

import { clusterIconSize } from '@gaming.tools/gtmap';

const clusters = map.layers.createClusteredLayer({
  clusterRadius: 80,
  minClusterSize: 2,
  clusterIconSizeFunction: clusterIconSize('exponentialLog', { max: 1.8 }),
  boundary: { fill: true, fillColor: 'rgba(0,100,255,0.1)', showOnHover: true },
});
map.layers.addLayer(clusters, { z: 3 });

// Add markers the same way as interactive layers
clusters.addMarker(1000, 1000, { visual: myVisual, data: { type: 'ore' } });

Layer Display

map.layers.setLayerVisible(layer, false);
map.layers.setLayerOpacity(layer, 0.5);
map.layers.setLayerZ(layer, 10);
map.layers.removeLayer(layer);

Visual System

Visuals are rendering templates that define appearance. They are separate from entities (Marker) which define position and interactivity.

import {
  ImageVisual, CircleVisual, RectVisual,
  TextVisual, SvgVisual, HtmlVisual, SpriteVisual,
} from '@gaming.tools/gtmap';

// Bitmap icon
const pin = new ImageVisual('/icons/pin.png', 32);
pin.anchor = 'bottom-center';

// Colored dot
const dot = new CircleVisual(6, { fill: '#ff0000', stroke: '#000' });

// Text label
const label = new TextVisual('Town', { fontSize: 12, color: '#fff', strokeColor: '#000', strokeWidth: 2 });

// SVG with color override
const icon = new SvgVisual('<svg>...</svg>', 24, { fill: '#ff0000', shadow: { blur: 4 } });

// Sprite atlas sub-region
const atlas = await layer.loadSpriteAtlas(atlasUrl, descriptor);
const sword = new SpriteVisual(atlas, 'sword', 32);

View Controls

// Instant jump
await map.view.setView({ center: { x: 4096, y: 4096 }, zoom: 3 });

// Animated fly-to
await map.view.setView({
  center: { x: 5000, y: 3000 },
  zoom: 4,
  animate: { durationMs: 800, easing: easings.easeInOutCubic },
});

// Fit bounds with padding
await map.view.setView({
  bounds: { minX: 1000, minY: 1000, maxX: 7000, maxY: 7000 },
  padding: 40,
  animate: { durationMs: 600 },
});

// Fit a set of points
await map.view.setView({
  points: [{ x: 500, y: 500 }, { x: 6000, y: 6000 }],
  padding: { top: 20, right: 20, bottom: 20, left: 200 },
});

// Cancel an in-flight animation
map.view.cancelView();

Coordinate Transforms (world -> pixels)

When your data lives in an external coordinate space (e.g., Unreal units), map it into image pixel coordinates:

// Initialize with world bounds
map.view.setCoordBounds({
  minX: -457200, minY: -457200,
  maxX: 355600, maxY: 355600,
});

// Convert world -> pixel
const p = map.view.translate(wx, wy);
layer.addMarker(p.x, p.y, { visual: myVisual });

// Optional orientation transforms
const q = map.view.translate(wx, wy, 'flipVertical');

Events

GTMap exposes a typed event surface. Subscribe with events.on(name, handler) or await a one-shot with events.once(name).

Map Events

map.events.on('move', ({ view }) => {
  console.log('center', view.center, 'zoom', view.zoom);
});

map.events.on('click', (e) => {
  console.log('screen', e.x, e.y, 'world', e.world);
});

map.events.on('load', ({ size, view }) => {
  console.log('ready', size.width, size.height, size.dpr);
});

Full event list: load, resize, move, moveend, zoom, zoomend, pointerdown, pointermove, pointerup, mousedown, mousemove, mouseup, click, dblclick, contextmenu, frame, markerenter, markerleave, markerclick, markerdown, markerup, markerlongpress.

Marker Events

mk.events.on('click', (e) => {
  console.log('clicked marker', e.marker.id, 'at', e.x, e.y);
});
mk.events.on('pointerenter', (e) => { /* hover in */ });
mk.events.on('pointerleave', (e) => { /* hover out */ });

Supported: click, tap, longpress, pointerdown, pointerup, pointerenter, pointerleave, positionchange, remove.

Pointer metadata (when available):

{
  device: 'mouse' | 'touch' | 'pen',
  isPrimary: boolean,
  buttons: number,
  modifiers: { alt, ctrl, meta, shift },
}

Markers

Markers are entities anchored at pixel coordinates with position, style, data, and typed events.

// Move (emits positionchange)
mk.moveTo(1100, 980);

// Update style
mk.setStyle({ scale: 1.5, rotation: 30, opacity: 0.8 });

// Attach user data
mk.setData({ name: 'POI B', category: 'viewpoint' });

// Animated transition
await mk.transition()
  .moveTo(2000, 1500)
  .setStyle({ scale: 2.0, rotation: 90 })
  .apply({ animate: { durationMs: 400 } });

// Snapshot
const data = mk.toData(); // { id, x, y, data }

// Remove via collection
layer.markers.remove(mk);
// or clear all
layer.clearMarkers();

EntityCollection

Layers expose typed collections (layer.markers, layer.vectors) with add/remove/filter/find:

layer.markers.setFilter<MyPOI>((m) => m.data.category === 'resource');
layer.markers.setFilter(null); // clear
layer.markers.setVisible(false);

const rares = layer.markers.find<MyPOI>((m) => m.data.tier === 'rare');
const count = layer.markers.count();

layer.markers.events.on('entityadd', ({ entity }) => console.log('added', entity.id));

Display & Rendering

// Grid overlay
map.display.setGridVisible(true);

// Upscale filtering for zoomed-in tiles
map.display.setUpscaleFilter('bicubic'); // 'auto' | 'linear' | 'bicubic'

// FPS cap
map.display.setFpsCap(60);

// Background color
map.display.setBackgroundColor('#0a0a0a');
map.display.setBackgroundColor({ r: 16, g: 16, b: 16 });

// Zoom snap threshold (0-1, default 0.4)
map.display.setZoomSnapThreshold(0.4);

Icon Scaling

Control marker icon size relative to zoom:

// World-like scaling around Z=3
map.view.setIconScaleFunction((z) => Math.pow(2, z - 3));

// Step behavior
map.view.setIconScaleFunction((z) => (z < 2 ? 0.75 : z < 4 ? 1.0 : 1.25));

// Reset to default (screen-fixed)
map.view.resetIconScale();

Bounds & Pan Constraints

map.view.setMaxBoundsPx({ minX: 0, minY: 0, maxX: 8192, maxY: 8192 });
map.view.setMaxBoundsViscosity(0.15); // 0-1
map.view.setClipToBounds(true);
map.view.setWrapX(false);

Input

map.input.setWheelSpeed(0.5);
map.input.setInertiaOptions({
  inertia: true,
  inertiaDeceleration: 3000,
  inertiaMaxSpeed: 1500,
});

Lifecycle

// Suspend/resume rendering
map.suspend({ releaseGL: true });
map.resume();

// Full teardown
map.destroy();

// Manual resize
map.view.invalidateSize();
map.view.setAutoResize(true); // enabled by default

Easings

import { easings } from '@gaming.tools/gtmap';

await map.view.setView({
  center: { x: 4096, y: 4096 },
  zoom: 4,
  animate: { durationMs: 700, easing: easings.easeInOutCubic },
});

Available: linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeOutExpo.

Types & Bundles

  • ESM bundle: dist/index.js
  • Type declarations: dist/index.d.ts plus dist/api/**, dist/entities/**
  • Import from @gaming.tools/gtmap (deep imports are not part of the public API)

License

MIT (c) gaming.tools