Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This project uses semantic commits and semver.

This repo is the library itself (`src/`, built with `tsc` into `dist/`). [`examples/`](examples) is a
This repo is the library itself (`src/`, built with Vite into `dist/`). [`examples/`](examples) is a
separate Vite app used as a live testbed while developing. It resolves `@kvvasuu/klipp` straight to `src/`
via a Vite alias, so there's no build step in the loop while iterating.

Expand All @@ -15,10 +15,10 @@ pnpm --filter examples dev

## Scripts (run at the repo root)

- `pnpm run build` - compiles `src/` to `dist/` via `tsc`.
- `pnpm run build` - builds `src/` to `dist/` with Vite, one file per source file, and the declarations with `tsc`.
- `pnpm run test` - the unit suite (`vitest`), including the golden trajectory tests.
- `pnpm run lint` - `oxlint`, which also enforces the layer boundaries below.
- `pnpm run typecheck` - `tsc` over `src/` and, via `test/tsconfig.json`, the tests.
- `pnpm run typecheck` - `tsc` over `src/`, the config files and, via `test/tsconfig.json`, the tests.
- `pnpm run format` - `prettier`.
- `pnpm run bench` - the performance benchmark suite (`@pmndrs/labs`).

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"url": "https://github.com/pmndrs/klipp/issues"
},
"type": "module",
"sideEffects": false,
"main": "dist/core/index.js",
"types": "dist/core/index.d.ts",
"exports": {
Expand Down Expand Up @@ -61,9 +62,9 @@
"scripts": {
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
"prebuild": "pnpm run clean",
"build": "tsc",
"build": "vite build && tsc -p tsconfig.build.json && node scripts/add-js-extensions-dts.mjs",
"lint": "oxlint",
"typecheck": "tsc --noEmit && tsc -p test",
"typecheck": "tsc -p tsconfig.lib.json && tsc -p tsconfig.node.json && tsc -p test",
"format": "prettier --write .",
"format:check": "prettier --check .",
"test": "vitest run",
Expand Down Expand Up @@ -109,6 +110,7 @@
"react-dom": "^19.3.0",
"three": "^0.186.1",
"typescript": "~7.0.2",
"vite": "^8.3.1",
"vitest": "^5.0.2"
},
"dependencies": {
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions scripts/add-js-extensions-dts.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// tsc keeps import paths as written, so declarations import './follow'. Node-style resolution needs './follow.js'.
import fs from 'node:fs';
import path from 'node:path';

const root = path.resolve('dist');

const walk = (dir) =>
fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const file = path.join(dir, entry.name);
return entry.isDirectory() ? walk(file) : [file];
});

for (const file of walk(root).filter((f) => f.endsWith('.d.ts'))) {
const dir = path.dirname(file);
const original = fs.readFileSync(file, 'utf8');
const fixed = original.replace(/(from\s+['"]|import\(['"])(\.\.?(?:\/[^'"]*)?)(['"])/g, (match, pre, spec, post) => {
if (fs.existsSync(path.join(dir, `${spec}.d.ts`))) return `${pre}${spec}.js${post}`;
if (fs.existsSync(path.join(dir, spec, 'index.d.ts'))) return `${pre}${spec}/index.js${post}`;
return match;
});
if (fixed !== original) fs.writeFileSync(file, fixed);
}
4 changes: 2 additions & 2 deletions src/core/CameraState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,5 @@ export const lensEquals = (a: CameraState, b: CameraState): boolean =>
a.viewOffset[0] === b.viewOffset[0] &&
a.viewOffset[1] === b.viewOffset[1];

export { lerp } from './blend/lerpCameraState.js';
export { mix } from './groups/mixCameraStates.js';
export { lerp } from './blend/lerpCameraState';
export { mix } from './groups/mixCameraStates';
20 changes: 10 additions & 10 deletions src/core/Klipp.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { CameraState } from './CameraState.js';
import * as cameraState from './CameraState.js';
import { EventDispatcher } from './EventDispatcher.js';
import { VirtualCamera, type VirtualCameraOptions } from './VirtualCamera.js';
import { DEFAULT_BLEND, type BlendDefinition, type CustomBlend } from './blend/BlendDefinition.js';
import type { BlendHints } from './blend/BlendHints.js';
import * as blend from './blend/blend.js';
import { advance, attachTo, checkName, prepare, register, run, setHints, setPriority, skip } from './internal.js';
import type { CameraTransitionEventMap, KlippParams, VirtualCameraConfig } from './klippState.js';
import * as klippState from './klippState.js';
import type { CameraState } from './CameraState';
import * as cameraState from './CameraState';
import { EventDispatcher } from './EventDispatcher';
import { VirtualCamera, type VirtualCameraOptions } from './VirtualCamera';
import { DEFAULT_BLEND, type BlendDefinition, type CustomBlend } from './blend/BlendDefinition';
import type { BlendHints } from './blend/BlendHints';
import * as blend from './blend/blend';
import { advance, attachTo, checkName, prepare, register, run, setHints, setPriority, skip } from './internal';
import type { CameraTransitionEventMap, KlippParams, VirtualCameraConfig } from './klippState';
import * as klippState from './klippState';

export type { CameraTransitionEventMap, VirtualCameraConfig };

Expand Down
4 changes: 2 additions & 2 deletions src/core/TargetPose.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Quat, Vec3 } from 'math';
import type { TargetExtent } from './TargetExtent.js';
import * as targetExtent from './TargetExtent.js';
import type { TargetExtent } from './TargetExtent';
import * as targetExtent from './TargetExtent';

/**
* A target's world transform and how far it reaches. `rotation` is only meaningful when `hasRotation` is true
Expand Down
12 changes: 6 additions & 6 deletions src/core/VirtualCamera.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Quat, Vec3 } from 'math';
import { BlendHints } from './blend/BlendHints.js';
import type { CameraState } from './CameraState.js';
import * as cameraState from './CameraState.js';
import { EventDispatcher } from './EventDispatcher.js';
import { attachTo, checkName, prepare, register, run, setHints, setPriority, skip } from './internal.js';
import type { CameraTransitionEventMap, Klipp } from './Klipp.js';
import { BlendHints } from './blend/BlendHints';
import type { CameraState } from './CameraState';
import * as cameraState from './CameraState';
import { EventDispatcher } from './EventDispatcher';
import { attachTo, checkName, prepare, register, run, setHints, setPriority, skip } from './internal';
import type { CameraTransitionEventMap, Klipp } from './Klipp';

/** Writes `out` for one frame. Return `true` when more work remains for a later frame. */
export type CameraStateWriter = (out: CameraState, dt: number, justActivated: boolean) => boolean | void;
Expand Down
6 changes: 3 additions & 3 deletions src/core/aim/HardLookAtAim.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { CameraState } from '../CameraState.js';
import type { TargetPose } from '../TargetPose.js';
import * as hardLookAt from './hardLookAt.js';
import type { CameraState } from '../CameraState';
import type { TargetPose } from '../TargetPose';
import * as hardLookAt from './hardLookAt';

/** Rotates so the target is dead-center. Layers override `readTarget` to read their own targets. */
export class HardLookAtAim<T = TargetPose | null> {
Expand Down
6 changes: 3 additions & 3 deletions src/core/aim/PanTiltAim.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Quat, Vec3 } from 'math';
import type { CameraState } from '../CameraState.js';
import type { TargetPose } from '../TargetPose.js';
import * as panTilt from './panTilt.js';
import type { CameraState } from '../CameraState';
import type { TargetPose } from '../TargetPose';
import * as panTilt from './panTilt';

