matrix-engine-wgpu
Version:
Networking implemented - based on kurento openvidu server. fix arcball camera,instanced draws added also effect pipeline blend with instancing option.Normalmap added, Fixed shadows casting vs camera/video texture, webGPU powered pwa application. Crazy fas
1,077 lines (926 loc) • 31.3 kB
JavaScript
export var supportsTouch = 'ontouchstart' in window || navigator.msMaxTouchPoints;
export function isMobile() {
if(supportsTouch == true) return true;
const toMatch = [/Android/i, /webOS/i, /iPhone/i, /iPad/i, /iPod/i, /BlackBerry/i, /Windows Phone/i];
return toMatch.some(toMatchItem => {
return navigator.userAgent.match(toMatchItem);
});
};
export const vec3 = {
cross(a, b, dst) {
dst = dst || new Float32Array(3);
const t0 = a[1] * b[2] - a[2] * b[1];
const t1 = a[2] * b[0] - a[0] * b[2];
const t2 = a[0] * b[1] - a[1] * b[0];
dst[0] = t0;
dst[1] = t1;
dst[2] = t2;
return dst;
},
subtract(a, b, dst) {
dst = dst || new Float32Array(3);
dst[0] = a[0] - b[0];
dst[1] = a[1] - b[1];
dst[2] = a[2] - b[2];
return dst;
},
normalize(v, dst) {
dst = dst || new Float32Array(3);
const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
// make sure we don't divide by 0.
if(length > 0.00001) {
dst[0] = v[0] / length;
dst[1] = v[1] / length;
dst[2] = v[2] / length;
} else {
dst[0] = 0;
dst[1] = 0;
dst[2] = 0;
}
return dst;
},
};
export const mat4 = {
projection(width, height, depth, dst) {
// Note: This matrix flips the Y axis so that 0 is at the top.
return mat4.ortho(0, width, height, 0, depth, -depth, dst);
},
perspective(fieldOfViewYInRadians, aspect, zNear, zFar, dst) {
dst = dst || new Float32Array(16);
const f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewYInRadians);
const rangeInv = 1 / (zNear - zFar);
dst[0] = f / aspect;
dst[1] = 0;
dst[2] = 0;
dst[3] = 0;
dst[4] = 0;
dst[5] = f;
dst[6] = 0;
dst[7] = 0;
dst[8] = 0;
dst[9] = 0;
dst[10] = zFar * rangeInv;
dst[11] = -1;
dst[12] = 0;
dst[13] = 0;
dst[14] = zNear * zFar * rangeInv;
dst[15] = 0;
return dst;
},
ortho(left, right, bottom, top, near, far, dst) {
dst = dst || new Float32Array(16);
dst[0] = 2 / (right - left);
dst[1] = 0;
dst[2] = 0;
dst[3] = 0;
dst[4] = 0;
dst[5] = 2 / (top - bottom);
dst[6] = 0;
dst[7] = 0;
dst[8] = 0;
dst[9] = 0;
dst[10] = 1 / (near - far);
dst[11] = 0;
dst[12] = (right + left) / (left - right);
dst[13] = (top + bottom) / (bottom - top);
dst[14] = near / (near - far);
dst[15] = 1;
return dst;
},
identity(dst) {
dst = dst || new Float32Array(16);
dst[0] = 1; dst[1] = 0; dst[2] = 0; dst[3] = 0;
dst[4] = 0; dst[5] = 1; dst[6] = 0; dst[7] = 0;
dst[8] = 0; dst[9] = 0; dst[10] = 1; dst[11] = 0;
dst[12] = 0; dst[13] = 0; dst[14] = 0; dst[15] = 1;
return dst;
},
multiply(a, b, dst) {
dst = dst || new Float32Array(16);
const b00 = b[0 * 4 + 0];
const b01 = b[0 * 4 + 1];
const b02 = b[0 * 4 + 2];
const b03 = b[0 * 4 + 3];
const b10 = b[1 * 4 + 0];
const b11 = b[1 * 4 + 1];
const b12 = b[1 * 4 + 2];
const b13 = b[1 * 4 + 3];
const b20 = b[2 * 4 + 0];
const b21 = b[2 * 4 + 1];
const b22 = b[2 * 4 + 2];
const b23 = b[2 * 4 + 3];
const b30 = b[3 * 4 + 0];
const b31 = b[3 * 4 + 1];
const b32 = b[3 * 4 + 2];
const b33 = b[3 * 4 + 3];
const a00 = a[0 * 4 + 0];
const a01 = a[0 * 4 + 1];
const a02 = a[0 * 4 + 2];
const a03 = a[0 * 4 + 3];
const a10 = a[1 * 4 + 0];
const a11 = a[1 * 4 + 1];
const a12 = a[1 * 4 + 2];
const a13 = a[1 * 4 + 3];
const a20 = a[2 * 4 + 0];
const a21 = a[2 * 4 + 1];
const a22 = a[2 * 4 + 2];
const a23 = a[2 * 4 + 3];
const a30 = a[3 * 4 + 0];
const a31 = a[3 * 4 + 1];
const a32 = a[3 * 4 + 2];
const a33 = a[3 * 4 + 3];
dst[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30;
dst[1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31;
dst[2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32;
dst[3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33;
dst[4] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30;
dst[5] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31;
dst[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32;
dst[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33;
dst[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30;
dst[9] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31;
dst[10] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32;
dst[11] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33;
dst[12] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30;
dst[13] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31;
dst[14] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32;
dst[15] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33;
return dst;
},
cameraAim(eye, target, up, dst) {
dst = dst || new Float32Array(16);
const zAxis = vec3.normalize(vec3.subtract(eye, target));
const xAxis = vec3.normalize(vec3.cross(up, zAxis));
const yAxis = vec3.normalize(vec3.cross(zAxis, xAxis));
dst[0] = xAxis[0]; dst[1] = xAxis[1]; dst[2] = xAxis[2]; dst[3] = 0;
dst[4] = yAxis[0]; dst[5] = yAxis[1]; dst[6] = yAxis[2]; dst[7] = 0;
dst[8] = zAxis[0]; dst[9] = zAxis[1]; dst[10] = zAxis[2]; dst[11] = 0;
dst[12] = eye[0]; dst[13] = eye[1]; dst[14] = eye[2]; dst[15] = 1;
return dst;
},
inverse(m, dst) {
dst = dst || new Float32Array(16);
const m00 = m[0 * 4 + 0];
const m01 = m[0 * 4 + 1];
const m02 = m[0 * 4 + 2];
const m03 = m[0 * 4 + 3];
const m10 = m[1 * 4 + 0];
const m11 = m[1 * 4 + 1];
const m12 = m[1 * 4 + 2];
const m13 = m[1 * 4 + 3];
const m20 = m[2 * 4 + 0];
const m21 = m[2 * 4 + 1];
const m22 = m[2 * 4 + 2];
const m23 = m[2 * 4 + 3];
const m30 = m[3 * 4 + 0];
const m31 = m[3 * 4 + 1];
const m32 = m[3 * 4 + 2];
const m33 = m[3 * 4 + 3];
const tmp0 = m22 * m33;
const tmp1 = m32 * m23;
const tmp2 = m12 * m33;
const tmp3 = m32 * m13;
const tmp4 = m12 * m23;
const tmp5 = m22 * m13;
const tmp6 = m02 * m33;
const tmp7 = m32 * m03;
const tmp8 = m02 * m23;
const tmp9 = m22 * m03;
const tmp10 = m02 * m13;
const tmp11 = m12 * m03;
const tmp12 = m20 * m31;
const tmp13 = m30 * m21;
const tmp14 = m10 * m31;
const tmp15 = m30 * m11;
const tmp16 = m10 * m21;
const tmp17 = m20 * m11;
const tmp18 = m00 * m31;
const tmp19 = m30 * m01;
const tmp20 = m00 * m21;
const tmp21 = m20 * m01;
const tmp22 = m00 * m11;
const tmp23 = m10 * m01;
const t0 = (tmp0 * m11 + tmp3 * m21 + tmp4 * m31) -
(tmp1 * m11 + tmp2 * m21 + tmp5 * m31);
const t1 = (tmp1 * m01 + tmp6 * m21 + tmp9 * m31) -
(tmp0 * m01 + tmp7 * m21 + tmp8 * m31);
const t2 = (tmp2 * m01 + tmp7 * m11 + tmp10 * m31) -
(tmp3 * m01 + tmp6 * m11 + tmp11 * m31);
const t3 = (tmp5 * m01 + tmp8 * m11 + tmp11 * m21) -
(tmp4 * m01 + tmp9 * m11 + tmp10 * m21);
const d = 1 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
dst[0] = d * t0;
dst[1] = d * t1;
dst[2] = d * t2;
dst[3] = d * t3;
dst[4] = d * ((tmp1 * m10 + tmp2 * m20 + tmp5 * m30) -
(tmp0 * m10 + tmp3 * m20 + tmp4 * m30));
dst[5] = d * ((tmp0 * m00 + tmp7 * m20 + tmp8 * m30) -
(tmp1 * m00 + tmp6 * m20 + tmp9 * m30));
dst[6] = d * ((tmp3 * m00 + tmp6 * m10 + tmp11 * m30) -
(tmp2 * m00 + tmp7 * m10 + tmp10 * m30));
dst[7] = d * ((tmp4 * m00 + tmp9 * m10 + tmp10 * m20) -
(tmp5 * m00 + tmp8 * m10 + tmp11 * m20));
dst[8] = d * ((tmp12 * m13 + tmp15 * m23 + tmp16 * m33) -
(tmp13 * m13 + tmp14 * m23 + tmp17 * m33));
dst[9] = d * ((tmp13 * m03 + tmp18 * m23 + tmp21 * m33) -
(tmp12 * m03 + tmp19 * m23 + tmp20 * m33));
dst[10] = d * ((tmp14 * m03 + tmp19 * m13 + tmp22 * m33) -
(tmp15 * m03 + tmp18 * m13 + tmp23 * m33));
dst[11] = d * ((tmp17 * m03 + tmp20 * m13 + tmp23 * m23) -
(tmp16 * m03 + tmp21 * m13 + tmp22 * m23));
dst[12] = d * ((tmp14 * m22 + tmp17 * m32 + tmp13 * m12) -
(tmp16 * m32 + tmp12 * m12 + tmp15 * m22));
dst[13] = d * ((tmp20 * m32 + tmp12 * m02 + tmp19 * m22) -
(tmp18 * m22 + tmp21 * m32 + tmp13 * m02));
dst[14] = d * ((tmp18 * m12 + tmp23 * m32 + tmp15 * m02) -
(tmp22 * m32 + tmp14 * m02 + tmp19 * m12));
dst[15] = d * ((tmp22 * m22 + tmp16 * m02 + tmp21 * m12) -
(tmp20 * m12 + tmp23 * m22 + tmp17 * m02));
return dst;
},
lookAt(eye, target, up, dst) {
return mat4.inverse(mat4.cameraAim(eye, target, up, dst), dst);
},
translation([tx, ty, tz], dst) {
dst = dst || new Float32Array(16);
dst[0] = 1; dst[1] = 0; dst[2] = 0; dst[3] = 0;
dst[4] = 0; dst[5] = 1; dst[6] = 0; dst[7] = 0;
dst[8] = 0; dst[9] = 0; dst[10] = 1; dst[11] = 0;
dst[12] = tx; dst[13] = ty; dst[14] = tz; dst[15] = 1;
return dst;
},
rotationX(angleInRadians, dst) {
const c = Math.cos(angleInRadians);
const s = Math.sin(angleInRadians);
dst = dst || new Float32Array(16);
dst[0] = 1; dst[1] = 0; dst[2] = 0; dst[3] = 0;
dst[4] = 0; dst[5] = c; dst[6] = s; dst[7] = 0;
dst[8] = 0; dst[9] = -s; dst[10] = c; dst[11] = 0;
dst[12] = 0; dst[13] = 0; dst[14] = 0; dst[15] = 1;
return dst;
},
rotationY(angleInRadians, dst) {
const c = Math.cos(angleInRadians);
const s = Math.sin(angleInRadians);
dst = dst || new Float32Array(16);
dst[0] = c; dst[1] = 0; dst[2] = -s; dst[3] = 0;
dst[4] = 0; dst[5] = 1; dst[6] = 0; dst[7] = 0;
dst[8] = s; dst[9] = 0; dst[10] = c; dst[11] = 0;
dst[12] = 0; dst[13] = 0; dst[14] = 0; dst[15] = 1;
return dst;
},
rotationZ(angleInRadians, dst) {
const c = Math.cos(angleInRadians);
const s = Math.sin(angleInRadians);
dst = dst || new Float32Array(16);
dst[0] = c; dst[1] = s; dst[2] = 0; dst[3] = 0;
dst[4] = -s; dst[5] = c; dst[6] = 0; dst[7] = 0;
dst[8] = 0; dst[9] = 0; dst[10] = 1; dst[11] = 0;
dst[12] = 0; dst[13] = 0; dst[14] = 0; dst[15] = 1;
return dst;
},
scaling([sx, sy, sz], dst) {
dst = dst || new Float32Array(16);
dst[0] = sx; dst[1] = 0; dst[2] = 0; dst[3] = 0;
dst[4] = 0; dst[5] = sy; dst[6] = 0; dst[7] = 0;
dst[8] = 0; dst[9] = 0; dst[10] = sz; dst[11] = 0;
dst[12] = 0; dst[13] = 0; dst[14] = 0; dst[15] = 1;
return dst;
},
translate(m, translation, dst) {
return mat4.multiply(m, mat4.translation(translation), dst);
},
rotateX(m, angleInRadians, dst) {
return mat4.multiply(m, mat4.rotationX(angleInRadians), dst);
},
rotateY(m, angleInRadians, dst) {
return mat4.multiply(m, mat4.rotationY(angleInRadians), dst);
},
rotateZ(m, angleInRadians, dst) {
return mat4.multiply(m, mat4.rotationZ(angleInRadians), dst);
},
scale(m, scale, dst) {
return mat4.multiply(m, mat4.scaling(scale), dst);
},
};
export function degToRad(degrees) {return (degrees * Math.PI) / 180};
export function radToDeg(r) {var pi = Math.PI; return r * (180 / pi)};
export function createAppEvent(name, myDetails) {
return new CustomEvent(name, {
detail: {
eventName: name,
data: myDetails,
},
bubbles: true,
});
}
/**
* @description
* Load script in runtime.
*/
export var scriptManager = {
SCRIPT_ID: 0,
LOAD: function addScript(src, id, type, parent, callback) {
var s = document.createElement('script');
s.onload = function() {
// console.log('Script id loaded [src]: ' + this.src);
if(typeof callback != 'undefined') callback();
};
if(typeof type !== 'undefined') {
s.setAttribute('type', type);
s.innerHTML = src;
} else {s.setAttribute('src', src)}
if(typeof id !== 'undefined') {s.setAttribute('id', id)}
if(typeof parent !== 'undefined') {
document.getElementById(parent).appendChild(s);
if(typeof callback != 'undefined') callback();
} else {
document.body.appendChild(s);
}
},
loadModule: function addScript(src, id, type, parent) {
console.log('Script id load called ');
var s = document.createElement('script');
s.onload = function() {
scriptManager.SCRIPT_ID++;
};
if(typeof type === 'undefined') {
s.setAttribute('type', 'module');
s.setAttribute('src', src);
} else {
s.setAttribute('type', type);
s.innerHTML = src;
}
s.setAttribute('src', src);
if(typeof id !== 'undefined') {
s.setAttribute('id', id);
}
if(typeof parent !== 'undefined') {
document.getElementById(parent).appendChild(s);
} else {
document.body.appendChild(s);
}
},
loadGLSL: function(src) {
return new Promise((resolve) => {
fetch(src).then((data) => {
resolve(data.text())
})
})
}
};
// GET PULSE VALUES IN REAL TIME
export function OSCILLATOR(min, max, step) {
if((typeof min === 'string' || typeof min === 'number') && (typeof max === 'string' || typeof max === 'number') && (typeof step === 'string' || typeof step === 'number')) {
var ROOT = this;
this.min = parseFloat(min);
this.max = parseFloat(max);
this.step = parseFloat(step);
this.value_ = parseFloat(min);
this.status = 0;
this.on_maximum_value = function() {};
this.on_minimum_value = function() {};
this.UPDATE = function(STATUS_) {
if(STATUS_ === undefined) {
if(this.status == 0 && this.value_ < this.max) {
this.value_ = this.value_ + this.step;
if(this.value_ >= this.max) {
this.value_ = this.max;
this.status = 1;
ROOT.on_maximum_value();
}
return this.value_;
} else if(this.status == 1 && this.value_ > this.min) {
this.value_ = this.value_ - this.step;
if(this.value_ <= this.min) {
this.value_ = this.min;
this.status = 0;
ROOT.on_minimum_value();
}
return this.value_;
}
} else {
return this.value_;
}
};
} else {
console.log("OSCILLATOR ERROR");
}
}
// this is class not func ecma5
export function SWITCHER() {
var ROOT = this;
ROOT.VALUE = 1;
ROOT.GET = function() {
ROOT.VALUE = ROOT.VALUE * -1;
return ROOT.VALUE;
};
}
export function ORBIT(cx, cy, angle, p) {
var s = Math.sin(angle);
var c = Math.cos(angle);
p.x -= cx;
p.y -= cy;
var xnew = p.x * c - p.y * s;
var ynew = p.x * s + p.y * c;
p.x = xnew + cx;
p.y = ynew + cy;
return p;
}
export function ORBIT_FROM_ARRAY(cx, cy, angle, p, byIndexs) {
var s = Math.sin(angle);
var c = Math.cos(angle);
p[byIndexs[0]] -= cx;
p[byIndexs[1]] -= cy;
var xnew = p[byIndexs[0]] * c - p[byIndexs[1]] * s;
var ynew = p[byIndexs[0]] * s + p[byIndexs[1]] * c;
p[byIndexs[0]] = xnew + cx;
p[byIndexs[1]] = ynew + cy;
return p;
}
export var byId = function(id) {return document.getElementById(id)};
export function randomFloatFromTo(min, max) {return Math.random() * (max - min) + min;}
export function randomIntFromTo(min, max) {
if(typeof min === 'object' || typeof max === 'object') {
console.log(
"SYS : warning Desciption : Replace object with string , this >> " + typeof min + ' and ' + typeof min + ' << must be string or number.'
);
} else if(typeof min === 'undefined' || typeof max === 'undefined') {
console.log(
"SYS : warning Desciption : arguments (min, max) cant be undefined , this >> " + typeof min + ' and ' + typeof min + ' << must be string or number.'
);
} else {
return Math.floor(Math.random() * (max - min + 1) + min);
}
}
export var urlQuery = (function() {
var query_string = {};
var query = window.location.search.substring(1);
var vars = query.split('&');
for(var i = 0;i < vars.length;i++) {
var pair = vars[i].split('=');
if(typeof query_string[pair[0]] === 'undefined') {
query_string[pair[0]] = decodeURIComponent(pair[1]);
} else if(typeof query_string[pair[0]] === 'string') {
var arr = [query_string[pair[0]], decodeURIComponent(pair[1])];
query_string[pair[0]] = arr;
} else {
query_string[pair[0]].push(decodeURIComponent(pair[1]));
}
}
return query_string;
})();
export function getAxisRot(q1) {
var x, y, z;
// if w>1 acos and sqrt will produce errors, this cant happen if quaternion is normalised
if(q1.w > 1) q1.normalise();
var angle = 2 * Math.acos(q1.w);
// assuming quaternion normalised then w is less than 1, so term always positive.
var s = Math.sqrt(1 - q1.w * q1.w);
// test to avoid divide by zero, s is always positive due to sqrt
if(s < 0.001) {
// if s close to zero then direction of axis not important
// if it is important that axis is normalised then replace with x=1; y=z=0;
x = q1.x;
y = q1.y;
z = q1.z;
} else {
x = q1.x / s; // normalise axis
y = q1.y / s;
z = q1.z / s;
}
return [radToDeg(x), radToDeg(y), radToDeg(z)]
}
export function getAxisRot2(targetAxis, Q) {
Q.normalize(); // if w>1 acos and sqrt will produce errors, this cant happen if quaternion is normalised
var angle = 2 * Math.acos(Q.w());
var s = Math.sqrt(1 - Q.w() * Q.w()); // assuming quaternion normalised then w is less than 1, so term always positive.
if(s < 0.001) { // test to avoid divide by zero, s is always positive due to sqrt
// if s close to zero then direction of axis not important
// if it is important that axis is normalised then replace with x=1; y=z=0;
// targetAxis.x = 1;
// targetAxis.y = 0;
// targetAxis.z = 0;
targetAxis.x = Q.x();
targetAxis.y = Q.y();
targetAxis.z = Q.z();
} else {
targetAxis.x = Q.x() / s; // normalise axis
targetAxis.y = Q.y() / s;
targetAxis.z = Q.z() / s;
}
return [targetAxis, angle];
}
export function getAxisRot3(Q) {
var angle = Math.acos(Q.w) * 2;
var axis = {};
if(Math.sin(Math.acos(angle)) > 0) {
axis.x = Q.x / Math.sin(Math.acos(angle / 2));
axis.y = Q.y / Math.sin(Math.acos(angle / 2));
axis.z = Q.z / Math.sin(Math.acos(angle / 2));
} else {
axis.x = 0;
axis.y = 0;
axis.z = 0;
}
return axis;
}
// NTO TESTED
export function quaternion_rotation_matrix(Q) {
// Covert a quaternion into a full three-dimensional rotation matrix.
// Input
// :param Q: A 4 element array representing the quaternion (q0,q1,q2,q3)
// Output
// :return: A 3x3 element matrix representing the full 3D rotation matrix.
// This rotation matrix converts a point in the local reference
// frame to a point in the global reference frame.
// """
// # Extract the values from Q
var q0 = Q[0]
var q1 = Q[1]
var q2 = Q[2]
var q3 = Q[3]
// # First row of the rotation matrix
var r00 = 2 * (q0 * q0 + q1 * q1) - 1
var r01 = 2 * (q1 * q2 - q0 * q3)
var r02 = 2 * (q1 * q3 + q0 * q2)
// # Second row of the rotation matrix
var r10 = 2 * (q1 * q2 + q0 * q3)
var r11 = 2 * (q0 * q0 + q2 * q2) - 1
var r12 = 2 * (q2 * q3 - q0 * q1)
// # Third row of the rotation matrix
var r20 = 2 * (q1 * q3 - q0 * q2)
var r21 = 2 * (q2 * q3 + q0 * q1)
var r22 = 2 * (q0 * q0 + q3 * q3) - 1
// # 3x3 rotation matrix
var rot_matrix = [[r00, r01, r02],
[r10, r11, r12],
[r20, r21, r22]]
return rot_matrix;
}
// copnsole log graphics
export const LOG_WARN = 'background: gray; color: yellow; font-size:10px';
export const LOG_INFO = 'background: green; color: white; font-size:11px';
export const LOG_MATRIX = "font-family: stormfaze;color: #lime; font-size:11px;text-shadow: 2px 2px 4px orangered;background: black;";
export const LOG_FUNNY = "font-family: stormfaze;color: #f1f033; font-size:14px;text-shadow: 2px 2px 4px #f335f4, 4px 4px 4px #d64444, 2px 2px 4px #c160a6, 6px 2px 0px #123de3;background: black;";
export const LOG_FUNNY_SMALL = "font-family: stormfaze;color: #f1f033; font-size:10px;text-shadow: 2px 2px 4px #f335f4, 4px 4px 4px #d64444, 1px 1px 2px #c160a6, 3px 1px 0px #123de3;background: black;";
export function genName(length) {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for(let i = 0;i < length;i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
export let mb = {
root: () => byId('msgBox'),
pContent: () => byId('not-content'),
copy: function() {
navigator.clipboard.writeText(mb.root().children[0].innerText);
},
c: 0, ic: 0, t: {},
setContent: function(content, t) {
var iMsg = document.createElement('div');
iMsg.innerHTML = content;
iMsg.id = `msgbox-loc-${mb.c}`;
mb.root().appendChild(iMsg);
iMsg.classList.add('animate1')
if(t == 'ok') {
iMsg.style = 'font-family: stormfaze;color:white;padding:7px;margin:2px';
} else {
iMsg.style = 'font-family: stormfaze;color:white;padding:7px;margin:2px';
}
},
kill: function() {
mb.root().remove();
},
show: function(content, t) {
mb.setContent(content, t);
mb.root().style.display = "block";
var loc2 = mb.c;
setTimeout(function() {
byId(`msgbox-loc-${loc2}`).classList.remove("fadeInDown");
byId(`msgbox-loc-${loc2}`).classList.add("fadeOut");
setTimeout(function() {
byId(`msgbox-loc-${loc2}`).style.display = "none";
byId(`msgbox-loc-${loc2}`).classList.remove("fadeOut");
byId(`msgbox-loc-${loc2}`).remove();
mb.ic++;
if(mb.c == mb.ic) {
mb.root().style.display = 'none';
}
}, 1000)
}, 3000);
mb.c++;
},
error: function(content) {
if(mb.root() == null) return;
mb.root().classList.remove("success")
mb.root().classList.add("error")
mb.root().classList.add("fadeInDown");
mb.show(content, 'err');
},
success: function(content) {
if(mb.root() == null) return;
mb.root().classList.remove("error")
mb.root().classList.add("success")
mb.root().classList.add("fadeInDown");
mb.show(content, 'ok');
}
}
// Registry to track running animations per element
const typingStates = new Map();
export function typeText(elementId, htmlString, delay = 50) {
const el = document.getElementById(elementId);
if(!el) return;
// If an existing typing is running for this element, cancel it
if(typingStates.has(elementId)) {
clearTimeout(typingStates.get(elementId).timeoutId);
typingStates.delete(elementId);
}
el.innerHTML = ''; // Clear previous content
const tempEl = document.createElement('div');
tempEl.innerHTML = htmlString;
const queue = [];
function flatten(node) {
if(node.nodeType === Node.TEXT_NODE) {
queue.push({type: 'text', text: node.textContent});
} else if(node.nodeType === Node.ELEMENT_NODE) {
if(node.tagName.toLowerCase() === 'img') {
queue.push({
type: 'img',
src: node.getAttribute('src'),
alt: node.getAttribute('alt') || ''
});
} else {
queue.push({
type: 'element',
tag: node.tagName.toLowerCase(),
attributes: Object.fromEntries([...node.attributes].map(attr => [attr.name, attr.value]))
});
for(const child of node.childNodes) flatten(child);
queue.push({type: 'end'});
}
}
}
for(const node of tempEl.childNodes) flatten(node);
let stack = [];
let currentElement = el;
function typeNextChar() {
if(queue.length === 0) {
typingStates.delete(elementId); // Cleanup after finish
return;
}
const item = queue[0];
if(item.type === 'text') {
if(!item.index) item.index = 0;
const ch = item.text[item.index];
if(ch === '\n') {
currentElement.appendChild(document.createElement('br'));
} else {
currentElement.appendChild(document.createTextNode(ch));
}
item.index++;
if(item.index >= item.text.length) queue.shift();
} else if(item.type === 'element') {
const newEl = document.createElement(item.tag);
if(item.attributes) {
for(let [key, val] of Object.entries(item.attributes)) {
newEl.setAttribute(key, val);
}
}
currentElement.appendChild(newEl);
stack.push(currentElement);
currentElement = newEl;
queue.shift();
} else if(item.type === 'end') {
currentElement = stack.pop();
queue.shift();
} else if(item.type === 'img') {
const img = document.createElement('img');
img.src = item.src;
img.alt = item.alt;
img.style.maxWidth = '100px';
img.style.verticalAlign = 'middle';
currentElement.appendChild(img);
queue.shift();
}
// Schedule next step and store timeoutId for control
const timeoutId = setTimeout(typeNextChar, delay);
typingStates.set(elementId, {timeoutId});
}
typeNextChar();
}
export function setupCanvasFilters(canvasId) {
let canvas = document.getElementById(canvasId);
if(canvas == null) {
canvas = document.getElementsByTagName('canvas')[0];
}
const filterState = {
blur: "0px",
grayscale: "0%",
brightness: "100%",
contrast: "100%",
saturate: "100%",
sepia: "0%",
invert: "0%",
hueRotate: "0deg"
};
function updateFilter() {
const filterString = `
blur(${filterState.blur})
grayscale(${filterState.grayscale})
brightness(${filterState.brightness})
contrast(${filterState.contrast})
saturate(${filterState.saturate})
sepia(${filterState.sepia})
invert(${filterState.invert})
hue-rotate(${filterState.hueRotate})
`.trim();
canvas.style.filter = filterString;
}
const bindings = {
blurControl: "blur",
grayscaleControl: "grayscale",
brightnessControl: "brightness",
contrastControl: "contrast",
saturateControl: "saturate",
sepiaControl: "sepia",
invertControl: "invert",
hueControl: "hueRotate"
};
Object.entries(bindings).forEach(([selectId, key]) => {
const el = document.getElementById(selectId);
el.addEventListener("change", (e) => {
filterState[key] = e.target.value;
updateFilter();
});
});
updateFilter(); // Initial
}
/**
* @description
* // Save an object
Storage.set('playerData', { name: 'Slayzer', hp: 120, mana: 80 });
// Load it back
const player = Storage.get('playerData');
console.log(player.name); // "Slayzer"
// Check if exists
if (Storage.has('playerData')) console.log('Found!');
// Remove one
Storage.remove('playerData');
// Clear all localStorage
Storage.clear();
*/
export const LS = {
set(key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
get(key, defaultValue = null) {
const item = localStorage.getItem(key);
try {
return item ? JSON.parse(item) : defaultValue;
} catch(e) {
console.warn(`Error parsing localStorage key "${key}"`, e);
return defaultValue;
}
},
has(key) {
return localStorage.getItem(key) !== null;
},
remove(key) {
localStorage.removeItem(key);
},
clear() {
localStorage.clear();
}
};
export const SS = {
set(key, value) {
sessionStorage.setItem(key, JSON.stringify(value));
},
get(key, defaultValue = null) {
const item = sessionStorage.getItem(key);
try {
return item ? JSON.parse(item) : defaultValue;
} catch(e) {
console.warn(`Error parsing sessionStorage key "${key}"`, e);
return defaultValue;
}
},
has(key) {
return sessionStorage.getItem(key) !== null;
},
remove(key) {
sessionStorage.removeItem(key);
},
clear() {
sessionStorage.clear();
}
};
export const jsonHeaders = new Headers({
"Content-Type": "application/json",
"Accept": "application/json",
});
export const htmlHeader = new Headers({
"Content-Type": "text/html",
"Accept": "text/plain",
});
export function isEven(n) {
return n % 2 === 0;
}
export function isOdd(n) {
return n % 2 !== 0;
}
export class FullscreenManagerElement {
constructor(targetElement = document.documentElement) {
this.target = targetElement;
}
isFullscreen() {
return !!(
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
);
}
request() {
const el = this.target;
return (
el.requestFullscreen?.() ||
el.webkitRequestFullscreen?.() ||
el.mozRequestFullScreen?.() ||
el.msRequestFullscreen?.()
);
}
exit() {
return (
document.exitFullscreen?.() ||
document.webkitExitFullscreen?.() ||
document.mozCancelFullScreen?.() ||
document.msExitFullscreen?.()
);
}
toggle() {
if (this.isFullscreen()) return this.exit();
return this.request();
}
onChange(callback) {
[
"fullscreenchange",
"webkitfullscreenchange",
"mozfullscreenchange",
"MSFullscreenChange"
].forEach(evt =>
document.addEventListener(evt, () => {
callback(this.isFullscreen(), this.target);
})
);
}
}
export class FullscreenManager {
constructor() {
this.target = document.documentElement; // fullscreen whole page / window
}
isFullscreen() {
return !!(
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
);
}
request() {
const el = this.target;
return (
el.requestFullscreen?.() ||
el.webkitRequestFullscreen?.() ||
el.mozRequestFullScreen?.() ||
el.msRequestFullscreen?.()
);
}
exit() {
return (
document.exitFullscreen?.() ||
document.webkitExitFullscreen?.() ||
document.mozCancelFullScreen?.() ||
document.msExitFullscreen?.()
);
}
toggle() {
return this.isFullscreen() ? this.exit() : this.request();
}
onChange(callback) {
[
"fullscreenchange",
"webkitfullscreenchange",
"mozfullscreenchange",
"MSFullscreenChange"
].forEach(evt =>
document.addEventListener(evt, () => {
callback(this.isFullscreen());
})
);
}
}