From: Ruben Beltran del Rio Date: Tue, 7 May 2024 20:37:27 +0000 (+0200) Subject: Make 3D X-Git-Url: https://git.r.bdr.sh/rbdr/lissajous/commitdiff_plain/362f91160b243453578633e3f9af67ce40179d8c?ds=inline Make 3D --- diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..d66512c --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,21 @@ +import hapi from '@hapi/eslint-plugin'; + +export default [{ + languageOptions: { + globals: { + document: true, + window: true, + console: true + } + }, + plugins: { + hapi + }, + rules: { + indent: [ + 2, + 2 + ], + 'no-undef': 2 + } +}]; diff --git a/images/far.svg b/images/far.svg new file mode 100644 index 0000000..c7ddda9 --- /dev/null +++ b/images/far.svg @@ -0,0 +1,8 @@ + + + Far + + + + + \ No newline at end of file diff --git a/images/fast-pitch.svg b/images/fast-pitch.svg new file mode 100644 index 0000000..2b33b10 --- /dev/null +++ b/images/fast-pitch.svg @@ -0,0 +1,9 @@ + + + Fast Pitch + + + + + + \ No newline at end of file diff --git a/images/fast-roll.svg b/images/fast-roll.svg new file mode 100644 index 0000000..5b31f82 --- /dev/null +++ b/images/fast-roll.svg @@ -0,0 +1,10 @@ + + + Fast Roll + + + + + + + \ No newline at end of file diff --git a/images/fast-yaw.svg b/images/fast-yaw.svg new file mode 100644 index 0000000..08fb702 --- /dev/null +++ b/images/fast-yaw.svg @@ -0,0 +1,9 @@ + + + Fast Yaw + + + + + + \ No newline at end of file diff --git a/images/ha.svg b/images/ha.svg new file mode 100644 index 0000000..8ce7cdc --- /dev/null +++ b/images/ha.svg @@ -0,0 +1,8 @@ + + + High Amplitude + + + + + \ No newline at end of file diff --git a/images/hf.svg b/images/hf.svg new file mode 100644 index 0000000..9d90165 --- /dev/null +++ b/images/hf.svg @@ -0,0 +1,8 @@ + + + High Frequency + + + + + \ No newline at end of file diff --git a/images/la.svg b/images/la.svg new file mode 100644 index 0000000..23f8167 --- /dev/null +++ b/images/la.svg @@ -0,0 +1,8 @@ + + + Low Amplitude + + + + + \ No newline at end of file diff --git a/images/lf.svg b/images/lf.svg new file mode 100644 index 0000000..99a49e3 --- /dev/null +++ b/images/lf.svg @@ -0,0 +1,8 @@ + + + Low Frequency + + + + + \ No newline at end of file diff --git a/images/long.svg b/images/long.svg new file mode 100644 index 0000000..4f816b1 --- /dev/null +++ b/images/long.svg @@ -0,0 +1,9 @@ + + + Long + + + + + + \ No newline at end of file diff --git a/images/near.svg b/images/near.svg new file mode 100644 index 0000000..7bd56b5 --- /dev/null +++ b/images/near.svg @@ -0,0 +1,8 @@ + + + Near + + + + + \ No newline at end of file diff --git a/images/no-pitch.svg b/images/no-pitch.svg new file mode 100644 index 0000000..cb62dd4 --- /dev/null +++ b/images/no-pitch.svg @@ -0,0 +1,8 @@ + + + No Pitch + + + + + \ No newline at end of file diff --git a/images/no-roll.svg b/images/no-roll.svg new file mode 100644 index 0000000..ded155e --- /dev/null +++ b/images/no-roll.svg @@ -0,0 +1,8 @@ + + + No Roll + + + + + \ No newline at end of file diff --git a/images/no-yaw.svg b/images/no-yaw.svg new file mode 100644 index 0000000..0acf9f1 --- /dev/null +++ b/images/no-yaw.svg @@ -0,0 +1,8 @@ + + + No Yaw + + + + + \ No newline at end of file diff --git a/images/short.svg b/images/short.svg new file mode 100644 index 0000000..a5dc1d8 --- /dev/null +++ b/images/short.svg @@ -0,0 +1,8 @@ + + + Short + + + + + \ No newline at end of file diff --git a/index.html b/index.html index 6c7faf5..db8e5b4 100644 --- a/index.html +++ b/index.html @@ -15,6 +15,9 @@
+
diff --git a/lib/components/configuration.js b/lib/components/configuration.js new file mode 100644 index 0000000..ddb4a27 --- /dev/null +++ b/lib/components/configuration.js @@ -0,0 +1,9 @@ +import { Component } from '@serpentity/serpentity'; + +export default class Configuration extends Component { + constructor(config) { + + super(config); + this.lineLength = this.lineLength || 1000; + } +}; diff --git a/lib/components/radius.js b/lib/components/radius.js new file mode 100644 index 0000000..2aa29a8 --- /dev/null +++ b/lib/components/radius.js @@ -0,0 +1,9 @@ +import { Component } from '@serpentity/serpentity'; + +export default class Radius extends Component { + constructor(config) { + + super(config); + this.radius = this.radius || 5; + } +}; diff --git a/lib/components/triple_amplitude.js b/lib/components/triple_amplitude.js new file mode 100644 index 0000000..376c321 --- /dev/null +++ b/lib/components/triple_amplitude.js @@ -0,0 +1,12 @@ +import { Component } from '@serpentity/serpentity'; + +export default class TripleAmplitude extends Component { + constructor(config) { + + super(config); + + this.a = this.a || Math.random(); + this.b = this.b || Math.random(); + this.c = this.c || Math.random(); + } +}; diff --git a/lib/components/triple_frequency.js b/lib/components/triple_frequency.js index aa768e3..1f118b9 100644 --- a/lib/components/triple_frequency.js +++ b/lib/components/triple_frequency.js @@ -5,8 +5,8 @@ export default class TripleFrequency extends Component { super(config); - this.a = Math.random(); - this.b = Math.random(); - this.c = Math.random(); + this.a = this.a || Math.random(); + this.b = this.b || Math.random(); + this.c = this.c || Math.random(); } }; diff --git a/lib/components/up.js b/lib/components/up.js new file mode 100644 index 0000000..f28cc20 --- /dev/null +++ b/lib/components/up.js @@ -0,0 +1,12 @@ +import { Component } from '@serpentity/serpentity'; + +export default class Up extends Component { + constructor(config) { + + super(config); + + this.x = this.x || 0; + this.y = this.y || 1; + this.z = this.z || 0; + } +}; diff --git a/lib/config.js b/lib/config.js index bb8e11e..e8fa3c9 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,3 +1,4 @@ export const canvasId = 'lissajous'; +export const settingsId = 'settings'; export const fps = 30; diff --git a/lib/factories/curves.js b/lib/factories/curves.js index d9f0f47..99bc588 100644 --- a/lib/factories/curves.js +++ b/lib/factories/curves.js @@ -1,6 +1,7 @@ import { Entity } from '@serpentity/serpentity'; import Position from '@serpentity/components.position'; import TripleFrequency from '../components/triple_frequency'; +import TripleAmplitude from '../components/triple_amplitude'; import Color from '../components/color'; export function lissajousCurve() { @@ -8,6 +9,7 @@ export function lissajousCurve() { const entity = new Entity(); entity.addComponent(new Position()); entity.addComponent(new TripleFrequency()); + entity.addComponent(new TripleAmplitude()); entity.addComponent(new Color()); return entity; diff --git a/lib/factories/global.js b/lib/factories/global.js new file mode 100644 index 0000000..c601bdb --- /dev/null +++ b/lib/factories/global.js @@ -0,0 +1,33 @@ +import { Entity } from '@serpentity/serpentity'; +import Position from '@serpentity/components.position'; +import EulerAngle from '@serpentity/components.euler_angle'; +import Velocity from '@serpentity/components.velocity'; +import Radius from '../components/radius'; +import Up from '../components/up'; +import Configuration from '../components/configuration'; + +export function configuration() { + + const entity = new Entity(); + entity.addComponent(new Configuration()); + + return entity; +} + +export function camera() { + + const entity = new Entity(); + entity.addComponent(new Position()); + entity.addComponent(new EulerAngle()); + entity.addComponent(new Velocity({ + x: 0, + y: Math.PI / 180, + z: 0, + })); + entity.addComponent(new Radius({ + radius: 5 + })); + entity.addComponent(new Up()); + + return entity; +} diff --git a/lib/factories/ui.js b/lib/factories/ui.js new file mode 100644 index 0000000..27ff6eb --- /dev/null +++ b/lib/factories/ui.js @@ -0,0 +1,28 @@ +export function settingsContainer({id, label, level=2}) { + + const container = document.createElement('div'); + container.id = id; + container.innerHTML = `${label}`; + return container; +}; + +export function slider({min, max, step, get, set, label = '', className}) { + + const sliderContainer = document.createElement('div'); + sliderContainer.classList.add('slider'); + sliderContainer.classList.add(className); + const labelElement = document.createElement('label'); + labelElement.innerHTML = label; + const slider = document.createElement('input'); + slider.type = 'range'; + slider.min = min; + slider.max = max; + slider.step = step; + slider.value = get().toString(); + + slider.addEventListener('input', () => set(slider.value)); + + sliderContainer.appendChild(labelElement); + sliderContainer.appendChild(slider); + return sliderContainer; +} diff --git a/lib/main.js b/lib/main.js index 6a2ea8f..6018957 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,8 +1,18 @@ import Serpentity from '@serpentity/serpentity'; +import { canvasId, settingsId, fps } from './config'; + +// Systems +import CameraRotator from './systems/camera_rotator'; import WebGLRenderer from './systems/webgl_renderer'; import LissajousPositionUpdater from './systems/lissajous_position_updater'; +import CameraAdjuster from './systems/camera_adjuster'; +import FrequencyAdjuster from './systems/frequency_adjuster'; +import AmplitudeAdjuster from './systems/amplitude_adjuster'; +import GlobalAdjuster from './systems/global_adjuster'; + +// Factories import { lissajousCurve } from './factories/curves'; -import { canvasId, fps } from './config'; +import { camera, configuration } from './factories/global'; const internals = { @@ -16,12 +26,26 @@ const internals = { // Create entities const engine = new Serpentity(); - // Add Systems - engine.addSystem(new LissajousPositionUpdater(canvasId)); - engine.addSystem(new WebGLRenderer(canvasId)); - // Add Entities + engine.addEntity(camera()); engine.addEntity(lissajousCurve()); + engine.addEntity(configuration()); + + // Select elements + const canvas = document.getElementById(canvasId); + const settings = document.getElementById(settingsId); + + // Add Systems + engine.addSystem(new CameraRotator()); + engine.addSystem(new LissajousPositionUpdater()); + engine.addSystem(new WebGLRenderer(canvas)); + + // IMPROVEMENT NOTE: I believe adjusters can be generalized further. + // We could potentially have a factory for this. + engine.addSystem(new GlobalAdjuster(settings)); + engine.addSystem(new CameraAdjuster(settings)); + engine.addSystem(new FrequencyAdjuster(settings)); + engine.addSystem(new AmplitudeAdjuster(settings)); internals.engine = engine; }, diff --git a/lib/nodes/ample.js b/lib/nodes/ample.js new file mode 100644 index 0000000..e48d250 --- /dev/null +++ b/lib/nodes/ample.js @@ -0,0 +1,7 @@ +import TripleAmplitude from '../components/triple_amplitude'; +import { Node } from '@serpentity/serpentity'; + +export default class Ample extends Node {}; +Ample.types = { + amplitude: TripleAmplitude +} diff --git a/lib/nodes/cameras.js b/lib/nodes/cameras.js new file mode 100644 index 0000000..56072d3 --- /dev/null +++ b/lib/nodes/cameras.js @@ -0,0 +1,15 @@ +import Position from '@serpentity/components.position'; +import EulerAngle from '@serpentity/components.euler_angle'; +import Velocity from '@serpentity/components.velocity'; +import Radius from '../components/radius'; +import Up from '../components/up'; +import { Node } from '@serpentity/serpentity'; + +export default class Cameras extends Node {}; +Cameras.types = { + position: Position, + angle: EulerAngle, + velocity: Velocity, + radius: Radius, + up: Up +} diff --git a/lib/nodes/configurable.js b/lib/nodes/configurable.js new file mode 100644 index 0000000..650f131 --- /dev/null +++ b/lib/nodes/configurable.js @@ -0,0 +1,7 @@ +import Configuration from '../components/configuration'; +import { Node } from '@serpentity/serpentity'; + +export default class Configurable extends Node {}; +Configurable.types = { + configuration: Configuration, +} diff --git a/lib/nodes/frequent.js b/lib/nodes/frequent.js new file mode 100644 index 0000000..de8de92 --- /dev/null +++ b/lib/nodes/frequent.js @@ -0,0 +1,7 @@ +import TripleFrequency from '../components/triple_frequency'; +import { Node } from '@serpentity/serpentity'; + +export default class Frequent extends Node {}; +Frequent.types = { + frequency: TripleFrequency +} diff --git a/lib/nodes/lissajous_curve.js b/lib/nodes/lissajous_curve.js index 402aea6..55179e8 100644 --- a/lib/nodes/lissajous_curve.js +++ b/lib/nodes/lissajous_curve.js @@ -1,9 +1,11 @@ import Position from '@serpentity/components.position'; import TripleFrequency from '../components/triple_frequency'; +import TripleAmplitude from '../components/triple_amplitude'; import { Node } from '@serpentity/serpentity'; export default class LissajousCurve extends Node {}; LissajousCurve.types = { position: Position, - frequency: TripleFrequency + frequency: TripleFrequency, + amplitude: TripleAmplitude } diff --git a/lib/systems/amplitude_adjuster.js b/lib/systems/amplitude_adjuster.js new file mode 100644 index 0000000..71f7277 --- /dev/null +++ b/lib/systems/amplitude_adjuster.js @@ -0,0 +1,64 @@ +import { System } from '@serpentity/serpentity'; +import Ample from '../nodes/ample'; +import { settingsContainer, slider } from '../factories/ui'; + +const internals = { + symbols: { + a: '𝛢', + b: '𝛣', + c: '𝛧' + } +}; + +export default class AmplitudeAdjuster extends System { + + constructor(container) { + + super(); + this.container = container; + } + + added(engine){ + + this.nodes = engine.getNodes(Ample); + this.adjusterContainer = settingsContainer({ + id: 'amplitude-adjuster', + label: 'Amplitude' + }); + + let i = 0; + for (const node of this.nodes) { + const nodeElement = settingsContainer({ + id: `amplitude-adjuster-${i}`, + label: `ɣ${i + 1}`, + level: 3 + }); + + ['a', 'b', 'c'].forEach(key => { + nodeElement.appendChild(slider({ + min: '0', + max: '1', + step: '0.01', + label: internals.symbols[key], + className: `amplitude`, + get: () => node.amplitude[key].toString(), + set: (value) => (node.amplitude[key] = parseFloat(value)) + })); + }); + + this.adjusterContainer.appendChild(nodeElement); + ++i; + } + + this.container.appendChild(this.adjusterContainer); + } + + removed(){ + + this.container.removeChild(this.adjusterContainer); + delete this.adjusterContainer; + delete this.nodes; + } + + update(){} +}; diff --git a/lib/systems/camera_adjuster.js b/lib/systems/camera_adjuster.js new file mode 100644 index 0000000..3b6ea41 --- /dev/null +++ b/lib/systems/camera_adjuster.js @@ -0,0 +1,74 @@ +import { System } from '@serpentity/serpentity'; +import Cameras from '../nodes/cameras'; +import { settingsContainer, slider } from '../factories/ui'; + +const internals = { + symbols: { + x: '𝜃', + y: '𝜓', + z: '𝜙' + } +}; + +export default class CameraAdjuster extends System { + + constructor(container) { + + super(); + this.container = container; + } + + added(engine){ + + this.nodes = engine.getNodes(Cameras); + this.adjusterContainer = settingsContainer({ + id: 'camera-adjuster', + label: 'Camera' + }) + + let i = 0; + for (const node of this.nodes) { + const nodeElement = settingsContainer({ + id: `camera-adjuster-${i}`, + label: `C${i + 1}`, + level: 3 + }); + + nodeElement.appendChild(slider({ + min: '2', + max: '10', + step: '0.1', + label: '𝑟', + className: 'radius', + get: () => node.radius.radius.toString(), + set: (value) => (node.radius.radius = parseFloat(value)) + })); + + ['x', 'y', 'z'].forEach(key => { + nodeElement.appendChild(slider({ + min: '0', + max: '0.5', + step: '0.001', + label: internals.symbols[key], + className: `rotation-${key}`, + get: () => node.velocity[key].toString(), + set: (value) => (node.velocity[key] = parseFloat(value)) + })); + }); + + this.adjusterContainer.appendChild(nodeElement); + ++i; + } + + this.container.appendChild(this.adjusterContainer); + } + + removed(){ + + this.container.removeChild(this.adjusterContainer); + delete this.adjusterContainer; + delete this.nodes; + } + + update(){} +}; diff --git a/lib/systems/camera_rotator.js b/lib/systems/camera_rotator.js new file mode 100644 index 0000000..3520f71 --- /dev/null +++ b/lib/systems/camera_rotator.js @@ -0,0 +1,49 @@ +import { mat4, vec3 } from 'gl-matrix'; +import { System } from '@serpentity/serpentity'; +import Cameras from '../nodes/cameras'; + +export default class CameraRotator extends System { + + constructor() { + + super(); + } + + added(engine){ + + this.cameras = engine.getNodes(Cameras); + } + + removed(){ + + delete this.cameras; + } + + update(dt){ + + for (const camera of this.cameras) { + + let rotationMatrix = mat4.create(); + mat4.rotateY(rotationMatrix, rotationMatrix, camera.angle.yaw); + mat4.rotateX(rotationMatrix, rotationMatrix, camera.angle.pitch); + mat4.rotateZ(rotationMatrix, rotationMatrix, camera.angle.roll); + + let eye = vec3.fromValues(0, 0, camera.radius.radius); + vec3.transformMat4(eye, eye, rotationMatrix); + + camera.position.x = eye[0]; + camera.position.y = eye[1]; + camera.position.z = eye[2]; + + let up = vec3.fromValues(0, 1, 0); + vec3.transformMat4(up, up, rotationMatrix); + camera.up.x = up[0]; + camera.up.y = up[1]; + camera.up.z = up[2]; + + camera.angle.pitch = (camera.angle.pitch + camera.velocity.x * dt / 100 + 2 * Math.PI) % (2 * Math.PI); + camera.angle.yaw = (camera.angle.yaw + camera.velocity.y * dt / 100 + 2 * Math.PI) % (2 * Math.PI); + camera.angle.roll = (camera.angle.roll + camera.velocity.z * dt / 100 + 2 * Math.PI) % (2 * Math.PI); + } + } +}; diff --git a/lib/systems/frequency_adjuster.js b/lib/systems/frequency_adjuster.js new file mode 100644 index 0000000..6aa11d4 --- /dev/null +++ b/lib/systems/frequency_adjuster.js @@ -0,0 +1,64 @@ +import { System } from '@serpentity/serpentity'; +import Frequent from '../nodes/frequent'; +import { settingsContainer, slider } from '../factories/ui'; + +const internals = { + symbols: { + a: '𝛼', + b: '𝛽', + c: '𝜁' + } +}; + +export default class FrequencyAdjuster extends System { + + constructor(container) { + + super(); + this.container = container; + } + + added(engine){ + + this.nodes = engine.getNodes(Frequent); + this.adjusterContainer = settingsContainer({ + id: 'frequency-adjuster', + label: 'Frequency' + }) + + let i = 0; + for (const node of this.nodes) { + const nodeElement = settingsContainer({ + id: `frequency-adjuster-${i}`, + label: `ɣ${i + 1}`, + level: 3 + }); + + ['a', 'b', 'c'].forEach(key => { + nodeElement.appendChild(slider({ + min: '0', + max: '1', + step: '0.01', + label: internals.symbols[key], + className: `frequency`, + get: () => node.frequency[key].toString(), + set: (value) => (node.frequency[key] = parseFloat(value)) + })); + }); + + this.adjusterContainer.appendChild(nodeElement); + ++i; + } + + this.container.appendChild(this.adjusterContainer); + } + + removed(){ + + this.container.removeChild(this.adjusterContainer); + delete this.adjusterContainer; + delete this.nodes; + } + + update(){} +}; diff --git a/lib/systems/global_adjuster.js b/lib/systems/global_adjuster.js new file mode 100644 index 0000000..7f6df91 --- /dev/null +++ b/lib/systems/global_adjuster.js @@ -0,0 +1,57 @@ +import { System } from '@serpentity/serpentity'; +import Frequent from '../nodes/configurable'; +import { settingsContainer, slider } from '../factories/ui'; + +export default class GlobalAdjuster extends System { + + constructor(container) { + + super(); + this.container = container; + } + + added(engine){ + + this.nodes = engine.getNodes(Frequent); + const container = document.getElementById('settings'); + this.adjusterContainer = settingsContainer({ + id: 'global-adjuster', + label: 'Global' + }); + + let i = 0; + for (const node of this.nodes) { + const nodeElement = settingsContainer({ + id: `global-adjuster-${i}`, + label: 'Line', + level: 3 + }); + + nodeElement.appendChild(slider({ + min: '5', + max: '2000', + step: '1', + label: '𝜆', + className: 'lineLength', + get: () => node.configuration.lineLength.toString(), + set: (value) => (node.configuration.lineLength = parseFloat(value)) + })); + + this.adjusterContainer.appendChild(nodeElement); + ++i; + } + + this.container.appendChild(this.adjusterContainer); + } + + removed(){ + + this.container.removeChild(this.adjusterContainer); + delete this.adjusterContainer; + delete this.nodes; + } + + update(){} +}; + + diff --git a/lib/systems/lissajous_position_updater.js b/lib/systems/lissajous_position_updater.js index 0f5792d..202ddc7 100644 --- a/lib/systems/lissajous_position_updater.js +++ b/lib/systems/lissajous_position_updater.js @@ -2,7 +2,7 @@ import { System } from '@serpentity/serpentity'; import LissajousCurve from '../nodes/lissajous_curve'; const internals = { - kAmplitude: 0.8, + kAmplitude: 1, kPeriod: Math.PI * 12000000 }; @@ -21,8 +21,8 @@ export default class WebGLRenderer extends System { removed(){ - this.curves = undefined; - this.time = undefined; + delete this.curves; + delete this.time; } update(dt){ @@ -30,11 +30,10 @@ export default class WebGLRenderer extends System { this.time = (this.time + dt / 100) % internals.kPeriod; for (const curve of this.curves) { - curve.position.x = this._getPosition(internals.kAmplitude, curve.frequency.a, this.time, 0); - curve.position.y = this._getPosition(internals.kAmplitude, curve.frequency.b, this.time, 0); - curve.position.z = this._getPosition(internals.kAmplitude, curve.frequency.c, this.time, 0); + curve.position.x = this._getPosition(curve.amplitude.a, curve.frequency.a, this.time, 0); + curve.position.y = this._getPosition(curve.amplitude.b, curve.frequency.b, this.time, 0); + curve.position.z = this._getPosition(curve.amplitude.c, curve.frequency.c, this.time, 0); } - console.log('UP'); } _getPosition(amplitude, frequency, time, phaseShift) { diff --git a/lib/systems/webgl_renderer.js b/lib/systems/webgl_renderer.js index 8535fde..3a92552 100644 --- a/lib/systems/webgl_renderer.js +++ b/lib/systems/webgl_renderer.js @@ -1,16 +1,34 @@ +import { mat4, vec3 } from 'gl-matrix'; import { System } from '@serpentity/serpentity'; import Drawable from '../nodes/drawable'; +import Configurable from '../nodes/configurable'; +import Cameras from '../nodes/cameras'; import { initializeShaderProgram, initializeBuffers } from '../webgl_utils'; const internals = { - kWidthRatio: 2.76, + kDefaultLineLength: 1000, + kCameraRadius: 5, + kCameraAngularVelocity: Math.PI / 180, + kFieldOfView: 45, // degrees + kNearLimit: 0.1, + kFarLimit: 100, + kWidthRatio: 1.5, kHeightRatio: 1, - kTargetVerticalResolution: 32 + Math.round(Math.random() * 1024), + kTargetVerticalResolution: 1024, kVertexShader: ` - attribute vec3 aVertexPosition; + attribute vec4 aVertexPosition; + attribute vec4 aColor; + + varying vec4 vColor; + + uniform mat4 uViewMatrix; + uniform mat4 uProjectionMatrix; + void main() { - gl_Position = vec4(aVertexPosition.xy, 0.0, 1.0); + + gl_Position = uProjectionMatrix * uViewMatrix * aVertexPosition; + vColor = aColor; gl_PointSize = 10.0; // Set the point size } `, @@ -18,25 +36,27 @@ const internals = { kFragmentShader: ` precision mediump float; - uniform vec4 uColor; + varying vec4 vColor; + void main() { - gl_FragColor = uColor; + + gl_FragColor = vColor; } ` }; export default class WebGLRenderer extends System { - constructor(canvasId) { + constructor(canvas) { super(); - this.canvasId = canvasId; + this.canvas = canvas; } added(engine){ // Set up canvas - const canvas = document.getElementById(this.canvasId); + const { canvas } = this; window.addEventListener('resize', () => this._resizeCanvas(canvas)); // Set up WebGL @@ -45,6 +65,12 @@ export default class WebGLRenderer extends System { }); this.gl = gl; + gl.clearColor(0.05882, 0.14902, 0.12157, 1); + gl.enable(gl.DEPTH_TEST); + gl.depthFunc(gl.LEQUAL); + + this.colorBuffer = gl.createBuffer(); + const shaderProgram = initializeShaderProgram( gl, internals.kVertexShader, @@ -54,10 +80,12 @@ export default class WebGLRenderer extends System { this.programInfo = { program: shaderProgram, attribLocations: { - vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition') + vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'), + vertexColor: gl.getAttribLocation(shaderProgram, 'aColor') }, uniformLocations: { - color: gl.getUniformLocation(shaderProgram, 'uColor') + projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'), + viewMatrix: gl.getUniformLocation(shaderProgram, 'uViewMatrix') } }; @@ -65,11 +93,23 @@ export default class WebGLRenderer extends System { this._resizeCanvas(canvas); this.points = engine.getNodes(Drawable); + this.positions = []; + this.colors = []; + + this.configurations = engine.getNodes(Configurable); + this.cameras = engine.getNodes(Cameras); } removed(engine){ - this.points = undefined; + delete this.gl; + delete this.points; + delete this.colorBuffer; + delete this.buffers; + delete this.positions; + delete this.colors; + delete this.configurations; + delete this.cameras; } update(){ @@ -78,31 +118,100 @@ export default class WebGLRenderer extends System { gl.useProgram(programInfo.program); - { - const color = [1, 1, 1, 1]; - gl.uniform4fv(programInfo.uniformLocations.color, color); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + const fieldOfView = internals.kFieldOfView * Math.PI / 180; + const aspectRatio = internals.kWidthRatio / internals.kHeightRatio; + const projectionMatrix = mat4.create(); + + mat4.perspective( + projectionMatrix, + fieldOfView, + aspectRatio, + internals.kNearLimit, + internals.kFarLimit + ); + + gl.uniformMatrix4fv( + programInfo.uniformLocations.projectionMatrix, + false, + projectionMatrix + ); + + // We only support one camera for now. + const camera = this.cameras.nodes[0]; + if (camera != undefined) { + const eye = vec3.fromValues(camera.position.x, camera.position.y, camera.position.z); + const center = vec3.fromValues(0, 0, 0); + const up = vec3.fromValues(camera.up.x, camera.up.y, camera.up.z); + const viewMatrix = mat4.create(); + mat4.lookAt(viewMatrix, eye, center, up); + gl.uniformMatrix4fv( + programInfo.uniformLocations.viewMatrix, + false, + viewMatrix + ); } - const positions = []; + let i = 0; for (const point of this.points) { - positions.push(point.position.x, point.position.y, point.position.z); - positions.push( + this.positions[i] = this.positions[i] || []; + this.positions[i].push(point.position.x, point.position.y, point.position.z, 1); + this.positions[i].push( point.position.prevX || point.position.x, point.position.prevY || point.position.y, - point.position.prevZ || point.position.z + point.position.prevZ || point.position.z, + 1 ); point.position.prevX = point.position.x; point.position.prevY = point.position.y; point.position.prevZ = point.position.z; + + this.colors[i] = this.colors[i] || []; + this.colors[i].push( + 0.5 + point.position.z / 4 + point.position.x / 4, + 0.5 + point.position.z / 4 + point.position.y / 4, + 0.75 + point.position.z / 4, 1, + 0.5 + point.position.prevZ / 4 + point.position.prevX / 4, + 0.5 + point.position.prevZ / 4 + point.position.prevY / 4, + 0.75 + point.position.prevZ / 4,1); + + ++i; + } + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + const colors = this.colors.flat(); + gl.bufferData(gl.ARRAY_BUFFER, + new Float32Array(colors), + gl.STATIC_DRAW); + + { + const numberOfComponents = 4; + const type = gl.FLOAT; + const normalize = false; + const stride = 0; + const offset = 0; + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer( + programInfo.attribLocations.vertexColor, + numberOfComponents, + type, + normalize, + stride, + offset + ); + gl.enableVertexAttribArray(programInfo.attribLocations.vertexColor); } gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position); + const positions = this.positions.flat(); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); { - const numberOfComponents = 3; + const numberOfComponents = 4; const type = gl.FLOAT; const normalize = false; const stride = 0; @@ -122,20 +231,20 @@ export default class WebGLRenderer extends System { { gl.lineWidth(2); - gl.drawArrays(gl.LINES, 0, this.points.nodes.length * 2); + gl.drawArrays(gl.LINES, 0, positions.length / 4); } + + this._cullLines(); } _resizeCanvas(canvas) { - console.log('Resizing Canvas'); let width = window.innerWidth; let height = Math.round(width * internals.kHeightRatio / internals.kWidthRatio); if (window.innerHeight < height) { height = window.innerHeight; width = Math.round(height * internals.kWidthRatio / internals.kHeightRatio); - console.log(width, height); } canvas.style.width = `${width}px`; @@ -146,4 +255,16 @@ export default class WebGLRenderer extends System { this.gl.viewport(0, 0, canvas.width, canvas.height); } + + _cullLines() { + const lineLength = this.configurations.nodes[0]?.configuration.lineLength || internals.kDefaultLineLength; + + for (const [i, position] of Object.entries(this.positions)) { + this.positions[i] = position.slice(-lineLength * 8); + } + + for (const [i, color] of Object.entries(this.colors)) { + this.colors[i] = color.slice(-lineLength * 8); + } + } }; diff --git a/lib/webgl_utils.js b/lib/webgl_utils.js index 1386bf7..42bd8b6 100644 --- a/lib/webgl_utils.js +++ b/lib/webgl_utils.js @@ -25,14 +25,14 @@ export function initializeShaderProgram(gl, vertexShaderSource, fragmentShaderSo */ export function initializeBuffers(gl) { - const positionBuffer = gl.createBuffer(); + const positionBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); - return { - position: positionBuffer - }; - } + return { + position: positionBuffer + }; +} function loadShader(gl, type, source) { diff --git a/package.json b/package.json index 11d57ae..13fdaa8 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,18 @@ "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "lint": "eslint ." }, "devDependencies": { + "@hapi/eslint-plugin": "^6.0.0", + "eslint": "^9.2.0", "vite": "^5.2.0" }, "dependencies": { + "@serpentity/components.euler_angle": "^4.0.0", "@serpentity/components.position": "^4.0.1", + "@serpentity/components.velocity": "^4.0.3", "@serpentity/serpentity": "^4.0.0", "gl-matrix": "3.4.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf92fbf..5cbcf02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,9 +5,15 @@ settings: excludeLinksFromLockfile: false dependencies: + '@serpentity/components.euler_angle': + specifier: ^4.0.0 + version: 4.0.0(@serpentity/serpentity@4.0.0) '@serpentity/components.position': specifier: ^4.0.1 version: 4.0.1(@serpentity/serpentity@4.0.0) + '@serpentity/components.velocity': + specifier: ^4.0.3 + version: 4.0.3(@serpentity/serpentity@4.0.0) '@serpentity/serpentity': specifier: ^4.0.0 version: 4.0.0 @@ -16,6 +22,12 @@ dependencies: version: 3.4.0 devDependencies: + '@hapi/eslint-plugin': + specifier: ^6.0.0 + version: 6.0.0 + eslint: + specifier: ^9.2.0 + version: 9.2.0 vite: specifier: ^5.2.0 version: 5.2.0 @@ -229,6 +241,101 @@ packages: dev: true optional: true + /@eslint-community/eslint-utils@4.4.0(eslint@9.2.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 9.2.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@eslint-community/regexpp@4.10.0: + resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint/eslintrc@3.0.2: + resolution: {integrity: sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 10.0.1 + globals: 14.0.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@eslint/js@9.2.0: + resolution: {integrity: sha512-ESiIudvhoYni+MdsI8oD7skpprZ89qKocwRM2KEvhhBJ9nl5MRh7BXU5GTod7Mdygq+AUl+QzId6iWJKR/wABA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true + + /@hapi/eslint-plugin@6.0.0: + resolution: {integrity: sha512-6eN1fnBO742nTTSpQtYB35aFGAT9I7r4u2v/+R2c9ToYTQp4QBoPOBA5R3+C7+Az3I0ugs+lTIqAsMPDLpsRAg==} + peerDependencies: + '@babel/core': ^7.14.3 + '@babel/eslint-parser': ^7.14.3 + peerDependenciesMeta: + '@babel/core': + optional: true + '@babel/eslint-parser': + optional: true + dev: true + + /@humanwhocodes/config-array@0.13.0: + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema@2.0.3: + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + dev: true + + /@humanwhocodes/retry@0.2.4: + resolution: {integrity: sha512-Ttl/jHpxfS3st5sxwICYfk4pOH0WrLI1SpW283GgQL7sCWU7EHIOhX4b4fkIxr3tkfzwg8+FNojtzsIEE7Ecgg==} + engines: {node: '>=18.18'} + dev: true + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + dev: true + /@rollup/rollup-android-arm-eabi@4.17.2: resolution: {integrity: sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==} cpu: [arm] @@ -357,6 +464,14 @@ packages: dev: true optional: true + /@serpentity/components.euler_angle@4.0.0(@serpentity/serpentity@4.0.0): + resolution: {integrity: sha512-DoWghww9eEBanuHAPlVtBGeGyIS2wQu/M90iChdMTHUWR9uITAWbFT1aFQmRyY++7lxUbAdrusizeQ6XlQIw8Q==} + peerDependencies: + '@serpentity/serpentity': ^4.0.0 + dependencies: + '@serpentity/serpentity': 4.0.0 + dev: false + /@serpentity/components.position@4.0.1(@serpentity/serpentity@4.0.0): resolution: {integrity: sha512-G2Q9huGgf7R61Lxx6ly+fYMfHBO5O+AvcDAJM0dENM3vmJl6lj8KZHNcIEOk+Vre+fTjWNX/aiNDphK9sDnAYA==} peerDependencies: @@ -365,6 +480,14 @@ packages: '@serpentity/serpentity': 4.0.0 dev: false + /@serpentity/components.velocity@4.0.3(@serpentity/serpentity@4.0.0): + resolution: {integrity: sha512-bPQf6Yz8gjraS2SNQquqsfopPnl1QtHjVz52om4CZ7gRqrFQzawsxs98I74/MtGjW3LAK4LSQyrPujy85j188g==} + peerDependencies: + '@serpentity/serpentity': ^4.0.0 + dependencies: + '@serpentity/serpentity': 4.0.0 + dev: false + /@serpentity/serpentity@4.0.0: resolution: {integrity: sha512-RlsG5lMFvho/15LW9SJ5uFcb8WjAH7uZaToTJPRJEzAUntMnyQxZpPl7ensAHjbuBasBkgg7IDipl5GBuf6S0Q==} engines: {node: '>= 20.0.0'} @@ -374,6 +497,109 @@ packages: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true + /acorn-jsx@5.3.2(acorn@8.11.3): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.11.3 + dev: true + + /acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + /esbuild@0.20.2: resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} engines: {node: '>=12'} @@ -405,6 +631,150 @@ packages: '@esbuild/win32-x64': 0.20.2 dev: true + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /eslint-scope@8.0.1: + resolution: {integrity: sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint-visitor-keys@4.0.0: + resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true + + /eslint@9.2.0: + resolution: {integrity: sha512-0n/I88vZpCOzO+PQpt0lbsqmn9AsnsJAQseIqhZFI8ibQT0U1AkEKRxA3EVMos0BoHSXDQvCXY25TUjB5tr8Og==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.2.0) + '@eslint-community/regexpp': 4.10.0 + '@eslint/eslintrc': 3.0.2 + '@eslint/js': 9.2.0 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.2.4 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + escape-string-regexp: 4.0.0 + eslint-scope: 8.0.1 + eslint-visitor-keys: 4.0.0 + espree: 10.0.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.1 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree@10.0.1: + resolution: {integrity: sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) + eslint-visitor-keys: 4.0.0 + dev: true + + /esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true + + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + dependencies: + reusify: 1.0.4 + dev: true + + /file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + dependencies: + flat-cache: 4.0.1 + dev: true + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + dev: true + + /flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + dev: true + /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -418,12 +788,169 @@ packages: deprecated: Broke various systems. Will investigate and likely republish as a new major version dev: false + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + dev: true + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + dev: true + + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: true + + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + dependencies: + json-buffer: 3.0.1 + dev: true + + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + /nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true dev: true + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + dev: true + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true @@ -437,6 +964,30 @@ packages: source-map-js: 1.2.0 dev: true + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + dev: true + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + /rollup@4.17.2: resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -463,11 +1014,65 @@ packages: fsevents: 2.3.3 dev: true + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + /source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} dev: true + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.1 + dev: true + /vite@5.2.0: resolution: {integrity: sha512-xMSLJNEjNk/3DJRgWlPADDwaU9AgYRodDH2t6oENhJnIlmU9Hx1Q6VpjyXua/JdMw1WJRbnAgHJ9xgET9gnIAg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -502,3 +1107,21 @@ packages: optionalDependencies: fsevents: 2.3.3 dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + dev: true + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true diff --git a/style.css b/style.css index c389fd8..d961e98 100644 --- a/style.css +++ b/style.css @@ -3,6 +3,152 @@ margin: 0; } -canvas, body { +body { + color: #80FFCC; background-color: #0F261F; + font-family: 'IBM 3270 Narrow', 'IBM 3270', 'Andale Mono', Courier, monospace; + font-size: 12px; + overflow: hidden; +} + +#settings { + position: absolute; + top: 5px; + right: 5px; +} + +h1, h2, h3 { + font-weight: normal; +} + +h1 { + font-size: 18px; +} + +h2 { + font-size: 16px; +} + +h3 { + font-size: 14px; +} + +input[type="range"] { + -webkit-appearance: none; + background-color: transparent; + height: 16px; + display: inline-block; + margin: 0 4px; + width: 128px; +} + +input[type="range"]:focus { + outline: none; +} + +input[type="range"]::-webkit-slider-runnable-track { + background: #fff; + height: 2px; +} + +input[type="range"]::-moz-range-track { + background: #fff; + height: 2px; +} + +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + height: 16px; + width: 16px; + background-color: #80FFCC; + margin-top: -8px; + border-radius: 50%; +} + +input[type="range"]::-moz-range-thumb { + height: 16px; + width: 16px; + background-color: #80FFCC; + margin-top: -8px; + border-radius: 50%; +} + + +.slider::before, +.slider::after { + content: ''; + background-color: #80FFCC; + height: 16px; + width: 16px; + display: inline-block; +} + +.slider { + margin: 4px 0; + position: relative; +} + +label { + display: inline-block; + position: relative; + width: 0; + height: 16px; + right: 32px; + top: -5px; +} + +.slider.lineLength::before { + mask-image: url('/images/short.svg'); +} + +.slider.lineLength::after { + mask-image: url('/images/long.svg'); +} + +.slider.radius::before { + mask-image: url('/images/near.svg'); +} + +.slider.radius::after { + mask-image: url('/images/far.svg'); +} + +.slider.rotation-x::before { + mask-image: url('/images/no-pitch.svg'); +} + +.slider.rotation-x::after { + mask-image: url('/images/fast-pitch.svg'); +} + +.slider.rotation-y::before { + mask-image: url('/images/no-yaw.svg'); +} + +.slider.rotation-y::after { + mask-image: url('/images/fast-yaw.svg'); +} + +.slider.rotation-z::before { + mask-image: url('/images/no-roll.svg'); +} + +.slider.rotation-z::after { + mask-image: url('/images/fast-roll.svg'); +} + +.slider.frequency::before { + mask-image: url('/images/lf.svg'); +} + +.slider.frequency::after { + mask-image: url('/images/hf.svg'); +} + +.slider.amplitude::before { + mask-image: url('/images/la.svg'); +} + +.slider.amplitude::after { + mask-image: url('/images/ha.svg'); }