How to create a sticky video effect with three.js? - javascript

I have seen that it is possible to create a sticky image effect on hover using three.js which inspired me with an idea of doing this effect over a video.
Initially, I converted the video to a gif hover the gif was stuck on the first frame. I then tried canvid.js which uses loops frames instead of video. Finally I tried to pause the video an take an image of the frame that is hovered on and then use the effect on the image.
/* S C R E E N S H O T */
$( document ).ready(function() {
$("#heroVideo").get(0).play();
var video = document.getElementById("heroVideo");
var currentTime = video.currentTime
var imgUrl = "./img/overlay.png";
$(".hoverZone").hover(function(){
$("img").css("opacity","1");
function getVideoImage(path, secs, callback) {
var me = this, video = document.createElement('video');
video.onloadedmetadata = function() {
if ('function' === typeof secs) {
secs = secs(this.duration);
}
this.currentTime = Math.min(Math.max(0, (secs < 0 ? this.duration : 0) + secs), this.duration);
};
video.onseeked = function(e) {
var canvas = document.createElement('canvas');
canvas.height = video.videoHeight;
canvas.width = video.videoWidth;
var ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
var img = new Image();
img.src = canvas.toDataURL();
callback.call(me, img, this.currentTime, e);
};
video.onerror = function(e) {
callback.call(me, undefined, undefined, e);
};
video.src = path;
}
function showImageAt(currentTime) {
var current;
getVideoImage(
'./img/Morph.mp4',
function(currentTime) {
current = video.currentTime;
return current;
},
function(img, secs, event) {
if (event.type == 'seeked') {
var li = document.createElement('li');
li.appendChild(img);
document.getElementById('olFrames').appendChild(li);
var imgUrl = $('img')[0].src;
console.log(imgUrl);
if (current >= ++secs) {
};
}
}
);
}
showImageAt(currentTime);
}, function(){
$("img").css("opacity","0");
});
/* D I S T O R T I O N */
const imgSize = [1250, 10097];
const vertex = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
`;
const fragment = `
precision highp float;
precision highp int;
uniform sampler2D tWater;
uniform sampler2D tFlow;
uniform float uTime;
varying vec2 vUv;
uniform vec4 res;
void main() {
// R and G values are velocity in the x and y direction
// B value is the velocity length
vec3 flow = texture2D(tFlow, vUv).rgb;
vec2 uv = .5 * gl_FragCoord.xy / res.xy ;
vec2 myUV = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV -= flow.xy * (0.15 * 0.7);
vec2 myUV2 = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV2 -= flow.xy * (0.125 * 0.7);
vec2 myUV3 = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV3 -= flow.xy * (0.10 * 0.7);
vec3 tex = texture2D(tWater, myUV).rgb;
vec3 tex2 = texture2D(tWater, myUV2).rgb;
vec3 tex3 = texture2D(tWater, myUV3).rgb;
gl_FragColor = vec4(tex.r, tex2.g, tex3.b, 1.0);
}
`;
{
const renderer = new ogl.Renderer({ dpr: 2 });
const gl = renderer.gl;
document.body.appendChild(gl.canvas);
// Variable inputs to control flowmap
let aspect = 1;
const mouse = new ogl.Vec2(-1);
const velocity = new ogl.Vec2();
function resize() {
let a1, a2;
var imageAspect = imgSize[1] / imgSize[0];
if (window.innerHeight / window.innerWidth < imageAspect) {
a1 = 1;
a2 = window.innerHeight / window.innerWidth / imageAspect;
} else {
a1 = (window.innerWidth / window.innerHeight) * imageAspect;
a2 = 1;
}
mesh.program.uniforms.res.value = new ogl.Vec4(
window.innerWidth,
window.innerHeight,
a1,
a2
);
renderer.setSize(window.innerWidth, window.innerHeight);
aspect = window.innerWidth / window.innerHeight;
}
const flowmap = new ogl.Flowmap(gl, {
falloff: 1.0, // size of the stamp, percentage of the size
alpha: 0.7, // opacity of the stamp
dissipation: 0.95// affects the speed that the stamp fades. Closer to 1 is slower
});
// Triangle that includes -1 to 1 range for 'position', and 0 to 1 range for 'uv'.
const geometry = new ogl.Geometry(gl, {
position: {
size: 2,
data: new Float32Array([-1, -1, 3, -1, -1, 3])
},
uv: { size: 2, data: new Float32Array([0, 0, 2, 0, 0, 2]) }
});
const texture = new ogl.Texture(gl, {
minFilter: gl.LINEAR,
magFilter: gl.LINEAR
});
const img = new Image();
img.onload = () => (texture.image = img);
img.crossOrigin = "Anonymous";
img.src = imgUrl;
let a1, a2;
var imageAspect = imgSize[1] / imgSize[0];
if (window.innerHeight / window.innerWidth < imageAspect) {
a1 = 1;
a2 = window.innerHeight / window.innerWidth / imageAspect;
} else {
a1 = (window.innerWidth / window.innerHeight) * imageAspect;
a2 = 1;
}
const program = new ogl.Program(gl, {
vertex,
fragment,
uniforms: {
uTime: { value: 0 },
tWater: { value: texture },
res: {
value: new ogl.Vec4(window.innerWidth, window.innerHeight, a1, a2)
},
img: { value: new ogl.Vec2(imgSize[0], imgSize[1]) },
// Note that the uniform is applied without using an object and value property
// This is because the class alternates this texture between two render targets
// and updates the value property after each render.
tFlow: flowmap.uniform
}
});
const mesh = new ogl.Mesh(gl, { geometry, program });
window.addEventListener("resize", resize, false);
resize();
// Create handlers to get mouse position and velocity
const isTouchCapable = "ontouchstart" in window;
if (isTouchCapable) {
window.addEventListener("touchstart", updateMouse, false);
window.addEventListener("touchmove", updateMouse, { passive: false });
} else {
window.addEventListener("mousemove", updateMouse, false);
}
let lastTime;
const lastMouse = new ogl.Vec2();
function updateMouse(e) {
e.preventDefault();
if (e.changedTouches && e.changedTouches.length) {
e.x = e.changedTouches[0].pageX;
e.y = e.changedTouches[0].pageY;
}
if (e.x === undefined) {
e.x = e.pageX;
e.y = e.pageY;
}
// Get mouse value in 0 to 1 range, with y flipped
mouse.set(e.x / gl.renderer.width, 1.0 - e.y / gl.renderer.height);
// Calculate velocity
if (!lastTime) {
// First frame
lastTime = performance.now();
lastMouse.set(e.x, e.y);
}
const deltaX = e.x - lastMouse.x;
const deltaY = e.y - lastMouse.y;
lastMouse.set(e.x, e.y);
let time = performance.now();
// Avoid dividing by 0
let delta = Math.max(10.4, time - lastTime);
lastTime = time;
velocity.x = deltaX / delta;
velocity.y = deltaY / delta;
// Flag update to prevent hanging velocity values when not moving
velocity.needsUpdate = true;
}
requestAnimationFrame(update);
function update(t) {
requestAnimationFrame(update);
// Reset velocity when mouse not moving
if (!velocity.needsUpdate) {
mouse.set(-1);
velocity.set(0);
}
velocity.needsUpdate = false;
// Update flowmap inputs
flowmap.aspect = aspect;
flowmap.mouse.copy(mouse);
// Ease velocity input, slower when fading out
flowmap.velocity.lerp(velocity, velocity.len ? 0.15 : 0.1);
flowmap.update();
program.uniforms.uTime.value = t * 0.01;
renderer.render({ scene: mesh });
}
}
});

Related

WebGL fade-out does not work for white color

I am trying to adapt a webgl code that generates firework explositions, where projectiles fade out from canvas by means of painting over with 0.1 alpha (as I understand it).
However, this technique somehow doesn't work for the explosition of white color.
You can see that white projectiles leave traces and never fully disappear, while other colors work fine (or maybe they also don't work, but I don't see the traces on black background).
Can somebody help me understand what is happening here?
// from https://codepen.io/towc/pen/oBvYEL
var gl = c.getContext('webgl2', {preserveDrawingBuffer: true})
, w = c.width = window.innerWidth
, h = c.height = window.innerHeight
, webgl = {}
, opts = {
projectileAlpha: .8,
projectileLineWidth: 2,
fireworkAngleSpan: .5,
baseFireworkVel: 3,
gravity: .02,
xFriction: .995,
baseShardVel: 1,
addedShardVel: .2,
fireworks: 1, // 1 firework for each 10x10 pixels,
baseShardsParFirework: 10,
addedShardsParFirework: 10,
shardFireworkVelMultiplier: .3
}
// updated to use WebGL2 and GL ES 3.0
// pass full color instead of just hue
// remove moving projectile, keep only the explosion
webgl.vertexShaderSource = `#version 300 es
uniform int u_mode;
uniform vec2 u_res;
in vec4 a_data;
out vec4 v_color;
vec3 c2rgb(int color_int){
int r = color_int >> 16;
int g = color_int >> 8 & 0xff;
int b = color_int & 0xff;
return vec3(r/255, g/255, b/255);
}
void clear(){
gl_Position = vec4( a_data.xy, 0, 1 );
v_color = vec4( 0, 0, 0, a_data.w );
}
void draw(){
gl_Position = vec4( vec2( 1, -1 ) * ( ( a_data.xy / u_res ) * 2. - 1. ), 0, 1 );
v_color = vec4( 1, 1, 1, a_data.w );
}
void main(){
if( u_mode == 0 )
draw();
else
clear();
}
`;
webgl.fragmentShaderSource = `#version 300 es
precision mediump float;
in vec4 v_color;
out vec4 fragColor;
void main(){
fragColor = v_color;
}
`;
webgl.vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(webgl.vertexShader, webgl.vertexShaderSource);
gl.compileShader(webgl.vertexShader);
if (!gl.getShaderParameter(webgl.vertexShader, gl.COMPILE_STATUS))
console.log(gl.getShaderInfoLog(webgl.vertexShader));
webgl.fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(webgl.fragmentShader, webgl.fragmentShaderSource);
gl.compileShader(webgl.fragmentShader);
if (!gl.getShaderParameter(webgl.fragmentShader, gl.COMPILE_STATUS))
console.log(gl.getShaderInfoLog(webgl.fragmentShader));
webgl.shaderProgram = gl.createProgram();
gl.attachShader(webgl.shaderProgram, webgl.vertexShader);
gl.attachShader(webgl.shaderProgram, webgl.fragmentShader);
gl.linkProgram(webgl.shaderProgram);
gl.useProgram(webgl.shaderProgram);
webgl.dataAttribLoc = gl.getAttribLocation(webgl.shaderProgram, 'a_data');
webgl.dataBuffer = gl.createBuffer();
gl.enableVertexAttribArray(webgl.dataAttribLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, webgl.dataBuffer);
gl.vertexAttribPointer(webgl.dataAttribLoc, 4, gl.FLOAT, false, 0, 0);
webgl.resUniformLoc = gl.getUniformLocation(webgl.shaderProgram, 'u_res');
webgl.modeUniformLoc = gl.getUniformLocation(webgl.shaderProgram, 'u_mode');
gl.viewport(0, 0, w, h);
gl.uniform2f(webgl.resUniformLoc, w, h);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
gl.lineWidth(opts.projectileLineWidth);
webgl.data = [];
webgl.clear = function () {
gl.uniform1i(webgl.modeUniformLoc, 1);
let a = .1;
webgl.data = [
-1, -1, 0, 0.1,
1, -1, 0, 0.1,
-1, 1, 0, 0.1,
-1, 1, 0, 0.1,
1, -1, 0, 0.1,
1, 1, 0, 0.1
];
webgl.draw(gl.TRIANGLES);
gl.uniform1i(webgl.modeUniformLoc, 0);
webgl.data.length = 0;
}
webgl.draw = function (glType) {
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(webgl.data), gl.STATIC_DRAW);
gl.drawArrays(glType, 0, webgl.data.length / 4);
}
let fireworks = []
, tick = 0
, sins = []
, coss = []
, maxShardsParFirework = opts.baseShardsParFirework + opts.addedShardsParFirework
, tau = 6.283185307179586476925286766559;
for (let i = 0; i < maxShardsParFirework; ++i) {
sins[i] = Math.sin(tau * i / maxShardsParFirework);
coss[i] = Math.cos(tau * i / maxShardsParFirework);
}
function Firework() {
this.reset();
this.shards = [];
for (let i = 0; i < maxShardsParFirework; ++i)
this.shards.push(new Shard(this));
}
Firework.prototype.reset = function () {
let angle = -Math.PI / 2 + (Math.random() - .5) * opts.fireworkAngleSpan
, vel = opts.baseFireworkVel * Math.random();
this.mode = 0;
this.vx = vel * Math.cos(angle);
this.vy = vel * Math.sin(angle);
this.x = Math.random() * w;
this.y = Math.random() * h;
this.hue = 0;
let ph = this.hue
, px = this.x
, py = this.y;
webgl.data.push(
px, py, ph, opts.projectileAlpha * .2,
this.x, this.y, this.hue, opts.projectileAlpha * .2);
}
Firework.prototype.step = function () {
if (this.mode === 0) {
this.mode = 1;
this.shardAmount = opts.baseShardsParFirework + opts.addedShardsParFirework * Math.random() | 0;
let baseAngle = Math.random() * tau
, x = Math.cos(baseAngle)
, y = Math.sin(baseAngle)
, sin = sins[this.shardAmount]
, cos = coss[this.shardAmount];
for (let i = 0; i < this.shardAmount; ++i) {
let vel = opts.baseShardVel + opts.addedShardVel * Math.random();
this.shards[i].reset(x * vel, y * vel)
let X = x;
x = x * cos - y * sin;
y = y * cos + X * sin;
}
}
if (this.mode === 1) {
this.ph = this.hue
this.hue = 0;
let allDead = true;
for (let i = 0; i < this.shardAmount; ++i) {
let shard = this.shards[i];
if (!shard.dead) {
shard.step();
allDead = false;
}
}
if (allDead)
this.reset();
}
}
function Shard(parent) {
this.parent = parent;
}
Shard.prototype.reset = function (vx, vy) {
this.x = this.parent.x;
this.y = this.parent.y;
this.vx = this.parent.vx * opts.shardFireworkVelMultiplier + vx;
this.vy = this.parent.vy * opts.shardFireworkVelMultiplier + vy;
this.starty = this.y;
this.dead = false;
this.tick = 1;
}
Shard.prototype.step = function () {
this.tick += .05;
let px = this.x
, py = this.y;
this.x += this.vx *= opts.xFriction;
this.y += this.vy += opts.gravity;
var proportion = 1 - (this.y - this.starty) / (h - this.starty);
webgl.data.push(
px, py, this.parent.ph, opts.projectileAlpha / this.tick,
this.x, this.y, this.parent.hue, opts.projectileAlpha / this.tick);
if (this.y > h)
this.dead = true;
}
function anim() {
window.requestAnimationFrame(anim);
webgl.clear();
++tick;
if (fireworks.length < opts.fireworks)
fireworks.push(new Firework);
fireworks.map(function (firework) {
firework.step();
});
webgl.draw(gl.LINES);
}
anim();
window.addEventListener('resize', function () {
w = c.width = window.innerWidth;
h = c.height = window.innerHeight;
gl.viewport(0, 0, w, h);
gl.uniform2f(webgl.resUniformLoc, w, h);
});
body {
overflow: hidden;
margin:0;
}
#c {
position: absolute;
top: 0;
left: 0;
background-color: #111;
}
<canvas id="c"> </canvas>

WebGL - importing MD2 Model, problems with textures

I'm making a webgl demo with md2 models. I know that it is an old format but I need to use only md2.
I read the documentation for the md2 format.
I watched how this site works:
and used part of it's sources in my program (i saved the author's name in the comments)
I made vertex loading, the model loads great!
But when I'm trying to map the texutre, something strange happens:
First, I thought that it's the problem of fragment shader, but I made an export by using assimp2json, used the data from it and it was drawn as it should draw.
The problem is that assimp2json changed the order vertices in triangles and uvs order, so I can't debug the program using it.
Maybe, someone could help to find the bug, to point to the error in my code?
P. S. since there is no animation, I only use the first frame
The most interesting is that if I will pass unindexed data (just the uvs from the file) it looks more right than the indexed textures:
The problem is that it is wrong in some places, like here:
Full source code and models:
ShaderProgram.js
class ShaderProgram
{
constructor(gl, VSSource, FSSource) // VS - Vertex Shader, FS - Fragment
Shader
{
this.gl = gl;
let vertexShader = this.getShader(VSSource, gl.VERTEX_SHADER);
let fragmentShader = this.getShader(FSSource, gl.FRAGMENT_SHADER);
this.shaderProgram = gl.createProgram();
gl.attachShader(this.shaderProgram, vertexShader);
gl.attachShader(this.shaderProgram, fragmentShader);
gl.linkProgram(this.shaderProgram);
if (!gl.getProgramParameter(this.shaderProgram, gl.LINK_STATUS)) {
alert("Can't load shaders");
}
this.enableAttributes();
this.getUniforms();
}
getShader(source, type)
{
let gl = this.gl;
let shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("Error compilation: " + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
getUniforms()
{
this.model = gl.getUniformLocation(this.shaderProgram, "model");
this.view = gl.getUniformLocation(this.shaderProgram, "view");
this.projection = gl.getUniformLocation(this.shaderProgram, "projection");
}
enableAttributes()
{
let gl = this.gl;
let shaderProgram = this.shaderProgram;
}
use()
{
this.gl.useProgram(this.shaderProgram);
}
}
Textures.js
function initTexture(filename) {
let texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
let image = new Image();
image.onload = function() { handleTextureLoaded(image, texture); }
image.src = filename;
return texture;
}
function handleTextureLoaded(image, texture) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.generateMipmap(gl.TEXTURE_2D);
gl.bindTexture(gl.TEXTURE_2D, null);
}
MD2 Import:
// BinaryReader
// Refactored by Vjeux <vjeuxx#gmail.com>
// http://blog.vjeux.com/2010/javascript/javascript-binary-reader.html
// Original
//+ Jonas Raoni Soares Silva
//# http://jsfromhell.com/classes/binary-parser [rev. #1]
BinaryReader = function (data) {
this._buffer = data;
this._pos = 0;
};
BinaryReader.prototype = {
/* Public */
readInt8: function (){ return this._decodeInt(8, true); },
readUInt8: function (){ return this._decodeInt(8, false); },
readInt16: function (){ return this._decodeInt(16, true); },
readUInt16: function (){ return this._decodeInt(16, false); },
readInt32: function (){ return this._decodeInt(32, true); },
readUInt32: function (){ return this._decodeInt(32, false); },
readFloat: function (){ return this._decodeFloat(23, 8); },
readDouble: function (){ return this._decodeFloat(52, 11); },
readChar: function () { return this.readString(1); },
readString: function (length) {
this._checkSize(length * 8);
var result = this._buffer.substr(this._pos, length);
this._pos += length;
return result;
},
seek: function (pos) {
this._pos = pos;
this._checkSize(0);
},
getPosition: function () {
return this._pos;
},
getSize: function () {
return this._buffer.length;
},
/* Private */
_decodeFloat: function(precisionBits, exponentBits)
{
return this._decodeFloat2(precisionBits, exponentBits);
var length = precisionBits + exponentBits + 1;
var size = length >> 3;
this._checkSize(length);
var bias = Math.pow(2, exponentBits - 1) - 1;
var signal = this._readBits(precisionBits + exponentBits, 1, size);
var exponent = this._readBits(precisionBits, exponentBits, size);
var significand = 0;
var divisor = 2;
var curByte = length + (-precisionBits >> 3) - 1;
do
{
var byteValue = this._readByte(++curByte, size);
var startBit = precisionBits % 8 || 8;
var mask = 1 << startBit;
while (mask >>= 1)
{
if (byteValue & mask)
{
significand += 1 / divisor;
}
divisor *= 2;
}
} while (precisionBits -= startBit);
this._pos += size;
return exponent == (bias << 1) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity
: (1 + signal * -2) * (exponent || significand ? !exponent ? Math.pow(2, -bias + 1) * significand
: Math.pow(2, exponent - bias) * (1 + significand) : 0);
},
// I added this because _decodeFloat gave me some real inaccuarate results? -Terry Butler
_decodeFloat2: function(precisionBits, exponentBits)
{
var length = precisionBits + exponentBits + 1;
var value = this._decodeInt(length);
var sign = (value >> 31) & 0x1;
var allZero = 1;
var mantissa = 0.0;
var exponent = 0.0;
// Mantissa
for (var i = 22; i > -1; i--)
{
var test = 1.0 / Math.pow(2, 23-i);
if ((value >> i & 0x1) == 1)
{
mantissa += test;
allZero = 0;
}
}
if (allZero == 0)
mantissa += 1.0;
// Exponent
for (var i = 30; i > 22; i--)
{
var test = Math.pow(2, i - 23);
if ((value >> i & 0x1) == 1)
{
exponent += test;
}
}
exponent -= 127.0;
//
var total = Math.pow(2.0, exponent) * mantissa;
//
if (sign == 1)
{
total *= -1.0;
}
return total;
},
_decodeInt: function(bits, signed){
var x = this._readBits(0, bits, bits / 8), max = Math.pow(2, bits);
var result = signed && x >= max / 2 ? x - max : x;
this._pos += bits / 8;
return result;
},
//shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni)
_shl: function (a, b){
for (++b; --b; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1);
return a;
},
_readByte: function (i, size) {
return this._buffer.charCodeAt(this._pos + size - i - 1) & 0xff;
},
_readBits: function (start, length, size) {
var offsetLeft = (start + length) % 8;
var offsetRight = start % 8;
var curByte = size - (start >> 3) - 1;
var lastByte = size + (-(start + length) >> 3);
var diff = curByte - lastByte;
var sum = (this._readByte(curByte, size) >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1);
if (diff && offsetLeft) {
sum += (this._readByte(lastByte++, size) & ((1 << offsetLeft) - 1)) << (diff-- << 3) - offsetRight;
}
while (diff) {
sum += this._shl(this._readByte(lastByte++, size), (diff-- << 3) - offsetRight);
}
return sum;
},
_checkSize: function (neededBits) {
if (!(this._pos + Math.ceil(neededBits / 8) < this._buffer.length)) {
throw new Error("Index out of bound");
}
}
};
/**
* #author oosmoxiecode
* based on http://www.terrybutler.co.uk/web-development/html5-canvas-md2- renderer/
* and
* http://tfc.duke.free.fr/coding/md2-specs-en.html
*
* dependant on binaryReader.js
*
* Returns a object like: {string: json_string, info: {status: "Success", faces: 10, vertices: 10, frames: 5 }}
*
**/
// Library is modified for this program by me
function MD2_converter (file) {
var scope = this;
// Create the Binary Reader
var reader = new BinaryReader(file);
// Setup
var header = {};
var frames = [];
var st = [];
var triag = [];
var string = "";
var info = {};
var returnObject = {string: string, info: info};
// Ident and version
header.ident = reader.readString(4);
header.version = reader.readInt32();
// Valid MD2 file?
if (header.ident != "IDP2" || header.version != 8) {
info.status = "Not a valid MD2 file";
return returnObject;
}
// header
header.skinwidth = reader.readInt32(); // texture width
header.skinheight = reader.readInt32(); // texture height
header.framesize = reader.readInt32(); // size in bytes of a frame
header.num_skins = reader.readInt32(); // number of skins
header.num_vertices = reader.readInt32(); // number of vertices per frame
header.num_st = reader.readInt32(); // number of texture coordinates
header.num_tris = reader.readInt32(); // number of triangles
header.num_glcmds = reader.readInt32(); // number of opengl commands
header.num_frames = reader.readInt32(); // number of frames
header.offset_skins = reader.readInt32(); // offset skin data
header.offset_st = reader.readInt32(); // offset texture coordinate data
header.offset_tris = reader.readInt32(); // offset triangle data
header.offset_frames = reader.readInt32(); // offset frame data
header.offset_glcmds = reader.readInt32(); // offset OpenGL command data
header.offset_end = reader.readInt32(); // offset end of file
// faulty size
if (reader.getSize() != header.offset_end) {
info.status = "Corrupted MD2 file";
return returnObject;
}
// texture coordinates
let count = 0;
reader.seek(header.offset_st);
for (var i = 0; i < header.num_st; i++) {
var s = reader.readInt16();
var t = reader.readInt16();
st[i] = {
s: s / header.skinwidth,
t: t / header.skinheight
};
}
reader.seek(header.offset_tris);
for (var i = 0; i < header.num_tris; i++) {
var a = reader.readInt16();
var b = reader.readInt16();
var c = reader.readInt16();
var uva_i = reader.readUInt16();
var uvb_i = reader.readUInt16();
var uvc_i = reader.readUInt16();
triag[i] = {};
triag[i].vertex = [];
triag[i].st = [];
triag[i].vertex[0] = a;
triag[i].vertex[1] = b;
triag[i].vertex[2] = c;
triag[i].st[0] = uva_i;
triag[i].st[1] = uvb_i;
triag[i].st[2] = uvc_i;
}
// frames
reader.seek(header.offset_frames);
for (var f = 0; f < header.num_frames; f++) {
var frame = {};
frame.name = "";
frame.scale = {};
frame.translate = {};
frame.scale.x = reader.readFloat();
frame.scale.y = reader.readFloat();
frame.scale.z = reader.readFloat();
frame.translate.x = reader.readFloat();
frame.translate.y = reader.readFloat();
frame.translate.z = reader.readFloat();
frame.vertices = [];
frame.name = reader.readString(16).replace(/[^a-z0-9]/gi,''); // 4+4+4 4+4+4 (12 + 12) = 24 + 16 = 40
for (var v = 0; v < header.num_vertices; v++) {
var tempX = reader.readUInt8();
var tempY = reader.readUInt8();
var tempZ = reader.readUInt8();
var normal = reader.readUInt8();
var xx = frame.scale.x * tempX + frame.translate.x;
var yy = frame.scale.z * tempZ + frame.translate.z;
var zz = frame.scale.y * tempY + frame.translate.y;
let vertex = [];
vertex[0] = xx;
vertex[1] = yy;
vertex[2] = zz;
frame.vertices.push(vertex);
}
frames.push(frame);
}
let res = {};
res.st = st;
res.triag = triag;
res.frame = [];
for (var i=0; i<frames.length; ++i )
res.frame[i]=frames[i];
res.faces_count = header.num_tris;
res.vertices_count = header.num_vertices;
res.frames_count = header.num_frames;
return res;
}
main.js
let gl;
let shaderProgram;
let firstMouse = true;
let mouseDown = false;
let lastTime = 0;
let deltaTime;
let test;
let lastX= 0;
let lastY = 0;
let DIRS = {
Forward: 0,
Backward: 1,
Left: 2,
Right: 3
};
let KeyCodes =
{
Up: 38,
Down : 40,
Left : 37,
Right : 39,
W : 87,
S : 83,
A : 65,
D : 68,
Q : 81,
E : 69,
PageDown : 34,
PageUp : 33
};
let pressedKeys = [];
let vertexShaderSource = `
precision highp float;
attribute vec3 aPos;
attribute vec2 aTexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
varying vec2 vTexCoord;
void main(void)
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
vTexCoord = aTexCoord;
}
`;
let fragmentShaderSource = `
precision highp float;
varying highp vec2 vTexCoord;
uniform sampler2D uSampler;
void main(void)
{
gl_FragColor = texture2D(uSampler, vec2(vTexCoord.s,1.0-vTexCoord.t));
}
`;
let camera;
function initGL()
{
let canvas = document.getElementById("Canvas3D");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
try
{
gl = canvas.getContext("webgl2") || canvas.getContext("experimental-webgl2");
}
catch(e)
{
alert("Your browser don't support WebGL");
}
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
shaderProgram = new ShaderProgram(gl,vertexShaderSource,fragmentShaderSource);
shaderProgram.use();
camera = {
Init(shaderProgram)
{
this.shaderProgram = shaderProgram;
this.pos = vec3.create();
this.up = vec3.create();
this.front = vec3.create();
this.right = vec3.create();
this.WorldUp = vec3.create();
this.pos = [51.656294013839215, 36.9293564686086, -28.23351748054847];
this.front = [-0.7667674915573891, -0.6401096994849556, 0.04824092159224934];
this.up = [-0.6388465891741784, 0.7682835235935235, 0.040192821190338686];
this.WorldUp = [0,1,0];
this.yaw = 176.39999999999998;
this.pitch = -39.8;
this.speed = 25;
this.sensivity = 0.1;
this.zoom = 100;
this.view = mat4.create();
this.projection = mat4.create();
mat4.perspective(this.projection, Math.PI/180.0*this.zoom, gl.viewportWidth/gl.viewportHeight, 0.1, 200);
this.shaderProgram.gl.uniformMatrix4fv(this.shaderProgram.projection, false, this.projection);
this.updateCameraVectors();
this.updateView();
},
updateView: function()
{
let tmp = vec3.create();
vec3.add(tmp, this.pos, this.front);
mat4.lookAt(this.view, this.pos, tmp, this.up);
this.shaderProgram.gl.uniformMatrix4fv(this.shaderProgram.view, false, this.view);
},
processKeyboard(deltaTime)
{
let velocity = this.speed * deltaTime;
let tmp = vec3.create();
if (pressedKeys[KeyCodes.W] || pressedKeys[KeyCodes.Up]) {
vec3.scale(tmp, this.front, velocity);
vec3.add(this.pos,this.pos,tmp);
}
if (pressedKeys[KeyCodes.S] || pressedKeys[KeyCodes.Down]) {
velocity *=(-1);
vec3.scale(tmp, this.front, velocity);
vec3.add(this.pos,this.pos,tmp);
}
if (pressedKeys[KeyCodes.A] || pressedKeys[KeyCodes.Left]) {
velocity *=(-1);
vec3.scale(tmp, this.right, velocity);
vec3.add(this.pos,this.pos,tmp);
}
if (pressedKeys[KeyCodes.D] || pressedKeys[KeyCodes.Right]) {
vec3.scale(tmp, this.right, velocity);
vec3.add(this.pos,this.pos,tmp);
}
},
processMouseMovement(xoffset, yoffset)
{
this.yaw += (xoffset*this.sensivity);
this.pitch += (yoffset*this.sensivity);
if (this.pitch > 89.0)
this.pitch = 89.0;
if (this.pitch < -89.0)
this.pitch = -89.0;
this.updateCameraVectors();
},
updateCameraVectors()
{
let ToRads = Math.PI/180;
let yaw = this.yaw;
let pitch = this.pitch;
this.front[0] = Math.cos(ToRads*yaw) * Math.cos(ToRads*pitch);
this.front[1] = Math.sin(ToRads*pitch);
this.front[2] = Math.sin(ToRads*yaw) * Math.cos(ToRads*pitch);
vec3.normalize(this.front, this.front);
vec3.cross(this.right, this.front, this.WorldUp);
vec3.normalize(this.right, this.right);
vec3.cross(this.up, this.right, this.front);
vec3.normalize(this.up, this.up);
}
};
function load_binary_resource(url) {
let req = new XMLHttpRequest();
req.open('GET', url, false);
req.overrideMimeType('text/plain; charset=x-user-defined'); // No unicode data
req.send(null);
if (req.status != 200) return '';
return req.responseText;
}
let file = load_binary_resource('../models/rhino/Tris.md2');
let res = MD2_converter(file);
let texture = initTexture("../models/rhino/rhino.png");
let verts = [];
let inds = [];
let uvs = [];
for (let i =0;i<res.frame[0].vertices.length;i++)
{
let vert = res.frame[0].vertices[i];
verts.push(vert[0]);
verts.push(vert[1]);
verts.push(vert[2]);
}
for (let i =0;i<res.triag.length;i++)
{
let triag = res.triag[i];
inds.push(triag.vertex[0]);
inds.push(triag.vertex[1]);
inds.push(triag.vertex[2]);
uvs.push(res.st[triag.st[0]].s);
uvs.push(res.st[triag.st[0]].t);
uvs.push(res.st[triag.st[1]].s);
uvs.push(res.st[triag.st[1]].t);
uvs.push(res.st[triag.st[2]].s);
uvs.push(res.st[triag.st[2]].t);
}
test = new Drawable(shaderProgram, verts, inds, uvs, texture);
camera.Init(shaderProgram);
requestAnimationFrame(gameCycle);
}
function recalculateFPS(gotTime)
{
deltaTime = (gotTime - lastTime)/1000;
lastTime = gotTime;
}
function gameCycle(gotTime)
{
recalculateFPS(gotTime);
gl.clearColor(0,0,0,1);
gl.clear(gl.COLOR_BUFFER_BIT);
camera.processKeyboard(deltaTime);
camera.updateView();
test.draw();
requestAnimationFrame(gameCycle);
}
window.onload=function()
{
initGL();
};
document.onkeydown = function(e)
{
pressedKeys[e.keyCode]=true;
};
document.onkeyup = function(e)
{
pressedKeys[e.keyCode]=false;
};
document.body.onmousedown = function(event)
{
mouseDown = true;
lastX = event.clientX;
lastY = event.clientY;
};
document.body.onmouseup = function(event)
{
mouseDown = false;
};
document.body.onmouseout = function(event)
{
mouseDown = false;
};
document.body.onmousemove = function (e)
{
if (!mouseDown)
return;
let xpos = e.clientX;
let ypos = e.clientY;
if(firstMouse) {
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
let xoffset = xpos - lastX;
let yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
camera.processMouseMovement(xoffset,yoffset);
}
main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body scroll="no" style="overflow: hidden">
<canvas id="Canvas3D"></canvas>
<script src="../js/glmatrix/dist/gl-matrix.js"></script>
<script src="../js/MD2Import.js"></script>
<script src="../js/ShaderProgram.js"></script>
<script src="../js/Textures.js"></script>
<script src="../js/Drawable.js"></script>
<script src="../js/main.js"></script>
</body>
</html>
Drawable.js
class Drawable {
constructor(shaderProgram, vertices, indices, texCoords, texture) {
this.gl = shaderProgram.gl;
let gl = shaderProgram.gl;
this.shaderProgram = shaderProgram;
shaderProgram.aTexCoord = gl.getAttribLocation(shaderProgram.shaderProgram, "aTexCoord");
shaderProgram.aPos = gl.getAttribLocation(shaderProgram.shaderProgram, "aPos");
this.VAO = gl.createVertexArray();
this.texture = texture;
gl.bindVertexArray(this.VAO);
this.vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.vertexAttribPointer(shaderProgram.aPos, 3, gl.FLOAT, false, 12, 0);
gl.enableVertexAttribArray(shaderProgram.aPos);
this.EBO = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.EBO);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
this.VBOTexCoords = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.VBOTexCoords);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords),gl.STATIC_DRAW);
gl.vertexAttribPointer(shaderProgram.aTexCoord, 2, gl.FLOAT, false, 8, 0);
gl.enableVertexAttribArray(shaderProgram.aTexCoord);
gl.bindVertexArray(null);
this.pos = vec3.create();
this.rot = vec3.create(); // в Градусах
this.scale = vec3.create();
this.pos = [0,0,0];
this.rot = [0,0,0];
this.scale=[1,1,1];
this.vertCount = indices.length;
this.model = mat4.create();
}
updateModel() {
mat4.identity(this.model);
mat4.translate(this.model, this.model, this.pos);
mat4.rotateX(this.model, this.model, Math.PI / 180 * this.rot[0]);
mat4.rotateY(this.model, this.model, Math.PI / 180 * this.rot[1]);
mat4.rotateZ(this.model, this.model, Math.PI / 180 * this.rot[2]);
mat4.scale(this.model, this.model, this.scale);
}
translate(transX, transY, transZ)
{
this.pos[0] += transX;
this.pos[1] += transY;
this.pos[2] += transZ;
}
rotate(rotX, rotY, rotZ)
{
this.rot[0] += rotX;
this.rot[1] += rotY;
this.rot[2] += rotZ;
}
draw() {
this.updateModel();
let gl =this.gl;
gl.uniformMatrix4fv(this.shaderProgram.model, false, this.model);
gl.bindVertexArray(this.VAO);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.uniform1i(gl.getUniformLocation(shaderProgram.shaderProgram, "uSampler"), 0);
//gl.drawArrays(gl.TRIANGLES, 0, this.vertCount);
gl.drawElements(gl.TRIANGLES, this.vertCount,gl.UNSIGNED_SHORT,0);
}
}
Tried using gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true), result:
By the way, it shouldn't work, because I already flipped the textures in the shader:
gl_FragColor = texture2D(uSampler, vec2(vTexCoord.s,1.0 - vTexCoord.t));
rhino.png (textures)
The md2 file: http://dropmefiles.com/zhQFU
The problem solved when I deleted the index buffer and placed the vertices and uvs in one huge buffer. What it could be?
The solution was to delete index buffer and place everything in the vertex buffer. It worked for me

Text smear effect in JavaScript

I'm pretty experienced with Javascript but haven't delved much into its advanced graphics capabilities (canvas, webGL, three.js, etc). I want to create a distortion effect kind of like this one, except I'd like to apply it to text instead of an image. Basically I want to have some text that looks like plain HTML at first glance but when the user mouses over it, the text should bend/warp/smear in response.
So far I've found two SO posts that are similar but not exactly what I want: the first is too simple, as I want to warp and bend the text, not just shift it down the page. The second is more interesting, as I have a hunch I'll need to use a library like Three.js to achieve this effect, but I want something 2d, not 3d, and I want to actually warp the "shape" of the text, not just spin it around an axis.
I'm wondering how to create this effect, whether there is a name for the specific effect I want (have had trouble finding good examples online), any good examples, advice, anything really. Thanks in advance!
Many possibilities.
Here is an example of a simple WEBGL 2D texture drawn onto a standard 2D canvas. There is a bit of boilerplate for mouse,canvas,webGL so you will have to pick it apart yourself.
The FX is in the Fragment Shader. Rather than move the texture coords I just mapped a 2D vector field over the image (pretty much randomly made it up as i went) The vectors offset the pixel lookup from the texture. The amount controlled by mouse up and down controls the amount of the FX and the mouse from left to right moves the Phase setting.
Moving mouse to the top of the image reduces the effect amount. Bottom right is at max.
.
The function at the bottom webGLRender sets the fragment shader values and renders the webGl then 2D context drawImage to render to display canvas. The Fragment shader is above that.
As the webGL image is rendered via ctx.draw2D it is easy to resize making the webGL render total display resolution independent. If you have performance issues (image in the mega pixel * 4 + range) you can reduce the input image size
WebGL can not render images that are not from the same domain (tained) unlike 2D canvas webGL requires access to the pixel data to draw textures and thus tained images will make it throw security errors. I have used a 2d canvas rather than an image as I could not find an image the would not taint the canvas.
// boiler plate
const U = undefined;const RESIZE_DEBOUNCE_TIME = 100;
var w,h,cw,ch,canvas,ctx,mouse,createCanvas,resizeCanvas,setGlobals,globalTime=0,resizeCount = 0;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () { var c,cs; cs = (c = document.createElement("canvas")).style; cs.position = "absolute"; cs.top = cs.left = "0px"; cs.zIndex = 1000; document.body.appendChild(c); return c;}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); } canvas.width = window.innerWidth; canvas.height = window.innerHeight; ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); } if (typeof onResize === "function"){ resizeCount += 1; setTimeout(debounceResize,RESIZE_DEBOUNCE_TIME);}
}
function debounceResize(){ resizeCount -= 1; if(resizeCount <= 0){ onResize();}}
setGlobals = function(){ cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; mouse.updateBounds(); }
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0, over : false, bm : [1, 2, 4, 6, 5, 3],
active : false,bounds : null, crashRecover : null, mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.x = e.clientX - m.bounds.left; m.y = e.clientY - m.bounds.top;
m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }
else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
else if (t === "mouseover") { m.over = true; }
else if (t === "mousewheel") { m.w = e.wheelDelta; }
else if (t === "DOMMouseScroll") { m.w = -e.detail; }
if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
if((m.buttonRaw & 2) && m.crashRecover !== null){ if(typeof m.crashRecover === "function"){ setTimeout(m.crashRecover,0);}}
e.preventDefault();
}
m.updateBounds = function(){
if(m.active){
m.bounds = m.element.getBoundingClientRect();
}
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === U) { m.callbacks = [callback]; }
else { m.callbacks.push(callback); }
} else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
if (m.element !== U) { m.removeMouse(); }
m.element = element === U ? document : element;
m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
m.active = true;
m.updateBounds();
}
m.remove = function () {
if (m.element !== U) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
m.element = m.callbacks = m.contextMenuBlocked = U;
m.active = false;
}
}
return mouse;
})();
resizeCanvas();
mouse.start(canvas,true);
window.addEventListener("resize",resizeCanvas);
function display(){
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
if(webGL !== undefined){
webGLRender();
}
}
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
requestAnimationFrame(update);
var globalTime = new Date().valueOf(); // global to this
// creates vertex and fragment shaders
function createProgramFromScripts( gl, ids) {
var shaders = [];
for (var i = 0; i < ids.length; i += 1) {
var script = shadersSource[ids[i]];
if (script !== undefined) {
var shader = gl.createShader(gl[script.type]);
gl.shaderSource(shader, script.source);
gl.compileShader(shader);
shaders.push(shader);
}else{
throw new ReferenceError("*** Error: unknown script ID : " + ids[i]);
}
}
var program = gl.createProgram();
shaders.forEach((shader) => { gl.attachShader(program, shader); });
gl.linkProgram(program);
return program;
}
// setup simple 2D webGL image processor
var webGL;
function startWebGL(image) {
// Get A WebGL context
webGL = document.createElement("canvas");
webGL.width = image.width;
webGL.height = image.height;
webGL.gl = webGL.getContext("webgl");
var gl = webGL.gl;
var program = createProgramFromScripts(gl, ["VertexShader", "FragmentShader"]);
gl.useProgram(program);
var positionLocation = gl.getAttribLocation(program, "a_position");
var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
var texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.0, 0.0,1.0, 0.0,0.0, 1.0,0.0, 1.0,1.0, 0.0,1.0, 1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(texCoordLocation);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
// lookup uniforms for frag shader
var locs = {}
locs.timer = gl.getUniformLocation(program, "time"); // the time used to control waves
locs.phase = gl.getUniformLocation(program, "phase"); // Sort of phase, moves to attractors around
locs.amount = gl.getUniformLocation(program, "amount"); // Mix amount of effect and flat image
webGL.locs = locs;
gl.uniform2f(resolutionLocation, webGL.width, webGL.height);
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
setRectangle(gl, 0, 0, image.width, image.height);
}
function setRectangle(gl, x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2]), gl.STATIC_DRAW);
}
function randomInt(range) {
return Math.floor(Math.random() * range);
}
var shadersSource = {
VertexShader : {
type : "VERTEX_SHADER",
source : `
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform vec2 u_resolution;
varying vec2 v_texCoord;
void main() {
vec2 zeroToOne = a_position / u_resolution;
vec2 zeroToTwo = zeroToOne * 2.0;
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
v_texCoord = a_texCoord;
}`
},
FragmentShader : {
type : "FRAGMENT_SHADER",
source : `
precision mediump float;
uniform sampler2D u_image;
uniform float time;
uniform float phase;
uniform float amount;
varying vec2 v_texCoord;
vec2 offset;
float dist;
float edge;
float v;
vec2 pos1 = vec2(0.5 + sin(phase * 0.03)*1.3, 0.5 + cos(phase * 0.032)*1.3);
vec2 pos2 = vec2(0.5 + cos(phase * 0.013)*1.3,0.5 + cos(phase*0.012)*1.3);
void main() {
dist = distance(pos1,v_texCoord) * distance(pos2,v_texCoord);
edge = 1. - distance(vec2(0.5,0.5),v_texCoord) / 0.707;
v = time * dist * 0.0001 * edge * phase;
offset = vec2(
v_texCoord.x + sin(v+time) * 0.1 * edge * amount,
v_texCoord.y + cos(v+time) * 0.1 * edge * amount
);
//offset = smoothstep(v_texCoord.x,offset.x,abs(0.5-v_textCoord.x) );
gl_FragColor = texture2D(u_image, offset);
}`
}
}
var md = 0;
var mr = 0;
var mdy = 0;
var mry = 0;
function webGLRender(){
var gl = webGL.gl;
md += (mouse.x / canvas.width - mr) * 0.16;
md *= 0.18;
mr += md;
mdy += (mouse.y - mry) * 0.16;
mdy *= 0.18;
mry += mdy;
gl.uniform1f(webGL.locs.timer, globalTime/100);
gl.uniform1f(webGL.locs.phase, mr * 400);
gl.uniform1f(webGL.locs.amount, ((mry/canvas.height)) * 9);
gl.drawArrays(gl.TRIANGLES, 0, 6);
ctx.drawImage(webGL,0,0, canvas.width, canvas.height);
}
var image = document.createElement("canvas");
image.width = 1024;
image.height = 512;
image.ctx = image.getContext("2d");
image.ctx.font = "192px Arial";
image.ctx.textAlign = "center";
image.ctx.textBaseline = "middle";
image.ctx.lineJoin = "round";
image.ctx.lineWidth = 32;
image.ctx.strokeStyle = "red";
image.ctx.fillStyle = "black";
image.ctx.strokeText("WOBBLE",512,256);
image.ctx.lineWidth = 16;
image.ctx.strokeStyle = "white";
image.ctx.strokeText("WOBBLE",512,256);
image.ctx.fillText("WOBBLE",512,256);
image.ctx.font = "32px Arial";
image.ctx.fillText("Mouse position on image controls wobble",512,32);
image.ctx.fillText("Using WebGL and 2D Canvas",512,512-32);
startWebGL(image);
/*var image = new Image(); // load image
image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1"; // MUST BE SAME DOMAIN!!!
image.onload = function() {
startWebGL(image);
}*/

unable to stop at breakpoints

The debugger statement (billed as surefire way to stop) gives me an "ERROR undeclared identifier" in chrome. Source code is JavaScript WebGL
I have even tried to set breakpoints in pages that run correctly. I have enabled WebGL Inspector extension and checked the "Allow access to file URLs" box
I'm sorry to bother anyone because I am missing something obvious and basic.
Julia mine
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 vPos;
void main(void) {
gl_Position = vec4(vPos, 1.);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
uniform vec2 c;
uniform vec2 scale;
void main(void) {
float x1 = 2.0;
float y1 = -2.0;
float R = (gl_FragCoord.x - scale.x) / scale.y;
float I = (gl_FragCoord.y - scale.x) / scale.y;
// float R2 = R*R, I2 = I*I;
float R2 = R;
debugger;
float I2 = I;
int mm;
for (int m = 0; m < 255; m++) {
debugger;
x1 = 2.0 + exp(R2) / 2.0 * cos(I2);
y1 = -2.0 + exp(R2) * sin(I2);
if (x1 >= 0.0000001) break;
R2 = x1;
I2 = y1;
} //end for m
if (mm == 254) gl_FragColor = vec4(0., 0., 0., 1.);
else {
float a = float(mm);
a = mod(a, 15.) / 5.;
gl_FragColor = vec4(max(0., abs(a - 1.5) - .5),
max(0., 1. - abs(a - 0.8)), max(0., 1. - abs(a - 2.)), 0.94);
} //end else
}
</script>
<script type="text/javascript">
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
var str = "";
var k = shaderScript.firstChild;
while (k) {
if (k.nodeType == 3) str += k.textContent;
k = k.nextSibling;
}
var shader;
if (shaderScript.type == "x-shader/x-fragment")
shader = gl.createShader(gl.FRAGMENT_SHADER);
else if (shaderScript.type == "x-shader/x-vertex")
shader = gl.createShader(gl.VERTEX_SHADER);
else return null;
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) == 0)
alert(gl.getShaderInfoLog(shader));
return shader;
}
var gl, canvas;
var cLoc, size, frames = 0,
timer_fr, time,
n = 8,
k, To = 30,
T, Tp, animation = true;
var orb = [
[.248, 0, .15],
[.27, 0, .2],
[.33, .033, .1],
[.42, .228, .1],
[.27, .564, .1],
[-.162, .78, .1],
[-.534, .612, .1],
[-.726, .3, .1],
[-.75, .0, .05],
[.248, 0, .15]
];
function webGLStart() {
canvas = document.getElementById("canned");
size = Math.min(window.innerWidth, window.innerHeight) - 35;
canvas.width = size;
canvas.height = size;
if (!window.WebGLRenderingContext) {
alert("Your browser does not support WebGL. See http://get.webgl.org");
return;
}
try {
gl = canvas.getContext("experimental-webgl");
} catch (e) {}
if (!gl) {
alert("Can't get WebGL");
return;
}
gl.viewport(0, 0, size, size);
var prog = gl.createProgram();
gl.attachShader(prog, getShader(gl, "shader-vs"));
gl.attachShader(prog, getShader(gl, "shader-fs"));
gl.linkProgram(prog);
gl.useProgram(prog);
var posAtrLoc = gl.getAttribLocation(prog, "vPos");
gl.enableVertexAttribArray(posAtrLoc);
var posBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
var vertices = new Float32Array([-1, -1, 0, 1, -1, 0, -1, 1, 0, 1, 1, 0]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(posAtrLoc, 3, gl.FLOAT, false, 0, 0);
cLoc = gl.getUniformLocation(prog, "c");
gl.uniform2f(gl.getUniformLocation(prog, "scale"), size / 2, size / 3);
time = new Date().getTime();
k = 0;
Tp = -1;
T = time / 1000 + orb[k][2] * To;
timer_fr = setInterval(fr, 500);
anim();
canvas.resize = function () {
var size = Math.min(window.innerWidth, window.innerHeight) - 35;
canvas.width = size;
canvas.height = size;
gl.uniform2f(gl.getUniformLocation(prog, "scale"), size / 2, size / 3);
gl.viewport(0, 0, size, size);
draw();
}
}
function anim() {
var tim = new Date().getTime() / 1000;
var a = (T - tim) / (To * orb[k][2]);
gl.uniform2f(cLoc, orb[k][0] * a + orb[k + 1][0] * (1 - a),
orb[k][1] * a + orb[k + 1][1] * (1 - a));
draw();
if (tim > T) {
k++;
if (k > n) k = 0;
T += orb[k][2] * To;
}
frames++;
if (animation) requestAnimationFrame(anim);
}
function draw() {
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
function setT(v) {
To = v.valueOf();
}
function run(v) {
if (animation) {
animation = false;
Tp = new Date().getTime() / 1000;
document.getElementById('runBtn').value = "Run ";
} else {
animation = true;
if (Tp > 0) {
T += new Date().getTime() / 1000 - Tp;
Tp = -1;
}
anim();
document.getElementById('runBtn').value = "Stop";
}
}
function fr() {
var ti = new Date().getTime();
var fps = Math.round(1000 * frames / (ti - time));
document.getElementById("framerate").value = fps;
frames = 0;
time = ti;
}
</script>
<br>T
<input size="2" value="30" onchange="setT( this.value )">sec
<input type="button" onclick="run()" value="Stop" size="1" id="runBtn">fps
<input size="2" id="framerate">
<br>Julia sets animation (canvas is matched to the browser window and you can change period of animation <i>T</i>). Simplified remake of the Java based
Julia Orbit trip.
<i>C</i> is moved near the main cardioid of the Mandelbrot set.
<hr>WebGL Demos
<i>updated</i> 18 August 2010
You cant use the "debugger" statement in glsl shader code.
The "debugger" statement is an extension by the developer tools and is only valid in javascript.

How to draw 3d objects on a 2d canvas

here's the full code, I had to remove spaces from some of the functions that weren't related to the problem to make sure im in the 30k character limit of stack overflow
const EPSILON = 0.000001;
const mat4 = { rotateZ: function(out, a, rad) { let s = Math.sin(rad); let c = Math.cos(rad); let a00 = a[0]; let a01 = a[1]; let a02 = a[2]; let a03 = a[3]; let a10 = a[4]; let a11 = a[5]; let a12 = a[6]; let a13 = a[7]; if (a !== out) { out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } out[0] = a00 * c + a10 * s; out[1] = a01 * c + a11 * s; out[2] = a02 * c + a12 * s; out[3] = a03 * c + a13 * s; out[4] = a10 * c - a00 * s; out[5] = a11 * c - a01 * s; out[6] = a12 * c - a02 * s; out[7] = a13 * c - a03 * s; return out; }, create: function() { let out = new Float32Array(16); out[0] = 1; out[5] = 1; out[10] = 1; out[15] = 1; return out; }, perspective: function(out, fovy, aspect, near, far) { let f = 1.0 / Math.tan(fovy / 2), nf; out[0] = f / aspect; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = f; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[11] = -1; out[12] = 0; out[13] = 0; out[15] = 0; if (far !== null && far !== Infinity) { nf = 1 / (near - far); out[10] = (far + near) * nf; out[14] = (2 * far * near) * nf; } else { out[10] = -1; out[14] = -2 * near; } return out; }, translate: function(out, a, v) { let x = v[0], y = v[1], z = v[2]; if (a === out) { out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; return out; } else { let a00, a01, a02, a03; let a10, a11, a12, a13; let a20, a21, a22, a23; a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3]; a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7]; a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11]; out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03; out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13; out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23; out[12] = a00 * x + a10 * y + a20 * z + a[12]; out[13] = a01 * x + a11 * y + a21 * z + a[13]; out[14] = a02 * x + a12 * y + a22 * z + a[14]; out[15] = a03 * x + a13 * y + a23 * z + a[15]; return out; } }, scale: function(out, a, v) { let x = v[0], y = v[1], z = v[2]; out[0] = a[0] * x; out[1] = a[1] * x; out[2] = a[2] * x; out[3] = a[3] * x; out[4] = a[4] * y; out[5] = a[5] * y; out[6] = a[6] * y; out[7] = a[7] * y; out[8] = a[8] * z; out[9] = a[9] * z; out[10] = a[10] * z; out[11] = a[11] * z; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return out; }, multiply: function(out, a, b) { let a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; let a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; let a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; let a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33; b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7]; out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33; b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11]; out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33; b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15]; out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33; return out; }, lookAt: function(out, eye, center, up) { let x0, x1, x2, y0, y1, y2, z0, z1, z2, len; let eyex = eye[0]; let eyey = eye[1]; let eyez = eye[2]; let upx = up[0]; let upy = up[1]; let upz = up[2]; let centerx = center[0]; let centery = center[1]; let centerz = center[2]; if (Math.abs(eyex - centerx) < EPSILON && Math.abs(eyey - centery) < EPSILON && Math.abs(eyez - centerz) < EPSILON) { return identity(out); } z0 = eyex - centerx; z1 = eyey - centery; z2 = eyez - centerz; len = 1 / Math.hypot(z0, z1, z2); z0 *= len; z1 *= len; z2 *= len; x0 = upy * z2 - upz * z1; x1 = upz * z0 - upx * z2; x2 = upx * z1 - upy * z0; len = Math.hypot(x0, x1, x2); if (!len) { x0 = 0; x1 = 0; x2 = 0; } else { len = 1 / len; x0 *= len; x1 *= len; x2 *= len; } y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0; len = Math.hypot(y0, y1, y2); if (!len) { y0 = 0; y1 = 0; y2 = 0; } else { len = 1 / len; y0 *= len; y1 *= len; y2 *= len; } out[0] = x0; out[1] = y0; out[2] = z0; out[3] = 0; out[4] = x1; out[5] = y1; out[6] = z1; out[7] = 0; out[8] = x2; out[9] = y2; out[10] = z2; out[11] = 0; out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez); out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez); out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez); out[15] = 1; return out; }, moveToVec3: function(out, v) { out[12] = v[0]; out[13] = v[1]; out[14] = v[2]; } }; const mat3 = { clone: function(a) { let out = new Float32Array(9); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; return out; }, create: function() { let out = new Float32Array(9); out[0] = 1; out[4] = 1; out[8] = 1; return out; } }; const vec3 = { multiply: function(out, a, b) { out[0] = a[0] * b[0]; out[1] = a[1] * b[1]; return out; }, create: function() { return new Float32Array(3);; }, copy: function(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; return out; } }; const vec2 = { create: function() { return new Float32Array(2);; }, copy: function(out, a) { out[0] = a[0]; out[1] = a[1]; return out; }, fromValues: function(x, y) { let out = new Float32Array(2); out[0] = x; out[1] = y; return out; }, multiply: function(out, a, b) { out[0] = a[0] * b[0]; out[1] = a[1] * b[1]; return out; }, add: function(out, a, b) { out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; return out; } };
const FRAGMENT_SHADER = ` precision highp float; varying highp vec2 vTextureCoord; varying lowp vec4 vColor; uniform sampler2D uSampler; uniform bool aUseText; void main(void) { if( aUseText ){ gl_FragColor = texture2D(uSampler, vTextureCoord); } else { gl_FragColor = vColor; } } `;
const VERTEX_SHADER = ` attribute vec4 aVertexPosition; attribute vec4 aVertexColor; attribute vec2 aTextureCoord; uniform mat4 uModelViewMatrix; uniform mat4 uProjectionMatrix; uniform mat3 uTextMatrix; uniform float uPointSize; varying lowp vec4 vColor; varying highp vec2 vTextureCoord; void main(void) { gl_PointSize = uPointSize; gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; vColor = aVertexColor; vTextureCoord = (vec3(aTextureCoord, 1)*uTextMatrix).xy; } `;
class WebglEntity {
constructor() {
this.matrix = mat4.create();
this.coords = vec3.create();
}
translate(newCoords) {
const {
matrix,
coords
} = this;
mat4.translate(matrix, matrix, newCoords);
vec3.copy(coords, [matrix[12], matrix[13], matrix[14]]);
return this;
}
move(newCoords) {
const {
matrix,
coords
} = this;
vec3.copy(coords, newCoords);
mat4.moveToVec3(matrix, coords);
return this;
}
}
class Camera extends WebglEntity {
constructor(fieldOfView, aspect, zNear, zFar) {
super();
this.projection = mat4.perspective(mat4.create(), fieldOfView, aspect, zNear, zFar);
}
lookAt(lookAt) {
const {
matrix,
projection,
coords
} = this;
mat4.lookAt(matrix, coords, lookAt, [0, 1, 0]);
mat4.multiply(matrix, projection, matrix);
return this;
}
}
class Rect extends WebglEntity{
constructor(){
super();
this.positionsBuffer = undefined;
this.fragColorPos = undefined;
this.strokeColorPos = undefined;
this.strokePositionBuffer = undefined;
this.vertexAttribInfo = undefined;
this.vertextColorAttribInfo = undefined;
this.vertexCount = undefined;
this.textureInfo = undefined;
this.multiTextures = false;
this.strokeSize = 1;
this.fillers = {
fill: false,
texture: false,
stroke: false
};
}
setup(matrix, positionsBuffer, strokePositionBuffer, vertexAttribInfo, vertextColorAttribInfo, vertexCount){
this.matrix = matrix;
this.positionsBuffer = positionsBuffer;
this.strokePositionBuffer = strokePositionBuffer;
this.vertexAttribInfo = vertexAttribInfo;
this.vertextColorAttribInfo = vertextColorAttribInfo;
this.vertexCount = vertexCount;
return this;
}
}
class Display{
constructor(gl, programInfo, zAxis, texture){
this.gl = gl;
this.programInfo = programInfo;
this.canvas = gl.canvas;
this.currentCamera = new Camera(45 * Math.PI / 180, gl.canvas.width/gl.canvas.height, 0.1, 100.0);
this.currentCamera.translate([0, 0, zAxis]).lookAt([0, 0, 0]);
this.zAxis = zAxis;
this.drawZAxis = 0;
this.last = {};
texture.textAttribInfo = {
numComponents: 2,
type: gl.FLOAT,
normalize: false,
stride: 0,
offset: 0
};
this.texture = texture;
this.spriteSheets = [];
const context = texture.context;
const canvas = texture.canvas;
this.images = {};
}
clear(color){
const gl = this.gl;
gl.clearColor(0.1, 0.1, 0.1, 1);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
rect(x, y, w, h){
const {rect, stroke} = this.createRectPos(w, h);
const square = new Rect();
square.setup(...this.getRectInfo(x, y, rect, stroke));
return square;
}
fillRect(rect, color){
const {createStaticDrawBuffer, gl, parseColor} = this;
rect.fillers.fill = true;
if(color){
rect.fragColorPos = createStaticDrawBuffer(gl, [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]);
}
}
createRectPos(w, h){
const rect = [ w/2, h/2, -w/2, h/2, w/2, -h/2, -w/2, -h/2 ];
const stroke = [ -w/2, h/2, w/2, h/2, w/2, -h/2, -w/2, -h/2, ];
return {rect, stroke};
}
getRectInfo(x, y, rect, stroke){
return this.createSquareBuffer(rect, stroke, [x, y, this.drawZAxis]);
}
createStaticDrawBuffer(gl, data){
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
return buffer;
}
createSquareBuffer(positions, strokePosition, coords) {
const {gl, createStaticDrawBuffer} = this;
const positionsBuffer = createStaticDrawBuffer(gl, positions);
const strokePositionBuffer = createStaticDrawBuffer(gl, strokePosition);
const modelViewMatrix = mat4.create();
mat4.translate(modelViewMatrix, modelViewMatrix, coords);
return [modelViewMatrix, positionsBuffer, strokePositionBuffer, this.createAttribInfo(2, gl.FLOAT, false, 0, 0), this.createAttribInfo(4, gl.FLOAT, false, 0, 0), positions.length/2]; }
createAttribInfo(numComponents, type, normalize, stride, offset){
return { numComponents, type, normalize, stride, offset};
}
enableAttrib(buffer, attrib, gl, {numComponents, type, normalize, stride, offset}){
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(attrib, numComponents,type,normalize,stride,offset);
gl.enableVertexAttribArray(attrib);
}
drawBuffer(buffer){
const {gl, drawTexture, enableAttrib, createStaticDrawBuffer, currentCamera, texture: {context, canvas, textAttribInfo}, programInfo: {uniformLocations, program, attribLocations: {vertexPosition, vertexColor, textureCoord}}} = this;
const cameraMatrix = currentCamera.matrix;
const {positionsBuffer, fragColorPos, strokeColorPos, strokePositionBuffer, matrix, vertexAttribInfo, vertextColorAttribInfo, vertexCount, fragTextPos, fillers: {fill, stroke, texture}, strokeSize, textureInfo, multiTextures} = buffer;
gl.uniformMatrix4fv(uniformLocations.projectionMatrix, false, cameraMatrix);
gl.uniformMatrix4fv(uniformLocations.modelViewMatrix, false, matrix);
if(fill){
enableAttrib(positionsBuffer, vertexPosition, gl, vertexAttribInfo);
enableAttrib(fragColorPos, vertexColor, gl, vertextColorAttribInfo);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount);
gl.disableVertexAttribArray(vertexColor);
}
}
static loadShader(gl, program, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
gl.attachShader(program, shader);
}
static async create(canvas, width, height, zAxis = 6){
canvas.width = width;
canvas.height = height;
const gl = canvas.getContext("webgl");
const shaderProgram = gl.createProgram();
Display.loadShader(gl, shaderProgram, gl.VERTEX_SHADER, VERTEX_SHADER);
Display.loadShader(gl, shaderProgram, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);
gl.linkProgram(shaderProgram);
const programInfo = {
program: shaderProgram,
attribLocations: {
vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'),
textureCoord: gl.getAttribLocation(shaderProgram, 'aTextureCoord'),
},
uniformLocations: {
projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
textMatrix: gl.getUniformLocation(shaderProgram, 'uTextMatrix'),
sampler: gl.getUniformLocation(shaderProgram, 'uSampler'),
useText: gl.getUniformLocation(shaderProgram, 'aUseText'),
pointSize: gl.getUniformLocation(shaderProgram, 'uPointSize'),
},
};
gl.useProgram(programInfo.program);
gl.uniform1f(programInfo.uniformLocations.pointSize, 1.0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
const textureBuffer = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, textureBuffer);
gl.uniform1i(programInfo.uniformLocations.uSampler, 0);
const textureCanvas = document.createElement("canvas");
textureCanvas.width = 0;
textureCanvas.height = 0;
let texture = {
canvas: textureCanvas,
buffer: textureBuffer,
context: textureCanvas.getContext("2d"),
};
return new Display(gl, programInfo, zAxis, texture);
}
}
class Engine { constructor(time_step, update, render, allowedSkippedFrames) { this.accumulated_time = 0; this.animation_frame_request = undefined, this.time = undefined, this.time_step = time_step, this.updated = false; this.update = update; this.render = render; this.allowedSkippedFrames = allowedSkippedFrames; this.run = this.run.bind(this); this.end = false; } run(time_stamp) { const { accumulated_time, time, time_step, updated, update, render, allowedSkippedFrames, end } = this; this.accumulated_time += time_stamp - time; this.time = time_stamp; if (accumulated_time > time_stamp * allowedSkippedFrames) { this.accumulated_time = time_stamp; } while (this.accumulated_time >= time_step) { this.accumulated_time -= time_step; update(time_stamp); this.updated = true; } if (updated) { this.updated = false; render(time_stamp); } if (end) { return; } this.animation_frame_request = requestAnimationFrame(this.run); } start() { this.accumulated_time = this.time_step; this.time = performance.now(); this.animation_frame_request = requestAnimationFrame(this.run); } stop() { this.end = true; cancelAnimationFrame(this.animation_frame_request); } }
class Entity extends Rect {
constructor(){
super();
this.velocity = vec2.create();
this.area = undefined;
this.mass = 2;
this.updateFillers = {};
this.delete = false;
this.draw = true;
}
setup(w, h, ...args){
this.area = vec2.fromValues(w, h);
super.setup(...args);
return this;
}
fill(...args){
this.updateFillers.fill = args;
}
update(deltaTime, speed){
return this;
}
move(x, y){
super.move([x, y, this.coords[2]]);
return this;
}
}
class Quixotic{
constructor(display){
this.display = display;
this.engine = undefined;
this.render = undefined;
this.update = undefined;
this.frameRate = undefined;
this.time = 0; this.speed = 1;
this.world = {
objects: {},
objectsCollisionInfo: {},
objectsArray: [],
classesInfo: {}
};
this.timePassed = 0;
}
createEntity(Class, ...args){
const display = this.display; const {rect, stroke} = display.createRectPos(5, 5); Class = Class ? Class : Entity; const className = Class.name; if(className !== "Entity" && !Entity.prototype.isPrototypeOf(Class.prototype)){ throw new TypeError("Expected extended class of Entity. Instead got: " + className); } let instance; const {objectsArray, classesInfo, objects} = this.world; const classInfo = classesInfo[className]; if(classInfo){ if(classInfo.args){ instance = new Class(...[...classInfo.args, ...args]); } else { instance = new Class(...args); } const name = classInfo.name; if(Array.isArray(objects[name])){ objects[name].push(instance); instance.name = name; } else { console.warn("Didn't save object in world.objects object, object wouldn't detect collision"); } } else { instance = new Class(...args); } instance.setup(5, 5, ...display.getRectInfo(0, 0, rect, stroke, "#000")); objectsArray.push(instance); return instance; }
createBackground(objects){
const buffer = document.createElement("canvas").getContext("2d");
const bufferRect = this.createEntity();
let {zAxis, canvas: {width, height}} = this.display;
zAxis--;
const halfZ = zAxis/2;
let {coords: [x, y], area: [w, h]} = objects[objects.length - 1];
let [mX, mY, mW, mH] = [x, y, w, h];
for(let i = objects.length-1; i--;){
const {coords: [_x, _y], area: [_w, _h]} = objects[i];
x < _x ? _x : x;
y < _y ? _y : y;
if(mX < _x){
mX = _x;
mW = _w;
}
if(mY < _y){
mY = _y;
mH = _h;
}
}
buffer.canvas.width = width;
buffer.canvas.height = height;
for(let i = objects.length; i--;){
const {coords: [_x, _y], area: [_w, _h]} = objects[i];
buffer.fillRect(((_x-halfZ-_w*2)/zAxis+1)*width, ((-_y-halfZ-_h*2)/zAxis+1)*height, _w*2/zAxis*width, _h*2/zAxis*height);
}
document.body.appendChild(buffer.canvas)
}
buildWorld({objects, classes, tileMap}){
const world = this.world;
if(Array.isArray(objects)){
for(let i = objects.length - 1; i > -1; i --){
const object = objects[i];
const {name, array, amount, position, collision, args, area} = object;
let createClass;
if(!object.class){
createClass = Entity;
}
const _args = args ? args : [];
let pos;
if(position){
let p = amount;
if(array){
const positions = position.positions;
pos = function(){
p--;
return positions[p];
};
} else {
pos = function(){
return position.position;
};
}
}
if(array){
let _array = [];
for(let j = amount; j--;){
const instance = this.createEntity(createClass, ..._args);
instance.name = name;
if(position){
instance.move(...pos());
}
if(area){
instance.setSize(area);
}
_array.push(instance);
}
world.objects[name] = _array;
world.objectsArray.push(..._array);
}
}
}
return;
}
setup(game){
const {style: {backgroundColor, backgroundImage, stroke}, world, engine: {frameRate, update, render}, setup} = game; this.buildWorld(world); const {display, entitySystem, world: {objectsArray, objects}} = this; if(backgroundImage){ display.gl.canvas.style.background = `url(${backgroundImage})`; if(repeatX || repeatY){ console.log("not read yet"); } } this.frameRate = frameRate; let lastUpdated = 0; this.update = (time) =>{ let deltaTime = time - lastUpdated; lastUpdated = time; const speed = this.speed; this.timePassed += deltaTime*speed; for(let i = objectsArray.length; i--;){ const object = objectsArray[i]; if(object.delete){ objectsArray.splice(i, 1); } object.update(deltaTime/1000, speed); } update(deltaTime/1000, this); }; let lastRendered = 0; this.render = (timeStamp) => { const deltaTime = timeStamp - lastRendered; lastRendered = timeStamp; if(backgroundColor) display.clear(backgroundColor); const length = objectsArray.length; for(let i = objectsArray.length; i--; ){ const object = objectsArray[length - i - 1]; if(object.draw){ const updateFillers = Object.entries(object.updateFillers); const fillersLength = updateFillers.length; if(fillersLength){ for(let i = fillersLength; i--;){ const [func, args] = updateFillers[fillersLength - i - 1]; display[func + "Rect"](object, ...args); } object.updateFillers = {}; } display.drawBuffer(object); } } const speed = this.speed; const spriteSheets = display.spriteSheets; for(let i = spriteSheets.length; i--;){ spriteSheets[i].update(deltaTime/1000*speed); } render(display, this); }; setup(this, display, this.world); this.engine = new Engine(this.frameRate, this.update, this.render, 3); this.engine.start(); return game;
}
static async create({display: {canvas, width, height, zAxis}, homeURL}){
const display = await Display.create(canvas, width, height, zAxis);
return new Quixotic(display);
}
}
const fps = document.querySelector("#fps");
const minLength = innerWidth > innerHeight ? innerHeight : innerWidth;
const game = {
create: {
display: {
canvas: document.querySelector("#canvas"),
zAxis: 96,
width: minLength,
height: minLength,
},
homeURL: "/src"
},
style: {
backgroundColor: "#111122"
},
world: {
objects: [
{
name: "trees",
array: true,
amount: 5,
position: {
type: "set",
positions: [ [-37.5, 37.5], [0,0], [-37.5,-37.5], [37.5,-37.5], [37.5,37.5], [10,10], [15,10], [20,10], [25,10], [30,10]]
}
}
]
},
engine: {
frameRate: 1000/30,
update: function(deltaTime, engine){
fps.innerText = 1/deltaTime;
},
render: function(display){}
},
setup: function(engine, display, {objects: {trees}}){
trees.forEach(tree => {
tree.fill("#00ff00")
})
engine.createBackground(trees);
}
};
Quixotic.create(game.create)
.then(engine => {
engine.setup(game);
});
* {
box-sizing:border-box;
margin:0;
padding:0;
}
body {
background-color: #111c31;
overflow: hidden;
align-items:space-around;
display:grid;
height:100%;
width:100%;
}
#canvas {
background-color: #152646;
/* justify-self: center; */
}
#fps {
position: fixed;
color: white;
right: 0;
}
canvas {
position: fixed
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>webgl x 2dCanvas</title>
</head>
<body>
<canvas id="canvas" width="300" height="300"></canvas>
<p id = "fps"></p>
</body>
</html>
Here's code from line 374 where the problem is happening
createBackground(objects){ //method
const buffer = document.createElement("canvas").getContext("2d");
const bufferRect = this.createEntity();
let {zAxis, canvas: {width, height}} = this.display;
zAxis--; //zAxis is where the camera is at, currently 96, but with webgl the objects have to be 1 point lower, so 95.
const halfZ = zAxis/2;
let {coords: [x, y], area: [w, h]} = objects[objects.length - 1];
let [mX, mY, mW, mH] = [x, y, w, h];
for(let i = objects.length-1; i--;){
const {coords: [_x, _y], area: [_w, _h]} = objects[i];
x < _x ? _x : x;
y < _y ? _y : y;
if(mX < _x){
mX = _x;
mW = _w;
}
if(mY < _y){
mY = _y;
mH = _h;
}
}
buffer.canvas.width = ((mX-halfZ+mW*2)/zAxis+1)*width;
buffer.canvas.height = ((mY-halfZ+mH*2)/zAxis+1)*height;
for(let i = objects.length; i--;){
const {coords: [_x, _y], area: [_w, _h]} = objects[i];
buffer.fillRect(((_x-halfZ-_w*2)/zAxis+1)*width, ((_y-halfZ-_h*2)/zAxis+1)*height, _w*2/zAxis*width, _h*2/zAxis*height);
}
document.body.appendChild(buffer.canvas)
}
I have this function that takes objects that are being drawn with webgl on a 3d world with a couple vectors and matrices, basically I get all their positions and volumes to draw them on a 2d canvas, heres the result I got so far
the green squares are the ones being drawn with webgl and the black squares are the ones being draw on a canvas rendering 2d, the end result should be the black squares covering the green squares but my math is off somewhere.
The full code can be found here
https://github.com/bahaaaldin214/Quixotic-Engine/tree/test
The shaders are in src/modules/webgl/shaders
other information
camera position: 96,
green squares positions:
[
[-37.5, 37.5], //bottom left
[0,0], //center
[-37.5,-37.5], //top left
[37.5,-37.5], //bottom right
[37.5,37.5], //top right
]
Well now that I've seen the code. First off, my bad but I didn't make it clear you should post minimal code. There is lots of unneeded code. Also I'm not sure if that's your own math library or if it's paired down glmatrix. If it's the latter you can just <script src="cdn/to/glmatrix"></script> to use it.
In any case you're positioning the squares using a perspective matrix and view matrix (the camera) so you need to use the same math for the 2D canvas.
const worldViewProjection = mat4.create();
buffer.canvas.width = width;
buffer.canvas.height = height;
for (let i = objects.length; i--;) {
const {
coords: [_x, _y],
area: [_w, _h]
} = objects[i];
mat4.multiply(worldViewProjection, this.display.currentCamera.matrix, objects[i].matrix);
const points = [
[-_w / 2, -_h / 2, 0],
[ _w / 2, _h / 2, 0],
].map(p => {
const ndc = vec3.transformMat4([], p, worldViewProjection);
return [
(ndc[0] * 0.5 + 0.5) * width,
(ndc[1] * -0.5 + 0.5) * height,
];
});
const ww = points[1][0] - points[0][0];
const hh = points[1][1] - points[0][1];
buffer.strokeStyle = 'red';
buffer.strokeRect(...points[0], ww, hh);
}
const EPSILON = 0.000001;
const FRAGMENT_SHADER = ` precision highp float; varying highp vec2 vTextureCoord; varying lowp vec4 vColor; uniform sampler2D uSampler; uniform bool aUseText; void main(void) { if( aUseText ){ gl_FragColor = texture2D(uSampler, vTextureCoord); } else { gl_FragColor = vColor; } } `;
const VERTEX_SHADER = ` attribute vec4 aVertexPosition; attribute vec4 aVertexColor; attribute vec2 aTextureCoord; uniform mat4 uModelViewMatrix; uniform mat4 uProjectionMatrix; uniform mat3 uTextMatrix; uniform float uPointSize; varying lowp vec4 vColor; varying highp vec2 vTextureCoord; void main(void) { gl_PointSize = uPointSize; gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; vColor = aVertexColor; vTextureCoord = (vec3(aTextureCoord, 1)*uTextMatrix).xy; } `;
mat4.moveToVec3 = function(out, v) {
out[12] = v[0];
out[13] = v[1];
out[14] = v[2];
};
class WebglEntity {
constructor() {
this.matrix = mat4.create();
this.coords = vec3.create();
}
translate(newCoords) {
const {
matrix,
coords
} = this;
mat4.translate(matrix, matrix, newCoords);
vec3.copy(coords, [matrix[12], matrix[13], matrix[14]]);
return this;
}
move(newCoords) {
const {
matrix,
coords
} = this;
vec3.copy(coords, newCoords);
mat4.moveToVec3(matrix, coords);
return this;
}
}
class Camera extends WebglEntity {
constructor(fieldOfView, aspect, zNear, zFar) {
super();
this.projection = mat4.perspective(mat4.create(), fieldOfView, aspect, zNear, zFar);
}
lookAt(lookAt) {
const {
matrix,
projection,
coords
} = this;
mat4.lookAt(matrix, coords, lookAt, [0, 1, 0]);
mat4.multiply(matrix, projection, matrix);
return this;
}
}
class Rect extends WebglEntity {
constructor() {
super();
this.positionsBuffer = undefined;
this.fragColorPos = undefined;
this.strokeColorPos = undefined;
this.strokePositionBuffer = undefined;
this.vertexAttribInfo = undefined;
this.vertextColorAttribInfo = undefined;
this.vertexCount = undefined;
this.textureInfo = undefined;
this.multiTextures = false;
this.strokeSize = 1;
this.fillers = {
fill: false,
texture: false,
stroke: false
};
}
setup(matrix, positionsBuffer, strokePositionBuffer, vertexAttribInfo, vertextColorAttribInfo, vertexCount) {
this.matrix = matrix;
this.positionsBuffer = positionsBuffer;
this.strokePositionBuffer = strokePositionBuffer;
this.vertexAttribInfo = vertexAttribInfo;
this.vertextColorAttribInfo = vertextColorAttribInfo;
this.vertexCount = vertexCount;
return this;
}
}
class Display {
constructor(gl, programInfo, zAxis, texture) {
this.gl = gl;
this.programInfo = programInfo;
this.canvas = gl.canvas;
this.currentCamera = new Camera(45 * Math.PI / 180, gl.canvas.width / gl.canvas.height, 0.1, 100.0);
this.currentCamera.translate([0, 0, zAxis]).lookAt([0, 0, 0]);
this.zAxis = zAxis;
this.drawZAxis = 0;
this.last = {};
texture.textAttribInfo = {
numComponents: 2,
type: gl.FLOAT,
normalize: false,
stride: 0,
offset: 0
};
this.texture = texture;
this.spriteSheets = [];
const context = texture.context;
const canvas = texture.canvas;
this.images = {};
}
clear(color) {
const gl = this.gl;
gl.clearColor(0.1, 0.1, 0.1, 1);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
rect(x, y, w, h) {
const {
rect,
stroke
} = this.createRectPos(w, h);
const square = new Rect();
square.setup(...this.getRectInfo(x, y, rect, stroke));
return square;
}
fillRect(rect, color) {
const {
createStaticDrawBuffer,
gl,
parseColor
} = this;
rect.fillers.fill = true;
if (color) {
rect.fragColorPos = createStaticDrawBuffer(gl, [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]);
}
}
createRectPos(w, h) {
const rect = [w / 2, h / 2, -w / 2, h / 2, w / 2, -h / 2, -w / 2, -h / 2];
const stroke = [-w / 2, h / 2, w / 2, h / 2, w / 2, -h / 2, -w / 2, -h / 2, ];
return {
rect,
stroke
};
}
getRectInfo(x, y, rect, stroke) {
return this.createSquareBuffer(rect, stroke, [x, y, this.drawZAxis]);
}
createStaticDrawBuffer(gl, data) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
return buffer;
}
createSquareBuffer(positions, strokePosition, coords) {
const {
gl,
createStaticDrawBuffer
} = this;
const positionsBuffer = createStaticDrawBuffer(gl, positions);
const strokePositionBuffer = createStaticDrawBuffer(gl, strokePosition);
const modelViewMatrix = mat4.create();
mat4.translate(modelViewMatrix, modelViewMatrix, coords);
return [modelViewMatrix, positionsBuffer, strokePositionBuffer, this.createAttribInfo(2, gl.FLOAT, false, 0, 0), this.createAttribInfo(4, gl.FLOAT, false, 0, 0), positions.length / 2];
}
createAttribInfo(numComponents, type, normalize, stride, offset) {
return {
numComponents,
type,
normalize,
stride,
offset
};
}
enableAttrib(buffer, attrib, gl, {
numComponents,
type,
normalize,
stride,
offset
}) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(attrib, numComponents, type, normalize, stride, offset);
gl.enableVertexAttribArray(attrib);
}
drawBuffer(buffer) {
const {
gl,
drawTexture,
enableAttrib,
createStaticDrawBuffer,
currentCamera,
texture: {
context,
canvas,
textAttribInfo
},
programInfo: {
uniformLocations,
program,
attribLocations: {
vertexPosition,
vertexColor,
textureCoord
}
}
} = this;
const cameraMatrix = currentCamera.matrix;
const {
positionsBuffer,
fragColorPos,
strokeColorPos,
strokePositionBuffer,
matrix,
vertexAttribInfo,
vertextColorAttribInfo,
vertexCount,
fragTextPos,
fillers: {
fill,
stroke,
texture
},
strokeSize,
textureInfo,
multiTextures
} = buffer;
gl.uniformMatrix4fv(uniformLocations.projectionMatrix, false, cameraMatrix);
gl.uniformMatrix4fv(uniformLocations.modelViewMatrix, false, matrix);
if (fill) {
enableAttrib(positionsBuffer, vertexPosition, gl, vertexAttribInfo);
enableAttrib(fragColorPos, vertexColor, gl, vertextColorAttribInfo);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount);
gl.disableVertexAttribArray(vertexColor);
}
}
static loadShader(gl, program, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
gl.attachShader(program, shader);
}
static async create(canvas, width, height, zAxis = 6) {
canvas.width = width;
canvas.height = height;
const gl = canvas.getContext("webgl");
const shaderProgram = gl.createProgram();
Display.loadShader(gl, shaderProgram, gl.VERTEX_SHADER, VERTEX_SHADER);
Display.loadShader(gl, shaderProgram, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);
gl.linkProgram(shaderProgram);
const programInfo = {
program: shaderProgram,
attribLocations: {
vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'),
textureCoord: gl.getAttribLocation(shaderProgram, 'aTextureCoord'),
},
uniformLocations: {
projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
textMatrix: gl.getUniformLocation(shaderProgram, 'uTextMatrix'),
sampler: gl.getUniformLocation(shaderProgram, 'uSampler'),
useText: gl.getUniformLocation(shaderProgram, 'aUseText'),
pointSize: gl.getUniformLocation(shaderProgram, 'uPointSize'),
},
};
gl.useProgram(programInfo.program);
gl.uniform1f(programInfo.uniformLocations.pointSize, 1.0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
const textureBuffer = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, textureBuffer);
gl.uniform1i(programInfo.uniformLocations.uSampler, 0);
const textureCanvas = document.createElement("canvas");
textureCanvas.width = 0;
textureCanvas.height = 0;
let texture = {
canvas: textureCanvas,
buffer: textureBuffer,
context: textureCanvas.getContext("2d"),
};
return new Display(gl, programInfo, zAxis, texture);
}
}
class Engine {
constructor(time_step, update, render, allowedSkippedFrames) {
this.accumulated_time = 0;
this.animation_frame_request = undefined, this.time = undefined, this.time_step = time_step, this.updated = false;
this.update = update;
this.render = render;
this.allowedSkippedFrames = allowedSkippedFrames;
this.run = this.run.bind(this);
this.end = false;
}
run(time_stamp) {
const {
accumulated_time,
time,
time_step,
updated,
update,
render,
allowedSkippedFrames,
end
} = this;
this.accumulated_time += time_stamp - time;
this.time = time_stamp;
if (accumulated_time > time_stamp * allowedSkippedFrames) {
this.accumulated_time = time_stamp;
}
while (this.accumulated_time >= time_step) {
this.accumulated_time -= time_step;
update(time_stamp);
this.updated = true;
}
if (updated) {
this.updated = false;
render(time_stamp);
}
if (end) {
return;
}
this.animation_frame_request = requestAnimationFrame(this.run);
}
start() {
this.accumulated_time = this.time_step;
this.time = performance.now();
this.animation_frame_request = requestAnimationFrame(this.run);
}
stop() {
this.end = true;
cancelAnimationFrame(this.animation_frame_request);
}
}
class Entity extends Rect {
constructor() {
super();
this.velocity = vec2.create();
this.area = undefined;
this.mass = 2;
this.updateFillers = {};
this.delete = false;
this.draw = true;
}
setup(w, h, ...args) {
this.area = vec2.fromValues(w, h);
super.setup(...args);
return this;
}
fill(...args) {
this.updateFillers.fill = args;
}
update(deltaTime, speed) {
return this;
}
move(x, y) {
super.move([x, y, this.coords[2]]);
return this;
}
}
class Quixotic {
constructor(display) {
this.display = display;
this.engine = undefined;
this.render = undefined;
this.update = undefined;
this.frameRate = undefined;
this.time = 0;
this.speed = 1;
this.world = {
objects: {},
objectsCollisionInfo: {},
objectsArray: [],
classesInfo: {}
};
this.timePassed = 0;
}
createEntity(Class, ...args) {
const display = this.display;
const {
rect,
stroke
} = display.createRectPos(5, 5);
Class = Class ? Class : Entity;
const className = Class.name;
if (className !== "Entity" && !Entity.prototype.isPrototypeOf(Class.prototype)) {
throw new TypeError("Expected extended class of Entity. Instead got: " + className);
}
let instance;
const {
objectsArray,
classesInfo,
objects
} = this.world;
const classInfo = classesInfo[className];
if (classInfo) {
if (classInfo.args) {
instance = new Class(...[...classInfo.args, ...args]);
} else {
instance = new Class(...args);
}
const name = classInfo.name;
if (Array.isArray(objects[name])) {
objects[name].push(instance);
instance.name = name;
} else {
console.warn("Didn't save object in world.objects object, object wouldn't detect collision");
}
} else {
instance = new Class(...args);
}
instance.setup(5, 5, ...display.getRectInfo(0, 0, rect, stroke, "#000"));
objectsArray.push(instance);
return instance;
}
createBackground(objects) {
const buffer = document.createElement("canvas").getContext("2d");
const bufferRect = this.createEntity();
let {
zAxis,
canvas: {
width,
height
}
} = this.display;
zAxis--;
const halfZ = zAxis / 2;
let {
coords: [x, y],
area: [w, h]
} = objects[objects.length - 1];
const worldViewProjection = mat4.create();
buffer.canvas.width = width;
buffer.canvas.height = height;
for (let i = objects.length; i--;) {
const {
coords: [_x, _y],
area: [_w, _h]
} = objects[i];
mat4.multiply(worldViewProjection, this.display.currentCamera.matrix, objects[i].matrix);
const points = [
[-_w / 2, -_h / 2, 0],
[_w / 2, _h / 2, 0],
].map(p => {
const ndc = vec3.transformMat4([], p, worldViewProjection);
return [
(ndc[0] * 0.5 + 0.5) * width,
(ndc[1] * -0.5 + 0.5) * height,
];
});
const ww = points[1][0] - points[0][0];
const hh = points[1][1] - points[0][1];
buffer.strokeStyle = 'red';
buffer.strokeRect(...points[0], ww, hh);
}
document.body.appendChild(buffer.canvas)
}
buildWorld({
objects,
classes,
tileMap
}) {
const world = this.world;
if (Array.isArray(objects)) {
for (let i = objects.length - 1; i > -1; i--) {
const object = objects[i];
const {
name,
array,
amount,
position,
collision,
args,
area
} = object;
let createClass;
if (!object.class) {
createClass = Entity;
}
const _args = args ? args : [];
let pos;
if (position) {
let p = amount;
if (array) {
const positions = position.positions;
pos = function() {
p--;
return positions[p];
};
} else {
pos = function() {
return position.position;
};
}
}
if (array) {
let _array = [];
for (let j = amount; j--;) {
const instance = this.createEntity(createClass, ..._args);
instance.name = name;
if (position) {
instance.move(...pos());
}
if (area) {
instance.setSize(area);
}
_array.push(instance);
}
world.objects[name] = _array;
world.objectsArray.push(..._array);
}
}
}
return;
}
setup(game) {
const {
style: {
backgroundColor,
backgroundImage,
stroke
},
world,
engine: {
frameRate,
update,
render
},
setup
} = game;
this.buildWorld(world);
const {
display,
entitySystem,
world: {
objectsArray,
objects
}
} = this;
if (backgroundImage) {
display.gl.canvas.style.background = `url(${backgroundImage})`;
if (repeatX || repeatY) {
console.log("not read yet");
}
}
this.frameRate = frameRate;
let lastUpdated = 0;
this.update = (time) => {
let deltaTime = time - lastUpdated;
lastUpdated = time;
const speed = this.speed;
this.timePassed += deltaTime * speed;
for (let i = objectsArray.length; i--;) {
const object = objectsArray[i];
if (object.delete) {
objectsArray.splice(i, 1);
}
object.update(deltaTime / 1000, speed);
}
update(deltaTime / 1000, this);
};
let lastRendered = 0;
this.render = (timeStamp) => {
const deltaTime = timeStamp - lastRendered;
lastRendered = timeStamp;
if (backgroundColor) display.clear(backgroundColor);
const length = objectsArray.length;
for (let i = objectsArray.length; i--;) {
const object = objectsArray[length - i - 1];
if (object.draw) {
const updateFillers = Object.entries(object.updateFillers);
const fillersLength = updateFillers.length;
if (fillersLength) {
for (let i = fillersLength; i--;) {
const [func, args] = updateFillers[fillersLength - i - 1];
display[func + "Rect"](object, ...args);
}
object.updateFillers = {};
}
display.drawBuffer(object);
}
}
const speed = this.speed;
const spriteSheets = display.spriteSheets;
for (let i = spriteSheets.length; i--;) {
spriteSheets[i].update(deltaTime / 1000 * speed);
}
render(display, this);
};
setup(this, display, this.world);
this.engine = new Engine(this.frameRate, this.update, this.render, 3);
this.engine.start();
return game;
}
static async create({
display: {
canvas,
width,
height,
zAxis
},
homeURL
}) {
const display = await Display.create(canvas, width, height, zAxis);
return new Quixotic(display);
}
}
const fps = document.querySelector("#fps");
const minLength = innerWidth > innerHeight ? innerHeight : innerWidth;
const game = {
create: {
display: {
canvas: document.querySelector("#canvas"),
zAxis: 96,
width: minLength,
height: minLength,
},
homeURL: "/src"
},
style: {
backgroundColor: "#111122"
},
world: {
objects: [{
name: "trees",
array: true,
amount: 5,
position: {
type: "set",
positions: [
[-37.5, 37.5],
[0, 0],
[-37.5, -37.5],
[37.5, -37.5],
[37.5, 37.5],
[10, 10],
[15, 10],
[20, 10],
[25, 10],
[30, 10]
]
}
}]
},
engine: {
frameRate: 1000 / 30,
update: function(deltaTime, engine) {
fps.innerText = 1 / deltaTime;
},
render: function(display) {}
},
setup: function(engine, display, {
objects: {
trees
}
}) {
trees.forEach(tree => {
tree.fill("#00ff00")
})
engine.createBackground(trees);
}
};
Quixotic.create(game.create)
.then(engine => {
engine.setup(game);
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: #111c31;
overflow: hidden;
align-items: space-around;
display: grid;
height: 100%;
width: 100%;
}
#canvas {
background-color: #152646;
/* justify-self: center; */
}
#fps {
position: fixed;
color: white;
right: 0;
}
canvas {
position: fixed
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>
<canvas id="canvas" width="300" height="300"></canvas>
<p id="fps"></p>
note: the code only works because the camera is not rotated, nor are the squares. If you did rotate the camera or the squares you'd need to draw triangles with canvas 2d after transforming each set of 3 vertices, just like WebGL does.

Categories

Resources