这是完整的代码,我不得不从一些与问题无关的函数中删除空格,以确保im处于堆栈溢出的30k个字符限制内
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>
这里是发生问题的第374行的代码
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)
}
我有这个功能,可以使用webgl在带有2个向量和矩阵的3d世界中绘制对象,基本上我可以将它们的所有位置和体积绘制到2d画布上,这就是到目前为止我得到的结果
绿色正方形是使用webgl绘制的,黑色正方形是在画布渲染2d上绘制的,最终结果应该是覆盖绿色正方形的黑色正方形,但我的数学位置不正确。
完整的代码可以在这里找到 https://github.com/bahaaaldin214/Quixotic-Engine/tree/test
着色器位于src / modules / webgl / shaders
其他信息
相机位置:96,
绿色方块位置:
[
[-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
]
答案 0 :(得分:1)
现在我已经看过代码了。首先,我很糟糕,但我不清楚您应该发布minimal code。有很多不需要的代码。另外,我不确定这是否是您自己的数学库或是否与glmatrix配对。如果是后者,则可以<script src="cdn/to/glmatrix"></script>
来使用它。
无论如何,您都是使用透视矩阵和视图矩阵(相机)来定位正方形,因此您需要对2D画布使用相同的数学运算。
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>
注意:该代码仅在未旋转相机的情况下有效,而正方形也未旋转。如果您确实旋转了相机或正方形,则需要像转换WebGL一样,在变换每组3个顶点后用画布2d绘制三角形。