diff --git a/.editorconfig b/.editorconfig
new file mode 100755
index 0000000..0ed8036
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+end_of_line = lf
+charset = utf-8
+indent_style = space
+indent_size = 2
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
index 9daa824..ff14e50
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
-.DS_Store
node_modules
+.DS_Store
+types
+lib
+web_modules
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..99f0922
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,10 @@
+web_modules
+examples
+docs
+coverage
+test
+.github
+screenshot.*
+index.html
+tsconfig.json
+.editorconfig
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..3f8fda1
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,44 @@
+# Changelog
+
+All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+
+# [3.0.0-alpha.1](https://github.com/pex-gl/pex-cam/compare/v3.0.0-alpha.0...v3.0.0-alpha.1) (2022-07-04)
+
+
+### Bug Fixes
+
+* export types ([8e8808e](https://github.com/pex-gl/pex-cam/commit/8e8808e8ae5db32c5db43dfcbab5119f4cb37df0))
+
+
+
+# [3.0.0-alpha.0](https://github.com/pex-gl/pex-cam/compare/v2.7.1...v3.0.0-alpha.0) (2022-07-04)
+
+
+### Bug Fixes
+
+* add missing file extension ([eca8bbf](https://github.com/pex-gl/pex-cam/commit/eca8bbf43a9c6ca33d631c5aba4b4154f8ea372f))
+* add passive false to onWheel event listener ([5030629](https://github.com/pex-gl/pex-cam/commit/503062949e2daafe79c6a7a081c3bcfba6580ea7))
+* add passive false to touchstart ([ec854e5](https://github.com/pex-gl/pex-cam/commit/ec854e546168df7c19d45f21f122d82b2f9b3d22))
+* orbiter use Object.getOwnPropertyDescriptor ([c0f1200](https://github.com/pex-gl/pex-cam/commit/c0f120078c925bc376bd4efbc134fa4da0380a8e))
+* update default min/maxDistance ([8fb81af](https://github.com/pex-gl/pex-cam/commit/8fb81afec0eb0ebfbfab2c9ecc4acbce7f81eadf)), closes [#20](https://github.com/pex-gl/pex-cam/issues/20)
+* use static getters for perspective and orthographic default options ([27be007](https://github.com/pex-gl/pex-cam/commit/27be0075105ea82fc5543575de24175e85e49187))
+
+
+### Code Refactoring
+
+* use ES modules ([3374096](https://github.com/pex-gl/pex-cam/commit/3374096e968355039c9c260e19b08990bb3c6086))
+
+
+### Features
+
+* add generic camera interface ([4963ed6](https://github.com/pex-gl/pex-cam/commit/4963ed629f4ca85b4b96a15bdfb1a11c898c0382))
+* add orthographic camera ([45bc72b](https://github.com/pex-gl/pex-cam/commit/45bc72bfa41e58e3873250b759b208c0458adc9c))
+* allow autoUpdate set ([ac442fc](https://github.com/pex-gl/pex-cam/commit/ac442fcc24a820ed47c546b5e0053609510f5521))
+* handle orthographic camera in orbiter ([31caa01](https://github.com/pex-gl/pex-cam/commit/31caa01764643c74262d2a9d45ec7d1418344bd7))
+* move to pointer events ([780caa6](https://github.com/pex-gl/pex-cam/commit/780caa6bf1128b8a8c2ec7678cb5ab5db303b290))
+* rename camera frustum to view ([01b5fe5](https://github.com/pex-gl/pex-cam/commit/01b5fe5d1d102367e6f449e3a35c11823634e4a9))
+
+
+### BREAKING CHANGES
+
+* switch to type module
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
index a9d9c6c..cb7d529
--- a/README.md
+++ b/README.md
@@ -1,76 +1,403 @@
# pex-cam
-Camera models and controllers.
+[![npm version](https://img.shields.io/npm/v/pex-cam)](https://www.npmjs.com/package/pex-cam)
+[![stability-stable](https://img.shields.io/badge/stability-stable-green.svg)](https://www.npmjs.com/package/pex-cam)
+[![npm minzipped size](https://img.shields.io/bundlephobia/minzip/pex-cam)](https://bundlephobia.com/package/pex-cam)
+[![dependencies](https://img.shields.io/librariesio/release/npm/pex-cam)](https://github.com/pex-gl/pex-cam/blob/main/package.json)
+[![types](https://img.shields.io/npm/types/pex-cam)](https://github.com/microsoft/TypeScript)
+[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-fa6673.svg)](https://conventionalcommits.org)
+[![styled with prettier](https://img.shields.io/badge/styled_with-Prettier-f8bc45.svg?logo=prettier)](https://github.com/prettier/prettier)
+[![linted with eslint](https://img.shields.io/badge/linted_with-ES_Lint-4B32C3.svg?logo=eslint)](https://github.com/eslint/eslint)
+[![license](https://img.shields.io/github/license/pex-gl/pex-cam)](https://github.com/pex-gl/pex-cam/blob/main/LICENSE.md)
-# API
+Cameras models and controllers for 3D rendering in [PEX](https://pex.gl).
-## Perspective Camera
+![](https://raw.githubusercontent.com/pex-gl/pex-cam/main/screenshot.png)
-```javascript
-var createPerspectiveCamera = require('pex-cam/perspective')
+## Installation
+
+```bash
+npm install pex-cam
```
-### `cam = createPerspectiveCamera(opts)`
+## Usage
+
+```js
+import {
+ perspective as createPerspectiveCamera,
+ orbiter as createOrbiter,
+} from "pex-cam";
-Creates new perspective camera
+const perspectiveCamera = createPerspectiveCamera({
+ position: [2, 2, 2],
+ target: [0, -0.5, 0],
+ aspect: window.innerWidth / window.innerHeight,
+});
-- `opts:` object with one or more of the following options
- - `position`: vec3 - camera position, `[0, 0, 3]`
- - `target`: vec3 - camera target, `[0, 0, 0]`
- - `up`: vec3 - camera up direction, `[0, 1, 0]`
- - `fov`: Number - vertical field of view, `PI/3 (60 deg)`
- - `aspect`: Number - aspect ratio , `1`
- - `near`: Number - near clipping plane, `0.1`
- - `far`: Number - far clipping plane, `100`
+const perspectiveOrbiter = createOrbiter({
+ camera: perspectiveCamera,
+});
-### `cam.set(opts)`
+console.log(perspectiveCamera.projectionMatrix);
+```
-- `opts`: see `createPerspectiveCamera`
+## API
-### `cam.getViewRay(x, y, windowWidth, windowHeight)`
+
-Create picking ray in view (camera) cooridinates
+## Modules
-- `x`: Number - mouse x
-- `y`: Number - mouse y
-- `windowWidth`: Number
-- `windowHeight`: Number
+
+- index
+Re-export classes and factory functions
+
+
-### `cam.getWorldRay(x, y, windowWidth, windowHeight)`
+## Classes
-Create picking ray in world coordinates
+
+- Camera
+An interface for cameras to extend
+
+- OrbiterControls
+Camera controls to orbit around a target
+
+- OrthographicCamera ⇐
Camera
+A class to create an orthographic camera
+
+- PerspectiveCamera ⇐
Camera
+A class to create a perspective camera
+
+
-- `x`: Number - mouse x
-- `y`: Number - mouse y
-- `windowWidth`: Number
-- `windowHeight`: Number
+## Typedefs
-## Orbiter
+
+- Radians :
number
+
+- Degrees :
number
+
+- CameraView :
Object
+
+- CameraOptions :
Object
+
+- PerspectiveCameraOptions :
Object
+
+- OrthographicCameraOptions :
Object
+
+- OrbiterControlsOptions :
Object
+
+
-Orbiter controller
+
-```javascript
-var createOrbiter = require('pex-cam/orbiter')
-```
+## index
+
+Re-export classes and factory functions
+
+- [index](#module_index)
+ - [.perspective](#module_index.perspective) ⇒ [PerspectiveCamera
](#PerspectiveCamera)
+ - [.orthographic](#module_index.orthographic) ⇒ [OrthographicCamera
](#OrthographicCamera)
+ - [.orbiter](#module_index.orbiter) ⇒ [OrbiterControls
](#OrbiterControls)
+
+
+
+### index.perspective ⇒ [PerspectiveCamera
](#PerspectiveCamera)
+
+Factory function for perspective camera
+
+**Kind**: static constant of [index
](#module_index)
+
+| Param | Type |
+| ----- | ------------------------------------------------------------------------------------------------------------------ |
+| opts | [CameraOptions
](#CameraOptions) \| [PerspectiveCameraOptions
](#PerspectiveCameraOptions) |
+
+
+
+### index.orthographic ⇒ [OrthographicCamera
](#OrthographicCamera)
+
+Factory function for orthographic camera
+
+**Kind**: static constant of [index
](#module_index)
+
+| Param | Type |
+| ----- | -------------------------------------------------------------------------------------------------------------------- |
+| opts | [CameraOptions
](#CameraOptions) \| [OrthographicCameraOptions
](#OrthographicCameraOptions) |
+
+
+
+### index.orbiter ⇒ [OrbiterControls
](#OrbiterControls)
+
+Factory function for orbiter controls
+
+**Kind**: static constant of [index
](#module_index)
+
+| Param | Type |
+| ----- | -------------------------------------------------------------- |
+| opts | [OrbiterControlsOptions
](#OrbiterControlsOptions) |
+
+
+
+## Camera
+
+An interface for cameras to extend
+
+**Kind**: global class
+
+
+### camera.set(opts)
+
+Update the camera
+
+**Kind**: instance method of [Camera
](#Camera)
+
+| Param | Type |
+| ----- | -------------------------------------------- |
+| opts | [CameraOptions
](#CameraOptions) |
+
+
+
+## OrbiterControls
+
+Camera controls to orbit around a target
+
+**Kind**: global class
+
+- [OrbiterControls](#OrbiterControls)
+ - [new OrbiterControls(opts)](#new_OrbiterControls_new)
+ - [.set(opts)](#OrbiterControls+set)
+ - [.dispose()](#OrbiterControls+dispose)
+
+
+
+### new OrbiterControls(opts)
+
+Create an instance of OrbiterControls
+
+| Param | Type |
+| ----- | -------------------------------------------------------------- |
+| opts | [OrbiterControlsOptions
](#OrbiterControlsOptions) |
+
+
+
+### orbiterControls.set(opts)
+
+Update the control
+
+**Kind**: instance method of [OrbiterControls
](#OrbiterControls)
+
+| Param | Type |
+| ----- | --------------------------- |
+| opts | OrbiterOptions
|
+
+
+
+### orbiterControls.dispose()
+
+Remove all event listeners
+
+**Kind**: instance method of [OrbiterControls
](#OrbiterControls)
+
+
+## OrthographicCamera ⇐ [Camera
](#Camera)
+
+A class to create an orthographic camera
+
+**Kind**: global class
+**Extends**: [Camera
](#Camera)
+
+- [OrthographicCamera](#OrthographicCamera) ⇐ [Camera
](#Camera)
+ - [new OrthographicCamera(opts)](#new_OrthographicCamera_new)
+ - [.set(opts)](#OrthographicCamera+set)
+
+
+
+### new OrthographicCamera(opts)
+
+Create an instance of PerspectiveCamera
+
+| Param | Type |
+| ----- | -------------------------------------------------------------------------------------------------------------------- |
+| opts | [CameraOptions
](#CameraOptions) \| [OrthographicCameraOptions
](#OrthographicCameraOptions) |
+
+
+
+### orthographicCamera.set(opts)
+
+Update the camera
+
+**Kind**: instance method of [OrthographicCamera
](#OrthographicCamera)
+**Overrides**: [set
](#Camera+set)
+
+| Param | Type |
+| ----- | -------------------------------------------------------------------------------------------------------------------- |
+| opts | [CameraOptions
](#CameraOptions) \| [OrthographicCameraOptions
](#OrthographicCameraOptions) |
+
+
+
+## PerspectiveCamera ⇐ [Camera
](#Camera)
+
+A class to create a perspective camera
+
+**Kind**: global class
+**Extends**: [Camera
](#Camera)
+
+- [PerspectiveCamera](#PerspectiveCamera) ⇐ [Camera
](#Camera)
+ - [new PerspectiveCamera(opts)](#new_PerspectiveCamera_new)
+ - [.set(opts)](#PerspectiveCamera+set)
+ - [.getViewRay(x, y, windowWidth, windowHeight)](#PerspectiveCamera+getViewRay) ⇒ module:pex-geom~ray
+ - [.getWorldRay(x, y, windowWidth, windowHeight)](#PerspectiveCamera+getWorldRay) ⇒ module:pex-geom~ray
+
+
+
+### new PerspectiveCamera(opts)
+
+Create an instance of PerspectiveCamera
+
+| Param | Type |
+| ----- | ------------------------------------------------------------------------------------------------------------------ |
+| opts | [CameraOptions
](#CameraOptions) \| [PerspectiveCameraOptions
](#PerspectiveCameraOptions) |
+
+
+
+### perspectiveCamera.set(opts)
+
+Update the camera
+
+**Kind**: instance method of [PerspectiveCamera
](#PerspectiveCamera)
+**Overrides**: [set
](#Camera+set)
+
+| Param | Type |
+| ----- | ------------------------------------------------------------------------------------------------------------------ |
+| opts | [CameraOptions
](#CameraOptions) \| [PerspectiveCameraOptions
](#PerspectiveCameraOptions) |
+
+
+
+### perspectiveCamera.getViewRay(x, y, windowWidth, windowHeight) ⇒ module:pex-geom~ray
+
+Create a picking ray in view (camera) coordinates
+
+**Kind**: instance method of [PerspectiveCamera
](#PerspectiveCamera)
+
+| Param | Type | Description |
+| ------------ | ------------------- | ----------- |
+| x | number
| mouse x |
+| y | number
| mouse y |
+| windowWidth | number
| |
+| windowHeight | number
| |
+
+
+
+### perspectiveCamera.getWorldRay(x, y, windowWidth, windowHeight) ⇒ module:pex-geom~ray
+
+Create a picking ray in world coordinates
+
+**Kind**: instance method of [PerspectiveCamera
](#PerspectiveCamera)
+
+| Param | Type |
+| ------------ | ------------------- |
+| x | number
|
+| y | number
|
+| windowWidth | number
|
+| windowHeight | number
|
+
+
+
+## Radians : number
+
+**Kind**: global typedef
+
+
+## Degrees : number
+
+**Kind**: global typedef
+
+
+## CameraView : Object
+
+**Kind**: global typedef
+**Properties**
+
+| Name | Type |
+| --------- | --------------------------------- |
+| offset | module:pex-math~vec2
|
+| size | module:pex-math~vec2
|
+| totalSize | module:pex-math~vec2
|
+
+
+
+## CameraOptions : Object
+
+**Kind**: global typedef
+**Properties**
+
+| Name | Type | Default |
+| ------------------ | -------------------------------------- | -------------------------- |
+| [projectionMatrix] | module:pex-math~mat4
| mat4.create()
|
+| [invViewMatrix] | module:pex-math~mat4
| mat4.create()
|
+| [viewMatrix] | module:pex-math~mat4
| mat4.create()
|
+| [position] | module:pex-math~vec3
| [0, 0, 3]
|
+| [target] | module:pex-math~vec3
| [0, 0, 0]
|
+| [up] | module:pex-math~vec3
| [0, 1, 0]
|
+| [aspect] | number
| 1
|
+| [near] | number
| 0.1
|
+| [far] | number
| 100
|
+| [view] | [CameraView
](#CameraView) |
|
+
+
+
+## PerspectiveCameraOptions : Object
+
+**Kind**: global typedef
+**Properties**
+
+| Name | Type | Default |
+| ----- | -------------------------------- | ------------------------ |
+| [fov] | [Radians
](#Radians) | Math.PI / 3
|
+
+
+
+## OrthographicCameraOptions : Object
+
+**Kind**: global typedef
+**Properties**
+
+| Name | Type | Default |
+| -------- | ------------------- | --------------- |
+| [left] | number
| -1
|
+| [right] | number
| 1
|
+| [bottom] | number
| -1
|
+| [top] | number
| 1
|
+| [zoom] | number
| 1
|
-### `orbiter = createOrbiter(opts)`
+
-Creates new orbiter controller
+## OrbiterControlsOptions : Object
-- `opts`: object with one or more of the following options
- - `camera`: PerspectiveCamera - camera to be controlled
- - `element`: DOM Element - mouse events target, `window`
- - `easing`: Number, amount of intertia, `0`
- - `drag`: Boolean - enable drag rotation, `true`
- - `zoom`: Boolean - enable mouse wheel zooming, `true`
- - `pan`: Boolean - enable shift + drag panning, `true`
- - `lat`: Number - latitude of the orbiter position, defaults to camera.position
- - `lon`: Number - longitude of the orbiter position, defaults to camera.position
+**Kind**: global typedef
+**Properties**
-### `orbiter.set(opts)`
+| Name | Type | Default |
+| -------------- | -------------------------------- | ---------------------- |
+| camera | [Camera
](#Camera) | |
+| [element] | HTMLElement
| document
|
+| [easing] | number
| 0.1
|
+| [zoom] | boolean
| true
|
+| [pan] | boolean
| true
|
+| [drag] | boolean
| true
|
+| [minDistance] | number
| 0.01
|
+| [maxDistance] | number
| Infinity
|
+| [minLat] | [Degrees
](#Degrees) | -89.5
|
+| [maxLat] | [Degrees
](#Degrees) | 89.5
|
+| [minLon] | number
| -Infinity
|
+| [maxLon] | number
| Infinity
|
+| [panSlowdown] | number
| 4
|
+| [zoomSlowdown] | number
| 400
|
+| [dragSlowdown] | number
| 4
|
+| [autoUpdate] | boolean
| true
|
-- `opts`: see `createOrbiter`
+
## License
-MIT, see [LICENSE.md](http://github.com/vorg/geom-merge/blob/master/LICENSE.md) for details.
+MIT. See [license file](https://github.com/pex-gl/pex-cam/blob/main/LICENSE.md).
diff --git a/camera.js b/camera.js
new file mode 100644
index 0000000..7aacfb0
--- /dev/null
+++ b/camera.js
@@ -0,0 +1,38 @@
+import { mat4 } from "pex-math";
+
+/**
+ * An interface for cameras to extend
+ */
+class Camera {
+ // Static getter to get different mat for each instances
+ static get DEFAULT_OPTIONS() {
+ return {
+ projectionMatrix: mat4.create(),
+ invViewMatrix: mat4.create(),
+ viewMatrix: mat4.create(),
+ position: [0, 0, 3],
+ target: [0, 0, 0],
+ up: [0, 1, 0],
+ aspect: 1,
+ near: 0.1,
+ far: 100,
+ view: null,
+ };
+ }
+
+ /**
+ * Update the camera
+ * @param {import("./types.js").CameraOptions} opts
+ */
+ set(opts) {
+ Object.assign(this, opts);
+
+ if (opts.position || opts.target || opts.up) {
+ mat4.lookAt(this.viewMatrix, this.position, this.target, this.up);
+ mat4.set(this.invViewMatrix, this.viewMatrix);
+ mat4.invert(this.invViewMatrix);
+ }
+ }
+}
+
+export default Camera;
diff --git a/example/index.html b/example/index.html
deleted file mode 100644
index 7683e1d..0000000
--- a/example/index.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
- pex
-
-
-
-
-
-
diff --git a/example/index.js b/example/index.js
index 8dbfd31..57184d1 100644
--- a/example/index.js
+++ b/example/index.js
@@ -1,135 +1,230 @@
-'use strict'
-const ctx = require('pex-context')()
-const createCube = require('primitive-cube')
-const glsl = require('glslify')
-const createCamera = require('../perspective')
-const createOrbiter = require('../orbiter')
-const mat4 = require('pex-math/mat4')
-const random = require('pex-random')
-
-const cube = createCube(0.2)
-
-const camera = createCamera({
- fov: Math.PI / 3,
- aspect: window.innerWidth / window.innerHeight,
- near: 0.1,
- far: 100,
- position: [3, 3, 3],
- target: [0, 0, 0],
- up: [0, 1, 0]
-})
-
-// const arcball = createArcball({
- // camera: camera,
- // element: gl.canvas
-// })
-
-const orbiter = createOrbiter({
- camera: camera,
- element: ctx.gl.canvas,
- easing: 0.1
-})
+import {
+ perspective as createPerspectiveCamera,
+ orthographic as createOrthographicCamera,
+ orbiter as createOrbiter,
+} from "../index.js";
+
+import createContext from "pex-context";
+import { cube as createCube } from "primitive-geometry";
+import { mat4 } from "pex-math";
+import * as random from "pex-random";
+import createGUI from "pex-gui";
+
+const canvas = document.createElement("canvas");
+document.querySelector("main").appendChild(canvas);
+
+const ctx = createContext({ canvas });
+const gui = createGUI(ctx);
+const cube = createCube({ sx: 0.2 });
+
+const State = { distance: 5, fov: Math.PI / 3 };
+
+const perspectiveCamera = createPerspectiveCamera({
+ position: [2, 2, 2],
+ target: [0, -0.5, 0],
+});
+
+const orthographicCamera = createOrthographicCamera({
+ position: [2, 2, 2],
+ target: [0, -0.5, 0],
+});
+
+const perspectiveOrbiter = createOrbiter({
+ camera: perspectiveCamera,
+});
+const orthographicOrbiter = createOrbiter({
+ camera: orthographicCamera,
+});
const clearCmd = {
pass: ctx.pass({
- clearColor: [0, 0, 0, 1],
- clearDepth: 1
- })
-}
+ clearColor: [0.1, 0.1, 0.1, 1],
+ clearDepth: 1,
+ }),
+};
+
+random.seed("0");
+const offsets = Array.from({ length: 200 }, () => random.vec3());
const drawCubeCmd = {
pipeline: ctx.pipeline({
- vert: glsl`
- #ifdef GL_ES
- #pragma glslify: transpose = require(glsl-transpose)
- #endif
- #pragma glslify: inverse = require(glsl-inverse)
-
- attribute vec3 aPosition;
- attribute vec3 aNormal;
-
- uniform mat4 uProjectionMatrix;
- uniform mat4 uViewMatrix;
- uniform mat4 uModelMatrix;
- uniform vec3 uPosition;
-
- varying vec3 vNormal;
-
- void main () {
- mat4 modelViewMatrix = uViewMatrix * uModelMatrix;
- mat3 normalMatrix = mat3(transpose(inverse(modelViewMatrix))); vNormal = normalMatrix * aNormal;
- gl_Position = uProjectionMatrix * modelViewMatrix * vec4(aPosition + uPosition, 1.0);
- }
- `,
- frag: `
- #ifdef GL_ES
- precision highp float;
- #endif
-
- varying vec3 vNormal;
-
- void main () {
- gl_FragColor.rgb = vNormal * 0.5 + 0.5;
- gl_FragColor.a = 1.0;
- }
- `,
- depthTest: true
+ vert: /* glsl */ `
+ attribute vec3 aPosition;
+ attribute vec3 aOffset;
+ attribute vec3 aNormal;
+
+ uniform mat4 uProjectionMatrix;
+ uniform mat4 uViewMatrix;
+ uniform mat4 uModelMatrix;
+
+ varying vec3 vNormal;
+
+ mat4 transpose(mat4 m) {
+ return mat4(m[0][0], m[1][0], m[2][0], m[3][0],
+ m[0][1], m[1][1], m[2][1], m[3][1],
+ m[0][2], m[1][2], m[2][2], m[3][2],
+ m[0][3], m[1][3], m[2][3], m[3][3]);
+ }
+
+ mat4 inverse(mat4 m) {
+ float
+ a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3],
+ a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3],
+ a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3],
+ a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3],
+
+ b00 = a00 * a11 - a01 * a10,
+ b01 = a00 * a12 - a02 * a10,
+ b02 = a00 * a13 - a03 * a10,
+ b03 = a01 * a12 - a02 * a11,
+ b04 = a01 * a13 - a03 * a11,
+ b05 = a02 * a13 - a03 * a12,
+ b06 = a20 * a31 - a21 * a30,
+ b07 = a20 * a32 - a22 * a30,
+ b08 = a20 * a33 - a23 * a30,
+ b09 = a21 * a32 - a22 * a31,
+ b10 = a21 * a33 - a23 * a31,
+ b11 = a22 * a33 - a23 * a32,
+
+ det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
+
+ return mat4(
+ a11 * b11 - a12 * b10 + a13 * b09,
+ a02 * b10 - a01 * b11 - a03 * b09,
+ a31 * b05 - a32 * b04 + a33 * b03,
+ a22 * b04 - a21 * b05 - a23 * b03,
+ a12 * b08 - a10 * b11 - a13 * b07,
+ a00 * b11 - a02 * b08 + a03 * b07,
+ a32 * b02 - a30 * b05 - a33 * b01,
+ a20 * b05 - a22 * b02 + a23 * b01,
+ a10 * b10 - a11 * b08 + a13 * b06,
+ a01 * b08 - a00 * b10 - a03 * b06,
+ a30 * b04 - a31 * b02 + a33 * b00,
+ a21 * b02 - a20 * b04 - a23 * b00,
+ a11 * b07 - a10 * b09 - a12 * b06,
+ a00 * b09 - a01 * b07 + a02 * b06,
+ a31 * b01 - a30 * b03 - a32 * b00,
+ a20 * b03 - a21 * b01 + a22 * b00) / det;
+ }
+
+ void main () {
+ mat4 modelViewMatrix = uViewMatrix * uModelMatrix;
+ mat3 normalMatrix = mat3(transpose(inverse(modelViewMatrix)));
+ vNormal = normalMatrix * aNormal;
+ gl_Position = uProjectionMatrix * modelViewMatrix * vec4(aPosition + aOffset, 1.0);
+ }
+ `,
+ frag: /* glsl */ `
+ precision highp float;
+
+ varying vec3 vNormal;
+
+ void main () {
+ gl_FragColor.rgb = vNormal * 0.5 + 0.5;
+ gl_FragColor.a = 1.0;
+ }
+ `,
+ depthTest: true,
}),
- uniforms: {
- uProjectionMatrix: camera.projectionMatrix,
- uViewMatrix: camera.viewMatrix,
- uModelMatrix: mat4.create(),
- uPosition: [0, 0, 0]
- },
attributes: {
aPosition: ctx.vertexBuffer(cube.positions),
- aNormal: ctx.vertexBuffer(cube.normals)
+ aNormal: ctx.vertexBuffer(cube.normals),
+ aOffset: { buffer: ctx.vertexBuffer(offsets), divisor: 1 },
},
- indices: ctx.indexBuffer(cube.cells)
-}
-
-var instances = []
-for (var i = 0; i < 200; i++) {
- instances.push({
- uniforms: {
- uPosition: random.vec3()
- }
- })
-}
-
-window.addEventListener('resize', (e) => {
- ctx.gl.canvas.width = window.innerWidth
- ctx.gl.canvas.height = window.innerHeight
- camera.set({
- aspect: ctx.gl.canvas.width / ctx.gl.canvas.height
- })
-})
-
-var zoom2 = document.createElement('a')
-zoom2.innerText = 'Zoom to 2'
-zoom2.style.position = 'absolute'
-zoom2.style.top = '10px'
-zoom2.style.left = '20px'
-zoom2.style.color = 'white'
-zoom2.setAttribute('href', '#')
-zoom2.addEventListener('click', () => {
- orbiter.set({ distance: 2 })
-})
-document.body.appendChild(zoom2)
-
-var zoom5 = document.createElement('a')
-zoom5.innerText = 'Zoom to 5'
-zoom5.style.position = 'absolute'
-zoom5.style.top = '30px'
-zoom5.style.left = '20px'
-zoom5.style.color = 'white'
-zoom5.setAttribute('href', '#')
-zoom5.addEventListener('click', () => {
- orbiter.set({ distance: 5 })
-})
-document.body.appendChild(zoom5)
-
+ instances: offsets.length,
+ indices: ctx.indexBuffer(cube.cells),
+};
+
+const onResize = () => {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+
+ const aspect = canvas.width / canvas.height;
+
+ const viewportWidth = window.innerWidth;
+ const viewportHeight = window.innerHeight;
+ const viewWidth = viewportWidth * 0.5;
+ const viewSize = 5;
+
+ const size = [viewportWidth, viewportHeight];
+ const totalSize = [viewportWidth, viewportHeight];
+
+ perspectiveCamera.set({
+ aspect,
+ view: {
+ offset: [viewWidth * 0.5, 0],
+ size,
+ totalSize,
+ },
+ });
+
+ orthographicCamera.set({
+ left: (-0.5 * viewSize * aspect) / 2,
+ right: (0.5 * viewSize * aspect) / 2,
+ top: (0.5 * viewSize) / 2,
+ bottom: (-0.5 * viewSize) / 2,
+ view: {
+ offset: [-viewWidth * 0.5, 0],
+ size,
+ totalSize,
+ },
+ });
+};
+window.addEventListener("resize", onResize);
+onResize();
+
+// GUI
+const addOrbiterGui = (orbiter) => {
+ gui.addParam("easing", orbiter, "easing", { min: 0, max: 1 });
+ gui.addParam("zoom", orbiter, "zoom");
+ gui.addParam("pan", orbiter, "pan");
+ gui.addParam("drag", orbiter, "drag");
+ gui.addParam("minDistance", orbiter, "minDistance", { min: 0, max: 10 });
+ gui.addParam("maxDistance", orbiter, "maxDistance", { min: 10, max: 100 });
+ gui.addParam("minLat", orbiter, "minLat", { min: -89.5, max: 10 });
+ gui.addParam("maxLat", orbiter, "maxLat", { min: 10, max: 89.5 });
+ gui.addParam("minLon", orbiter, "minLon", { min: -1000, max: 0 });
+ gui.addParam("maxLon", orbiter, "maxLon", { min: 0, max: 1000 });
+ gui.addParam("panSlowdown", orbiter, "panSlowdown", { min: 0, max: 10 });
+ gui.addParam("zoomSlowdown", orbiter, "zoomSlowdown", { min: 0, max: 1000 });
+ gui.addParam("dragSlowdown", orbiter, "dragSlowdown", { min: 0, max: 10 });
+ gui.addParam("autoUpdate", orbiter, "autoUpdate", {}, (v) =>
+ orbiter.set({ autoUpdate: v })
+ );
+};
+
+gui.addColumn("Perspective");
+addOrbiterGui(perspectiveOrbiter);
+gui.addSeparator();
+gui.addParam("fov", State, "fov", { min: Math.PI / 8, max: Math.PI / 2 }, () =>
+ perspectiveCamera.set({ fov: State.fov })
+);
+gui.addColumn("Orthographic");
+addOrbiterGui(orthographicOrbiter);
+
+gui.addColumn("Shared");
+gui.addParam("Distance", State, "distance", { min: 2, max: 20 }, () => {
+ perspectiveOrbiter.set({ distance: State.distance });
+ orthographicOrbiter.set({ distance: State.distance });
+});
+
+// Frame
ctx.frame(() => {
- ctx.submit(clearCmd)
- ctx.submit(drawCubeCmd, instances)
-})
+ ctx.submit(clearCmd);
+ ctx.submit(drawCubeCmd, {
+ uniforms: {
+ uProjectionMatrix: perspectiveCamera.projectionMatrix,
+ uViewMatrix: perspectiveCamera.viewMatrix,
+ uModelMatrix: mat4.create(),
+ },
+ });
+ ctx.submit(drawCubeCmd, {
+ uniforms: {
+ uProjectionMatrix: orthographicCamera.projectionMatrix,
+ uViewMatrix: orthographicCamera.viewMatrix,
+ uModelMatrix: mat4.create(),
+ },
+ });
+
+ gui.draw();
+});
diff --git a/example/package-lock.json b/example/package-lock.json
deleted file mode 100644
index 8461943..0000000
--- a/example/package-lock.json
+++ /dev/null
@@ -1,876 +0,0 @@
-{
- "name": "example",
- "version": "1.0.0",
- "lockfileVersion": 1,
- "requires": true,
- "dependencies": {
- "for-each": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz",
- "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=",
- "requires": {
- "is-function": "~1.0.0"
- }
- },
- "glob": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
- "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "dependencies": {
- "balanced-match": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
- "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
- },
- "brace-expansion": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
- "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
- "requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
- },
- "inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "requires": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "inherits": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
- },
- "minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "requires": {
- "wrappy": "1"
- }
- },
- "path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
- }
- }
- },
- "glsl-inverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/glsl-inverse/-/glsl-inverse-1.0.0.tgz",
- "integrity": "sha1-EsCx0GX1WERNHm/q95td34qRiuY="
- },
- "glsl-transpose": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/glsl-transpose/-/glsl-transpose-1.0.0.tgz",
- "integrity": "sha1-Y6RKJIJur7x4B9fGzR1ZHAGWrpA="
- },
- "glslify": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/glslify/-/glslify-6.1.0.tgz",
- "integrity": "sha1-zf/P0qZXFyISjT0TNWwTbebOl0I=",
- "requires": {
- "bl": "^1.0.0",
- "concat-stream": "^1.5.2",
- "duplexify": "^3.4.5",
- "falafel": "^2.0.0",
- "from2": "^2.3.0",
- "glsl-resolve": "0.0.1",
- "glsl-token-whitespace-trim": "^1.0.0",
- "glslify-bundle": "^5.0.0",
- "glslify-deps": "^1.2.5",
- "minimist": "^1.2.0",
- "resolve": "^1.1.5",
- "stack-trace": "0.0.9",
- "static-eval": "^1.1.1",
- "tape": "^4.6.0",
- "through2": "^2.0.1",
- "xtend": "^4.0.0"
- },
- "dependencies": {
- "acorn": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz",
- "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug=="
- },
- "bl": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz",
- "integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=",
- "requires": {
- "readable-stream": "^2.0.5"
- }
- },
- "colors": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz",
- "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w="
- },
- "commander": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz",
- "integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E="
- },
- "concat-stream": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
- "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
- "requires": {
- "inherits": "^2.0.3",
- "readable-stream": "^2.2.2",
- "typedarray": "^0.0.6"
- }
- },
- "core-util-is": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
- },
- "deep-equal": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
- "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU="
- },
- "deep-is": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
- "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
- },
- "define-properties": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
- "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=",
- "requires": {
- "foreach": "^2.0.5",
- "object-keys": "^1.0.8"
- }
- },
- "defined": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
- "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM="
- },
- "duplexify": {
- "version": "3.5.3",
- "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.3.tgz",
- "integrity": "sha512-g8ID9OroF9hKt2POf8YLayy+9594PzmM3scI00/uBXocX3TWNgoB67hjzkFe9ITAbQOne/lLdBxHXvYUM4ZgGA==",
- "requires": {
- "end-of-stream": "^1.0.0",
- "inherits": "^2.0.1",
- "readable-stream": "^2.0.0",
- "stream-shift": "^1.0.0"
- }
- },
- "end-of-stream": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
- "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
- "requires": {
- "once": "^1.4.0"
- }
- },
- "es-abstract": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.10.0.tgz",
- "integrity": "sha512-/uh/DhdqIOSkAWifU+8nG78vlQxdLckUdI/sPgy0VhuXi2qJ7T8czBmqIYtLQVpCIFYafChnsRsB5pyb1JdmCQ==",
- "requires": {
- "es-to-primitive": "^1.1.1",
- "function-bind": "^1.1.1",
- "has": "^1.0.1",
- "is-callable": "^1.1.3",
- "is-regex": "^1.0.4"
- }
- },
- "es-to-primitive": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
- "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=",
- "requires": {
- "is-callable": "^1.1.1",
- "is-date-object": "^1.0.1",
- "is-symbol": "^1.0.1"
- }
- },
- "escodegen": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.0.tgz",
- "integrity": "sha512-v0MYvNQ32bzwoG2OSFzWAkuahDQHK92JBN0pTAALJ4RIxEZe766QJPDR8Hqy7XNUy5K3fnVL76OqYAdc4TZEIw==",
- "requires": {
- "esprima": "^3.1.3",
- "estraverse": "^4.2.0",
- "esutils": "^2.0.2",
- "optionator": "^0.8.1",
- "source-map": "~0.5.6"
- }
- },
- "esprima": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
- "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
- },
- "estraverse": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
- "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
- },
- "esutils": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
- "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
- },
- "events": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
- "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
- },
- "falafel": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.1.0.tgz",
- "integrity": "sha1-lrsXdh2rqU9G0AFzizzt86Z/4Gw=",
- "requires": {
- "acorn": "^5.0.0",
- "foreach": "^2.0.5",
- "isarray": "0.0.1",
- "object-keys": "^1.0.6"
- },
- "dependencies": {
- "isarray": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
- "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
- }
- }
- },
- "fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
- },
- "findup": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/findup/-/findup-0.1.5.tgz",
- "integrity": "sha1-itkpozk7rGJ5V6fl3kYjsGsOLOs=",
- "requires": {
- "colors": "~0.6.0-1",
- "commander": "~2.1.0"
- }
- },
- "foreach": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
- "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
- },
- "from2": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
- "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
- "requires": {
- "inherits": "^2.0.1",
- "readable-stream": "^2.0.0"
- }
- },
- "function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
- },
- "glsl-inject-defines": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/glsl-inject-defines/-/glsl-inject-defines-1.0.3.tgz",
- "integrity": "sha1-3RqswsF/yyvT/DJBHGYz0Ne2D9Q=",
- "requires": {
- "glsl-token-inject-block": "^1.0.0",
- "glsl-token-string": "^1.0.1",
- "glsl-tokenizer": "^2.0.2"
- }
- },
- "glsl-resolve": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/glsl-resolve/-/glsl-resolve-0.0.1.tgz",
- "integrity": "sha1-iUvvc5ENeSyBtRQxgANdCnivdtM=",
- "requires": {
- "resolve": "^0.6.1",
- "xtend": "^2.1.2"
- },
- "dependencies": {
- "resolve": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz",
- "integrity": "sha1-3ZV5gufnNt699TtYpN2RdUV13UY="
- },
- "xtend": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz",
- "integrity": "sha1-7vax8ZjByN6vrYsXZaBNrUoBxak="
- }
- }
- },
- "glsl-token-assignments": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/glsl-token-assignments/-/glsl-token-assignments-2.0.2.tgz",
- "integrity": "sha1-pdgqt4SZwuimuDy2lJXm5mXOAZ8="
- },
- "glsl-token-defines": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/glsl-token-defines/-/glsl-token-defines-1.0.0.tgz",
- "integrity": "sha1-y4kqqVmTYjFyhHDU90AySJaX+p0=",
- "requires": {
- "glsl-tokenizer": "^2.0.0"
- }
- },
- "glsl-token-depth": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/glsl-token-depth/-/glsl-token-depth-1.1.2.tgz",
- "integrity": "sha1-I8XjDuK9JViEtKKLyFC495HpXYQ="
- },
- "glsl-token-descope": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/glsl-token-descope/-/glsl-token-descope-1.0.2.tgz",
- "integrity": "sha1-D8kKsyYYa4L1l7LnfcniHvzTIHY=",
- "requires": {
- "glsl-token-assignments": "^2.0.0",
- "glsl-token-depth": "^1.1.0",
- "glsl-token-properties": "^1.0.0",
- "glsl-token-scope": "^1.1.0"
- }
- },
- "glsl-token-inject-block": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/glsl-token-inject-block/-/glsl-token-inject-block-1.1.0.tgz",
- "integrity": "sha1-4QFfWYDBCRgkraomJfHf3ovQADQ="
- },
- "glsl-token-properties": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/glsl-token-properties/-/glsl-token-properties-1.0.1.tgz",
- "integrity": "sha1-SD3D2Dnw1LXGFx0VkfJJvlPCip4="
- },
- "glsl-token-scope": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/glsl-token-scope/-/glsl-token-scope-1.1.2.tgz",
- "integrity": "sha1-oXKOeN8kRE+cuT/RjvD3VQOmQ7E="
- },
- "glsl-token-string": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/glsl-token-string/-/glsl-token-string-1.0.1.tgz",
- "integrity": "sha1-WUQdL4V958NEnJRWZgIezjWOSOw="
- },
- "glsl-token-whitespace-trim": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/glsl-token-whitespace-trim/-/glsl-token-whitespace-trim-1.0.0.tgz",
- "integrity": "sha1-RtHf6Yx1vX1QTAXX0RsbPpzJOxA="
- },
- "glsl-tokenizer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/glsl-tokenizer/-/glsl-tokenizer-2.1.2.tgz",
- "integrity": "sha1-cgMHUi4DxXrzXABVGVDEpw7y37k=",
- "requires": {
- "through2": "^0.6.3"
- },
- "dependencies": {
- "isarray": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
- "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
- },
- "readable-stream": {
- "version": "1.0.34",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
- "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.1",
- "isarray": "0.0.1",
- "string_decoder": "~0.10.x"
- }
- },
- "string_decoder": {
- "version": "0.10.31",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
- "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
- },
- "through2": {
- "version": "0.6.5",
- "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
- "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
- "requires": {
- "readable-stream": ">=1.0.33-1 <1.1.0-0",
- "xtend": ">=4.0.0 <4.1.0-0"
- }
- }
- }
- },
- "glslify-bundle": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/glslify-bundle/-/glslify-bundle-5.0.0.tgz",
- "integrity": "sha1-AlKtoe+d8wtmAAbguyH9EwtIbkI=",
- "requires": {
- "glsl-inject-defines": "^1.0.1",
- "glsl-token-defines": "^1.0.0",
- "glsl-token-depth": "^1.1.1",
- "glsl-token-descope": "^1.0.2",
- "glsl-token-scope": "^1.1.1",
- "glsl-token-string": "^1.0.1",
- "glsl-token-whitespace-trim": "^1.0.0",
- "glsl-tokenizer": "^2.0.2",
- "murmurhash-js": "^1.0.0",
- "shallow-copy": "0.0.1"
- }
- },
- "glslify-deps": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/glslify-deps/-/glslify-deps-1.3.0.tgz",
- "integrity": "sha1-CyI0yOqePT/X9rPLfwOuWea1Glk=",
- "requires": {
- "events": "^1.0.2",
- "findup": "^0.1.5",
- "glsl-resolve": "0.0.1",
- "glsl-tokenizer": "^2.0.0",
- "graceful-fs": "^4.1.2",
- "inherits": "^2.0.1",
- "map-limit": "0.0.1",
- "resolve": "^1.0.0"
- }
- },
- "graceful-fs": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
- "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
- },
- "has": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
- "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
- "requires": {
- "function-bind": "^1.0.2"
- }
- },
- "inherits": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
- },
- "is-callable": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
- "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI="
- },
- "is-date-object": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
- "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
- },
- "is-regex": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
- "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
- "requires": {
- "has": "^1.0.1"
- }
- },
- "is-symbol": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz",
- "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI="
- },
- "isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
- },
- "levn": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
- "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
- "requires": {
- "prelude-ls": "~1.1.2",
- "type-check": "~0.3.2"
- }
- },
- "map-limit": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz",
- "integrity": "sha1-63lhAxwPDo0AG/LVb6toXViCLzg=",
- "requires": {
- "once": "~1.3.0"
- },
- "dependencies": {
- "once": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
- "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
- "requires": {
- "wrappy": "1"
- }
- }
- }
- },
- "minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
- },
- "murmurhash-js": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz",
- "integrity": "sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E="
- },
- "object-inspect": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.3.0.tgz",
- "integrity": "sha512-OHHnLgLNXpM++GnJRyyhbr2bwl3pPVm4YvaraHrRvDt/N3r+s/gDVHciA7EJBTkijKXj61ssgSAikq1fb0IBRg=="
- },
- "object-keys": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
- "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0="
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "requires": {
- "wrappy": "1"
- }
- },
- "optionator": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
- "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
- "requires": {
- "deep-is": "~0.1.3",
- "fast-levenshtein": "~2.0.4",
- "levn": "~0.3.0",
- "prelude-ls": "~1.1.2",
- "type-check": "~0.3.2",
- "wordwrap": "~1.0.0"
- }
- },
- "prelude-ls": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
- "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
- },
- "process-nextick-args": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
- "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
- },
- "readable-stream": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
- "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~1.0.6",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.0.3",
- "util-deprecate": "~1.0.1"
- }
- },
- "resolve": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz",
- "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==",
- "requires": {
- "path-parse": "^1.0.5"
- }
- },
- "resumer": {
- "version": "0.0.0",
- "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz",
- "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=",
- "requires": {
- "through": "~2.3.4"
- }
- },
- "shallow-copy": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz",
- "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA="
- },
- "source-map": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
- "optional": true
- },
- "stack-trace": {
- "version": "0.0.9",
- "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz",
- "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU="
- },
- "static-eval": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-1.1.1.tgz",
- "integrity": "sha1-yoEwIQNUzxPZpyK8fpI3eEV7sZI=",
- "requires": {
- "escodegen": "^1.8.1"
- }
- },
- "stream-shift": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
- "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
- },
- "string.prototype.trim": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz",
- "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=",
- "requires": {
- "define-properties": "^1.1.2",
- "es-abstract": "^1.5.0",
- "function-bind": "^1.0.2"
- }
- },
- "string_decoder": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
- "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- },
- "tape": {
- "version": "4.8.0",
- "resolved": "https://registry.npmjs.org/tape/-/tape-4.8.0.tgz",
- "integrity": "sha512-TWILfEnvO7I8mFe35d98F6T5fbLaEtbFTG/lxWvid8qDfFTxt19EBijWmB4j3+Hoh5TfHE2faWs73ua+EphuBA==",
- "requires": {
- "deep-equal": "~1.0.1",
- "defined": "~1.0.0",
- "for-each": "~0.3.2",
- "function-bind": "~1.1.0",
- "glob": "~7.1.2",
- "has": "~1.0.1",
- "inherits": "~2.0.3",
- "minimist": "~1.2.0",
- "object-inspect": "~1.3.0",
- "resolve": "~1.4.0",
- "resumer": "~0.0.0",
- "string.prototype.trim": "~1.1.2",
- "through": "~2.3.8"
- },
- "dependencies": {
- "resolve": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz",
- "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==",
- "requires": {
- "path-parse": "^1.0.5"
- }
- }
- }
- },
- "through": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
- "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
- },
- "through2": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
- "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
- "requires": {
- "readable-stream": "^2.1.5",
- "xtend": "~4.0.1"
- }
- },
- "type-check": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
- "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
- "requires": {
- "prelude-ls": "~1.1.2"
- }
- },
- "typedarray": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
- "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
- },
- "util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
- },
- "wordwrap": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
- "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
- },
- "xtend": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
- "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
- }
- }
- },
- "is-browser": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-browser/-/is-browser-2.0.1.tgz",
- "integrity": "sha1-i/C695mpxi/Z3lvO5M8zl8PnUpo="
- },
- "is-function": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz",
- "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU="
- },
- "is-plask": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-plask/-/is-plask-1.1.1.tgz",
- "integrity": "sha1-3mOSZXYfKK939JtBI4tnp/R8sgg=",
- "requires": {
- "plask-wrap": "^1.0.1"
- }
- },
- "path-parse": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
- "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME="
- },
- "performance-now": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
- "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
- },
- "pex-context": {
- "version": "2.0.0-34",
- "resolved": "https://registry.npmjs.org/pex-context/-/pex-context-2.0.0-34.tgz",
- "integrity": "sha512-aiMZXn7d403iXSA+VWzsRVGTP3wp5uaSBtvI00hRkHaVvPgMjRKsXUE7PkNprIFz9OQXWC+Y/5LlJhhdOPitlQ==",
- "requires": {
- "debug": "^2.6.3",
- "is-browser": "^2.0.1",
- "is-plask": "^1.1.0",
- "pex-gl": "^2.4.1",
- "plask-wrap": "^1.0.0",
- "raf": "^3.3.0",
- "ramda": "^0.23.0"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "requires": {
- "ms": "2.0.0"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- },
- "pex-gl": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/pex-gl/-/pex-gl-2.4.1.tgz",
- "integrity": "sha1-MqZNLqathNo31KvrcfvHq5Wn0W4=",
- "requires": {
- "is-plask": "^1.1.1"
- }
- }
- }
- },
- "pex-gl": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/pex-gl/-/pex-gl-1.4.1.tgz",
- "integrity": "sha1-m9Fbr1zzK3VOOf7547zkxMAjM1Y=",
- "requires": {
- "is-plask": "^1.1.1"
- }
- },
- "pex-math": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/pex-math/-/pex-math-2.0.0.tgz",
- "integrity": "sha512-6Ca4OSIzCg0saX2iV6jAQpZoMrmgjWw+AAOKfjDJ3NDdsK7J7c1x2hpx6vJdGWDaSkeCCzEAX6cxGSoCdnQTPg=="
- },
- "pex-random": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/pex-random/-/pex-random-1.0.1.tgz",
- "integrity": "sha1-Cwp9kOLgV9V3d+2ROjm3IlUxm+U=",
- "requires": {
- "seedrandom": "^2.3.10",
- "simplex-noise": "2.1.1"
- }
- },
- "plask-wrap": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/plask-wrap/-/plask-wrap-1.0.1.tgz",
- "integrity": "sha1-xfXBtdh0wcfZtxf3chJvJauzHd8="
- },
- "primitive-cube": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/primitive-cube/-/primitive-cube-2.0.1.tgz",
- "integrity": "sha1-iqW8PL/y7+6DzOdM/KuKybW0awY="
- },
- "raf": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz",
- "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==",
- "requires": {
- "performance-now": "^2.1.0"
- }
- },
- "ramda": {
- "version": "0.23.0",
- "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.23.0.tgz",
- "integrity": "sha1-zNE//3NJepOXTj6GMnv9h71ujis="
- },
- "safe-buffer": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
- "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
- },
- "seedrandom": {
- "version": "2.4.3",
- "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz",
- "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw="
- },
- "simplex-noise": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/simplex-noise/-/simplex-noise-2.1.1.tgz",
- "integrity": "sha1-kuZ4XxaptanxMdHplsM3q/X0KF0="
- }
- }
-}
diff --git a/example/package.json b/example/package.json
deleted file mode 100644
index a79f664..0000000
--- a/example/package.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "name": "example",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "start": "budo index.js:bundle.js --open --live -- -t glslify"
- },
- "author": "Marcin Ignac (http://marcinignac.com/)",
- "license": "MIT",
- "dependencies": {
- "glsl-inverse": "^1.0.0",
- "glsl-transpose": "^1.0.0",
- "glslify": "^6.0.1",
- "pex-context": "^2.0.0-34",
- "pex-gl": "^1.3.0",
- "pex-math": "^2.0.0",
- "pex-random": "^1.0.1",
- "primitive-cube": "^2.0.0"
- },
- "devDependencies": {
- "budo": "^11.3.2"
- }
-}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..cb838af
--- /dev/null
+++ b/index.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ pex-cam by pex-gl (https://github.com/pex-gl)
+
+
+
+
+
+
+
+
+
+
diff --git a/index.js b/index.js
old mode 100644
new mode 100755
index 92f031f..55ca9bc
--- a/index.js
+++ b/index.js
@@ -1,4 +1,29 @@
-module.exports = {
- orbiter: require('./orbiter'),
- perspective: require('./perspective')
-}
+/**
+ * Re-export classes and factory functions
+ * @module index
+ */
+
+import PerspectiveCamera from "./perspective.js";
+import OrthographicCamera from "./orthographic.js";
+import OrbiterControls from "./orbiter.js";
+
+/**
+ * Factory function for perspective camera
+ * @param {import("./types.js").CameraOptions & import("./types.js").PerspectiveCameraOptions} opts
+ * @returns {PerspectiveCamera}
+ */
+export const perspective = (opts) => new PerspectiveCamera(opts);
+
+/**
+ * Factory function for orthographic camera
+ * @param {import("./types.js").CameraOptions & import("./types.js").OrthographicCameraOptions} opts
+ * @returns {OrthographicCamera}
+ */
+export const orthographic = (opts) => new OrthographicCamera(opts);
+
+/**
+ * Factory function for orbiter controls
+ * @param {import("./types.js").OrbiterControlsOptions} opts
+ * @returns {OrbiterControls}
+ */
+export const orbiter = (opts) => new OrbiterControls(opts);
diff --git a/orbiter.js b/orbiter.js
index aafb3aa..24b378e 100644
--- a/orbiter.js
+++ b/orbiter.js
@@ -1,289 +1,362 @@
-'use strict'
-const vec3 = require('pex-math/vec3')
-const mat4 = require('pex-math/mat4')
-const ray = require('pex-geom/ray')
-const clamp = require('pex-math/utils').clamp
-const raf = require('raf')
-const interpolateAngle = require('interpolate-angle')
-const lerp = require('pex-math/utils').lerp
-const toRadians = require('pex-math/utils').toRadians
-const toDegrees = require('pex-math/utils').toDegrees
-const latLonToXyz = require('latlon-to-xyz')
-const xyzToLatLon = require('xyz-to-latlon')
-const eventOffset = require('mouse-event-offset')
-
-function offset (e, target) {
- if (e.touches) return eventOffset(e.touches[0], target)
- else return eventOffset(e, target)
-}
+import { vec2, vec3, utils } from "pex-math";
+import { ray } from "pex-geom";
+
+import interpolateAngle from "interpolate-angle";
+import latLonToXyz from "latlon-to-xyz";
+import xyzToLatLon from "xyz-to-latlon";
+import eventOffset from "mouse-event-offset";
+
+/**
+ * Camera controls to orbit around a target
+ */
+class OrbiterControls {
+ static get DEFAULT_OPTIONS() {
+ return {
+ element: document,
+ easing: 0.1,
+
+ zoom: true,
+ pan: true,
+ drag: true,
+
+ minDistance: 0.01,
+ maxDistance: Infinity,
+ minLat: -89.5,
+ maxLat: 89.5,
+ minLon: -Infinity,
+ maxLon: Infinity,
+ panSlowdown: 4,
+ zoomSlowdown: 400,
+ dragSlowdown: 4,
-function Orbiter (opts) {
- // TODO: split into internal state and public state
- const initialState = {
- camera: opts.camera,
- invViewMatrix: mat4.create(),
- dragging: false,
- lat: 0, // Y
- minLat: -89.5,
- maxLat: 89.5,
- lon: 0, // XZ
- minLon: -Infinity,
- maxLon: Infinity,
- currentLat: 0,
- currentLon: 0,
- easing: 1,
- element: opts.element || window,
- width: 0,
- height: 0,
- clickPosWindow: [0, 0],
- dragPos: [0, 0, 0],
- dragPosWindow: [0, 0],
- distance: 1,
- currentDistance: 1,
- minDistance: 1,
- maxDistance: 1,
- zoomSlowdown: 400,
- zoom: true,
- pan: true,
- drag: true,
- dragSlowdown: 4,
- clickTarget: [0, 0, 0],
- clickPosPlane: [0, 0, 0],
- dragPosPlane: [0, 0, 0],
- clickPosWorld: [0, 0, 0],
- dragPosWorld: [0, 0, 0],
- panPlane: null,
- autoUpdate: true
+ autoUpdate: true,
+ };
}
- this.set(initialState)
- this.set(opts)
- this.setup()
-}
+ get domElement() {
+ return this.element === document ? this.element.body : this.element;
+ }
+
+ /**
+ * Create an instance of OrbiterControls
+ * @param {import("./types.js").OrbiterControlsOptions} opts
+ */
+ constructor(opts) {
+ // Internals
+ // Set initially by .set
+ this.lat = null; // Y
+ this.lon = null; // XZ
+ this.currentLat = null;
+ this.currentLon = null;
+ this.distance = null;
+ this.currentDistance = null;
+
+ // Updated by user interaction
+ this.panning = false;
+ this.dragging = false;
+ this.zooming = false;
+ this.width = 0;
+ this.height = 0;
-Orbiter.prototype.set = function (opts) {
- if (opts.camera) {
- const distance = vec3.distance(opts.camera.position, opts.camera.target)
- const latLon = xyzToLatLon(vec3.normalize(vec3.sub(vec3.copy(opts.camera.position), opts.camera.target)))
- this.lat = latLon[0]
- this.lon = latLon[1]
- this.currentLat = this.lat
- this.currentLon = this.lon
- this.distance = distance
- this.currentDistance = this.distance
- this.minDistance = opts.minDistance || distance / 10
- this.maxDistance = opts.maxDistance || distance * 10
+ this.zoomTouchDistance = null;
+
+ this.panPlane = null;
+ this.clickTarget = [0, 0, 0];
+ this.clickPosWorld = [0, 0, 0];
+ this.clickPosPlane = [0, 0, 0];
+ this.dragPos = [0, 0, 0];
+ this.dragPosWorld = [0, 0, 0];
+ this.dragPosPlane = [0, 0, 0];
+
+ // TODO: add ability to set lat/lng instead of position/target
+ this.set({
+ ...OrbiterControls.DEFAULT_OPTIONS,
+ ...opts,
+ });
+ this.setup();
}
- Object.assign(this, opts)
-}
+ /**
+ * Update the control
+ * @param {import("./types.js").OrbiterOptions} opts
+ */
+ set(opts) {
+ Object.assign(this, opts);
-Orbiter.prototype.updateWindowSize = function () {
- const width = this.element.clientWidth || this.element.innerWidth
- const height = this.element.clientHeight || this.element.innerHeight
- if (width !== this.width) {
- this.width = width
- this.height = height
- this.radius = Math.min(this.width / 2, this.height / 2)
+ if (opts.camera) {
+ const latLon = xyzToLatLon(
+ vec3.normalize(
+ vec3.sub(vec3.copy(opts.camera.position), opts.camera.target)
+ )
+ );
+ const distance =
+ opts.distance ||
+ vec3.distance(opts.camera.position, opts.camera.target);
+
+ this.lat = latLon[0];
+ this.lon = latLon[1];
+ this.currentLat = this.lat;
+ this.currentLon = this.lon;
+ this.distance = distance;
+ this.currentDistance = this.distance;
+ }
+
+ if (Object.getOwnPropertyDescriptor(opts, "autoUpdate")) {
+ if (this.autoUpdate) {
+ const self = this;
+ this.rafHandle = requestAnimationFrame(function tick() {
+ self.updateCamera();
+ if (self.autoUpdate) self.rafHandle = requestAnimationFrame(tick);
+ });
+ } else if (this.rafHandle) {
+ cancelAnimationFrame(this.rafHandle);
+ }
+ }
}
-}
-Orbiter.prototype.updateCamera = function () {
- // instad of rotating the object we want to move camera around it
- // state.currRot[3] *= -1
- if (!this.camera) return
+ updateCamera() {
+ // instad of rotating the object we want to move camera around it
+ if (!this.camera) return;
- const position = this.camera.position
- const target = this.camera.target
+ const position = this.camera.position;
+ const target = this.camera.target;
- this.lat = clamp(this.lat, this.minLat, this.maxLat)
- this.lon = clamp(this.lon, this.minLon, this.maxLon) % 360
+ this.lat = utils.clamp(this.lat, this.minLat, this.maxLat);
- this.currentLat = toDegrees(
- interpolateAngle(
- (toRadians(this.currentLat) + 2 * Math.PI) % (2 * Math.PI),
- (toRadians(this.lat) + 2 * Math.PI) % (2 * Math.PI),
- this.easing
- )
- )
- this.currentLon = toDegrees(
- interpolateAngle(
- (toRadians(this.currentLon) + 2 * Math.PI) % (2 * Math.PI),
- (toRadians(this.lon) + 2 * Math.PI) % (2 * Math.PI),
- this.easing
- )
- )
- this.currentDistance = lerp(this.currentDistance, this.distance, this.easing)
-
- // set new camera position according to the current
- // rotation at distance relative to target
- latLonToXyz(this.currentLat, this.currentLon, position)
- vec3.scale(position, this.currentDistance)
- vec3.add(position, target)
-
- this.camera.set({
- position: position
- })
-}
+ if (this.minLon !== -Infinity && this.maxLon !== Infinity) {
+ this.lon = utils.clamp(this.lon, this.minLon, this.maxLon) % 360;
+ }
-Orbiter.prototype.setup = function () {
- var orbiter = this
-
- function down (x, y, shift) {
- orbiter.dragging = true
- orbiter.dragPos[0] = x
- orbiter.dragPos[1] = y
- if (shift && orbiter.pan) {
- orbiter.clickPosWindow[0] = x
- orbiter.clickPosWindow[1] = y
- vec3.set(orbiter.clickTarget, orbiter.camera.target)
- const targetInViewSpace = vec3.multMat4(vec3.copy(orbiter.clickTarget), orbiter.camera.viewMatrix)
- orbiter.panPlane = [targetInViewSpace, [0, 0, 1]]
- ray.hitTestPlane(
- orbiter.camera.getViewRay(orbiter.clickPosWindow[0], orbiter.clickPosWindow[1], orbiter.width, orbiter.height),
- orbiter.panPlane[0],
- orbiter.panPlane[1],
- orbiter.clickPosPlane
+ this.currentLat = utils.toDegrees(
+ interpolateAngle(
+ (utils.toRadians(this.currentLat) + 2 * Math.PI) % (2 * Math.PI),
+ (utils.toRadians(this.lat) + 2 * Math.PI) % (2 * Math.PI),
+ this.easing
)
- ray.hitTestPlane(
- orbiter.camera.getViewRay(orbiter.dragPosWindow[0], orbiter.dragPosWindow[1], orbiter.width, orbiter.height),
- orbiter.panPlane[0],
- orbiter.panPlane[1],
- orbiter.dragPosPlane
- )
- } else {
- orbiter.panPlane = null
- }
+ );
+
+ this.currentLon += (this.lon - this.currentLon) * this.easing;
+
+ this.currentDistance = utils.lerp(
+ this.currentDistance,
+ this.distance,
+ this.easing
+ );
+
+ // Set position from lat/lon
+ latLonToXyz(this.currentLat, this.currentLon, position);
+
+ // Move position according to distance and target
+ vec3.scale(position, this.currentDistance);
+ vec3.add(position, target);
+
+ if (this.camera.zoom) this.camera.set({ zoom: vec3.length(position) });
+ this.camera.set({ position });
+ }
+
+ updateWindowSize() {
+ const width = this.domElement.clientWidth || this.domElement.innerWidth;
+ const height = this.domElement.clientHeight || this.domElement.innerHeight;
+
+ if (width !== this.width) this.width = width;
+ if (height !== this.height) this.height = height;
+ }
+
+ handleDragStart(position) {
+ this.dragging = true;
+ this.dragPos = position;
}
- function move (x, y, shift) {
- if (!orbiter.dragging) {
- return
+ handlePanZoomStart(touch0, touch1) {
+ this.dragging = false;
+
+ if (this.zoom && touch1) {
+ this.zooming = true;
+ this.zoomTouchDistance = vec2.distance(touch1, touch0);
}
- if (shift && orbiter.panPlane) {
- orbiter.dragPosWindow[0] = x
- orbiter.dragPosWindow[1] = y
- ray.hitTestPlane(
- orbiter.camera.getViewRay(orbiter.clickPosWindow[0], orbiter.clickPosWindow[1], orbiter.width, orbiter.height),
- orbiter.panPlane[0],
- orbiter.panPlane[1],
- orbiter.clickPosPlane
- )
+
+ const camera = this.camera;
+
+ if (this.pan && camera) {
+ this.panning = true;
+ this.updateWindowSize();
+
+ // TODO: use dragPos?
+ const clickPosWindow = touch1
+ ? [(touch0[0] + touch1[0]) * 0.5, (touch0[1] + touch1[1]) * 0.5]
+ : touch0;
+
+ vec3.set(this.clickTarget, camera.target);
+ const targetInViewSpace = vec3.multMat4(
+ vec3.copy(this.clickTarget),
+ camera.viewMatrix
+ );
+ this.panPlane = [targetInViewSpace, [0, 0, 1]];
+
ray.hitTestPlane(
- orbiter.camera.getViewRay(orbiter.dragPosWindow[0], orbiter.dragPosWindow[1], orbiter.width, orbiter.height),
- orbiter.panPlane[0],
- orbiter.panPlane[1],
- orbiter.dragPosPlane
- )
- mat4.set(orbiter.invViewMatrix, orbiter.camera.viewMatrix)
- mat4.invert(orbiter.invViewMatrix)
- vec3.multMat4(vec3.set(orbiter.clickPosWorld, orbiter.clickPosPlane), orbiter.invViewMatrix)
- vec3.multMat4(vec3.set(orbiter.dragPosWorld, orbiter.dragPosPlane), orbiter.invViewMatrix)
- const diffWorld = vec3.sub(vec3.copy(orbiter.dragPosWorld), orbiter.clickPosWorld)
- const target = vec3.sub(vec3.copy(orbiter.clickTarget), diffWorld)
- orbiter.camera.set({ target: target })
- orbiter.updateCamera()
- } else if (orbiter.drag) {
- const dx = x - orbiter.dragPos[0]
- const dy = y - orbiter.dragPos[1]
- orbiter.dragPos[0] = x
- orbiter.dragPos[1] = y
-
- orbiter.lat += dy / orbiter.dragSlowdown
- orbiter.lon -= dx / orbiter.dragSlowdown
-
- // TODO: how to have resolution independed scaling? will this code behave differently with retina/pixelRatio=2?
- orbiter.updateCamera()
+ camera.getViewRay(
+ clickPosWindow[0],
+ clickPosWindow[1],
+ this.width,
+ this.height
+ ),
+ this.panPlane,
+ this.clickPosPlane
+ );
}
}
- function up () {
- orbiter.dragging = false
- orbiter.panPlane = null
+ handleDragMove(position) {
+ const dx = position[0] - this.dragPos[0];
+ const dy = position[1] - this.dragPos[1];
+
+ this.lat += dy / this.dragSlowdown;
+ this.lon -= dx / this.dragSlowdown;
+
+ this.dragPos = position;
}
- function scroll (dy) {
- if (!orbiter.zoom) {
- return false
+ handlePanZoomMove(touch0, touch1) {
+ if (this.zoom && touch1) {
+ const distance = vec2.distance(touch1, touch0);
+ this.handleZoom(this.zoomTouchDistance - distance);
+ this.zoomTouchDistance = distance;
}
- orbiter.distance *= 1 + dy / orbiter.zoomSlowdown
- orbiter.distance = clamp(orbiter.distance, orbiter.minDistance, orbiter.maxDistance)
- orbiter.updateCamera()
- return true
- }
- function onMouseDown (e) {
- orbiter.updateWindowSize()
- const pos = offset(e, orbiter.element)
- down(
- pos[0],
- pos[1],
- e.shiftKey || (e.touches && e.touches.length === 2)
- )
- }
+ const camera = this.camera;
- function onMouseMove (e) {
- const pos = offset(e, orbiter.element)
- move(
- pos[0],
- pos[1],
- e.shiftKey || (e.touches && e.touches.length === 2)
- )
- }
+ if (this.pan && camera && this.panPlane) {
+ const dragPosWindow = touch1
+ ? [(touch0[0] + touch1[0]) * 0.5, (touch0[1] + touch1[1]) * 0.5]
+ : touch0;
- function onMouseUp (e) {
- up()
- }
+ ray.hitTestPlane(
+ camera.getViewRay(
+ dragPosWindow[0],
+ dragPosWindow[1],
+ this.width,
+ this.height
+ ),
+ this.panPlane,
+ this.dragPosPlane
+ );
- function onWheel (e) {
- if (scroll(e.deltaY) === true) {
- e.preventDefault()
- return false
+ vec3.multMat4(
+ vec3.set(this.clickPosWorld, this.clickPosPlane),
+ camera.invViewMatrix
+ );
+ vec3.multMat4(
+ vec3.set(this.dragPosWorld, this.dragPosPlane),
+ camera.invViewMatrix
+ );
+
+ const diffWorld = vec3.sub(
+ vec3.copy(this.dragPosWorld),
+ this.clickPosWorld
+ );
+ camera.set({
+ distance: this.distance,
+ target: vec3.sub(vec3.copy(this.clickTarget), diffWorld),
+ });
}
}
- function onTouchStart (e) {
- e.preventDefault()
- onMouseDown(e)
+ handleZoom(dy) {
+ this.distance *= 1 + dy / this.zoomSlowdown;
+ this.distance = utils.clamp(
+ this.distance,
+ this.minDistance,
+ this.maxDistance
+ );
}
- this._onMouseDown = onMouseDown
- this._onTouchStart = onTouchStart
- this._onMouseMove = onMouseMove
- this._onMouseUp = onMouseUp
- this._onWheel = onWheel
-
- this.element.addEventListener('mousedown', onMouseDown)
- this.element.addEventListener('touchstart', onTouchStart)
- this.element.addEventListener('wheel', onWheel)
- window.addEventListener('mousemove', onMouseMove)
- window.addEventListener('touchmove', onMouseMove, { passive: false })
- window.addEventListener('mouseup', onMouseUp)
- window.addEventListener('touchend', onMouseUp)
-
- this.updateCamera()
-
- if (this.autoUpdate) {
- const self = this
- this._rafHandle = raf(function tick () {
- orbiter.updateCamera()
- self._rafHandle = raf(tick)
- })
+ handleEnd() {
+ this.dragging = false;
+ this.panning = false;
+ this.zooming = false;
+ this.panPlane = null;
}
-}
-Orbiter.prototype.dispose = function () {
- this.element.removeEventListener('mousedown', this._onMouseDown)
- this.element.removeEventListener('touchstart', this._onTouchStart)
- this.element.removeEventListener('wheel', this._onWheel)
- window.removeEventListener('mousemove', this._onMouseMove)
- window.removeEventListener('touchmove', this._onMouseMove)
- window.removeEventListener('mouseup', this._onMouseUp)
- window.removeEventListener('touchend', this._onMouseUp)
- raf.cancel(this._rafHandle)
- this.camera = null
-}
+ setup() {
+ this.onPointerDown = (event) => {
+ const pan =
+ event.ctrlKey ||
+ event.metaKey ||
+ event.shiftKey ||
+ (event.touches && event.touches.length === 2);
+
+ const touch0 = eventOffset(
+ event.touches ? event.touches[0] : event,
+ this.domElement
+ );
+ if (this.drag && !pan) {
+ this.handleDragStart(touch0);
+ } else if ((this.pan || this.zoom) && pan) {
+ const touch1 =
+ event.touches && eventOffset(event.touches[1], this.domElement);
+ this.handlePanZoomStart(touch0, touch1);
+ }
+ };
+
+ this.onPointerMove = (event) => {
+ const touch0 = eventOffset(
+ event.touches ? event.touches[0] : event,
+ this.domElement
+ );
+ if (this.dragging) {
+ this.handleDragMove(touch0);
+ } else if (this.panning || this.zooming) {
+ if (event.touches && !event.touches[1]) return;
+ const touch1 =
+ event.touches && eventOffset(event.touches[1], this.domElement);
+ this.handlePanZoomMove(touch0, touch1);
+ }
+ };
+
+ this.onPointerUp = () => {
+ this.handleEnd();
+ };
+
+ this.onTouchStart = (event) => {
+ event.preventDefault();
+
+ if (event.touches.length <= 2) this.onPointerDown(event);
+ };
+
+ this.onTouchMove = (event) => {
+ !!event.cancelable && event.preventDefault();
+
+ if (event.touches.length <= 2) this.onPointerMove(event);
+ };
+
+ this.onWheel = (event) => {
+ if (!this.zoom) return;
-module.exports = function createOrbiter (opts) {
- return new Orbiter(opts)
+ event.preventDefault();
+ this.handleZoom(event.deltaY);
+ };
+
+ this.element.addEventListener("pointerdown", this.onPointerDown);
+ this.element.addEventListener("wheel", this.onWheel, { passive: false });
+
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+
+ this.domElement.style.touchAction = "none";
+ }
+
+ /**
+ * Remove all event listeners
+ */
+ dispose() {
+ if (this.rafHandle) cancelAnimationFrame(this.rafHandle);
+
+ this.element.removeEventListener("pointerdown", this.onPointerDown);
+ this.element.removeEventListener("wheel", this.onWheel);
+
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
}
+
+export default OrbiterControls;
diff --git a/orbiter.test.js b/orbiter.test.js
deleted file mode 100644
index b0537e1..0000000
--- a/orbiter.test.js
+++ /dev/null
@@ -1,21 +0,0 @@
-const latLonToXyz = require('./orbiter').latLonToXyz
-const xyzToLatLon = require('./orbiter').xyzToLatLon
-const Vec3 = require('pex-math/Vec3')
-
-function toFixed2 (v) {
- return +v.toFixed(2)
-}
-
-console.log('0, 0', latLonToXyz(0, 0).map(toFixed2), 'expecting', [0, 0, 1])
-console.log('0, 90', latLonToXyz(0, 90 / 180 * Math.PI).map(toFixed2), 'expecting', [1, 0, 0])
-console.log('0, -90', latLonToXyz(0, -90 / 180 * Math.PI).map(toFixed2), 'expecting', [-1, 0, 0])
-console.log('90, 0', latLonToXyz(90 / 180 * Math.PI, 0).map(toFixed2), 'expecting', [0, 1, 0])
-console.log('45, 90', latLonToXyz(45 / 180 * Math.PI, 90 / 180 * Math.PI).map(toFixed2), 'expecting', Vec3.normalize([1, 1, 0]))
-
-const pos = Vec3.normalize([5, 5, 0])
-const latLon = xyzToLatLon(pos)
-const pos2 = latLonToXyz(latLon[0], latLon[1])
-const latLon2 = xyzToLatLon(pos2)
-console.log(' ')
-console.log('pos', pos.map(toFixed2), '->', pos2.map(toFixed2))
-console.log('latLon', latLon.map(toFixed2), '->', latLon2.map(toFixed2))
diff --git a/orthographic.js b/orthographic.js
new file mode 100644
index 0000000..2024546
--- /dev/null
+++ b/orthographic.js
@@ -0,0 +1,107 @@
+import { mat4, vec3 } from "pex-math";
+
+import Camera from "./camera.js";
+
+/**
+ * A class to create an orthographic camera
+ * @extends Camera
+ */
+class OrthographicCamera extends Camera {
+ static get DEFAULT_OPTIONS() {
+ return {
+ left: -1,
+ right: 1,
+ bottom: -1,
+ top: 1,
+ zoom: 1,
+ };
+ }
+
+ /**
+ * Create an instance of PerspectiveCamera
+ * @param {import("./types.js").CameraOptions & import("./types.js").OrthographicCameraOptions} opts
+ */
+ constructor(opts = {}) {
+ super();
+
+ this.set({
+ ...Camera.DEFAULT_OPTIONS,
+ ...OrthographicCamera.DEFAULT_OPTIONS,
+ ...opts,
+ });
+ }
+
+ /**
+ * Update the camera
+ * @param {import("./types.js").CameraOptions & import("./types.js").OrthographicCameraOptions} opts
+ */
+ set(opts) {
+ super.set(opts);
+
+ if (
+ opts.left ||
+ opts.right ||
+ opts.bottom ||
+ opts.top ||
+ opts.zoom ||
+ opts.near ||
+ opts.far ||
+ opts.view
+ ) {
+ const dx = (this.right - this.left) / (2 / this.zoom);
+ const dy = (this.top - this.bottom) / (2 / this.zoom);
+ const cx = (this.right + this.left) / 2;
+ const cy = (this.top + this.bottom) / 2;
+
+ let left = cx - dx;
+ let right = cx + dx;
+ let top = cy + dy;
+ let bottom = cy - dy;
+
+ if (this.view) {
+ const zoomW =
+ 1 / this.zoom / (this.view.size[0] / this.view.totalSize[0]);
+ const zoomH =
+ 1 / this.zoom / (this.view.size[1] / this.view.totalSize[1]);
+ const scaleW = (this.right - this.left) / this.view.size[0];
+ const scaleH = (this.top - this.bottom) / this.view.size[1];
+
+ left += scaleW * (this.view.offset[0] / zoomW);
+ right = left + scaleW * (this.view.size[0] / zoomW);
+ top -= scaleH * (this.view.offset[1] / zoomH);
+ bottom = top - scaleH * (this.view.size[1] / zoomH);
+ }
+
+ mat4.ortho(
+ this.projectionMatrix,
+ left,
+ right,
+ bottom,
+ top,
+ this.near,
+ this.far
+ );
+ }
+ }
+
+ getViewRay(x, y, windowWidth, windowHeight) {
+ if (this.view) {
+ x += this.view.offset[0];
+ y += this.view.offset[1];
+ windowWidth = this.view.totalSize[0];
+ windowHeight = this.view.totalSize[1];
+ }
+
+ // [origin, direction]
+ return [
+ [0, 0, 0],
+ vec3.normalize([
+ (x * (this.right - this.left)) / this.zoom / windowWidth,
+ ((1 - y) * (this.top - this.bottom)) / this.zoom / windowHeight,
+ -this.near,
+ ]),
+ ];
+ }
+}
+
+export default OrthographicCamera;
diff --git a/package-lock.json b/package-lock.json
index 2004a80..68805ae 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,306 +1,269 @@
{
"name": "pex-cam",
- "version": "2.8.0",
- "lockfileVersion": 1,
+ "version": "3.0.0-alpha.1",
+ "lockfileVersion": 2,
"requires": true,
- "dependencies": {
- "balanced-match": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
- "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
- },
- "brace-expansion": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
- "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
- "requires": {
- "balanced-match": "1.0.0",
- "concat-map": "0.0.1"
+ "packages": {
+ "": {
+ "name": "pex-cam",
+ "version": "3.0.0-alpha.1",
+ "license": "MIT",
+ "dependencies": {
+ "interpolate-angle": "^1.0.2",
+ "latlon-to-xyz": "^1.0.1",
+ "mouse-event-offset": "^3.0.2",
+ "pex-geom": "^3.0.0-alpha.0",
+ "pex-math": "^4.0.0-alpha.1",
+ "xyz-to-latlon": "^1.0.2"
+ },
+ "devDependencies": {
+ "es-module-shims": "^1.5.8",
+ "pex-context": "github:pex-gl/pex-context#v3",
+ "pex-gui": "github:pex-gl/pex-gui#v3",
+ "pex-random": "github:pex-gl/pex-random#v2",
+ "primitive-geometry": "^2.7.0"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=7.0.0"
}
},
- "concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
- },
- "deep-equal": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
- "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU="
+ "node_modules/es-module-shims": {
+ "version": "1.5.8",
+ "resolved": "https://registry.npmjs.org/es-module-shims/-/es-module-shims-1.5.8.tgz",
+ "integrity": "sha512-HHRl0wLqpMRHKdeJAIj7rkqP72sN9QcLTNvTvtsYGs1Nt98PJ0tIPKW5ZfpCWBFawb7MBY0yIRsBeCxlwSHqeQ==",
+ "dev": true
},
- "define-properties": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
- "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=",
- "requires": {
- "foreach": "2.0.5",
- "object-keys": "1.0.11"
+ "node_modules/interpolate-angle": {
+ "version": "1.0.2",
+ "license": "MIT",
+ "dependencies": {
+ "lerp": "^1.0.3"
}
},
- "defined": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
- "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM="
+ "node_modules/latlon-to-xyz": {
+ "version": "1.0.1",
+ "license": "MIT"
},
- "es-abstract": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.10.0.tgz",
- "integrity": "sha512-/uh/DhdqIOSkAWifU+8nG78vlQxdLckUdI/sPgy0VhuXi2qJ7T8czBmqIYtLQVpCIFYafChnsRsB5pyb1JdmCQ==",
- "requires": {
- "es-to-primitive": "1.1.1",
- "function-bind": "1.1.1",
- "has": "1.0.1",
- "is-callable": "1.1.3",
- "is-regex": "1.0.4"
- }
+ "node_modules/lerp": {
+ "version": "1.0.3",
+ "license": "MIT"
},
- "es-to-primitive": {
+ "node_modules/mouse-event-offset": {
+ "version": "3.0.2",
+ "license": "MIT"
+ },
+ "node_modules/pex-color": {
"version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
- "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=",
- "requires": {
- "is-callable": "1.1.3",
- "is-date-object": "1.0.1",
- "is-symbol": "1.0.1"
+ "resolved": "git+ssh://git@github.com/pex-gl/pex-color.git#942007b3ce164e23ad85bd59d6a04d046321445b",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=7.0.0"
}
},
- "for-each": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz",
- "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=",
- "requires": {
- "is-function": "1.0.1"
+ "node_modules/pex-context": {
+ "version": "2.11.0-2",
+ "resolved": "git+ssh://git@github.com/pex-gl/pex-context.git#e9b88af96bc5aca9be5cce1103aa7a45ae73bdfe",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pex-gl": "^3.0.0-alpha.0"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=7.0.0"
}
},
- "foreach": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
- "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
- },
- "function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ "node_modules/pex-geom": {
+ "version": "3.0.0-alpha.0",
+ "resolved": "git+ssh://git@github.com/pex-gl/pex-geom.git#4523420f126284939e1ad42eefc01a24e62f463d",
+ "license": "MIT",
+ "dependencies": {
+ "pex-math": "4.0.0-alpha.1"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=7.0.0"
+ }
},
- "glob": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
- "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
- "requires": {
- "fs.realpath": "1.0.0",
- "inflight": "1.0.6",
- "inherits": "2.0.3",
- "minimatch": "3.0.4",
- "once": "1.4.0",
- "path-is-absolute": "1.0.1"
+ "node_modules/pex-gl": {
+ "version": "3.0.0-alpha.0",
+ "resolved": "https://registry.npmjs.org/pex-gl/-/pex-gl-3.0.0-alpha.0.tgz",
+ "integrity": "sha512-F5xRhI2A3iVhcSmBIIsjEoAeLBa8tqTUzQy2cwZ9X5i1yKRXH8mfQd21JegAuY2aIgzHylquQmWXKzskR8reIA==",
+ "dev": true,
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=7.0.0"
}
},
- "has": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
- "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
- "requires": {
- "function-bind": "1.1.1"
+ "node_modules/pex-gui": {
+ "version": "2.4.0",
+ "resolved": "git+ssh://git@github.com/pex-gl/pex-gui.git#e64c6d12d1742be7a88885559b96c6c41438b55f",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pex-color": "github:pex-gl/pex-color#v2",
+ "pex-geom": "github:pex-gl/pex-geom#v3",
+ "pex-math": "github:pex-gl/pex-math#v4"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=7.0.0"
}
},
- "inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "requires": {
- "once": "1.4.0",
- "wrappy": "1.0.2"
+ "node_modules/pex-math": {
+ "version": "4.0.0-alpha.1",
+ "resolved": "git+ssh://git@github.com/pex-gl/pex-math.git#a4f95e0eef7071a62bae5f1bd2a494b5a3746684",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=7.0.0"
}
},
- "inherits": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ "node_modules/pex-random": {
+ "version": "1.0.1",
+ "resolved": "git+ssh://git@github.com/pex-gl/pex-random.git#e43efec8c740fb64f7349f4006417c3ec1a82ddb",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "seedrandom": "^3.0.5",
+ "simplex-noise": "3.0.1"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=7.0.0"
+ }
},
- "interpolate-angle": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/interpolate-angle/-/interpolate-angle-1.0.2.tgz",
- "integrity": "sha1-q6mSFyJy4ucJOMlTC0HcNcV+5do=",
- "requires": {
- "lerp": "1.0.3"
+ "node_modules/primitive-geometry": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/primitive-geometry/-/primitive-geometry-2.7.0.tgz",
+ "integrity": "sha512-WlZ2FiNpGSytzqVGkcQu5cixyggOwdE6L77TU53h80hq0A/fGSINHmSAVM01y0TnMWUd7LYs9Bovbdi45XeJzA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paypal.me/dmnsgn"
+ },
+ {
+ "type": "individual",
+ "url": "https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3"
+ }
+ ],
+ "engines": {
+ "node": ">=15.0.0",
+ "npm": ">=7.0.0"
}
},
- "is-callable": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
- "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI="
+ "node_modules/seedrandom": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
+ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
+ "dev": true
},
- "is-date-object": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
- "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
+ "node_modules/simplex-noise": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/simplex-noise/-/simplex-noise-3.0.1.tgz",
+ "integrity": "sha512-eww0SFiWLyOaUKQMJ7gbdvQJvULeJdM/Y4BiC3rrOQnYHo+MSPh465/qeXSZkpTdB9/HthumpnYD3DobZweBBQ==",
+ "dev": true
},
- "is-function": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz",
- "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU="
+ "node_modules/xyz-to-latlon": {
+ "version": "1.0.2",
+ "license": "MIT"
+ }
+ },
+ "dependencies": {
+ "es-module-shims": {
+ "version": "1.5.8",
+ "resolved": "https://registry.npmjs.org/es-module-shims/-/es-module-shims-1.5.8.tgz",
+ "integrity": "sha512-HHRl0wLqpMRHKdeJAIj7rkqP72sN9QcLTNvTvtsYGs1Nt98PJ0tIPKW5ZfpCWBFawb7MBY0yIRsBeCxlwSHqeQ==",
+ "dev": true
},
- "is-regex": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
- "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+ "interpolate-angle": {
+ "version": "1.0.2",
"requires": {
- "has": "1.0.1"
+ "lerp": "^1.0.3"
}
},
- "is-symbol": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz",
- "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI="
- },
"latlon-to-xyz": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/latlon-to-xyz/-/latlon-to-xyz-1.0.1.tgz",
- "integrity": "sha512-OF65aE60Y9aMOibxWFC2Vl7EN2BOtMKabm0t97+VaVgR9LoStZHbNNMc8dS/LG6iE1BpEXmnoH85wWr1drp/Tw=="
+ "version": "1.0.1"
},
"lerp": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/lerp/-/lerp-1.0.3.tgz",
- "integrity": "sha1-oYyJaPkXiW3hXM/MKNVaa3Med24="
- },
- "minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "requires": {
- "brace-expansion": "1.1.8"
- }
- },
- "minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ "version": "1.0.3"
},
"mouse-event-offset": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/mouse-event-offset/-/mouse-event-offset-3.0.2.tgz",
- "integrity": "sha1-39hqbiSMa6jK1TuQXVA3ogY+mYQ="
- },
- "object-inspect": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.3.0.tgz",
- "integrity": "sha512-OHHnLgLNXpM++GnJRyyhbr2bwl3pPVm4YvaraHrRvDt/N3r+s/gDVHciA7EJBTkijKXj61ssgSAikq1fb0IBRg=="
+ "version": "3.0.2"
},
- "object-keys": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
- "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0="
+ "pex-color": {
+ "version": "git+ssh://git@github.com/pex-gl/pex-color.git#942007b3ce164e23ad85bd59d6a04d046321445b",
+ "dev": true,
+ "from": "pex-color@github:pex-gl/pex-color#v2"
},
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "pex-context": {
+ "version": "git+ssh://git@github.com/pex-gl/pex-context.git#e9b88af96bc5aca9be5cce1103aa7a45ae73bdfe",
+ "dev": true,
+ "from": "pex-context@github:pex-gl/pex-context#v3",
"requires": {
- "wrappy": "1.0.2"
+ "pex-gl": "^3.0.0-alpha.0"
}
},
- "path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
- },
- "path-parse": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
- "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME="
- },
- "performance-now": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
- "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
- },
"pex-geom": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/pex-geom/-/pex-geom-2.0.1.tgz",
- "integrity": "sha512-2/Ci2rGGyqm3nQNKKsR8ZaUL+DK/2U0CIKNQLEt5K5OfFFMY4ALh9DyhL0gVADT3Znpe3BSjymqSST3kheNhIQ==",
+ "version": "git+ssh://git@github.com/pex-gl/pex-geom.git#4523420f126284939e1ad42eefc01a24e62f463d",
+ "from": "pex-geom@^3.0.0-alpha.0",
"requires": {
- "pex-math": "2.0.0",
- "tape": "4.8.0"
+ "pex-math": "4.0.0-alpha.1"
}
},
- "pex-math": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/pex-math/-/pex-math-2.0.0.tgz",
- "integrity": "sha512-6Ca4OSIzCg0saX2iV6jAQpZoMrmgjWw+AAOKfjDJ3NDdsK7J7c1x2hpx6vJdGWDaSkeCCzEAX6cxGSoCdnQTPg=="
- },
- "raf": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz",
- "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==",
- "requires": {
- "performance-now": "2.1.0"
- }
+ "pex-gl": {
+ "version": "3.0.0-alpha.0",
+ "resolved": "https://registry.npmjs.org/pex-gl/-/pex-gl-3.0.0-alpha.0.tgz",
+ "integrity": "sha512-F5xRhI2A3iVhcSmBIIsjEoAeLBa8tqTUzQy2cwZ9X5i1yKRXH8mfQd21JegAuY2aIgzHylquQmWXKzskR8reIA==",
+ "dev": true
},
- "resolve": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz",
- "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==",
+ "pex-gui": {
+ "version": "git+ssh://git@github.com/pex-gl/pex-gui.git#e64c6d12d1742be7a88885559b96c6c41438b55f",
+ "dev": true,
+ "from": "pex-gui@github:pex-gl/pex-gui#v3",
"requires": {
- "path-parse": "1.0.5"
+ "pex-color": "github:pex-gl/pex-color#v2",
+ "pex-geom": "github:pex-gl/pex-geom#v3",
+ "pex-math": "github:pex-gl/pex-math#v4"
}
},
- "resumer": {
- "version": "0.0.0",
- "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz",
- "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=",
- "requires": {
- "through": "2.3.8"
- }
+ "pex-math": {
+ "version": "git+ssh://git@github.com/pex-gl/pex-math.git#a4f95e0eef7071a62bae5f1bd2a494b5a3746684",
+ "from": "pex-math@^4.0.0-alpha.1"
},
- "string.prototype.trim": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz",
- "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=",
+ "pex-random": {
+ "version": "git+ssh://git@github.com/pex-gl/pex-random.git#e43efec8c740fb64f7349f4006417c3ec1a82ddb",
+ "dev": true,
+ "from": "pex-random@github:pex-gl/pex-random#v2",
"requires": {
- "define-properties": "1.1.2",
- "es-abstract": "1.10.0",
- "function-bind": "1.1.1"
+ "seedrandom": "^3.0.5",
+ "simplex-noise": "3.0.1"
}
},
- "tape": {
- "version": "4.8.0",
- "resolved": "https://registry.npmjs.org/tape/-/tape-4.8.0.tgz",
- "integrity": "sha512-TWILfEnvO7I8mFe35d98F6T5fbLaEtbFTG/lxWvid8qDfFTxt19EBijWmB4j3+Hoh5TfHE2faWs73ua+EphuBA==",
- "requires": {
- "deep-equal": "1.0.1",
- "defined": "1.0.0",
- "for-each": "0.3.2",
- "function-bind": "1.1.1",
- "glob": "7.1.2",
- "has": "1.0.1",
- "inherits": "2.0.3",
- "minimist": "1.2.0",
- "object-inspect": "1.3.0",
- "resolve": "1.4.0",
- "resumer": "0.0.0",
- "string.prototype.trim": "1.1.2",
- "through": "2.3.8"
- }
+ "primitive-geometry": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/primitive-geometry/-/primitive-geometry-2.7.0.tgz",
+ "integrity": "sha512-WlZ2FiNpGSytzqVGkcQu5cixyggOwdE6L77TU53h80hq0A/fGSINHmSAVM01y0TnMWUd7LYs9Bovbdi45XeJzA==",
+ "dev": true
},
- "through": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
- "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
+ "seedrandom": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
+ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
+ "dev": true
},
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ "simplex-noise": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/simplex-noise/-/simplex-noise-3.0.1.tgz",
+ "integrity": "sha512-eww0SFiWLyOaUKQMJ7gbdvQJvULeJdM/Y4BiC3rrOQnYHo+MSPh465/qeXSZkpTdB9/HthumpnYD3DobZweBBQ==",
+ "dev": true
},
"xyz-to-latlon": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/xyz-to-latlon/-/xyz-to-latlon-1.0.2.tgz",
- "integrity": "sha512-iTQe5HIzAE0r2L2u2abs17s/SekCCUAlIHzYnHHo10aR0B1iKRSGye33z7a52IMJoUUMTpPwNUTNIy+OhZVRIg=="
+ "version": "1.0.2"
}
}
}
diff --git a/package.json b/package.json
old mode 100644
new mode 100755
index 63d80ef..47908d2
--- a/package.json
+++ b/package.json
@@ -1,43 +1,55 @@
{
"name": "pex-cam",
- "version": "2.8.0",
- "description": "Camera models and modifiers.",
- "main": "index.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "repository": {
- "type": "git",
- "url": "https://github.com/pex-gl/pex-cam.git"
- },
+ "version": "3.0.0-alpha.1",
+ "description": "Cameras models and controllers for 3D rendering in PEX.",
"keywords": [
"pex",
- "webgl"
+ "cam",
+ "webgl",
+ "3d",
+ "cameras",
+ "perspective",
+ "orthographic",
+ "orbiter"
],
- "author": {
- "name": "Henryk Wollik",
- "email": "hwollik@hotmail.com",
- "url": "http://henrykwollik.com"
- },
+ "homepage": "https://github.com/pex-gl/pex-cam",
+ "bugs": "https://github.com/pex-gl/pex-cam/issues",
+ "repository": "pex-gl/pex-cam",
+ "license": "MIT",
+ "author": "Henryk Wollik (http://henrykwollik.com)",
"contributors": [
- {
- "name": "Marcin Ignac",
- "email": "marcin.ignac@gmail.com",
- "url": "http://marcinignac.com"
- }
+ "Marcin Ignac (http://marcinignac.com)",
+ "Damien Seguin (https://github.com/dmnsgn)"
],
- "license": "MIT",
- "bugs": {
- "url": "https://github.com/pex-gl/pex-cam/issues"
+ "type": "module",
+ "exports": "./index.js",
+ "main": "index.js",
+ "types": "types/index.d.ts",
+ "scripts": {
+ "build": "snowdev build",
+ "dev": "snowdev dev",
+ "release": "snowdev release"
},
- "homepage": "https://github.com/pex-gl/pex-cam",
"dependencies": {
"interpolate-angle": "^1.0.2",
"latlon-to-xyz": "^1.0.1",
"mouse-event-offset": "^3.0.2",
- "pex-geom": "^2.0.1",
- "pex-math": "^2.0.0",
- "raf": "^3.4.0",
+ "pex-geom": "^3.0.0-alpha.0",
+ "pex-math": "^4.0.0-alpha.1",
"xyz-to-latlon": "^1.0.2"
+ },
+ "devDependencies": {
+ "es-module-shims": "^1.5.8",
+ "pex-context": "github:pex-gl/pex-context#v3",
+ "pex-gui": "github:pex-gl/pex-gui#v3",
+ "pex-random": "github:pex-gl/pex-random#v2",
+ "primitive-geometry": "^2.7.0"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=7.0.0"
+ },
+ "snowdev": {
+ "dependencies": "all"
}
}
diff --git a/perspective.js b/perspective.js
index 16def4a..4069591 100644
--- a/perspective.js
+++ b/perspective.js
@@ -1,124 +1,122 @@
-const vec3 = require('pex-math/vec3')
-const mat4 = require('pex-math/mat4')
-
-function setFrustumOffset (camera, x, y, width, height, widthTotal, heightTotal) {
- // console.log('frustum', x, y, width, height, widthTotal, heightTotal)
- widthTotal = widthTotal === undefined ? width : widthTotal
- heightTotal = heightTotal === undefined ? height : heightTotal
-
- var near = camera.near
- var far = camera.far
- var fov = camera.fov
-
- var aspectRatio = widthTotal / heightTotal
-
- var top = Math.tan(fov * 0.5) * near
- var bottom = -top
- var left = aspectRatio * bottom
- var right = aspectRatio * top
- var width_ = Math.abs(right - left)
- var height_ = Math.abs(top - bottom)
- var widthNormalized = width_ / widthTotal
- var heightNormalized = height_ / heightTotal
-
- var l = left + x * widthNormalized
- var r = left + (x + width) * widthNormalized
- var b = top - (y + height) * heightNormalized
- var t = top - y * heightNormalized
-
- camera.aspect = aspectRatio
- mat4.frustum(camera.projectionMatrix, l, r, b, t, near, far)
-}
-
-function PerspectiveCamera (opts) {
- this.set({
- projectionMatrix: mat4.create(),
- invViewMatrix: mat4.create(),
- viewMatrix: mat4.create(),
- position: [0, 0, 3],
- target: [0, 0, 0],
- up: [0, 1, 0],
- fov: Math.PI / 3,
- aspect: 1,
- near: 0.1,
- far: 100
- })
-
- this.set(opts)
-}
-
-PerspectiveCamera.prototype.set = function (opts) {
- Object.assign(this, opts)
-
- if (opts.position || opts.target || opts.up) {
- mat4.lookAt(
- this.viewMatrix,
- this.position,
- this.target,
- this.up
- )
- mat4.set(this.invViewMatrix, this.viewMatrix)
- mat4.invert(this.invViewMatrix)
+import { vec3, mat4 } from "pex-math";
+
+import Camera from "./camera.js";
+
+/**
+ * A class to create a perspective camera
+ * @extends Camera
+ */
+class PerspectiveCamera extends Camera {
+ static get DEFAULT_OPTIONS() {
+ return {
+ fov: Math.PI / 3,
+ };
}
- if (opts.fov || opts.aspect || opts.near || opts.far) {
- mat4.perspective(
- this.projectionMatrix,
- this.fov,
- this.aspect,
- this.near,
- this.far
- )
+ /**
+ * Create an instance of PerspectiveCamera
+ * @param {import("./types.js").CameraOptions & import("./types.js").PerspectiveCameraOptions} opts
+ */
+ constructor(opts = {}) {
+ super();
+
+ this.set({
+ ...Camera.DEFAULT_OPTIONS,
+ ...PerspectiveCamera.DEFAULT_OPTIONS,
+ ...opts,
+ });
}
- if (this.frustum) {
- setFrustumOffset(
- this,
- this.frustum.offset[0], this.frustum.offset[1],
- this.frustum.size[0], this.frustum.size[1],
- this.frustum.totalSize[0], this.frustum.totalSize[1]
- )
+ /**
+ * Update the camera
+ * @param {import("./types.js").CameraOptions & import("./types.js").PerspectiveCameraOptions} opts
+ */
+ set(opts) {
+ super.set(opts);
+
+ if (opts.fov || opts.aspect || opts.near || opts.far || opts.view) {
+ if (this.view) {
+ const aspectRatio = this.view.totalSize[0] / this.view.totalSize[1];
+
+ const top = Math.tan(this.fov * 0.5) * this.near;
+ const bottom = -top;
+ const left = aspectRatio * bottom;
+ const right = aspectRatio * top;
+ const width = Math.abs(right - left);
+ const height = Math.abs(top - bottom);
+ const widthNormalized = width / this.view.totalSize[0];
+ const heightNormalized = height / this.view.totalSize[1];
+
+ const l = left + this.view.offset[0] * widthNormalized;
+ const r =
+ left + (this.view.offset[0] + this.view.size[0]) * widthNormalized;
+ const b =
+ top - (this.view.offset[1] + this.view.size[1]) * heightNormalized;
+ const t = top - this.view.offset[1] * heightNormalized;
+
+ mat4.frustum(this.projectionMatrix, l, r, b, t, this.near, this.far);
+ } else {
+ mat4.perspective(
+ this.projectionMatrix,
+ this.fov,
+ this.aspect,
+ this.near,
+ this.far
+ );
+ }
+ }
}
-}
-PerspectiveCamera.prototype.getViewRay = function (x, y, windowWidth, windowHeight) {
- if (this.frustum) {
- x += this.frustum.offset[0]
- y += this.frustum.offset[1]
- windowWidth = this.frustum.totalSize[0]
- windowHeight = this.frustum.totalSize[1]
+ /**
+ * Create a picking ray in view (camera) coordinates
+ * @param {number} x mouse x
+ * @param {number} y mouse y
+ * @param {number} windowWidth
+ * @param {number} windowHeight
+ * @returns {import("pex-geom").ray}
+ */
+ getViewRay(x, y, windowWidth, windowHeight) {
+ if (this.view) {
+ x += this.view.offset[0];
+ y += this.view.offset[1];
+ windowWidth = this.view.totalSize[0];
+ windowHeight = this.view.totalSize[1];
+ }
+ let nx = (2 * x) / windowWidth - 1;
+ let ny = 1 - (2 * y) / windowHeight;
+
+ const hNear = 2 * Math.tan(this.fov / 2) * this.near;
+ const wNear = hNear * this.aspect;
+
+ nx *= wNear * 0.5;
+ ny *= hNear * 0.5;
+
+ // [origin, direction]
+ return [[0, 0, 0], vec3.normalize([nx, ny, -this.near])];
}
- let nx = 2 * x / windowWidth - 1
- let ny = 1 - 2 * y / windowHeight
-
- let hNear = 2 * Math.tan(this.fov / 2) * this.near
- let wNear = hNear * this.aspect
-
- nx *= (wNear * 0.5)
- ny *= (hNear * 0.5)
-
- let origin = [0, 0, 0]
- let direction = vec3.normalize([nx, ny, -this.near])
- let ray = [origin, direction]
- return ray
-}
-
-PerspectiveCamera.prototype.getWorldRay = function (x, y, windowWidth, windowHeight) {
- let ray = this.getViewRay(x, y, windowWidth, windowHeight)
- let origin = ray[0]
- let direction = ray[1]
-
- vec3.multMat4(origin, this.invViewMatrix)
- // this is correct as origin is [0, 0, 0] so direction is also a point
- vec3.multMat4(direction, this.invViewMatrix)
-
- // is this necessary?
- vec3.normalize(vec3.sub(direction, origin))
-
- return ray
+ /**
+ * Create a picking ray in world coordinates
+ * @param {number} x
+ * @param {number} y
+ * @param {number} windowWidth
+ * @param {number} windowHeight
+ * @returns {import("pex-geom").ray}
+ */
+ getWorldRay(x, y, windowWidth, windowHeight) {
+ let ray = this.getViewRay(x, y, windowWidth, windowHeight);
+ const origin = ray[0];
+ const direction = ray[1];
+
+ vec3.multMat4(origin, this.invViewMatrix);
+ // this is correct as origin is [0, 0, 0] so direction is also a point
+ vec3.multMat4(direction, this.invViewMatrix);
+
+ // TODO: is this necessary?
+ vec3.normalize(vec3.sub(direction, origin));
+
+ return ray;
+ }
}
-module.exports = function createPerspectiveCamera (opts) {
- return new PerspectiveCamera(opts)
-}
+export default PerspectiveCamera;
diff --git a/screenshot.png b/screenshot.png
new file mode 100644
index 0000000..8665df9
Binary files /dev/null and b/screenshot.png differ
diff --git a/types.js b/types.js
new file mode 100644
index 0000000..1292af7
--- /dev/null
+++ b/types.js
@@ -0,0 +1,63 @@
+/**
+ * @typedef {number} Radians
+ */
+/**
+ * @typedef {number} Degrees
+ */
+
+/**
+ * @typedef {Object} CameraView
+ * @property {import("pex-math").vec2} offset
+ * @property {import("pex-math").vec2} size
+ * @property {import("pex-math").vec2} totalSize
+ */
+
+/**
+ * @typedef {Object} CameraOptions
+ * @property {import("pex-math").mat4} [projectionMatrix=mat4.create()]
+ * @property {import("pex-math").mat4} [invViewMatrix=mat4.create()]
+ * @property {import("pex-math").mat4} [viewMatrix=mat4.create()]
+ * @property {import("pex-math").vec3} [position=[0, 0, 3]]
+ * @property {import("pex-math").vec3} [target=[0, 0, 0]]
+ * @property {import("pex-math").vec3} [up=[0, 1, 0]]
+ * @property {number} [aspect=1]
+ * @property {number} [near=0.1]
+ * @property {number} [far=100]
+ * @property {CameraView} [view=null]
+ */
+
+/**
+ * @typedef {Object} PerspectiveCameraOptions
+ * @property {Radians} [fov=Math.PI / 3]
+ */
+
+/**
+ * @typedef {Object} OrthographicCameraOptions
+ * @property {number} [left=-1]
+ * @property {number} [right=1]
+ * @property {number} [bottom=-1]
+ * @property {number} [top=1]
+ * @property {number} [zoom=1]
+ */
+
+/**
+ * @typedef {Object} OrbiterControlsOptions
+ * @property {import("./camera.js").Camera} camera
+ * @property {HTMLElement} [element=document]
+ * @property {number} [easing=0.1]
+ * @property {boolean} [zoom=true]
+ * @property {boolean} [pan=true]
+ * @property {boolean} [drag=true]
+ * @property {number} [minDistance=0.01]
+ * @property {number} [maxDistance=Infinity]
+ * @property {Degrees} [minLat=-89.5]
+ * @property {Degrees} [maxLat=89.5]
+ * @property {number} [minLon=-Infinity]
+ * @property {number} [maxLon=Infinity]
+ * @property {number} [panSlowdown=4]
+ * @property {number} [zoomSlowdown=400]
+ * @property {number} [dragSlowdown=4]
+ * @property {boolean} [autoUpdate=true]
+ */
+
+export {};