/** Rotation from two `InputAxis`: `pan` (yaw) and `tilt` (pitch), relative to an optional target's rotation. */
export class PanTiltAim<T = TargetPose | null> {
Expand Down
10 changes: 5 additions & 5 deletions src/core/aim/RotateWithFollowTargetAim.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { Quat } from 'math';
import type { CameraState } from '../CameraState.js';
import type { DampingConstant } from '../damping/damping.js';
import type { TargetPose } from '../TargetPose.js';
import type { RotateWithFollowTargetParams } from './rotateWithFollowTarget.js';
import * as rotateWithFollowTarget from './rotateWithFollowTarget.js';
import type { CameraState } from '../CameraState';
import type { DampingConstant } from '../damping/damping';
import type { TargetPose } from '../TargetPose';
import type { RotateWithFollowTargetParams } from './rotateWithFollowTarget';
import * as rotateWithFollowTarget from './rotateWithFollowTarget';

export type RotateWithFollowTargetOptions = Partial<RotateWithFollowTargetParams>;

Expand Down
10 changes: 5 additions & 5 deletions src/core/aim/RotationComposerAim.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { Quat, Vec3 } from 'math';
import type { CameraState } from '../CameraState.js';
import type { DampingConstant } from '../damping/damping.js';
import type { TargetPose } from '../TargetPose.js';
import type { RotationComposerParams } from './rotationComposer.js';
import * as rotationComposer from './rotationComposer.js';
import type { CameraState } from '../CameraState';
import type { DampingConstant } from '../damping/damping';
import type { TargetPose } from '../TargetPose';
import type { RotationComposerParams } from './rotationComposer';
import * as rotationComposer from './rotationComposer';

/** Rotates the camera to place a target at `screenPosition`. Layers override `readTarget`. */
export class RotationComposerAim<T = TargetPose | null> implements RotationComposerParams {
Expand Down
2 changes: 1 addition & 1 deletion src/core/aim/hardLookAt.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mat4, quat, vec3, type Mat4, type Vec3 } from 'math';
import type { CameraState } from '../CameraState.js';
import type { CameraState } from '../CameraState';

const scratchLookMatrix: Mat4 = mat4.create();

Expand Down
22 changes: 11 additions & 11 deletions src/core/aim/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
export { HardLookAtAim } from './HardLookAtAim.js';
export * as hardLookAt from './hardLookAt.js';
export { HardLookAtAim } from './HardLookAtAim';
export * as hardLookAt from './hardLookAt';

export { RotateWithFollowTargetAim, type RotateWithFollowTargetOptions } from './RotateWithFollowTargetAim.js';
export * as rotateWithFollowTarget from './rotateWithFollowTarget.js';
export type { RotateWithFollowTargetParams, RotateWithFollowTargetState } from './rotateWithFollowTarget.js';
export { RotateWithFollowTargetAim, type RotateWithFollowTargetOptions } from './RotateWithFollowTargetAim';
export * as rotateWithFollowTarget from './rotateWithFollowTarget';
export type { RotateWithFollowTargetParams, RotateWithFollowTargetState } from './rotateWithFollowTarget';

export { RotationComposerAim } from './RotationComposerAim.js';
export * as rotationComposer from './rotationComposer.js';
export type { RotationComposerParams, RotationComposerState } from './rotationComposer.js';
export { RotationComposerAim } from './RotationComposerAim';
export * as rotationComposer from './rotationComposer';
export type { RotationComposerParams, RotationComposerState } from './rotationComposer';

export { PanTiltAim } from './PanTiltAim.js';
export * as panTilt from './panTilt.js';
export type { PanTiltState } from './panTilt.js';
export { PanTiltAim } from './PanTiltAim';
export * as panTilt from './panTilt';
export type { PanTiltState } from './panTilt';
4 changes: 2 additions & 2 deletions src/core/aim/panTilt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { degreesToRadians, quat, radiansToDegrees, vec3, type Euler, type Quat, type Vec3 } from 'math';
import type { CameraState } from '../CameraState.js';
import { InputAxis } from '../input/InputAxis.js';
import type { CameraState } from '../CameraState';
import { InputAxis } from '../input/InputAxis';

/** `pan` (yaw, wraps ±180°) and `tilt` (pitch, clamped) in degrees. */
export type PanTiltState = { pan: InputAxis; tilt: InputAxis };
Expand Down
8 changes: 4 additions & 4 deletions src/core/aim/rotateWithFollowTarget.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Quat } from 'math';
import type { CameraState } from '../CameraState.js';
import type { DamperState, DampingConstant } from '../damping/damping.js';
import * as damping from '../damping/damping.js';
import { withDefaults } from '../params.js';
import type { CameraState } from '../CameraState';
import type { DamperState, DampingConstant } from '../damping/damping';
import * as damping from '../damping/damping';
import { withDefaults } from '../params';

