Created
May 31, 2017 13:58
-
-
Save nkint/77be542bf32cba2ff9b548701b4084d6 to your computer and use it in GitHub Desktop.
Sketch of new regl camera
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { projectionMatrix, viewMatrix } from './turntable-camera' | |
import mouseChange from 'mouse-change' | |
import mouseWheel from 'mouse-wheel' | |
import identity from 'gl-mat4/identity' | |
import key from 'key-pressed' | |
// const isBrowser = typeof window !== 'undefined' | |
const getStartingState = (initialStatePlusOptions) => ({ | |
view: identity(new Float32Array(16)), | |
projection: identity(new Float32Array(16)), | |
theta: initialStatePlusOptions.theta || 0, | |
phi: initialStatePlusOptions.phi || 0, | |
distance: Math.log(initialStatePlusOptions.distance || 10.0), | |
center: new Float32Array(initialStatePlusOptions.center || [0, 0, 1]), | |
eye: new Float32Array(3), | |
dtheta: 0, | |
dphi: 0, | |
ddistance: 0, | |
dcenter: [0, 0, 0], | |
mouse: Boolean(initialStatePlusOptions.mouse) || true, | |
}) | |
const getStartingOptions = (initialStatePlusOptions) => ({ | |
fovy: initialStatePlusOptions.fovy || Math.PI / 4.0, | |
near: typeof initialStatePlusOptions.near !== 'undefined' ? initialStatePlusOptions.near : 0.01, | |
far: typeof initialStatePlusOptions.far !== 'undefined' ? initialStatePlusOptions.far : 1000.0, | |
flipY: Boolean(initialStatePlusOptions.flipY), | |
damping: typeof initialStatePlusOptions.damping !== 'undefined' ? initialStatePlusOptions.damping : 0.6, | |
minDistance: Math.log('minDistance' in initialStatePlusOptions ? initialStatePlusOptions.minDistance : 0.1), | |
maxDistance: Math.log('maxDistance' in initialStatePlusOptions ? initialStatePlusOptions.maxDistance : 1000), | |
preventDefault: Boolean(initialStatePlusOptions.preventDefault : false), | |
}) | |
const noop = function () {} | |
export default function createCamera(regl, initialStatePlusOptions, drawFunction = noop, callbacks = {}) { | |
const { onPan = noop, onZoom = noop, onRotate = noop } = callbacks | |
const cameraState = getStartingState(initialStatePlusOptions) | |
const options = getStartingOptions(initialStatePlusOptions) | |
const element = regl._gl.canvas | |
element.addEventListener('mousewheel', function (e) { | |
if (cameraState.preventDefault) e.preventDefault() | |
}) | |
element.addEventListener('contextmenu', function (event) { | |
event.preventDefault() | |
}) | |
document.addEventListener('mousedown', function (event) { | |
if (event.ctrlKey || event.button === 2) { | |
event.preventDefault() | |
} | |
}) | |
let prevX = 0 | |
let prevY = 0 | |
mouseChange(element, function (buttons, x, y) { | |
const ctrl = key('<control>') | |
if (buttons === 1 && ctrl) { | |
// rotation | |
const dx = (x - prevX) / window.innerWidth | |
const dy = (y - prevY) / window.innerHeight | |
// costants from here: https://github.com/mapbox/mapbox-gl-js/blob/b9e10b939c6a3fe5d7ecac209f751b4871970ede/src/ui/handler/drag_rotate.js#L106 | |
cameraState.dtheta -= dy * 0.8 | |
cameraState.dphi += dx * 1.5 | |
onRotate(cameraState.theta, cameraState.phi) | |
drawFunction() | |
} else if (buttons === 1 && !ctrl) { | |
// pan | |
const exp = Math.exp(cameraState.distance) * 0.001 | |
cameraState.dcenter[0] = -(x - prevX) * exp | |
cameraState.dcenter[1] = (y - prevY) * exp | |
onPan(cameraState.center) | |
drawFunction() | |
} | |
prevX = x | |
prevY = y | |
}) | |
mouseWheel(element, function (dx, dy) { | |
cameraState.ddistance += dy / window.innerHeight | |
onZoom(cameraState.distance) | |
drawFunction() | |
}) | |
const projection = function (context) { | |
return projectionMatrix(cameraState.projection, | |
options.fovy, | |
context.viewportWidth / context.viewportHeight, | |
options.near, | |
options.far) | |
} | |
const view = function (context, props) { | |
Object.assign(cameraState, props) | |
viewMatrix(cameraState.view, cameraState, options) | |
return cameraState.view | |
} | |
const injectContextCamera = regl({ | |
context: { | |
projection, | |
view, | |
// this is a dirty workaround on DynamicProperties to have access at updated cameraState in the context | |
cameraState: () => cameraState, | |
}, | |
uniforms: { | |
projection: regl.context('projection'), | |
view: regl.context('view'), | |
}, | |
}) | |
injectContextCamera.projection = projection | |
injectContextCamera.view = view | |
return injectContextCamera | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import perspective from 'gl-mat4/perspective' | |
import lookAt from 'gl-mat4/lookAt' | |
export function projectionMatrix(out, fovy, aspectRatio, near, far, flipY) { | |
perspective(out, fovy, aspectRatio, near, far) | |
if (flipY) { out[5] *= -1 } | |
return out | |
} | |
function damp(damping, x) { | |
const xd = x * damping | |
if (Math.abs(xd) < 0.0001) { | |
return 0 | |
} | |
return xd | |
} | |
function clamp(x, lo, hi) { | |
return Math.min(Math.max(x, lo), hi) | |
} | |
const front = new Float32Array([1, 0, 0]) | |
const right = new Float32Array([0, 1, 0]) | |
const up = new Float32Array([0, 0, 1]) | |
function updateCameraWidthDeltas(cameraState, options) { | |
cameraState.center[0] += cameraState.dcenter[0] | |
cameraState.center[1] += cameraState.dcenter[1] | |
cameraState.phi += cameraState.dphi | |
cameraState.theta += cameraState.dtheta | |
cameraState.theta = clamp( | |
cameraState.theta, | |
Math.PI / 2 + 0.0001, | |
Math.PI) | |
cameraState.distance = clamp( | |
cameraState.distance + cameraState.ddistance, | |
options.minDistance, | |
options.maxDistance) | |
cameraState.dtheta = damp(options.damping, cameraState.dtheta) | |
cameraState.dphi = damp(options.damping, cameraState.dphi) | |
cameraState.ddistance = damp(options.damping, cameraState.ddistance) | |
cameraState.dcenter[0] = damp(options.damping, cameraState.dcenter[0]) | |
cameraState.dcenter[1] = damp(options.damping, cameraState.dcenter[1]) | |
} | |
export function viewMatrix(out, cameraState, options) { | |
if (cameraState.mouse) { | |
updateCameraWidthDeltas(cameraState, options) | |
} else { | |
} | |
const theta = cameraState.theta | |
const phi = cameraState.phi | |
const r = Math.exp(cameraState.distance) | |
const vf = r * Math.sin(phi) * Math.cos(theta) | |
const vr = r * Math.cos(phi) * Math.cos(theta) | |
const vu = r * Math.sin(theta) | |
const center = cameraState.center | |
const eye = cameraState.eye | |
for (let i = 0; i < 3; ++i) { | |
eye[i] = center[i] + vf * front[i] + vr * right[i] + vu * up[i] | |
} | |
lookAt(cameraState.view, eye, center, up) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment