dev21w04a

This commit is contained in:
Victor Westerlund 2021-01-26 02:01:59 +01:00
parent 9030fe1f17
commit 3501d1b0fa
10 changed files with 149 additions and 101 deletions

View file

@ -1,43 +1,32 @@
html,
body {
--size: 50px;
margin: 0;
overflow: hidden;
background: gray;
}
html {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background: #eee;
}
body {
--resolution: 200px;
width: calc(var(--size) * 4);
height: calc(var(--size) * 5);
#screen {
--blank-level: rgb(0,0,0);
--line-level: rgb(var(--contrast),var(--contrast),var(--contrast));
width: calc(1px * var(--resolution-width));
height: calc(1px * var(--resolution-height));
transform: scale(var(--scale));
background: black;
display: grid;
}
#electron {
position: absolute;
width: var(--size);
height: var(--size);
background: red;
/*animation: interpolate 1ms linear infinite;*/
.pixel {
width: 1px;
height: 1px;
background: var(--blank-level);
}
@keyframes interpolate {
@keyframes decay {
to {
box-shadow:
0 -20px 0 red, 0 20px 0 red, /* y 1 */
0 -40px 0 red, 0 40px 0 red, /* y 2 */
0 -80px 0 red, 0 80px 0 red, /* y 3 */
0 -100px 0 red, 0 100px 0 red, /* y 4 */
-20px 0 0 red, 20px 0 0 red, /* x 1 */
-40px 0 0 red, 40px 0 0 red, /* x 2 */
-80px 0 0 red, 80px 0 0 red, /* x 3*/
-100px 0 0 red, 100px 0 0 red; /* x 4 */
background: var(--blank-level);
}
}

View file

@ -5,9 +5,21 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/style.css">
<title>Cathode Ray</title>
<style>
:root {
/* Screen */
--resolution-width: 64;
--resolution-height: 64;
--scale: 8;
/* Phosphor */
--contrast: 128;
--decay: 2s;
}
</style>
</head>
<body>
<div id="electron"></div>
<script src="js/main.js"></script>
<div id="screen"></div>
<script src="js/main.js" type="module"></script>
</body>
</html>

View file

@ -1,34 +1,7 @@
const worker = new Worker("js/worker.js");
import { FluorescentScreen } from "./modules/Screen.mjs";
import { RasterScan } from "./modules/Gun.mjs";
// Electron
const ray = {
electron: document.getElementById("electron"),
get size() {
const size = getComputedStyle(this.electron).getPropertyValue("--size");
return parseInt(size);
},
set size(value) {
this.electron.style.setProperty("--size",value + "px");
this.value = value;
}
}
const screen = new FluorescentScreen(document.getElementById("screen"));
ray.size = 50;
// Screen resolution
const resolution = {
width: 4,
height: 5
};
// Aim electron gun at pixel
function aim(x,y) {
const translate = `${x * ray.size}px,${y * ray.size}px`;
ray.electron.style.setProperty("transform",`translate(${translate})`);
}
// Clock
worker.postMessage(resolution);
worker.addEventListener("message",event => {
aim(event.data.x,event.data.y);
});
const CRT = new RasterScan(screen.pixels);
CRT.load("../../tapes/sample");

24
js/modules/Gun.mjs Normal file
View file

@ -0,0 +1,24 @@
export class RasterScan {
constructor(pixels) {
this.pixels = pixels;
this.coils = new Worker("./js/modules/Raster.mjs");
this.coils.addEventListener("message",event => {
this.fire(event.data);
});
}
fire(data) {
this.pixels[data.pixel].style.setProperty("background",data.color);
this.pixels[data.pixel].style.setProperty("animation",`decay 10ms ${data.pixel} linear forwards`);
}
load(tape) {
this.coils.postMessage({
density: this.pixels.length,
tape: tape
});
}
}

29
js/modules/Raster.mjs Normal file
View file

@ -0,0 +1,29 @@
let field;
let clock;
let tape = {
header: (data) => {
clock = data.framerate;
this.format = format;
self.postMessage({
type: "resolutionUpdate",
data: resolution
});
},
stream: []
}
async function fetchJSON(url) {
const response = await fetch(url);
return response.json();
}
function loadTape(ref) {
fetchJSON(ref + "/header.json").then(data => tape.header(data));
fetchJSON(ref + "/data.json").then(data => tape.stream = data);
}
self.addEventListener("message",event => {
loadTape(event.data.tape);
});

29
js/modules/Screen.mjs Normal file
View file

@ -0,0 +1,29 @@
export class FluorescentScreen {
constructor(screen) {
this.screen = screen;
this.pixels = [];
this.spawnPixels();
}
createMatrix() {
this.screen.style.setProperty("grid-template-columns",`repeat(${this.screen.clientWidth},1px)`);
this.screen.style.setProperty("grid-template-rows",`repeat(${this.screen.clientHeight},1px)`);
}
spawnPixels() {
const density = this.screen.clientWidth * this.screen.clientHeight;
for(let i = 0; i < density; i++) {
const pixel = document.createElement("div");
pixel.classList.add("pixel");
this.screen.appendChild(pixel);
this.pixels.push(pixel);
}
this.createMatrix();
}
}

View file

@ -1,41 +0,0 @@
let resolution = {
width: 0,
height: 0
};
// Gun/deflector angle
const pos = {
x: 0,
y: 0,
get advance() {
this.x++;
// Hortizontal blank
if(this.x == resolution.width) {
this.x = 0;
this.y++;
}
// Vertical blank
if(this.y == resolution.height) {
this.y = 0;
}
return {
x: this.x,
y: this.y
}
}
};
const refresh = 1; // Refresh rate
let clock;
function scanline() {
self.postMessage(pos.advance);
}
self.addEventListener("message",event => {
resolution = event.data;
clock = setInterval(scanline,refresh);
});

1
tapes/sample/data.json Normal file

File diff suppressed because one or more lines are too long

27
tapes/sample/generator.py Normal file
View file

@ -0,0 +1,27 @@
import random
def rand():
dec = random.randint(0,255)
tohex = str(hex(dec).split("x")[-1])
if(dec < 16):
tohex = "0" + tohex
return tohex
def randomColor():
pixel = rand() + rand() + rand()
return f"\"#{pixel}\""
# ----
file = open("data.json","a+")
file.write("[")
# 3 Seconds of 64x64px@10fps uncompressed video (~7MB)
for x in range(737280):
file.write(randomColor() + ",")
file.write(randomColor())
file.write("]")
file.close()

5
tapes/sample/header.json Normal file
View file

@ -0,0 +1,5 @@
{
"format": "signed-hex",
"resolution": ["64","64"],
"framerate": 10
}