export type RotateWithFollowTargetParams = {
/** Spring response time to the target's rotation (or `{into, from}` for asymmetric damping). */
Expand Down
16 changes: 8 additions & 8 deletions src/core/aim/rotationComposer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { clamp, degreesToRadians, mat4, quat, vec3, vec4, type Mat4, type Quat, type Vec3 } from 'math';
import type { CameraState } from '../CameraState.js';
import type { DamperState, DampingConstant } from '../damping/damping.js';
import * as damping from '../damping/damping.js';
import type { PredictorState } from '../damping/predictor.js';
import * as predictor from '../damping/predictor.js';
import type { TargetPose } from '../TargetPose.js';
import { withDefaults } from '../params.js';
import * as targetExtent from '../TargetExtent.js';
import type { CameraState } from '../CameraState';
import type { DamperState, DampingConstant } from '../damping/damping';
import * as damping from '../damping/damping';
import type { PredictorState } from '../damping/predictor';
import * as predictor from '../damping/predictor';
import type { TargetPose } from '../TargetPose';
import { withDefaults } from '../params';
import * as targetExtent from '../TargetExtent';

export type RotationComposerParams = {
/** Where the target should land on screen: `[x, y]`, `0` = center, `±1` = edge. */
Expand Down
2 changes: 1 addition & 1 deletion src/core/blend/BlendDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BlendCurves, type Ease } from './BlendCurves.js';
import { BlendCurves, type Ease } from './BlendCurves';

/** A fixed-duration curve or a damped transition. */
export type BlendDefinition =
Expand Down
8 changes: 4 additions & 4 deletions src/core/blend/BlendDriver.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { CameraState } from '../CameraState.js';
import type { BlendDefinition } from './BlendDefinition.js';
import { BlendHints } from './BlendHints.js';
import * as blend from './blend.js';
import type { CameraState } from '../CameraState';
import type { BlendDefinition } from './BlendDefinition';
import { BlendHints } from './BlendHints';
import * as blend from './blend';

/** Stateful wrapper over the blend functions, resolving camera states by id. */
export class BlendDriver<Id> {
Expand Down
12 changes: 6 additions & 6 deletions src/core/blend/blend.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { clamp } from 'math';
import type { CameraState } from '../CameraState.js';
import * as cameraState from '../CameraState.js';
import type { DamperState } from '../damping/damping.js';
import * as damping from '../damping/damping.js';
import type { BlendDefinition, CustomBlend } from './BlendDefinition.js';
import { BlendHints } from './BlendHints.js';
import type { CameraState } from '../CameraState';
import * as cameraState from '../CameraState';
import type { DamperState } from '../damping/damping';
import * as damping from '../damping/damping';
import type { BlendDefinition, CustomBlend } from './BlendDefinition';
import { BlendHints } from './BlendHints';

/** A transition in flight from a frozen `from` state toward the live `toId` camera. */
export type BlendTransition<Id> = {
Expand Down
12 changes: 6 additions & 6 deletions src/core/blend/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export { BlendDriver } from './BlendDriver.js';
export * as blend from './blend.js';
export type { BlendState, BlendTransition } from './blend.js';
export { BlendDriver } from './BlendDriver';
export * as blend from './blend';
export type { BlendState, BlendTransition } from './blend';

export { DEFAULT_BLEND, type BlendDefinition, type CustomBlend } from './BlendDefinition.js';
export { DEFAULT_BLEND, type BlendDefinition, type CustomBlend } from './BlendDefinition';

export { BlendHints, hasBlendHint } from './BlendHints.js';
export { BlendHints, hasBlendHint } from './BlendHints';

export { BlendCurves, type Ease } from './BlendCurves.js';
export { BlendCurves, type Ease } from './BlendCurves';
4 changes: 2 additions & 2 deletions src/core/blend/lerpCameraState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
type Spherical,
type Vec3,
} from 'math';
import type { CameraState } from '../CameraState.js';
import { BlendHints, hasBlendHint } from './BlendHints.js';
import type { CameraState } from '../CameraState';
import { BlendHints, hasBlendHint } from './BlendHints';

/** Reused quaternion for the sign-adjusted destination case. */
const negatedB: Quat = [0, 0, 0, 1];
Expand Down
12 changes: 6 additions & 6 deletions src/core/body/FollowBody.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Vec3 } from 'math';
import type { CameraState } from '../CameraState.js';
import type { DampingConstant } from '../damping/damping.js';
import type { TargetPose } from '../TargetPose.js';
import type { BindingMode } from './BindingModes.js';
import type { FollowParams } from './follow.js';
import * as follow from './follow.js';
import type { CameraState } from '../CameraState';
import type { DampingConstant } from '../damping/damping';
import type { TargetPose } from '../TargetPose';
import type { BindingMode } from './BindingModes';
import type { FollowParams } from './follow';
import * as follow from './follow';

export type FollowOptions = Partial<FollowParams>;

Expand Down
10 changes: 5 additions & 5 deletions src/core/body/HardLockToTargetBody.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { CameraState } from '../CameraState.js';
import type { DampingConstant } from '../damping/damping.js';
import type { TargetPose } from '../TargetPose.js';
import type { HardLockToTargetParams } from './hardLockToTarget.js';
import * as hardLockToTarget from './hardLockToTarget.js';
import type { CameraState } from '../CameraState';
import type { DampingConstant } from '../damping/damping';
import type { TargetPose } from '../TargetPose';
import type { HardLockToTargetParams } from './hardLockToTarget';
import * as hardLockToTarget from './hardLockToTarget';

export type HardLockToTargetOptions = Partial<HardLockToTargetParams>;

Expand Down
10 changes: 5 additions & 5 deletions src/core/body/PositionComposerBody.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { Vec3 } from 'math';
import type { CameraState } from '../CameraState.js';
import type { DampingConstant } from '../damping/damping.js';
import type { TargetPose } from '../TargetPose.js';
import type { PositionComposerParams } from './positionComposer.js';
import * as positionComposer from './positionComposer.js';
import type { CameraState } from '../CameraState';
import type { DampingConstant } from '../damping/damping';
import type { TargetPose } from '../TargetPose';
import type { PositionComposerParams } from './positionComposer';
import * as positionComposer from './positionComposer';

/** Positions the camera using depth and screen-space composition. Layers override `readTarget`. */
export class PositionComposerBody<T = TargetPose | null> implements PositionComposerParams {
Expand Down
12 changes: 6 additions & 6 deletions src/core/body/follow.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { mat4, quat, vec3, type Mat4, type Quat, type Vec3 } from 'math';
import type { CameraState } from '../CameraState.js';
import type { DampingConstant, Vector3DamperState } from '../damping/damping.js';
import * as damping from '../damping/damping.js';
import type { TargetPose } from '../TargetPose.js';
import { BindingModes, type BindingMode } from './BindingModes.js';
import { withDefaults } from '../params.js';
import type { CameraState } from '../CameraState';
import type { DampingConstant, Vector3DamperState } from '../damping/damping';
import * as damping from '../damping/damping';
import type { TargetPose } from '../TargetPose';
import { BindingModes, type BindingMode } from './BindingModes';
import { withDefaults } from '../params';

export type FollowParams = {
/** Offset from the target, rotated according to `bindingMode`. */
Expand Down
Loading
Loading