I was attempting to convert a piece of code from Class to Constructor function for adaption. Although there was no indication of bugs in the console, the pie does not appear in live preview at all.
Basically, I swapped class Slice and class Piechart to the below.
'use strict';
function Slice(lbl, val, strt, fll) {
this.label = lbl;
this.value = constrain(val, 0.0, 1.0);
this.start = constrain(strt, 0.0, TWO_PI);
var valAngle = map(this.value, 0.0, 1.0, 0.0, TWO_PI);
this.middle = this.start + valAngle * 0.5;
this.stop = this.start + valAngle;
this.fillFocus = color(fll, 99, 99, 255);
this.fillNoFocus = color(fll, 89, 49, 189);
this.fill = this.fillNoFocus;
this.hasFocus = false;
this.labelInset = 0.667;
this.toString = function() {
return this.label + ": " + (this.value * 100).toFixed(2) + "%";
};
this.draw = function(cx, cy, radius) {
if (this.hasFocus) {
this.fill = lerpColor(this.fill,
this.fillFocus,
Slice.focusFadeIn);
} else {
this.fill = lerpColor(this.fill,
this.fillNoFocus,
Slice.focusFadeOut);
}
fill(this.fill);
arc(cx, cy, radius, radius,
this.start, this.stop);
};
this.showLabel = function(cx, cy, radius) {
var x = cx + cos(this.middle) * radius * this.labelInset;
var y = cy + sin(this.middle) * radius * this.labelInset;
fill(0, 0, 0);
text(this, x, y);
fill(0, 0, 99);
text(this, x - 1, y - 1);
};
this.hover = function(angle) {
angle = angle < 0 ? angle + TWO_PI : angle;
return angle > this.start && angle < this.stop;
};
}
Here is the code to be adapted.
Edit: I am an absolute beginner at coding who is trying out different mini tutorial projects, the coding style I have above is based on the teachings of a beginner lesson to Javascript.
I think conversion is not correct Please replace your js with following code.
'use strict';
function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["##iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
var Slice = /*#__PURE__*/function () {
function Slice(lbl, val, strt, fll) {
this.label = lbl;
this.value = constrain(val, 0.0, 1.0);
this.start = constrain(strt, 0.0, TWO_PI);
var valAngle = map(this.value, 0.0, 1.0, 0.0, TWO_PI);
this.middle = this.start + valAngle * 0.5;
this.stop = this.start + valAngle;
this.fillFocus = color(fll, 99, 99, 255);
this.fillNoFocus = color(fll, 89, 49, 189);
this.fill = this.fillNoFocus;
this.hasFocus = false;
this.labelInset = 0.667;
}
var _proto = Slice.prototype;
_proto.toString = function toString() {
return this.label + ": " + (this.value * 100).toFixed(2) + "%";
};
_proto.draw = function draw(cx, cy, radius) {
if (this.hasFocus) {
this.fill = lerpColor(this.fill, this.fillFocus, Slice.focusFadeIn);
} else {
this.fill = lerpColor(this.fill, this.fillNoFocus, Slice.focusFadeOut);
}
fill(this.fill);
arc(cx, cy, radius, radius, this.start, this.stop);
};
_proto.showLabel = function showLabel(cx, cy, radius) {
var x = cx + cos(this.middle) * radius * this.labelInset;
var y = cy + sin(this.middle) * radius * this.labelInset;
fill(0, 0, 0);
text(this, x, y);
fill(0, 0, 99);
text(this, x - 1, y - 1);
} // atan2 returns a value in the range of -PI to PI.
// If the angle is less than 0 radians, we can fix the
// problem by adding TWO_PI to it.
;
_proto.hover = function hover(angle) {
angle = angle < 0 ? angle + TWO_PI : angle;
return angle > this.start && angle < this.stop;
};
return Slice;
}();
Slice.focusFadeIn = 0.05;
Slice.focusFadeOut = 0.025;
var PieChart = /*#__PURE__*/function () {
function PieChart(x, y, rad, name, data) {
this.scale(x, y, rad);
this.hasFocus = false;
this.name = name; // Assumes data is an ES6 map.
// Sum the values in the data so as to
// normalize them to a percent.
var sum = 0;
var itr = data.entries();
for (var _iterator = _createForOfIteratorHelperLoose(data.values()), _step; !(_step = _iterator()).done;) {
var value = _step.value;
sum += value;
console.log(value);
}
sum = sum <= 1 ? 1 : sum;
console.log("sum: ", sum);
this.slices = [];
var i = 0;
for (var _iterator2 = _createForOfIteratorHelperLoose(data.entries()), _step2; !(_step2 = _iterator2()).done;) {
var _step2$value = _step2.value,
key = _step2$value[0],
value = _step2$value[1];
this.slices.push(new Slice(key, isNaN(value) ? 0 : value / sum, i === 0 ? 0 : this.slices[i - 1].stop, i / data.size * 360));
i++;
}
}
var _proto2 = PieChart.prototype;
_proto2.draw = function draw() {
push();
noStroke();
ellipseMode(RADIUS);
textSize(16);
textAlign(CENTER, CENTER);
for (var i = 0, size = this.slices.length; i < size; ++i) {
this.slices[i].draw(this.x, this.y, this.radius);
}
if (this.hasFocus) {
var y = this.y + this.radius + 16;
fill(0, 0, 0);
text(this.name, this.x, y);
fill(0, 0, 99);
text(this.name, this.x - 1, y - 1);
}
pop();
this.hover(mouseX, mouseY);
};
_proto2.hover = function hover(x, y) {
if (dist(x, y, this.x, this.y) < this.radius) {
this.hasFocus = true;
for (var i = 0, size = this.slices.length; i < size; ++i) {
var a = atan2(y - this.y, x - this.x);
if (this.slices[i].hover(a)) {
this.slices[i].hasFocus = true;
this.slices[i].showLabel(this.x, this.y, this.radius);
} else {
this.slices[i].hasFocus = false;
}
}
} else {
this.hasFocus = false;
for (var i = 0, size = this.slices.length; i < size; ++i) {
this.slices[i].hasFocus = false;
}
}
};
_proto2.scale = function scale(x, y, rad) {
this.x = x;
this.y = y;
this.radius = rad;
};
return PieChart;
}();
var cnvs = null;
var data = new Map();
var pie = null;
function setup() {
cnvs = createCanvas(windowWidth, windowHeight);
pixelDensity(displayDensity());
smooth();
colorMode(HSB, 359, 99, 99);
data.set('Alpha', random(1, 50));
data.set('Bravo', random(1, 50));
data.set('Charlie', random(1, 50));
data.set('Delta', random(1, 50));
data.set('Echo', random(1, 50));
data.set('Foxtrot', random(1, 50));
pie = new PieChart(width * 0.5, height * 0.5, min(width, height) * 0.333, "Example", data);
}
function draw() {
background(0, 0, 0);
pie.draw();
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
pie.scale(width * 0.5, height * 0.5, min(width, height) * 0.333);
}
Here is a working sample.
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
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.