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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@codyjasonbennett/fourwastaken3

v0.2.2

Published

Minimal three.js alternative.

Downloads

3

Readme

Size Version Downloads

four

Minimal three.js alternative.

Table of Contents

Installation

To install, use your preferred package manager:

npm install four@npm:fourwastaken
yarn add four@npm:fourwastaken
pnpm add four@npm:fourwastaken

This will install fourwastaken as four; both will be available.

import * as FOUR from 'four'

Note: Vite may have issues consuming WebGPU code which relies on top-level await via ESM. This is well supported since 2021, but you may need to use vite-plugin-top-level-await to use this library with vite.optimizeDeps.

Getting Started

The following creates a renderer, camera, and renders a red cube:

import { WebGLRenderer, PerspectiveCamera, Geometry, Material, Mesh } from 'four'

const renderer = new WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.canvas)

const camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight)
camera.position.z = 5

const geometry = new Geometry({
  position: {
    size: 3,
    data: new Float32Array([
      0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
      -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5,
      0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5,
      0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5,
      -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5,
      -0.5, -0.5, -0.5, 0.5, -0.5,
    ]),
  },
})
const material = new Material({
  vertex: /* glsl */ `#version 300 es
    uniform mat4 projectionMatrix;
    uniform mat4 modelViewMatrix;
    in vec3 position;
    void main() {
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1);
    }
  `,
  fragment: /* glsl */ `#version 300 es
    out lowp vec4 color;
    void main() {
      color = vec4(1, 0, 0, 1);
    }
  `,
})
const mesh = new Mesh(geometry, material)

renderer.render(mesh, camera)
import { WebGPURenderer, PerspectiveCamera, Geometry, Material, Mesh } from 'four'

const renderer = new WebGPURenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.canvas)

const camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight)
camera.position.z = 5

const geometry = new Geometry({
  position: {
    size: 3,
    data: new Float32Array([
      0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
      -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5,
      0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5,
      0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5,
      -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5,
      -0.5, -0.5, -0.5, 0.5, -0.5,
    ]),
  },
})
const material = new Material({
  vertex: /* wgsl */ `
    struct Uniforms {
      projectionMatrix: mat4x4<f32>,
      modelViewMatrix: mat4x4<f32>,
    };
    @group(0) @binding(0) var<uniform> uniforms: Uniforms;

    @vertex
    fn main(@location(0) position: vec3<f32>) -> @builtin(position) vec4<f32> {
      return uniforms.projectionMatrix * uniforms.modelViewMatrix * vec4(position, 1);
    }
  `,
  fragment: /* wgsl */ `
    @fragment
    fn main() -> @location(0) vec4<f32> {
      return vec4(1, 0, 0, 1);
    }
  `,
})
const mesh = new Mesh(geometry, material)

renderer.render(mesh, camera)

Object3D

An Object3D represents a basic 3D object and its transforms. Objects are linked via their parent and children properties, constructing a rooted scene-graph.

const object = new Object3D()
object.add(new Object3D(), new Object3D())
object.traverse((node) => {
  if (node !== object) object.remove(node)
  if (!node.visible) return true
})

Vector3

A Vector3 represents a three-dimensional (x, y, z) vector and describes local position in Object3D.position. It is also used to control local scale in Object3D.scale.

object.position.set(1, 2, 3)
object.position.x = 4
object.position[0] = 5

Quaternion

A Quaternion represents a four-dimensional vector with a rotation axis (x, y, z) and magnitude (w) and describes local orientation in Object3D.quaternion.

object.quaternion.set(0, 0, 0, 1)
object.quaternion.fromEuler(Math.PI / 2, 0, 0)
object.quaternion.x *= -1
object.quaternion[0] *= -1

Matrix4

A Matrix4 represents a 4x4 transformation matrix and describes world transforms in Object3D.matrix.

object.matrix.set(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 2, 3, 1)
object.matrix[12] = 4
object.matrix.invert()
object.matrix.identity()

Mesh

A Mesh contains a Geometry and Material to describe visual behavior, and can be manipulated in 3D as an Object3D.

const geometry = new Geometry({ ... })
const material = new Material({ ... })
const mesh = new Mesh(geometry, material)

Geometry

A Geometry contains an Attribute list of vertex or storage buffer data, with a GPU buffer allocated for each Attribute.

const geometry = new Geometry({
  position: { size: 2, data: new Float32Array([-1, -1, 3, -1, -1, 3]) },
  uv: { size: 2, data: new Float32Array([0, 0, 2, 0, 0, 2]) },
  index: { size: 1, data: new Uint16Array([0, 1, 2]) },
})

A DrawRange can also be configured to control rendering without submitting vertex data. This is useful for GPU-computed geometry or vertex pulling, as demonstrated in the fullscreen demos.

const geometry = new Geometry()
geometry.drawRange = { start: 0, count: 3 } // renders 3 vertices at starting index 0

Attribute

An Attribute defines a data view, its per-vertex size, and an optional per-instance divisor (see instancing).

// Creates a 4x4 instance matrix for 2 instances
{
  data: new Float32Array([
    1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
    1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
  ]),
  size: 16,
  divisor: 1,
}

Material

A Material describes a program or shader interface for rasterization and compute (see compute), defining a vertex and fragment or compute shader, respectively.

const material = new Material({
  vertex: /* glsl */ `#version 300 es
    uniform mat4 projectionMatrix;
    uniform mat4 modelViewMatrix;
    in vec3 position;
    void main() {
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1);
    }
  `,
  fragment: /* glsl */ `#version 300 es
    out lowp vec4 color;
    void main() {
      color = vec4(1, 0, 0, 1);
    }
  `,
  side: 'front',
  transparent: false,
  depthTest: true,
  depthWrite: true,
})
const material = new Material({
  vertex: /* wgsl */ `
    struct Uniforms {
      projectionMatrix: mat4x4<f32>,
      modelViewMatrix: mat4x4<f32>,
    };
    @group(0) @binding(0) var<uniform> uniforms: Uniforms;

    @vertex
    fn main(@location(0) position: vec3<f32>) -> @builtin(position) vec4<f32> {
      return uniforms.projectionMatrix * uniforms.modelViewMatrix * vec4(position, 1);
    }
  `,
  fragment: /* wgsl */ `
    @fragment
    fn main() -> @location(0) vec4<f32> {
      return vec4(1, 0, 0, 1);
    }
  `,
  side: 'front',
  transparent: false,
  depthTest: true,
  depthWrite: true,
})

Uniforms

The following uniforms are built-in and will be automatically populated when specified:

| Type | Name | Description | Conversion | | -------- | ---------------- | -------------------------------------------------- | -------------------------- | | mat4x4 | modelMatrix | world-space mesh transform | local space => world space | | mat4x4 | projectionMatrix | clip-space camera projection | view space => clip space | | mat4x4 | viewMatrix | inverse camera transform | world space => view space | | mat4x4 | modelViewMatrix | premultiplied model-view transform | local space => view space | | mat4x4 | normalMatrix | isotropic inverse model-view or "normal" transform | local space => view space |

In WebGPU, uniforms are bound to a single uniform buffer, preceded by storage buffers, and followed by sampler-texture for texture uniforms.

// Storage buffers
@group(0) @binding(0)
var<storage, read_write> data: array<vec2<f32>>;

// Uniform buffer
struct Uniforms {
  time: f32,
};
@group(0) @binding(1) var<uniform> uniforms: Uniforms;

// Texture bindings
@group(0) @binding(2) var sample: sampler;
@group(0) @binding(3) var color: texture_2d<f32>;

@group(0) @binding(4) var sample_2: sampler;
@group(0) @binding(5) var color_2: texture_2d<f32>;

Blending

