diff --git a/ROADMAP.md b/ROADMAP.md index 391840b..da91b37 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -116,7 +116,7 @@ - [ ] Closest pair of points solver for geometry toolkit ## Milestone 0.6.0 – Fold Barrier Physics Suite (Planned) -- [ ] Align Fold barrier scope with the paper and define shared constraint interfaces in `src/physics/fold` +- [x] Align Fold barrier scope with the paper and define shared constraint interfaces in `src/physics/fold` - **Barrier primitives** (each item: runtime module + `docs/index.d.ts` entry + Vitest coverage + runnable example when feasible) - [ ] Cubic barrier potential (energy, gradient, Hessian evaluation) - [ ] Stiffness design principle for frozen barrier stiffness diff --git a/docs/index.d.ts b/docs/index.d.ts index ba76c62..723aa36 100644 --- a/docs/index.d.ts +++ b/docs/index.d.ts @@ -115,6 +115,9 @@ export const examples: { readonly lz77Compress: 'examples/lz77.ts'; readonly lz77Decompress: 'examples/lz77.ts'; }; + readonly physics: { + readonly createFoldConstraintRegistry: 'examples/foldSetup.ts'; + }; readonly performance: { readonly debounce: 'examples/requestDedup.ts'; readonly throttle: 'examples/requestDedup.ts'; @@ -3285,6 +3288,80 @@ export function bresenhamLine(start: Point, end: Point): Point[]; export interface ClosestPairResult { distance: number; pair: [Point, Point] | null } export function closestPair(points: ReadonlyArray): ClosestPairResult; +// ============================================================================ +// ⚙️ PHYSICS & FOLD BARRIERS +// ============================================================================ + +/** + * Registry for Fold constraint factory implementations. + * Use for: hooking cubic/contact/wall barrier evaluators into the solver. + * Import: physics/fold/index.ts + */ +export function createFoldConstraintRegistry(): FoldConstraintRegistry; + +export type FoldConstraintType = + | 'cubic-barrier' + | 'contact-barrier' + | 'pin-barrier' + | 'wall-barrier' + | 'strain-barrier' + | 'friction' + | 'assembly' + | 'gap-evaluator'; + +export interface FoldConstraintState { + gap: number; + maxGap: number; + stiffness: number; + direction: Vector3D; + extendedDirection?: Vector3D; + effectiveMass?: number; + metadata?: Record; +} + +export interface FoldComputationContext { + deltaTime: number; + iteration?: number; + time?: number; +} + +export interface FoldConstraintEvaluation { + energy: number; + gradient: Vector3D; + hessian: Matrix3x3; +} + +export interface FoldConstraint { + readonly type: FoldConstraintType; + readonly id?: string; + enabled: boolean; + evaluate(state: TState, context: FoldComputationContext): TResult; +} + +export interface FoldConstraintFactory { + readonly type: FoldConstraintType; + create(config: TConfig): FoldConstraint; +} + +export interface FoldConstraintRegistry { + register(factory: FoldConstraintFactory): void; + get(type: FoldConstraintType): FoldConstraintFactory | undefined; + list(): ReadonlyArray>; +} + +export interface FoldSolverSettings { + maxIterations: number; + tolerance: number; + allowEarlyExit?: boolean; +} + +export interface FoldSystemState { + positions: Array; + velocities: Array; + constraints: Array; + settings: FoldSolverSettings; +} + /** * Common easing curves for animation. * Use for: UI transitions, motion design, data viz. @@ -3717,6 +3794,12 @@ export interface Vector3D { z: number; } +export type Matrix3x3 = [ + [number, number, number], + [number, number, number], + [number, number, number] +]; + export interface Rect { x: number; y: number; diff --git a/examples/foldSetup.ts b/examples/foldSetup.ts new file mode 100644 index 0000000..1ad66bb --- /dev/null +++ b/examples/foldSetup.ts @@ -0,0 +1,27 @@ +import { createFoldConstraintRegistry } from '../src/index.js'; + +const registry = createFoldConstraintRegistry(); + +registry.register({ + type: 'cubic-barrier', + create(config: { stiffness: number }) { + return { + type: 'cubic-barrier', + enabled: true, + evaluate(state) { + const stiffness = config.stiffness; + return { + energy: stiffness * state.gap ** 2, + gradient: { x: 0, y: 0, z: 0 }, + hessian: [ + [stiffness, 0, 0], + [0, stiffness, 0], + [0, 0, stiffness], + ], + }; + }, + }; + }, +}); + +console.log(registry.list().length); diff --git a/src/index.ts b/src/index.ts index 33ce7a6..dd16494 100644 --- a/src/index.ts +++ b/src/index.ts @@ -113,6 +113,9 @@ export const examples = { lz77Compress: 'examples/lz77.ts', lz77Decompress: 'examples/lz77.ts', }, + physics: { + createFoldConstraintRegistry: 'examples/foldSetup.ts', + }, performance: { debounce: 'examples/requestDedup.ts', throttle: 'examples/requestDedup.ts', @@ -1180,6 +1183,24 @@ export { bresenhamLine } from './geometry/bresenham.js'; export { closestPair } from './geometry/closestPair.js'; export type { ClosestPairResult } from './geometry/closestPair.js'; +// ============================================================================ +// ⚙️ PHYSICS & FOLD BARRIERS +// ============================================================================ + +export { createFoldConstraintRegistry } from './physics/fold/index.js'; + +export type { + FoldConstraintType, + FoldConstraintState, + FoldComputationContext, + FoldConstraintEvaluation, + FoldConstraint, + FoldConstraintFactory, + FoldConstraintRegistry, + FoldSolverSettings, + FoldSystemState, +} from './physics/fold/index.js'; + // ============================================================================ // 🎨 VISUAL & ANIMATION // ============================================================================ diff --git a/src/physics/fold/index.ts b/src/physics/fold/index.ts new file mode 100644 index 0000000..d470296 --- /dev/null +++ b/src/physics/fold/index.ts @@ -0,0 +1 @@ +export * from './types.js'; diff --git a/src/physics/fold/types.ts b/src/physics/fold/types.ts new file mode 100644 index 0000000..e156269 --- /dev/null +++ b/src/physics/fold/types.ts @@ -0,0 +1,90 @@ +import type { Matrix3x3, Vector3D } from '../../types.js'; + +export type FoldConstraintType = + | 'cubic-barrier' + | 'contact-barrier' + | 'pin-barrier' + | 'wall-barrier' + | 'strain-barrier' + | 'friction' + | 'assembly' + | 'gap-evaluator'; + +export interface FoldConstraintState { + /** Signed distance or constraint gap value. */ + gap: number; + /** Maximum admissible distance before penalties engage. */ + maxGap: number; + /** Effective stiffness (frozen) for this evaluation. */ + stiffness: number; + /** Primary constraint direction or normal. */ + direction: Vector3D; + /** Optional extended direction used by Fold contact formulations. */ + extendedDirection?: Vector3D; + /** Effective mass or mass-like term used for stiffness design. */ + effectiveMass?: number; + /** Optional metadata bag for constraint-specific state. */ + metadata?: Record; +} + +export interface FoldComputationContext { + /** Current simulation time step. */ + deltaTime: number; + /** Iteration index inside the solver/integrator. */ + iteration?: number; + /** Optional absolute time for schedule dependent systems. */ + time?: number; +} + +export interface FoldConstraintEvaluation { + energy: number; + gradient: Vector3D; + hessian: Matrix3x3; +} + +export interface FoldConstraint { + readonly type: FoldConstraintType; + readonly id?: string; + enabled: boolean; + evaluate(state: TState, context: FoldComputationContext): TResult; +} + +export interface FoldConstraintFactory { + readonly type: FoldConstraintType; + create(config: TConfig): FoldConstraint; +} + +export interface FoldConstraintRegistry { + register(factory: FoldConstraintFactory): void; + get(type: FoldConstraintType): FoldConstraintFactory | undefined; + list(): ReadonlyArray>; +} + +export function createFoldConstraintRegistry(): FoldConstraintRegistry { + const factories = new Map>(); + + return { + register(factory: FoldConstraintFactory) { + factories.set(factory.type, factory as FoldConstraintFactory); + }, + get(type: FoldConstraintType) { + return factories.get(type); + }, + list() { + return Array.from(factories.values()); + }, + }; +} + +export interface FoldSolverSettings { + maxIterations: number; + tolerance: number; + allowEarlyExit?: boolean; +} + +export interface FoldSystemState { + positions: Array; + velocities: Array; + constraints: Array; + settings: FoldSolverSettings; +} diff --git a/src/types.ts b/src/types.ts index 553fbbb..650e1ba 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,6 +18,12 @@ export interface Vector3D { z: number; } +export type Matrix3x3 = [ + [number, number, number], + [number, number, number], + [number, number, number] +]; + export interface Rect { x: number; y: number; diff --git a/tests/fold.test.ts b/tests/fold.test.ts new file mode 100644 index 0000000..4cad3da --- /dev/null +++ b/tests/fold.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, expectTypeOf, it } from 'vitest'; + +import { + createFoldConstraintRegistry, + type FoldConstraint, + type FoldConstraintEvaluation, + type FoldConstraintState, + type FoldConstraintType, +} from '../src/physics/fold/index.js'; + +const testConstraint: FoldConstraint = { + type: 'cubic-barrier', + enabled: true, + evaluate(state) { + return { + energy: state.gap ** 2, + gradient: { x: 0, y: 0, z: 0 }, + hessian: [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + ], + }; + }, +}; + +const registry = createFoldConstraintRegistry(); +registry.register({ + type: 'cubic-barrier', + create: () => testConstraint, +}); + +describe('Fold constraint registry', () => { + it('registers and retrieves factories', () => { + expect(registry.get('cubic-barrier')).toBeDefined(); + expect(registry.list()).toHaveLength(1); + }); + + it('evaluates through registered constraint', () => { + const factory = registry.get('cubic-barrier'); + expect(factory).toBeDefined(); + if (!factory) return; + + const instance = factory.create({} as unknown); + const evaluation = instance.evaluate( + { + gap: 0.1, + maxGap: 1, + stiffness: 10, + direction: { x: 0, y: 0, z: 1 }, + }, + { deltaTime: 1 } + ); + + expect(evaluation.energy).toBeTypeOf('number'); + }); + + it('provides type safety helpers', () => { + expectTypeOf().toEqualTypeOf< + | 'cubic-barrier' + | 'contact-barrier' + | 'pin-barrier' + | 'wall-barrier' + | 'strain-barrier' + | 'friction' + | 'assembly' + | 'gap-evaluator' + >(); + + expectTypeOf().toMatchTypeOf>(); + }); +}); diff --git a/tests/index.test.ts b/tests/index.test.ts index c64928b..341ff08 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -57,6 +57,7 @@ describe('package entry point', () => { expect(examples.spatial.buildBvh).toBe('examples/bvh.ts'); expect(examples.spatial.queryBvh).toBe('examples/bvh.ts'); expect(examples.spatial.raycastBvh).toBe('examples/bvh.ts'); + expect(examples.physics.createFoldConstraintRegistry).toBe('examples/foldSetup.ts'); }); it('provides strong typing for example categories and names', () => { @@ -71,6 +72,7 @@ describe('package entry point', () => { | 'gameplay' | 'graph' | 'geometry' + | 'physics' | 'visual' >(); @@ -195,6 +197,10 @@ describe('package entry point', () => { | 'closestPair' >(); + expectTypeOf>().toEqualTypeOf< + 'createFoldConstraintRegistry' + >(); + expectTypeOf>().toEqualTypeOf< | 'seek' | 'flee'