A.Olokhtonov
2 years ago
7 changed files with 437 additions and 14 deletions
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022/Debian) (preloaded format=pdflatex 2023.3.25) 8 APR 2023 22:14 |
||||
entering extended mode |
||||
restricted \write18 enabled. |
||||
%&-line parsing enabled. |
||||
** |
||||
|
||||
! Emergency stop. |
||||
<*> |
||||
|
||||
End of file on the terminal! |
||||
|
||||
|
||||
Here is how much of TeX's memory you used: |
||||
3 strings out of 476091 |
||||
111 string characters out of 5794081 |
||||
1849330 words of memory out of 5000000 |
||||
20488 multiletter control sequences out of 15000+600000 |
||||
512287 words of font info for 32 fonts, out of 8000000 for 9000 |
||||
1141 hyphenation exceptions out of 8191 |
||||
0i,0n,0p,1b,6s stack positions out of 10000i,1000n,20000p,200000b,200000s |
||||
! ==> Fatal error occurred, no output PDF file produced! |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<title>Desk</title> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> |
||||
<link rel="shortcut icon" href="icons/favicon.svg" id="favicon"> |
||||
<script type="text/javascript" src="math.js?v=1"></script> |
||||
<script type="text/javascript" src="webgl.js?v=1"></script> |
||||
|
||||
<style> |
||||
html, body { |
||||
margin: 0; |
||||
padding: 0; |
||||
width: 100%; |
||||
height: 100%; |
||||
overflow: hidden; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
<canvas id="c"></canvas> |
||||
</body> |
||||
</html> |
@ -0,0 +1,358 @@
@@ -0,0 +1,358 @@
|
||||
document.addEventListener('DOMContentLoaded', main); |
||||
|
||||
const vertex_shader_source = ` |
||||
attribute vec2 pos; |
||||
|
||||
uniform vec2 u_scale; |
||||
uniform vec2 u_res; |
||||
uniform vec2 u_translation; |
||||
uniform int u_layer; |
||||
|
||||
void main() { |
||||
vec2 screen01 = (pos * u_scale + u_translation) / u_res; |
||||
vec2 screen02 = screen01 * 2.0; |
||||
screen02.y = 2.0 - screen02.y; |
||||
vec2 screen11 = screen02 - 1.0; |
||||
gl_Position = vec4(screen11, u_layer, 1); |
||||
} |
||||
`;
|
||||
|
||||
const fragment_shader_source = ` |
||||
precision mediump float; |
||||
|
||||
uniform vec3 u_color; |
||||
|
||||
void main() { |
||||
gl_FragColor = vec4(u_color, 1); |
||||
} |
||||
`;
|
||||
|
||||
function create_shader(gl, type, source) { |
||||
const shader = gl.createShader(type); |
||||
|
||||
gl.shaderSource(shader, source); |
||||
gl.compileShader(shader); |
||||
|
||||
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { |
||||
return shader; |
||||
} |
||||
|
||||
console.error(type, ':', gl.getShaderInfoLog(shader)); |
||||
|
||||
gl.deleteShader(shader); |
||||
} |
||||
|
||||
function create_program(gl, vs, fs) { |
||||
const program = gl.createProgram(); |
||||
|
||||
gl.attachShader(program, vs); |
||||
gl.attachShader(program, fs); |
||||
gl.linkProgram(program); |
||||
|
||||
if (gl.getProgramParameter(program, gl.LINK_STATUS)) { |
||||
return program; |
||||
} |
||||
|
||||
console.error('link:', gl.getProgramInfoLog(program)); |
||||
|
||||
gl.deleteProgram(program); |
||||
} |
||||
|
||||
function perpendicular(ax, ay, bx, by, width) { |
||||
// Place points at (stroke_width / 2) distance from the line
|
||||
// The direction is an average of perpenducalars to the previous and next points
|
||||
// if (i === 0) {
|
||||
const dirx = bx - ax; |
||||
const diry = by - ay; |
||||
|
||||
let pdirx = diry; |
||||
let pdiry = -dirx; |
||||
|
||||
const pdir_norm = Math.sqrt(pdirx * pdirx + pdiry * pdiry); |
||||
|
||||
pdirx /= pdir_norm; |
||||
pdiry /= pdir_norm; |
||||
|
||||
return { |
||||
'p1': { |
||||
'x': ax + pdirx * width / 2, |
||||
'y': ay + pdiry * width / 2, |
||||
}, |
||||
|
||||
'p2': { |
||||
'x': ax - pdirx * width / 2, |
||||
'y': ay - pdiry * width / 2, |
||||
} |
||||
}; |
||||
} |
||||
|
||||
const canvas_offset = { 'x': 0, 'y': 0 }; |
||||
let moving = false; |
||||
let spacedown = false; |
||||
let drawing = false; |
||||
let canvas_zoom = 1.0; |
||||
let current_stroke = []; |
||||
|
||||
function push_stroke_positions(stroke, stroke_width, positions) { |
||||
let last_x1; |
||||
let last_y1; |
||||
let last_x2; |
||||
let last_y2; |
||||
|
||||
const points = stroke.points; |
||||
|
||||
for (let i = 0; i < points.length; ++i) { |
||||
const px = points[i].x; |
||||
const py = points[i].y; |
||||
|
||||
// These might be undefined
|
||||
let nextpx; |
||||
let nextpy; |
||||
|
||||
if (i < points.length - 1) { |
||||
nextpx = points[i + 1].x; |
||||
nextpy = points[i + 1].y; |
||||
} |
||||
|
||||
if (i === 0) { |
||||
const pps = perpendicular(px, py, nextpx, nextpy, stroke_width); |
||||
last_x1 = pps.p1.x; |
||||
last_y1 = pps.p1.y; |
||||
last_x2 = pps.p2.x; |
||||
last_y2 = pps.p2.y; |
||||
continue; |
||||
} |
||||
|
||||
// Place points at (stroke_width / 2) distance from the line
|
||||
const prevpx = points[i - 1].x; |
||||
const prevpy = points[i - 1].y; |
||||
|
||||
let x1; |
||||
let y1; |
||||
let x2; |
||||
let y2; |
||||
|
||||
if (i < points.length - 1) { |
||||
const pps1 = perpendicular(px, py, nextpx, nextpy, stroke_width); |
||||
const pps2 = perpendicular(px, py, prevpx, prevpy, stroke_width); |
||||
|
||||
const dp1x = (pps1.p2.x - pps1.p1.x); |
||||
const dp1y = (pps1.p2.y - pps1.p1.y); |
||||
|
||||
const dp2x = (pps2.p2.x - pps2.p1.x); |
||||
const dp2y = (pps2.p2.y - pps2.p1.y); |
||||
|
||||
if (dp1x * dp2x + dp1y * dp2y < 0) { |
||||
x1 = (pps1.p1.x + pps2.p2.x) / 2.0; |
||||
y1 = (pps1.p1.y + pps2.p2.y) / 2.0; |
||||
|
||||
x2 = (pps1.p2.x + pps2.p1.x) / 2.0; |
||||
y2 = (pps1.p2.y + pps2.p1.y) / 2.0; |
||||
} else { |
||||
x1 = (pps1.p1.x + pps2.p1.x) / 2.0; |
||||
y1 = (pps1.p1.y + pps2.p1.y) / 2.0; |
||||
|
||||
x2 = (pps1.p2.x + pps2.p2.x) / 2.0; |
||||
y2 = (pps1.p2.y + pps2.p2.y) / 2.0; |
||||
} |
||||
} else { |
||||
const pps = perpendicular(px, py, prevpx, prevpy, stroke_width); |
||||
|
||||
x1 = pps.p2.x; |
||||
y1 = pps.p2.y; |
||||
x2 = pps.p1.x; |
||||
y2 = pps.p1.y; |
||||
} |
||||
|
||||
positions.push(last_x1, last_y1); |
||||
positions.push(x2, y2); |
||||
positions.push(last_x2, last_y2); |
||||
|
||||
positions.push(last_x1, last_y1); |
||||
positions.push(x1, y1); |
||||
positions.push(x2, y2); |
||||
|
||||
last_x1 = x1; |
||||
last_y1 = y1; |
||||
last_x2 = x2; |
||||
last_y2 = y2; |
||||
} |
||||
} |
||||
|
||||
function draw(gl, program, locations, buffers, strokes) { |
||||
const width = window.innerWidth; |
||||
const height = window.innerHeight; |
||||
|
||||
if (gl.canvas.width !== width || gl.canvas.height !== height) { |
||||
gl.canvas.width = width; |
||||
gl.canvas.height = height; |
||||
gl.viewport(0, 0, width, height); |
||||
} |
||||
|
||||
gl.clearColor(0, 0, 0, 1); |
||||
gl.clear(gl.COLOR_BUFFER_BIT); |
||||
gl.useProgram(program); |
||||
gl.enableVertexAttribArray(locations['pos']); |
||||
|
||||
gl.uniform2f(locations['u_res'], width, height); |
||||
gl.uniform2f(locations['u_scale'], canvas_zoom, canvas_zoom); |
||||
gl.uniform2f(locations['u_translation'], canvas_offset.x, canvas_offset.y); |
||||
|
||||
const positions = []; |
||||
const stroke_width = 4; |
||||
|
||||
for (const stroke of strokes) { |
||||
push_stroke_positions(stroke, stroke_width, positions); |
||||
} |
||||
|
||||
if (current_stroke.length > 0) { |
||||
push_stroke_positions({'points': current_stroke}, stroke_width, positions); |
||||
} |
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['pos']); |
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); |
||||
|
||||
{ |
||||
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
|
||||
const size = 2; // 2 components per iteration
|
||||
const type = gl.FLOAT; // the data is 32bit floats
|
||||
const normalize = false; // don't normalize the data
|
||||
const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
|
||||
const offset = 0; // start at the beginning of the buffer
|
||||
gl.vertexAttribPointer(locations['pos'], size, type, normalize, stride, offset); |
||||
} |
||||
|
||||
{ |
||||
const offset = 0; |
||||
const count = positions.length / 2; |
||||
gl.uniform3f(locations['u_color'], 0.2, 0.2, 0.2); |
||||
gl.uniform1i(locations['u_layer'], 0); |
||||
gl.drawArrays(gl.TRIANGLES, offset, count); |
||||
|
||||
gl.uniform3f(locations['u_color'], 1, 0, 0); |
||||
gl.uniform1i(locations['u_layer'], 1); |
||||
gl.drawArrays(gl.POINTS, offset, count); |
||||
} |
||||
|
||||
window.requestAnimationFrame(() => draw(gl, program, locations, buffers, strokes)); |
||||
} |
||||
|
||||
function main() { |
||||
const canvas = document.querySelector('#c'); |
||||
const gl = canvas.getContext('webgl'); |
||||
|
||||
if (!gl) { |
||||
console.error('FUCK!') |
||||
return; |
||||
} |
||||
|
||||
const vertex_shader = create_shader(gl, gl.VERTEX_SHADER, vertex_shader_source); |
||||
const fragment_shader = create_shader(gl, gl.FRAGMENT_SHADER, fragment_shader_source); |
||||
const program = create_program(gl, vertex_shader, fragment_shader) |
||||
|
||||
const locations = {}; |
||||
const buffers = {}; |
||||
|
||||
locations['pos'] = gl.getAttribLocation(program, 'pos'); |
||||
locations['u_res'] = gl.getUniformLocation(program, 'u_res'); |
||||
locations['u_scale'] = gl.getUniformLocation(program, 'u_scale'); |
||||
locations['u_translation'] = gl.getUniformLocation(program, 'u_translation'); |
||||
locations['u_color'] = gl.getUniformLocation(program, 'u_color'); |
||||
locations['u_layer'] = gl.getUniformLocation(program, 'u_layer'); |
||||
|
||||
buffers['pos'] = gl.createBuffer(); |
||||
|
||||
const strokes = [ |
||||
{ |
||||
'points': [ |
||||
{'x': 100, 'y': 100}, |
||||
{'x': 200, 'y': 200}, |
||||
{'x': 300, 'y': 100}, |
||||
] |
||||
} |
||||
] |
||||
|
||||
window.addEventListener('keydown', (e) => { |
||||
if (e.code === 'Space') { |
||||
spacedown = true; |
||||
} |
||||
}); |
||||
|
||||
window.addEventListener('keyup', (e) => { |
||||
if (e.code === 'Space') { |
||||
spacedown = false; |
||||
moving = false; |
||||
} |
||||
}); |
||||
|
||||
canvas.addEventListener('mousedown', (e) => { |
||||
if (spacedown) { |
||||
moving = true; |
||||
return; |
||||
} |
||||
|
||||
const x = cursor_x = (e.clientX - canvas_offset.x) / canvas_zoom; |
||||
const y = cursor_y = (e.clientY - canvas_offset.y) / canvas_zoom; |
||||
|
||||
current_stroke.length = 0; |
||||
current_stroke.push({'x': x, 'y': y}); |
||||
drawing = true; |
||||
}); |
||||
|
||||
canvas.addEventListener('mousemove', (e) => { |
||||
if (moving) { |
||||
canvas_offset.x += e.movementX; |
||||
canvas_offset.y += e.movementY; |
||||
return; |
||||
} |
||||
|
||||
if (drawing) { |
||||
const x = cursor_x = (e.clientX - canvas_offset.x) / canvas_zoom; |
||||
const y = cursor_y = (e.clientY - canvas_offset.y) / canvas_zoom; |
||||
|
||||
current_stroke.push({'x': x, 'y': y}); |
||||
} |
||||
}); |
||||
|
||||
canvas.addEventListener('mouseup', (e) => { |
||||
if (spacedown) { |
||||
moving = false; |
||||
return; |
||||
} |
||||
|
||||
if (drawing) { |
||||
strokes.push({'points': process_stroke(current_stroke)}); |
||||
current_stroke.length = 0; |
||||
drawing = false; |
||||
return; |
||||
} |
||||
}); |
||||
|
||||
canvas.addEventListener('wheel', (e) => { |
||||
const x = Math.round((e.clientX - canvas_offset.x) / canvas_zoom); |
||||
const y = Math.round((e.clientY - canvas_offset.y) / canvas_zoom); |
||||
|
||||
const dz = (e.deltaY < 0 ? 0.1 : -0.1); |
||||
const old_zoom = canvas_zoom; |
||||
|
||||
canvas_zoom *= (1.0 + dz); |
||||
|
||||
if (canvas_zoom > 10.0) { |
||||
canvas_zoom = old_zoom; |
||||
return; |
||||
} |
||||
|
||||
if (canvas_zoom < 0.2) { |
||||
canvas_zoom = old_zoom; |
||||
return; |
||||
} |
||||
|
||||
const zoom_offset_x = Math.round((dz * old_zoom) * x); |
||||
const zoom_offset_y = Math.round((dz * old_zoom) * y); |
||||
|
||||
canvas_offset.x -= zoom_offset_x; |
||||
canvas_offset.y -= zoom_offset_y; |
||||
}); |
||||
|
||||
window.requestAnimationFrame(() => draw(gl, program, locations, buffers, strokes)); |
||||
} |
Loading…
Reference in new issue