By default, opaque meshes do not blend but replace values, and transparent meshes alpha blend by the following blend equation:

material.blending = {
  color: {
    operation: 'add',
    srcFactor: 'src-alpha',
    dstFactor: 'one-minus-src-alpha',
  },
  alpha: {
    operation: 'add',
    srcFactor: 'one',
    dstFactor: 'one-minus-src-alpha',
  },
}

This gets applied to the final fragment color as src * srcFactor + dst * dstFactor, assuming a premultiplied alpha.

Custom blending can be used for postprocessing and various VFX. The following are the most common configurations:

| Blend Mode | BlendOperation | BlendFactor (src) | BlendFactor (dst) | | -------------- | ------------------ | --------------------- | ----------------- | | Additive | add | src-alpha | one | | Subtractive | reverse-subtract | src-alpha | one | | Multiply | add | dst-color | zero | | Screen | add | one-minus-src-color | one | | Maximize | max | src-alpha | dst-alpha | | Custom | add | one | one | | Local Additive | add | dst-alpha | one | | Disabled | add | one | zero |

Texture

A Texture transports or stores image or video data to the GPU as well as data like normals or depth.

const pixel = new Uint8ClampedArray([76, 51, 128, 255])
const image = await createImageBitmap(new ImageData(pixel, 1, 1))
const texture = new Texture(image)

Sampler

A Sampler configures texel filtering and transforms for a texture, and can be used to sample a texture multiple times with different configurations in a shader.

const sampler = new Sampler({
  magFilter: 'nearest',
  minFilter: 'nearest',
  wrapS: 'clamp',
  wrapT: 'clamp',
  anisotropy: 1,
})
texture.sampler = sampler

RenderTarget

A RenderTarget constructs a frame buffer object which can be drawn to, similar to the canvas itself. Unlike the canvas, render targets can have multiple attachments or texture channels, configurable as the third argument count, enabling efficient use of techniques like deferred rendering and postprocessing.

// Create render target with 4 channels
const width = window.innerWidth
const height = window.innerHeight
const count = 4
const renderTarget = new RenderTarget(width, height, count)

// Resize with page
window.addEventListener('resize', () => {
  const width = window.innerWidth
  const height = window.innerHeight
  renderTarget.setSize(width, height)
})

// Bind and render to render target
renderer.setRenderTarget(renderTarget)
renderer.render(scene, camera)

// Unbind to canvas
renderer.setRenderTarget(null)

Camera

A Camera contains matrices and a frustum for projection transforms and queries. The type of projection is defined by Camera.projectionMatrix.

Frustum

A Frustum contains clipping planes used for frustum culling and queries, set from a projectionViewMatrix.

camera.frustum.fromMatrix4(camera.projectionViewMatrix)
if (camera.frustum.contains(mesh)) {
  // ...
}

PerspectiveCamera

A PerspectiveCamera calculates a perspective or non-linear projectionMatrix, where objects appear smaller by distance.

const fov = 75
const aspect = canvas.width / canvas.height
const near = 0.1
const far = 1000
const camera = new PerspectiveCamera(fov, aspect, near, far)

OrthographicCamera

An OrthographicCamera calculates an orthographic or linear projectionMatrix, where objects are unaffected by distance.

const near = 0.1
const far = 1000
const left = -(canvas.width / 2)
const right = canvas.width / 2
const bottom = -(canvas.height / 2)
const top = canvas.height / 2
const camera = new OrthographicCamera(near, far, left, right, bottom, top)

Rendering

Four supports WebGL 2 and WebGPU with WebGLRenderer and WebGPURenderer, respectively, and implements a shared API for rendering and compute.

const renderer = new WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.canvas)

//

const renderer = new WebGPURenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.canvas)

Instancing

Four instances by default, which better aligns with WebGPU. Instanced rendering rasterizes multiple vertex primitives with the same shader interface to render multiple meshes at the cost of one.

You can specify the number of instances to render with Mesh.instances and add variance or control each instance with Attribute.divisor to specify a per-instance divisor. A divisor of one will be used by a single instance, and a divisor greater than one will be used by multiple instances.

Note: Attributes can only allocate primitive types in WebGPU (gpuweb/gpuweb#1652), so you must allocate and index storage or uniform buffers via the instance_index built-in for complex types like matrices.

const geometry = new Geometry({
  position: { size: 3, data: new Float32Array([...]) },
  instanceMatrix: { divisor: 1, size: 16, data: new Float32Array([...]) }
})

const material = new Material({
  vertex: /* glsl */`#version 300 es
    uniform mat4 projectionMatrix;
    uniform mat4 modelViewMatrix;
    in mat4 instanceMatrix;
    in vec3 position;
    void main() {
      gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position, 1);
    }
  `,
  fragment: /* glsl */`#version 300 es
    out lowp vec4 color;
    void main() {
      color = vec4(1, 0, 0, 1);
    }
  `
})

const mesh = new Mesh(geometry, material)
mesh.instances = 2
const geometry = new Geometry({
  position: { size: 3, data: new Float32Array([...]) },
})

const material = new Material({
  uniforms: {
    instanceMatrix: new Float32Array([...]),
  },
  vertex: /* wgsl */ `
    struct Uniforms {
      projectionMatrix: mat4x4<f32>,
      modelViewMatrix: mat4x4<f32>,
      instanceMatrix: array<mat4x4<f32>, 2>,
    };
    @group(0) @binding(0) var<uniform> uniforms: Uniforms;

    @vertex
    fn main(
      @builtin(instance_index) instanceID: u32,
      @location(0) position: vec3<f32>,
    ) -> @builtin(position) vec4<f32> {
      return uniforms.projectionMatrix * uniforms.modelViewMatrix * uniforms.instanceMatrix[instanceID] * vec4(position, 1.0);
    }
  `,
  fragment: /* wgsl */ `
    @fragment
    fn main() -> @location(0) vec4<f32> {
      return vec4(1, 0, 0, 1);
    }
  `,
})

const mesh = new Mesh(geometry, material)
mesh.instances = 2

Compute

Four supports compute for both WebGL and WebGPU via transform feedback and compute pipelines, respectively. This can be used in lieu of pixel shaders to write directly to buffer storage without any CPU reads/writes to textures. Useful for high precision compute or large simulations where VRAM is limited.

The following populates geometry buffers on the GPU, computing a fullscreen triangle geometry:

const geometry = new Geometry({
  position: { size: 2, data: new Float32Array(6) },
  uv: { size: 2, data: new Float32Array(6) },
})
const computeMaterial = new Material({
  compute: /* glsl */ `#version 300 es
    out vec2 position;
    out vec2 uv;

    const vec2 vertex[3] = vec2[](vec2(-1), vec2(3, -1), vec2(-1, 3));

    void main() {
      position = vertex[gl_VertexID];
      uv = abs(position) - 1.0;
    }
  `,
})
const mesh = new Mesh(geometry, computeMaterial)

renderer.compute(mesh)
const geometry = new Geometry({
  position: { size: 2, data: new Float32Array(6) },
  uv: { size: 2, data: new Float32Array(6) },
})
const computeMaterial = new Material({
  compute: /* wgsl */ `
    @group(0) @binding(0)
    var<storage, read_write> position: array<vec2<f32>>;

    @group(0) @binding(1)
    var<storage, read_write> uv: array<vec2<f32>>;

    const vertex = array<vec2<f32>, 3>(vec2(-1), vec2(3, -1), vec2(-1, 3));

    @compute @workgroup_size(64)
    fn main(@builtin(local_invocation_index) i: u32) {
      position[i] = vertex[i];
      uv[i] = abs(vertex[i]) - 1.0;
    }
  `,
})
const mesh = new Mesh(geometry, computeMaterial)

renderer.compute(mesh)