mirror of
https://github.com/rreusser/rreusser.github.io.git
synced 2026-01-25 16:25:56 +00:00
😡
This commit is contained in:
parent
9543964bf1
commit
3d2f06af09
1
lawsons-klein-bottle/bundle.js
Normal file
1
lawsons-klein-bottle/bundle.js
Normal file
File diff suppressed because one or more lines are too long
13
lawsons-klein-bottle/index.html
Normal file
13
lawsons-klein-bottle/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html><html lang="en" dir="ltr"><head><title>Lawson's Klein Bottle</title><meta charset="utf-8"><meta name="application-name" content="Lawson's Klein Bottle">
|
||||
<meta name="subject" content="3D sterographic projection of a 4D Klein bottle">
|
||||
<meta name="abstract" content="3D sterographic projection of a 4D Klein bottle">
|
||||
<meta name="twitter:title" content="Lawson's Klein Bottle">
|
||||
<meta name="description" content="3D sterographic projection of a 4D Klein bottle">
|
||||
<meta name="twitter:description" content="3D sterographic projection of a 4D Klein bottle">
|
||||
<meta name="author" content="Ricky Reusser">
|
||||
<meta name="twitter:creator" content="Ricky Reusser">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta property="og:title" content="Lawson's Klein Bottle">
|
||||
<meta property="og:description" content="3D sterographic projection of a 4D Klein bottle">
|
||||
<meta property="article:author" content="Ricky Reusser">
|
||||
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" /></head><body><script src="bundle.js"></script><script src="../nav.bundle.js"></script></body></html>
|
||||
134
nav.bundle.js
134
nav.bundle.js
File diff suppressed because one or more lines are too long
BIN
sketches/static/lawsons-klein-bottle-thumbnail.jpg
Normal file
BIN
sketches/static/lawsons-klein-bottle-thumbnail.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@ -47,7 +47,7 @@ switch (entryFile.type) {
|
||||
ssr: true,
|
||||
theme: 'none',
|
||||
layout: 'none',
|
||||
transform: ['glslify']
|
||||
transform: [],//['glslify']
|
||||
});
|
||||
|
||||
idyll.build();
|
||||
@ -56,7 +56,7 @@ switch (entryFile.type) {
|
||||
var cpInputDir = path.join(__dirname, '..', projectDir, dir);
|
||||
var cpOutputDir = path.join(__dirname, '..', outputDir, dir);
|
||||
|
||||
if (fs.existsSync(cpInputDir)) {
|
||||
if (cpInputDir && fs.existsSync(cpInputDir)) {
|
||||
console.log('copying', dir);
|
||||
cpr(cpInputDir, cpOutputDir, {});
|
||||
}
|
||||
@ -130,7 +130,7 @@ switch (entryFile.type) {
|
||||
var cpInputDir = path.join(__dirname, '..', projectDir, dir);
|
||||
var cpOutputDir = path.join(__dirname, '..', outputDir, dir);
|
||||
|
||||
if (fs.existsSync(cpInputDir)) {
|
||||
if (cpInputDir && fs.existsSync(cpInputDir)) {
|
||||
console.log('copying', dir);
|
||||
cpr(cpInputDir, cpOutputDir, {});
|
||||
}
|
||||
|
||||
19
src/src/lawsons-klein-bottle/README.md
Normal file
19
src/src/lawsons-klein-bottle/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# WIP Lawson's Klein Bottle
|
||||
|
||||
It uses the [regl](https://github.com/regl-project/regl) library for interfacing with WebGL and uses [this shader technique](https://observablehq.com/@rreusser/faking-transparency-for-3d-surfaces) for drawing the surface.
|
||||
|
||||
There are some shared dependencies a couple directories up, so you'll need to run `npm install` both in the root project directory and in this directory:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rreusser/explorations.git
|
||||
cd explorations
|
||||
npm install
|
||||
cd posts/lawsons-klein-bottle
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
© 2020 Ricky Reusser. MIT License.
|
||||
|
||||
176
src/src/lawsons-klein-bottle/index.js
Normal file
176
src/src/lawsons-klein-bottle/index.js
Normal file
@ -0,0 +1,176 @@
|
||||
const createREGL = require('regl');
|
||||
const createCamera = require('./regl-turntable-camera');
|
||||
const meshSurface = require('./mesh-surface-2d');
|
||||
const mat4create = require('gl-mat4/create');
|
||||
const mat4multiply = require('gl-mat4/multiply');
|
||||
const mat4lookAt = require('gl-mat4/lookAt');
|
||||
|
||||
const State = require('controls-state');
|
||||
const GUI = require('controls-gui');
|
||||
|
||||
const state = GUI(State({
|
||||
tau: State.Slider(1, {min: 0, max: 2.0, step: 0.01, label: 'τ'}),
|
||||
uRange: State.Slider(1, {min: 0, max: 2.0, step: 0.01, label: 'uRange'}),
|
||||
vRange: State.Slider(1, {min: 0, max: 2.0, step: 0.01, label: 'vRange'}),
|
||||
}), {
|
||||
containerCSS: "position:absolute; top:0; right:10px; width:350px; z-index: 1",
|
||||
});
|
||||
|
||||
function createDrawThingy (regl, res) {
|
||||
const mesh = meshSurface({}, (out, u, v) => {
|
||||
out[0] = u * 0.999999;
|
||||
out[1] = v * 0.999999;
|
||||
}, {
|
||||
resolution: [res, res],
|
||||
uDomain: [-Math.PI, Math.PI],
|
||||
vDomain: [-Math.PI * 0.5, Math.PI * 0.5],
|
||||
});
|
||||
|
||||
return regl({
|
||||
vert: `
|
||||
precision highp float;
|
||||
attribute vec2 uv;
|
||||
uniform mat4 uProjectionView;
|
||||
uniform float uTau;
|
||||
uniform vec2 uRange;
|
||||
varying vec3 vPosition, vNormal;
|
||||
varying float vRadius;
|
||||
varying vec2 vUV;
|
||||
|
||||
/*
|
||||
vec4 quatMult(vec4 q1, vec4 q2) {
|
||||
return vec4(
|
||||
(q1.w * q2.x) + (q1.x * q2.w) + (q1.y * q2.z) - (q1.z * q2.y),
|
||||
(q1.w * q2.y) - (q1.x * q2.z) + (q1.y * q2.w) + (q1.z * q2.x),
|
||||
(q1.w * q2.z) + (q1.x * q2.y) - (q1.y * q2.x) + (q1.z * q2.w),
|
||||
(q1.w * q2.w) - (q1.x * q2.x) - (q1.y * q2.y) - (q1.z * q2.z)
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
vec3 f(vec2 uv, float tau) {
|
||||
float tx = tau * uv.x;
|
||||
vec4 p = vec4(
|
||||
cos(uv.y) * vec2(cos(uv.x), sin(uv.x)),
|
||||
sin(uv.y) * vec2(cos(tx), sin(tx))
|
||||
);
|
||||
|
||||
//p.xyz = mat3(uView) * p.xzy;
|
||||
|
||||
// Compute the stereographic projection
|
||||
return p.yzx / (1.0 - p.w);
|
||||
}
|
||||
|
||||
void main () {
|
||||
vUV = uv;
|
||||
vec2 uvScaled = uv * uRange;
|
||||
vUV *= uRange;
|
||||
vPosition = f(uvScaled, uTau);
|
||||
vRadius = dot(vPosition, vPosition);
|
||||
|
||||
// Taint bad triangles to prevent them from passing through the origin as they cross from -Ininity to Infinity
|
||||
if (vRadius > 400.0) vPosition /= 0.0;
|
||||
|
||||
// Compute the normal via numerical differentiation
|
||||
const float dx = 5e-3;
|
||||
vNormal = normalize(cross(
|
||||
f(uvScaled + vec2(dx / uRange.x, 0), uTau) - vPosition,
|
||||
f(uvScaled + vec2(0, dx / uRange.y), uTau) - vPosition
|
||||
));
|
||||
|
||||
gl_Position = uProjectionView * vec4(vPosition, 1);
|
||||
}
|
||||
`,
|
||||
frag: `
|
||||
#extension GL_OES_standard_derivatives : enable
|
||||
precision highp float;
|
||||
varying vec3 vPosition, vNormal;
|
||||
uniform vec3 uEye;
|
||||
uniform bool uWire;
|
||||
varying vec2 vUV;
|
||||
varying float vRadius;
|
||||
uniform float pixelRatio;
|
||||
|
||||
#define PI 3.141592653589
|
||||
|
||||
// From https://github.com/rreusser/glsl-solid-wireframe
|
||||
float gridFactor (vec2 parameter, float width, float feather) {
|
||||
float w1 = width - feather * 0.5;
|
||||
vec2 d = fwidth(parameter);
|
||||
vec2 looped = 0.5 - abs(mod(parameter, 1.0) - 0.5);
|
||||
vec2 a2 = smoothstep(d * w1, d * (w1 + feather), looped);
|
||||
return min(a2.x, a2.y);
|
||||
}
|
||||
|
||||
void main () {
|
||||
if (dot(vPosition, vPosition) > 100.0) discard;
|
||||
// Shading technique adapted/simplified/customized from: https://observablehq.com/@rreusser/faking-transparency-for-3d-surfaces
|
||||
vec3 normal = normalize(vNormal);
|
||||
float vDotN = abs(dot(normal, normalize(vPosition - uEye)));
|
||||
float vDotNGrad = fwidth(vDotN);
|
||||
float cartoonEdge = smoothstep(0.75, 1.25, vDotN / (vDotNGrad * 3.0 * pixelRatio));
|
||||
float sgn = gl_FrontFacing ? 1.0 : -1.0;
|
||||
float grid = gridFactor(vUV * vec2(2.0, 2.0) * 4.0 / PI, 0.25 * pixelRatio, 1.0);
|
||||
vec3 baseColor = gl_FrontFacing ? vec3(0.9, 0.2, 0.1) : vec3(0.1, 0.4, 0.8);
|
||||
float vDotN4 = vDotN * vDotN;
|
||||
vDotN *= vDotN4;
|
||||
vDotN *= vDotN4;
|
||||
float shade = mix(1.0, vDotN4, 0.6) + 0.2;
|
||||
|
||||
if (uWire) {
|
||||
gl_FragColor.rgb = vec3(1);
|
||||
gl_FragColor.a = mix(0.15, (1.0 - grid) * 0.055, cartoonEdge);
|
||||
} else {
|
||||
gl_FragColor = vec4(pow(
|
||||
mix(baseColor, (0.5 + sgn * 0.5 * normal), 0.4) * cartoonEdge * mix(1.0, 0.6, 1.0 - grid) * shade,
|
||||
vec3(0.454)),
|
||||
1.0);
|
||||
}
|
||||
}
|
||||
`,
|
||||
uniforms: {
|
||||
uRange: (ctx, props) => [state.uRange, state.vRange],
|
||||
uTau: regl.prop('tau'),
|
||||
uWire: regl.prop('wire'),
|
||||
pixelRatio: regl.context('pixelRatio'),
|
||||
},
|
||||
attributes: {uv: mesh.positions},
|
||||
depth: {enable: (ctx, props) => props.wire ? false : true},
|
||||
blend: {
|
||||
enable: (ctx, props) => props.wire ? true : false,
|
||||
func: {srcRGB: 'src alpha', srcAlpha: 1, dstRGB: 1, dstAlpha: 1},
|
||||
equation: {rgb: 'reverse subtract', alpha: 'add'}
|
||||
},
|
||||
elements: mesh.cells
|
||||
});
|
||||
}
|
||||
|
||||
const regl = createREGL({extensions: ['OES_standard_derivatives']});
|
||||
|
||||
const camera = createCamera(regl, {
|
||||
distance: 8,
|
||||
theta: Math.PI * 1.1,
|
||||
phi: Math.PI * 0.1,
|
||||
far: 200,
|
||||
rotateAbountCenter: true
|
||||
});
|
||||
|
||||
const drawTorus = createDrawThingy(regl, 200);
|
||||
|
||||
state.$onChanges(camera.taint);
|
||||
|
||||
let frame = regl.frame(({tick, time}) => {
|
||||
camera({
|
||||
rotationCenter: camera.params.center
|
||||
}, ({dirty}) => {
|
||||
if (!dirty) return;
|
||||
regl.clear({color: [1, 1, 1, 1]});
|
||||
|
||||
// Perform two drawing passes, first for the solid surface, then for the wireframe overlayed on top
|
||||
// to give a fake transparency effect
|
||||
drawTorus([
|
||||
{wire: false, tau: state.tau},
|
||||
{wire: true, tau: state.tau}
|
||||
]);
|
||||
});
|
||||
});
|
||||
148
src/src/lawsons-klein-bottle/interactions.js
Normal file
148
src/src/lawsons-klein-bottle/interactions.js
Normal file
@ -0,0 +1,148 @@
|
||||
'use strict';
|
||||
|
||||
const vec3TransformMat4 = require('gl-vec3/transformMat4');
|
||||
const interactionEvents = require('normalized-interaction-events');
|
||||
const assert = require('assert');
|
||||
|
||||
module.exports = attachCameraControls;
|
||||
|
||||
const RADIANS_PER_HALF_SCREEN_WIDTH = Math.PI * 0.75;
|
||||
|
||||
function attachCameraControls (camera, opts) {
|
||||
opts = opts || {};
|
||||
var element = camera.element;
|
||||
|
||||
var onStart = null;
|
||||
var onEnd = null;
|
||||
var onMove = null;
|
||||
|
||||
var singletonEventData = {
|
||||
defaultPrevented: false
|
||||
};
|
||||
|
||||
function localPreventDefault () {
|
||||
singletonEventData.defaultPrevented = true;
|
||||
}
|
||||
|
||||
function resetLocalPreventDefault () {
|
||||
singletonEventData.defaultPrevented = false;
|
||||
}
|
||||
|
||||
function providePreventDefault (ev) {
|
||||
ev.defaultPrevented = singletonEventData.defaultPrevented;
|
||||
ev.preventDefault = function () {
|
||||
ev.defaultPrevented = true;
|
||||
localPreventDefault();
|
||||
};
|
||||
return ev;
|
||||
}
|
||||
|
||||
var v = [0, 0, 0];
|
||||
var xy = [0, 0];
|
||||
function transformXY(ev) {
|
||||
v[0] = ev.x;
|
||||
v[1] = ev.y;
|
||||
v[2] = 0;
|
||||
if (opts.invViewportShift) {
|
||||
vec3TransformMat4(v, v, invViewportShift);
|
||||
}
|
||||
xy[0] = v[0];
|
||||
xy[1] = v[1];
|
||||
return xy;
|
||||
}
|
||||
|
||||
interactionEvents(element)
|
||||
.on('wheel', function (ev) {
|
||||
ev.originalEvent.preventDefault();
|
||||
|
||||
camera.zoom(ev.x0, ev.y0, Math.exp(-ev.dy) - 1.0);
|
||||
})
|
||||
.on('mousedown', function (ev) {
|
||||
resetLocalPreventDefault();
|
||||
|
||||
ev = providePreventDefault(ev);
|
||||
onStart && onStart(ev);
|
||||
|
||||
ev.originalEvent.preventDefault();
|
||||
})
|
||||
.on('mousemove', function (ev) {
|
||||
ev = providePreventDefault(ev);
|
||||
onMove && onMove(ev);
|
||||
|
||||
if (ev.defaultPrevented) return;
|
||||
|
||||
if (!ev.active || ev.buttons !== 1) return;
|
||||
|
||||
if (ev.mods.alt) {
|
||||
camera.zoom(ev.x0, ev.y0, Math.exp(ev.dy) - 1.0);
|
||||
ev.originalEvent.preventDefault();
|
||||
} else if (ev.mods.shift) {
|
||||
camera.pan(ev.dx, ev.dy);
|
||||
ev.originalEvent.preventDefault();
|
||||
} else if (ev.mods.meta) {
|
||||
camera.pivot(ev.dx, ev.dy);
|
||||
ev.originalEvent.preventDefault();
|
||||
} else {
|
||||
camera.rotate(
|
||||
-ev.dx * RADIANS_PER_HALF_SCREEN_WIDTH,
|
||||
-ev.dy * RADIANS_PER_HALF_SCREEN_WIDTH
|
||||
);
|
||||
ev.originalEvent.preventDefault();
|
||||
}
|
||||
})
|
||||
.on('mouseup', function (ev) {
|
||||
ev.originalEvent.preventDefault();
|
||||
resetLocalPreventDefault();
|
||||
ev = providePreventDefault(ev);
|
||||
onEnd && onEnd(ev);
|
||||
})
|
||||
.on('touchstart', function (ev) {
|
||||
ev.originalEvent.preventDefault();
|
||||
|
||||
ev = providePreventDefault(ev);
|
||||
onStart && onStart(ev);
|
||||
})
|
||||
.on('touchmove', function (ev) {
|
||||
ev = providePreventDefault(ev);
|
||||
onMove && onMove(ev);
|
||||
|
||||
if (ev.defaultPrevented) return;
|
||||
|
||||
if (!ev.active) return;
|
||||
camera.rotate(
|
||||
-ev.dx * RADIANS_PER_HALF_SCREEN_WIDTH,
|
||||
-ev.dy * RADIANS_PER_HALF_SCREEN_WIDTH
|
||||
);
|
||||
ev.originalEvent.preventDefault();
|
||||
})
|
||||
.on('touchend', function (ev) {
|
||||
ev.originalEvent.preventDefault();
|
||||
resetLocalPreventDefault();
|
||||
ev = providePreventDefault(ev);
|
||||
onEnd && onEnd(ev);
|
||||
})
|
||||
.on('pinchmove', function (ev) {
|
||||
if (!ev.active) return;
|
||||
transformXY(ev);
|
||||
camera.zoom(xy[0], xy[1], 1 - ev.zoomx);
|
||||
camera.pan(ev.dx, ev.dy);
|
||||
|
||||
ev.originalEvent.preventDefault();
|
||||
})
|
||||
.on('pinchstart', function (ev) {
|
||||
ev.originalEvent.preventDefault();
|
||||
});
|
||||
|
||||
onStart = opts.onStart;
|
||||
onMove = opts.onMove;
|
||||
onEnd = opts.onEnd;
|
||||
|
||||
return {
|
||||
setInteractions: function (interactions) {
|
||||
assert(interactions);
|
||||
onStart = interactions.onStart;
|
||||
onEnd = interactions.onEnd;
|
||||
onMove = interactions.onMove;
|
||||
}
|
||||
};
|
||||
}
|
||||
106
src/src/lawsons-klein-bottle/mesh-surface-2d.js
Normal file
106
src/src/lawsons-klein-bottle/mesh-surface-2d.js
Normal file
@ -0,0 +1,106 @@
|
||||
'use strict';
|
||||
|
||||
function assert (condition, message) {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
var DEFAULT_RESOLUTION = 30;
|
||||
var tmp1 = [0.0, 0.0, 0.0];
|
||||
var tmp2 = [0.0, 0.0, 0.0];
|
||||
var tmp3 = [0.0, 0.0, 0.0];
|
||||
|
||||
module.exports = function (meshData, surfaceFn, opts) {
|
||||
var i, j, u, v, index, nbUFaces, nbVFaces;
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
var res = opts.resolution || DEFAULT_RESOLUTION;
|
||||
var nbUFaces = Array.isArray(opts.resolution) ? opts.resolution[0] : res;
|
||||
var nbVFaces = Array.isArray(opts.resolution) ? opts.resolution[1] : res;
|
||||
|
||||
var uDomain = opts.uDomain === undefined ? [0, 1] : opts.uDomain;
|
||||
var vDomain = opts.vDomain === undefined ? [0, 1] : opts.vDomain;
|
||||
|
||||
meshData = meshData || {};
|
||||
|
||||
var nbBoundaryAdjustedUFaces = nbUFaces;
|
||||
var nbBoundaryAdjustedVFaces = nbVFaces;
|
||||
if (!opts.uClosed) nbBoundaryAdjustedUFaces += 1;
|
||||
if (!opts.vClosed) nbBoundaryAdjustedVFaces += 1;
|
||||
|
||||
var nbPositions = nbBoundaryAdjustedUFaces * nbBoundaryAdjustedVFaces;
|
||||
var positionDataLength = nbPositions * 2;
|
||||
var positions = meshData.positions = meshData.positions || new Float32Array(positionDataLength);
|
||||
assert(positions.length, positionDataLength, 'Incorrect number of positions in pre-allocated array');
|
||||
|
||||
var nbFaces = nbUFaces * nbVFaces * 2;
|
||||
var cellDataLength = nbFaces * 3;
|
||||
var cells = meshData.cells = meshData.cells || new Int16Array(cellDataLength);
|
||||
assert(cells.length, cellDataLength, 'Incorrect number of cells in pre-allocated array');
|
||||
|
||||
if (opts.attributes) {
|
||||
meshData.attributes = {};
|
||||
var attrSize = {};
|
||||
var attributeKeys = Object.keys(opts.attributes);
|
||||
|
||||
for (i = 0; i < attributeKeys.length; i++) {
|
||||
var key = attributeKeys[i];
|
||||
var attrFn = opts.attributes[key];
|
||||
var test = [];
|
||||
attrFn(test, uDomain[0], vDomain[0]);
|
||||
attrSize[key] = test.length;
|
||||
|
||||
var attrDataLength = nbPositions * attrSize[key];
|
||||
meshData.attributes[key] = meshData.attributes[key] || new Float32Array(attrDataLength);
|
||||
assert(meshData.attributes[key].length, attrDataLength, 'Incorrect attr size in pre-allocated array for attr ' + key);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < nbBoundaryAdjustedUFaces; i++) {
|
||||
u = uDomain[0] + (uDomain[1] - uDomain[0]) * i / nbUFaces;
|
||||
for (j = 0; j < nbBoundaryAdjustedVFaces; j++) {
|
||||
v = vDomain[0] + (vDomain[1] - vDomain[0]) * j / nbVFaces;
|
||||
|
||||
index = 2 * (i + nbBoundaryAdjustedUFaces * j);
|
||||
|
||||
surfaceFn(tmp1, u, v);
|
||||
|
||||
positions[index + 0] = tmp1[0];
|
||||
positions[index + 1] = tmp1[1];
|
||||
|
||||
if (attributeKeys) {
|
||||
for (var k = 0; k < attributeKeys.length; k++) {
|
||||
var key = attributeKeys[k];
|
||||
var attrFn = opts.attributes[key];
|
||||
attrFn(tmp1, u, v);
|
||||
var attrIndex = (i + nbBoundaryAdjustedUFaces * j) * attrSize[key];
|
||||
var attrData = meshData.attributes[key];
|
||||
for (var l = 0; l < attrSize[key]; l++) {
|
||||
attrData[attrIndex + l] = tmp1[l];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var faceIndex = 0;
|
||||
for (i = 0; i < nbUFaces; i++) {
|
||||
var iPlusOne = i + 1;
|
||||
if (opts.uClosed) iPlusOne = iPlusOne % nbUFaces;
|
||||
for (j = 0; j < nbVFaces; j++) {
|
||||
var jPlusOne = j + 1;
|
||||
if (opts.vClosed) jPlusOne = jPlusOne % nbVFaces;
|
||||
|
||||
cells[faceIndex++] = i + nbBoundaryAdjustedUFaces * j;
|
||||
cells[faceIndex++] = iPlusOne + nbBoundaryAdjustedUFaces * j;
|
||||
cells[faceIndex++] = iPlusOne + nbBoundaryAdjustedUFaces * jPlusOne;
|
||||
|
||||
cells[faceIndex++] = i + nbBoundaryAdjustedUFaces * j;
|
||||
cells[faceIndex++] = iPlusOne + nbBoundaryAdjustedUFaces * jPlusOne;
|
||||
cells[faceIndex++] = i + nbBoundaryAdjustedUFaces * jPlusOne;
|
||||
}
|
||||
}
|
||||
|
||||
return meshData;
|
||||
};
|
||||
6
src/src/lawsons-klein-bottle/metadata.json
Normal file
6
src/src/lawsons-klein-bottle/metadata.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"title": "Lawson's Klein Bottle",
|
||||
"description": "3D sterographic projection of a 4D Klein bottle",
|
||||
"order": 3200,
|
||||
"image": "http://rreusser.github.io/src/src/lawsons-klein-bottle/thumbnail.jpg"
|
||||
}
|
||||
83
src/src/lawsons-klein-bottle/regl-turntable-camera.js
Normal file
83
src/src/lawsons-klein-bottle/regl-turntable-camera.js
Normal file
@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
var mat4create = require('gl-mat4/create');
|
||||
var mat4multiply = require('gl-mat4/multiply');
|
||||
var createCamera = require('inertial-turntable-camera');
|
||||
var createInteractions = require('./interactions');
|
||||
|
||||
var RADIANS_PER_HALF_SCREEN_WIDTH = Math.PI * 2 * 0.4;
|
||||
|
||||
module.exports = function createReglCamera (regl, opts) {
|
||||
var element = regl._gl.canvas;
|
||||
element.addEventListener('wheel', event => event.preventDefault());
|
||||
|
||||
function getAspectRatio () {
|
||||
return element.clientWidth / element.clientHeight;
|
||||
}
|
||||
|
||||
var camera = createCamera(Object.assign({}, {
|
||||
aspectRatio: getAspectRatio(),
|
||||
}, opts || {}));
|
||||
|
||||
var mProjectionView = mat4create();
|
||||
var setCameraUniforms = regl({
|
||||
context: {
|
||||
projection: () => camera.state.projection,
|
||||
view: () => camera.state.view,
|
||||
viewInv: () => camera.state.viewInv,
|
||||
eye: () => camera.state.eye,
|
||||
},
|
||||
uniforms: {
|
||||
uProjectionView: ctx => mat4multiply(mProjectionView, ctx.projection, ctx.view),
|
||||
uProjection: regl.context('projection'),
|
||||
uView: regl.context('view'),
|
||||
uEye: regl.context('eye'),
|
||||
}
|
||||
});
|
||||
|
||||
function invokeCamera (props, callback) {
|
||||
if (!callback) {
|
||||
callback = props;
|
||||
props = {};
|
||||
}
|
||||
|
||||
camera.tick(props);
|
||||
|
||||
setCameraUniforms(function () {
|
||||
callback(camera.state, camera.params);
|
||||
});
|
||||
}
|
||||
|
||||
invokeCamera.taint = camera.taint;
|
||||
invokeCamera.resize = camera.resize;
|
||||
invokeCamera.tick = camera.tick;
|
||||
invokeCamera.setUniforms = setCameraUniforms;
|
||||
|
||||
invokeCamera.rotate = camera.rotate;
|
||||
invokeCamera.pan = camera.pan;
|
||||
invokeCamera.pivot = camera.pivot;
|
||||
invokeCamera.zoom = camera.zoom;
|
||||
|
||||
Object.defineProperties(invokeCamera, {
|
||||
state: {
|
||||
get: function () { return camera.state; },
|
||||
set: function (value) { camera.state = value; }
|
||||
},
|
||||
params: {
|
||||
get: function () { return camera.params; },
|
||||
set: function (value) { camera.params = value; }
|
||||
},
|
||||
element: {
|
||||
get: function () { return element; }
|
||||
},
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
camera.resize(getAspectRatio());
|
||||
}, false);
|
||||
|
||||
camera.element = regl._gl.canvas;
|
||||
createInteractions(camera);
|
||||
|
||||
return invokeCamera;
|
||||
};
|
||||
BIN
src/src/lawsons-klein-bottle/thumbnail.jpg
Normal file
BIN
src/src/lawsons-klein-bottle/thumbnail.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
File diff suppressed because one or more lines are too long
BIN
src/src/sketches/static/lawsons-klein-bottle-thumbnail.jpg
Normal file
BIN
src/src/sketches/static/lawsons-klein-bottle-thumbnail.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Loading…
x
Reference in New Issue
Block a user