First commits be like
This commit is contained in:
parent
f0e071212f
commit
93eea321a8
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.vscode/
|
104
sunpy/canvas/renderer.js
Normal file
104
sunpy/canvas/renderer.js
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import { register } from "../node/node.js";
|
||||||
|
import { PropertyNode } from "../node/nodes/propertynode.js";
|
||||||
|
import { PropertyReader, Property } from "../node/propertyreader.js";
|
||||||
|
import { sleep } from "../core/utils.js";
|
||||||
|
import { Vector } from "../core/vector.js";
|
||||||
|
|
||||||
|
register("canvas", initRenderNode);
|
||||||
|
|
||||||
|
function initRenderNode(node) {
|
||||||
|
const renderNode = new RenderNode(node);
|
||||||
|
renderNode.start();
|
||||||
|
window.a = renderNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const propertyReader = new PropertyReader(
|
||||||
|
new Property("size", "800x600", (v) => new Vector(Int32Array, ...v.split("x").map(x => parseInt(x)))),
|
||||||
|
new Property("fps", "60", (v) => parseInt(v)),
|
||||||
|
);
|
||||||
|
|
||||||
|
export class RenderNode extends PropertyNode {
|
||||||
|
constructor(node) {
|
||||||
|
super(node, document.createElement("canvas"), propertyReader);
|
||||||
|
|
||||||
|
this.gl = this.node.getContext("webgl");
|
||||||
|
|
||||||
|
if (!this.gl)
|
||||||
|
throw Error("Unable to initialize webgl");
|
||||||
|
|
||||||
|
this.gl.clearDepth(1.0);
|
||||||
|
this.gl.enable(this.gl.DEPTH_TEST);
|
||||||
|
this.gl.depthFunc(this.gl.LEQUAL);
|
||||||
|
|
||||||
|
this.fps = 0;
|
||||||
|
this.objects = [];
|
||||||
|
this.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get width() {
|
||||||
|
return this.node.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
get height() {
|
||||||
|
return this.node.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.running = true;
|
||||||
|
this.loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(object) {
|
||||||
|
if (!(object instanceof Renderable))
|
||||||
|
throw TypeError("Object must be instance of Renderable");
|
||||||
|
|
||||||
|
this.objects.push(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
async loop() { // TODO: Fix all variables
|
||||||
|
let ts = Date.now();
|
||||||
|
let tsf = ts;
|
||||||
|
let frames = 0;
|
||||||
|
while (this.running) {
|
||||||
|
await this.render();
|
||||||
|
|
||||||
|
let tsn = Date.now();
|
||||||
|
let ms = tsn - ts; // delta
|
||||||
|
let s = 1000 / this.properties.fps - ms;
|
||||||
|
ts = tsn + s;
|
||||||
|
|
||||||
|
frames++;
|
||||||
|
if ((frames %= this.properties.fps) == 0) {
|
||||||
|
this.fps = (1000 / (tsn - tsf)) * this.properties.fps;
|
||||||
|
tsf = tsn;
|
||||||
|
//console.log(this.node, this.fps);
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async render() {
|
||||||
|
|
||||||
|
this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
||||||
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
for (const object of this.objects) {
|
||||||
|
await object.render(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Renderable {
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async render(renderNode) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
3
sunpy/core/utils.js
Normal file
3
sunpy/core/utils.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export function sleep(ms) {
|
||||||
|
return new Promise(_ => setTimeout(_, ms));
|
||||||
|
}
|
116
sunpy/core/vector.js
Normal file
116
sunpy/core/vector.js
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
const DIM_NAMES = ["xr", "yg", "zb", "wa"];
|
||||||
|
const ARRAY_TYPES = [
|
||||||
|
Int8Array, Int16Array, Int32Array,
|
||||||
|
Uint8Array, Uint16Array, Uint32Array,
|
||||||
|
Float32Array, Float64Array,
|
||||||
|
BigInt64Array, BigUint64Array // Note: These requires values to be of type BigInt
|
||||||
|
];
|
||||||
|
|
||||||
|
export class Vector {
|
||||||
|
constructor(...values) {
|
||||||
|
this.type = Float32Array;
|
||||||
|
|
||||||
|
for (const value of values) {
|
||||||
|
if (ARRAY_TYPES.includes(value)) {
|
||||||
|
this.type = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.values = new this.type(values.filter(v => !ARRAY_TYPES.includes(v)));
|
||||||
|
|
||||||
|
const defineMacro = (index, key) => {
|
||||||
|
Object.defineProperty(this, key, {
|
||||||
|
get() { return this.values[index] },
|
||||||
|
set(value) { this.values[index] = value }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const i in this.values) {
|
||||||
|
defineMacro(i, i);
|
||||||
|
|
||||||
|
if (i >= DIM_NAMES.length)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (const names of DIM_NAMES[i]) {
|
||||||
|
for (const char of names) {
|
||||||
|
defineMacro(i, char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return new Vector(this.type, ...this.values);
|
||||||
|
}
|
||||||
|
|
||||||
|
static _requireSameVector(vecA, vecB) {
|
||||||
|
if (!(vecA instanceof Vector))
|
||||||
|
throw new TypeError("vecA has to be instance of `Vector`");
|
||||||
|
|
||||||
|
if (!(vecB instanceof Vector))
|
||||||
|
throw new TypeError("vecB has to be instance of `Vector`");
|
||||||
|
|
||||||
|
if (vecA.values.length !== vecB.values.length)
|
||||||
|
throw new TypeError("Vectors are of different lengths");
|
||||||
|
|
||||||
|
if (vecA.type !== vecB.type)
|
||||||
|
console.warn("Vectors are not of same array type.. be careful!");
|
||||||
|
}
|
||||||
|
|
||||||
|
_modifyComponents(vec, func) {
|
||||||
|
if (vec instanceof Vector) {
|
||||||
|
Vector._requireSameVector(this, vec);
|
||||||
|
|
||||||
|
for (const i in vec.values) {
|
||||||
|
this[i] = func(this[i], vec[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const i in this.values) {
|
||||||
|
this[i] = func(this[i], vec); // vec is number here
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic operations
|
||||||
|
add = (vec) => this._modifyComponents(vec, (a, b) => a + b);
|
||||||
|
subtract = (vec) => this._modifyComponents(vec, (a, b) => a - b);
|
||||||
|
multiply = (vec) => this._modifyComponents(vec, (a, b) => a * b);
|
||||||
|
divide = (vec) => this._modifyComponents(vec, (a, b) => a / b);
|
||||||
|
|
||||||
|
// Basic algebra
|
||||||
|
length() {
|
||||||
|
return Math.sqrt( this.values.map(v => v * v).reduce((a, b) => a + b, 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static normalize(vec) {
|
||||||
|
return vec.clone().divide(vec.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
static distance(vecA, vecB) {
|
||||||
|
Vector._requireSameVector(vecA, vecB);
|
||||||
|
return Math.sqrt( vecA.values.map((v, i) => v - vecB[i]).map(v => v * v).reduce((a, b) => a + b, 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static dot(vecA, vecB) {
|
||||||
|
Vector._requireSameVector(vecA, vecB);
|
||||||
|
return vecA.values.map((v, i) => v * vecB[i]).reduce((a, b) => a + b, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static cross(vecA, vecB) {
|
||||||
|
Vector._requireSameVector(vecA, vecB);
|
||||||
|
|
||||||
|
if (vecA.values.length !== 3)
|
||||||
|
throw TypeError(`Vector3 required, got Vector${vecA.values.length}`);
|
||||||
|
|
||||||
|
return new Vector(vecA.type,
|
||||||
|
vecA.y * vecB.z - vecA.z * vecB.y,
|
||||||
|
vecA.z * vecB.x - vecA.x * vecB.z,
|
||||||
|
vecA.x * vecB.y - vecA.y * vecB.x
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
26
sunpy/node/node.js
Normal file
26
sunpy/node/node.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
const handlers = {};
|
||||||
|
|
||||||
|
var onloadOld = onload || (() => {});
|
||||||
|
onload = () => {
|
||||||
|
onloadOld();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
for(const k in handlers) {
|
||||||
|
for (const n of getNodes(k)) {
|
||||||
|
handlers[k](n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodes(scriptType) {
|
||||||
|
return Array.from( document.querySelectorAll(`script[type="orep/${scriptType}"]`) ).filter(n => !n.dataset.handled);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function register(scriptType, handler) {
|
||||||
|
if (scriptType in handlers)
|
||||||
|
throw new Error(`'${scriptType}' is already registered`);
|
||||||
|
|
||||||
|
handlers[scriptType] = handler;
|
||||||
|
}
|
5
sunpy/node/nodes/basenode.js
Normal file
5
sunpy/node/nodes/basenode.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export class BaseNode {
|
||||||
|
constructor(node) {
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
}
|
22
sunpy/node/nodes/propertynode.js
Normal file
22
sunpy/node/nodes/propertynode.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { SwapNode } from "./swapnode.js";
|
||||||
|
import { PropertyReader } from "../propertyreader.js";
|
||||||
|
|
||||||
|
export class PropertyNode extends SwapNode {
|
||||||
|
constructor(node, swapNode, propertyReader) {
|
||||||
|
super(node, swapNode);
|
||||||
|
|
||||||
|
if (!(propertyReader instanceof PropertyReader))
|
||||||
|
throw TypeError("propertyReader must be an instance of PropertyReader");
|
||||||
|
|
||||||
|
this.propertyReader = propertyReader;
|
||||||
|
|
||||||
|
super.replaceNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
linker(node, swapNode) {
|
||||||
|
this.properties = this.propertyReader.read(node.innerText);
|
||||||
|
}
|
||||||
|
}
|
35
sunpy/node/nodes/swapnode.js
Normal file
35
sunpy/node/nodes/swapnode.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { BaseNode } from "./basenode.js";
|
||||||
|
|
||||||
|
export class SwapNode extends BaseNode {
|
||||||
|
constructor(node, swapNode) {
|
||||||
|
super(node);
|
||||||
|
|
||||||
|
if (!(swapNode instanceof Node))
|
||||||
|
throw TypeError("swapNode must be instance of Node");
|
||||||
|
|
||||||
|
this.swapNode = swapNode;
|
||||||
|
|
||||||
|
this.hasReplaceNodes = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceNodes() {
|
||||||
|
if (this.hasReplaceNodes)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this.linker(this.node, this.swapNode);
|
||||||
|
|
||||||
|
this.node.parentNode.replaceChild(this.swapNode, this.node);
|
||||||
|
this.node = this.swapNode;
|
||||||
|
|
||||||
|
delete this.swapNode;
|
||||||
|
|
||||||
|
return this.hasReplaceNodes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override this to have access to the script tag that gets converted to the new node inplace
|
||||||
|
* @param {Node} node Node of the original script tag
|
||||||
|
* @param {Node} swapNode Node of the new element that replaces the script tag inline
|
||||||
|
*/
|
||||||
|
linker(node, swapNode) {}
|
||||||
|
}
|
69
sunpy/node/propertyreader.js
Normal file
69
sunpy/node/propertyreader.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
class PropertyList {
|
||||||
|
constructor(...properties) {
|
||||||
|
for (const property of properties) {
|
||||||
|
if (!(property instanceof Property))
|
||||||
|
throw TypeError("properties has to be instances of Property");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.properties = [];
|
||||||
|
|
||||||
|
for (const property of properties) {
|
||||||
|
this.add(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add(property) {
|
||||||
|
Object.defineProperty(this, property.key, {
|
||||||
|
get() { return property.value },
|
||||||
|
set(value) { property.parse(value) }
|
||||||
|
});
|
||||||
|
|
||||||
|
this.properties.push(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Property {
|
||||||
|
constructor(key, defaultValue = null, parser = (v) => v) {
|
||||||
|
this.key = key;
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
this.parser = parser;
|
||||||
|
|
||||||
|
this.parse(this.defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(v) {
|
||||||
|
this.value = this.parser(v);
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return new Property(this.key, this.defaultValue, this.parser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Regex attempts (first to final)
|
||||||
|
* ^\s*({0})\s*(=|:)\s*(.*)$
|
||||||
|
* ^\s*({0})\s*(=|:)\s*([^#\n]*).*$
|
||||||
|
* ^\s*({0})\s*(=|:)\s*(.*?)((#|\/\/).*)?$
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class PropertyReader {
|
||||||
|
constructor(...properties) {
|
||||||
|
this.propertyList = new PropertyList(...properties);
|
||||||
|
this.regex = new RegExp(`^\\s*(${this.propertyList.properties.map(p => p.key).join("|")})\\s*(=|:)\\s*(.*?)((#|\\/\\/).*)?$`, "mg");
|
||||||
|
}
|
||||||
|
|
||||||
|
read(str) {
|
||||||
|
let match = null;
|
||||||
|
while ((match = this.regex.exec(str)) !== null) {
|
||||||
|
if (match.index === this.regex.lastIndex) // Infinite loop fix
|
||||||
|
this.regex.lastIndex++;
|
||||||
|
|
||||||
|
const key = match[1];
|
||||||
|
const value = match[3].trim();
|
||||||
|
this.propertyList[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.propertyList;
|
||||||
|
}
|
||||||
|
}
|
13
sunpy/orep/orep.js
Normal file
13
sunpy/orep/orep.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { RenderNode } from "../canvas/renderer.js";
|
||||||
|
|
||||||
|
/*
|
||||||
|
var onloadOld = onload || (() => {});
|
||||||
|
onload = () => {
|
||||||
|
onloadOld();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
31
tests/init.html
Normal file
31
tests/init.html
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Tests - init</title>
|
||||||
|
<script>
|
||||||
|
var onloadPre = onload || (() => {});
|
||||||
|
onload = () => {
|
||||||
|
onloadPre();
|
||||||
|
console.log("TEST - onload defined before module load");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script type="module" src="/sunpy/orep/orep.js" async></script>
|
||||||
|
<script>
|
||||||
|
var onloadPost = onload || (() => {});
|
||||||
|
onload = () => {
|
||||||
|
onloadPost();
|
||||||
|
console.log("TEST - onload defined after module load");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="orep/canvas">
|
||||||
|
size = 400x300
|
||||||
|
</script>
|
||||||
|
<script type="orep/canvas">
|
||||||
|
size = 600x450
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
15
tests/render.html
Normal file
15
tests/render.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Tests - render</title>
|
||||||
|
<script type="module" src="/sunpy/orep/orep.js" async></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="orep/canvas">
|
||||||
|
size = 800x600 # Render resolution
|
||||||
|
fps = 60 # Max fps
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user