Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <html>
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Flat Warped World</title>
- </head>
- <style>
- html {
- background: #333;
- color: #eee;
- text-align: center;
- font-family: sans-serif;
- margin: 0;
- }
- ::-webkit-scrollbar {
- width: 0.6em;
- }
- ::-webkit-scrollbar-thumb {
- background: #888;
- background: linear-gradient(90deg, #888 0%, #aaa 80%, #888 100%);
- border-radius: 1em;
- }
- canvas {
- outline: #5af solid 1px;
- display: block;
- margin: 1em auto;
- width: 720px;
- max-width: 95%;
- }
- p {
- width: 80%;
- margin: 0.1em auto;
- }
- </style>
- <body>
- <h1>Flat Warped World Test</h1>
- <p>Ходим по 2D-поверхности в 3D-пространстве. Пока что в роли поверхности выставлена сфера. Разные цвета - это 6 точек на сфере. Возможность переключить поверхность и раскраску добавлю потом, наверное. Управление: WASD + QE.</p>
- <script>
- // Developed during:
- // 20.05.2018
- // 21.05.2018
- // 22.05.2018
- // 25.05.2018
- const DIM = 3;
- const DIMLAST = DIM - 1;
- // vector methods
- function vecAll(fillWith, size) {
- return Array(size).fill(fillWith);
- }
- function vecMix(arr, brr, f) {
- return Array.isArray(brr) ? Array.from(arr, (a, i) => f(a, brr[i])) : arr.map((a) => f(a, brr));
- }
- function vecMul(arr, brr) {
- return vecMix(arr, brr, (a, b) => a * b);
- }
- function vecAdd(arr, brr) {
- return vecMix(arr, brr, (a, b) => a + b);
- }
- function vecSub(arr, brr) {
- return vecMix(arr, brr, (a, b) => a - b);
- }
- function vecSum(arr) {
- return arr.reduce((x, y) => x + y);
- }
- function vecDot(arr, brr) {
- return vecSum(vecMul(arr, brr));
- }
- function vecSqr(arr) {
- return arr.reduce((x, y) => x + (y ** 2), 0);
- }
- function vecDir(arr) {
- const dist = Math.sqrt(vecSqr(arr));
- return arr.map((x) => x / dist);
- }
- // only for 3D
- function vecCross(arr, brr) {
- const result = Array(DIM);
- for (let i = 0; i < DIM; i++) {
- const j = (i + 1) % DIM;
- const k = (i + 2) % DIM;
- result[i] = arr[j] * brr[k] - brr[j] * arr[k];
- }
- return result;
- }
- /////////
- const canv = document.createElement('canvas');
- const mini = document.createElement('canvas');
- let scale = 4;
- let w = 200;
- let h = 120;
- let w2 = w / 2;
- let h2 = h / 2;
- mini.width = w;
- mini.height = h;
- canv.width = w * scale;
- canv.height = h * scale;
- const bm = canv.getContext('2d');
- const bmm = mini.getContext('2d');
- // params
- /*
- AAA__BBB name means AAA-to-BBB ratio.
- view: from center to corner of screen
- M: meter (1.0) in dimension
- R: angle in radians
- R360: angle in turns (360 degrees)
- rs: ray step
- rt: ray tick
- ms: move step
- mt: move tick
- ts: rotate step
- dot: pixel of bmm
- */
- // screen params
- let ray__R360 = 32; // number of rays
- let rs__view = 32; // number of steps
- // world params
- let M__view = 5.0;
- // ray params
- let M__rt = 0.1; // ray travel quality
- let M__rs = M__view / rs__view;
- let rt__rs = Math.floor(M__rs / M__rt) + 1;
- // move params
- let ms__view = rs__view;
- let M__mt = M__rt;
- let M__ms = M__view / ms__view;
- let mt__ms = Math.floor(M__ms / M__mt) + 1;
- let attr__rt = 3;
- // rotate params
- let R__ts = 0.15;
- // screen params
- let dot__view = Math.hypot(w2, h2);
- let dot__rs = Math.floor(dot__view / rs__view) + 1;
- // surface data
- let surfaces = {
- plane: {
- start: {
- x: [0, 0, 0],
- d: [0, 1, 0],
- },
- f: (x) => x[DIMLAST],
- df: (x) => [0, 0, 1],
- },
- sphere: {
- start: {
- x: [0, 0, 1],
- d: [0, 1, 0],
- },
- f: (x) => vecSqr(x) - 1,
- df: (x) => vecMul(x, 2),
- },
- innersphere: {
- start: {
- x: [0, 0, 1],
- d: [0, 1, 0],
- },
- f: (x) => 1 - vecSqr(x),
- df: (x) => vecMul(x, -2),
- },
- paraboloid: {
- start: {
- x: [0, 0, 0],
- d: [0, 1, 0],
- },
- f: (x) => vecSum(x.map((a, i) => (i == DIMLAST ? a : -(a ** 2)))),
- df: (x) => x.map((a, i) => (i == DIMLAST ? 1 : -2 * a)),
- },
- saddle: {
- start: {
- x: [0, 0, 0],
- d: [0, 1, 0],
- },
- f: (x) => (x[0] ** 2) - (x[1] ** 2) + x[2], // z = y^2 - x^2
- df: (x) => [2 * x[0], -2 * x[1], 1],
- },
- };
- // colorings
- function test01(x) {
- if (x < 0) return 0;
- if (x < 1) return x;
- return 1;
- }
- function nearExp(x, y, k=1) {
- return Math.exp(-k * vecSqr(vecSub(x, y)));
- }
- function nearDiv(x, y, k=1) {
- return 1 / (1 + k * vecSqr(vecSub(x, y)));
- }
- function exp1(x) {
- if (x < 0) {
- return 0;
- }
- return Math.exp(-1 / ((x / Math.LN2) ** 2));
- }
- let colorings = {
- border01: (x) => x.map(test01),
- near0: (x) => Array(DIM).fill(nearDiv(x, vecAll(0, DIM))),
- near110: (x) => [nearDiv(x, [1, 0, 0]), nearDiv(x, [0, 1, 0]), nearDiv(x, [0, 0, 0])],
- near111: (x) => [nearDiv(x, [1, 0, 0]), nearDiv(x, [0, 1, 0]), nearDiv(x, [0, 0, 1])],
- neare111: (x) => [nearExp(x, [1, 0, 0]), nearExp(x, [0, 1, 0]), nearExp(x, [0, 0, 1])],
- nan: (x) => x.map((t) => t != t),
- exp6: (x) => vecAdd(x.map((t) => 0.5 * exp1(t)), x.map((t) => 0.5 * exp1(-t))),
- tan3: (x) => x.map((t) => (Math.atan(t) / Math.PI + 0.5)),
- };
- let dotmaps = {
- compass: [
- // 6 dots in 6 directions
- [[1, 0, 0], [1, 0, 0]],
- [[0, 1, 0], [0, 1, 0]],
- [[0, 0, 1], [0, 0, 1]],
- [[-1, 0, 0], [0, 1, 1]],
- [[0, -1, 0], [1, 0, 1]],
- [[0, 0, -1], [1, 1, 0]],
- // white center
- [[0, 0, 0], [1, 1, 1]],
- ],
- compassParaboloid: [
- [[1, 0, 1], [1, 0, 0]],
- [[0, 1, 1], [0, 1, 0]],
- [[-1, 0, 1], [0, 1, 1]],
- [[0, -1, 1], [1, 0, 1]],
- [[2, 0, 4], [1, 0.8, 0]],
- [[0, 2, 4], [0.8, 1, 0]],
- [[-2, 0, 4], [0.8, 1, 1]],
- [[0, -2, 4], [1, 0.8, 1]],
- [[0, 0, 0], [1, 1, 1]],
- ],
- compassSaddle: [
- [[1, 0, -1], [1, 0, 0]],
- [[0, 1, 1], [0, 1, 0]],
- [[-1, 0, -1], [0, 1, 1]],
- [[0, -1, 1], [1, 0, 1]],
- [[2, 0, -4], [1, 0.8, 0]],
- [[0, 2, 4], [0.8, 1, 0]],
- [[-2, 0, -4], [0.8, 1, 1]],
- [[0, -2, 4], [1, 0.8, 1]],
- [[0, 0, 0], [1, 1, 1]],
- ],
- }
- function neardots(x) {
- const weighted = dotmap.map(([y, color]) => [nearExp(x, y, 2), color]);
- const summed = weighted.reduce((acc, [w, color]) => vecAdd(acc, vecMul(color, w)), vecAll(0, 3));
- const light = weighted.reduce((acc, [w, color]) => acc + w, 0);
- const k = 1 / Math.sqrt(1 + (light ** 2));
- return vecMul(summed, k);
- }
- // user current position
- /*let view = {
- x: [0, 0, 1], // position vector
- // all dir vectors are normed
- d: [0, 1, 0], // forward direction vector
- r: [1, 0, 0], // right direction vector
- n: [0, 0, 1], // surface normal vector
- };*/
- // utils
- function ease(x) {
- return x// * x * (3 - 2 * x);
- }
- // surface operations
- function getAirAt(x) {
- return surface.f(x);
- }
- function getDeltaAt(x) {
- return surface.df(x);
- }
- // ray pos operations
- // create a ray with specified angle
- function initRay(angle) {
- return {
- x: view.x,
- d: vecSub(vecMul(view.d, Math.cos(angle)), vecMul(view.r, Math.sin(angle))),
- }
- }
- // move ray forward along the surface
- function updateRay(ray, move) {
- for (let i = 0; i < (move ? mt__ms : rt__rs); i++) {
- ray.x = vecAdd(ray.x, vecMul(ray.d, (move ? M__mt : M__rt)));
- // attracting x to surface
- for (let j = 0; j < attr__rt; j++) {
- const air = getAirAt(ray.x);
- const delta = getDeltaAt(ray.x);
- const attr = vecSqr(delta);
- ray.x = vecSub(ray.x, vecMul(delta, air / attr));
- }
- // updating direction
- const norm = getDeltaAt(ray.x);
- const prod = vecSum(vecMul(norm, ray.d)); // dot prod
- const dist = vecSqr(norm);
- const proj = vecMul(norm, prod / dist); // ray.d projected on norm direction
- const flat = vecSub(ray.d, proj); // ray.d projected on surface environ
- ray.d = vecDir(flat);
- if (move) {
- ray.n = vecDir(norm);
- }
- }
- }
- // get color
- function getRayData(ray) {
- return coloring(ray.x, ray.d);
- }
- // render logic
- function computeSectorColors() {
- const sectors = [];
- // compute each ray
- // (can be parallelized)
- for (let i = 0; i < ray__R360; i++) {
- const raydata = [];
- const angle = i * 2 * Math.PI / ray__R360;
- const ray = initRay(angle);
- while (true) {
- raydata.push(getRayData(ray));
- if (raydata.length > rs__view) {
- break;
- }
- updateRay(ray);
- }
- sectors.push(raydata);
- }
- return sectors;
- }
- function drawSectors(sectors) {
- const dat = bmm.getImageData(0, 0, w, h);
- const d = dat.data;
- const m = Array(4);
- const p = Array(4);
- let offset = 0;
- // find a sector for each bmm's dot
- // and compute its color by linear radial interpolation
- // (can be parallelized)
- for (let j = 0; j < h; j++) {
- for (let i = 0; i < w; i++) {
- const x = i - w2;
- const y = j - h2;
- // determining radius
- const rval = Math.hypot(x, y) / dot__rs;
- p[0] = Math.floor(rval);
- p[1] = p[0] + 1;
- m[0] = 1 - (rval - p[0]);
- m[1] = 1 - m[0];
- // determining angle
- let aval = ray__R360 * (Math.atan2(x, y) / (2 * Math.PI) + 0.5);
- if (aval >= ray__R360) {
- aval = 0;
- }
- p[2] = Math.floor(aval);
- p[3] = (p[2] + 1) % ray__R360;
- m[2] = 1 - (aval - p[2]);
- m[3] = 1 - m[2];
- // sum sector corner colors multipled by vicinity
- const sdat = [0, 0, 0, 1];
- for (let k = 0; k < 4; k++) {
- const ri = k & 1;
- const ai = (k >> 1) | 2;
- const color = sectors[p[ai]][p[ri]];
- const mul = m[ri] * m[ai];
- for (let c = 0; c < 3; c++) {
- sdat[c] += color[c] * mul;
- }
- }
- // set data
- for (let c = 0; c < 4; c++) {
- d[offset + c] = sdat[c] * 255;
- }
- offset += 4;
- }
- }
- bmm.putImageData(dat, 0, 0);
- bm.drawImage(mini, 0, 0, w * scale, h * scale);
- }
- function drawAll() {
- let sectors = debugTime('sectors', computeSectorColors);
- debugTime('draw', drawSectors, sectors);
- }
- // moving and rotating
- function initView(surface) {
- const view = {};
- view.x = surface.start.x;
- view.d = surface.start.d;
- view.n = vecDir(getDeltaAt(view.x));
- view.r = vecCross(view.d, view.n);
- return view;
- }
- function updateView(view, surface, inp) {
- // moving
- let angle = 0;
- if (inp.d || inp.r) {
- angle = Math.atan2(inp.r, inp.d);
- const ray = initRay(angle);
- updateRay(ray, surface, true);
- view.x = ray.x;
- view.d = ray.d;
- view.n = ray.n;
- view.r = vecCross(view.d, view.n);
- }
- // rotating
- angle += inp.t * R__ts;
- const x = view.r;
- const y = view.d;
- const c = Math.cos(angle);
- const s = Math.sin(angle);
- view.r = vecSub(vecMul(x, c), vecMul(y, s));
- view.d = vecAdd(vecMul(x, s), vecMul(y, c));
- }
- // button controls
- let keylist = {
- '65': false, // A
- '87': false, // W
- '68': false, // D
- '83': false, // S
- '37': false, // left
- '38': false, // up
- '39': false, // right
- '40': false, // down
- '69': false, // E: rotate clockwise
- '81': false, // Q: rotate counter-clockwise
- //16: false, // Shift * smod
- //18: false, // Alt / smod
- //187: false, // +
- //189: false, // -
- //112: false, // F1
- //113: false, // F2
- //114: false, // F3
- }
- function setKey(key, state) {
- key = String(key);
- if (keylist[key] == undefined) {
- return false;
- }
- keylist[key] = state;
- return true;
- }
- function getInputs() {
- return {
- // along y
- d: (keylist['38'] || keylist['87']) - (keylist['40'] || keylist['83']),
- // along x
- r: (keylist['37'] || keylist['65']) - (keylist['39'] || keylist['68']),
- // rotation
- t: keylist['69'] - keylist['81'],
- };
- }
- window.onkeydown = function (e) {
- if (setKey(e.keyCode, true)) {
- e.preventDefault();
- }
- }
- window.onkeyup = function (e) {
- if (setKey(e.keyCode, false)) {
- e.preventDefault();
- }
- }
- window.onblur = function(e) {
- for (let k in keylist) {
- keylist[k] = false;
- }
- }
- // tick
- function updateAll(view) {
- const inp = getInputs();
- if (inp.d || inp.r || inp.t) {
- debugTime('change', updateView, view, surface, inp);
- console.log(view);
- debugTime('redraw', drawAll);
- }
- setTimeout(updateAll, tickDelay, view);
- }
- // debug time
- function debugTime(name, f, ...args) {
- console.time(name);
- const ret = f(...args);
- console.timeEnd(name);
- return ret;
- }
- // params
- //let params
- const tickDelay = 20;
- let surface = surfaces.sphere;
- //let coloring = colorings.tan3;
- let coloring = neardots;
- let dotmap = dotmaps.compass;
- // init all
- function initAll() {
- let view = initView(surface);
- window.view = view; // to be changed
- drawAll(view);
- updateAll(view);
- document.body.appendChild(canv);
- }
- debugTime('initAll', initAll);
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement