diff --git a/src/gameobjects/mesh/Mesh.js b/src/gameobjects/mesh/Mesh.js index 5ff18cac0..1a07a474f 100644 --- a/src/gameobjects/mesh/Mesh.js +++ b/src/gameobjects/mesh/Mesh.js @@ -11,7 +11,9 @@ var GameObjectEvents = require('../events'); var GetCalcMatrix = require('../GetCalcMatrix'); var MeshRender = require('./MeshRender'); var MeshCamera = require('./MeshCamera'); +var MeshLight = require('./MeshLight'); var Model = require('../../geom/mesh/Model'); +var Vector3 = require('../../math/Vector3'); /** * @classdesc @@ -84,6 +86,8 @@ var Mesh = new Class({ */ this.camera = new MeshCamera(45, 0, 0, -10, 0.01, 1000); + this.light = new MeshLight(); + /** * An array of Model instances that have been created in this Mesh. * @@ -152,7 +156,8 @@ var Mesh = new Class({ this.setSize(renderer.width, renderer.height); - this.initPipeline(); + // TODO - Change to const + this.initPipeline('MeshPipeline'); if (vertices) { @@ -324,6 +329,7 @@ var Mesh = new Class({ var vertices = modelData.vertices; var textureCoords = modelData.textureCoords; + var normals = modelData.vertexNormals; var faces = modelData.faces; var defaultUV1 = { u: 0, v: 1 }; @@ -334,14 +340,20 @@ var Mesh = new Class({ { var face = faces[i]; + // {textureCoordsIndex: 0, vertexIndex: 16, vertexNormalIndex: 16} var v1 = face.vertices[0]; var v2 = face.vertices[1]; var v3 = face.vertices[2]; + // {x: 0.19509, y: 0.980785, z: 0} var m1 = vertices[v1.vertexIndex]; var m2 = vertices[v2.vertexIndex]; var m3 = vertices[v3.vertexIndex]; + var n1 = normals[v1.vertexNormalIndex]; + var n2 = normals[v2.vertexNormalIndex]; + var n3 = normals[v3.vertexNormalIndex]; + var t1 = v1.textureCoordsIndex; var t2 = v2.textureCoordsIndex; var t3 = v3.textureCoordsIndex; @@ -350,9 +362,9 @@ var Mesh = new Class({ var uv2 = (t2 === -1) ? defaultUV2 : textureCoords[t2]; var uv3 = (t3 === -1) ? defaultUV3 : textureCoords[t3]; - var vert1 = model.addVertex(originX + m1.x * scale, originY + m1.y * scale, originZ + m1.z * scale, uv1.u, uv1.v); - var vert2 = model.addVertex(originX + m2.x * scale, originY + m2.y * scale, originZ + m2.z * scale, uv2.u, uv2.v); - var vert3 = model.addVertex(originX + m3.x * scale, originY + m3.y * scale, originZ + m3.z * scale, uv3.u, uv3.v); + var vert1 = model.addVertex(originX + m1.x * scale, originY + m1.y * scale, originZ + m1.z * scale, uv1.u, uv1.v, n1.x, n1.y, n1.z); + var vert2 = model.addVertex(originX + m2.x * scale, originY + m2.y * scale, originZ + m2.z * scale, uv2.u, uv2.v, n2.x, n2.y, n2.z); + var vert3 = model.addVertex(originX + m3.x * scale, originY + m3.y * scale, originZ + m3.z * scale, uv3.u, uv3.v, n3.x, n3.y, n3.z); model.addFace(vert1, vert2, vert3); } @@ -533,6 +545,7 @@ var Mesh = new Class({ var camera = this.camera; + /* if (camera.dirty || width !== this._prevWidth || height !== this._prevHeight) { // Mesh has resized, flow that down to the Camera @@ -541,6 +554,9 @@ var Mesh = new Class({ this._prevWidth = width; this._prevHeight = height; } + */ + + camera.update(width, height); var models = this.models; diff --git a/src/gameobjects/mesh/MeshCamera.js b/src/gameobjects/mesh/MeshCamera.js index 2030ed55c..f5852f9c9 100644 --- a/src/gameobjects/mesh/MeshCamera.js +++ b/src/gameobjects/mesh/MeshCamera.js @@ -39,9 +39,10 @@ var MeshCamera = new Class({ this.up = new Vector4(); // What the up direction is, invert to get bottom this.right = new Vector4(); // What the right direction is, invert to get left - this.matView = new Matrix4(); - this.viewMatrix = new Matrix4(); - this.projectionMatrix = new Matrix4(); + this.matrix = new Matrix4(); // the transform matrix + this.viewMatrix = new Matrix4(); // the inverse of the transform matrix + this.projectionMatrix = new Matrix4(); // perspective projection matrix + this.viewProjectionMatrix = new Matrix4(); // perspective projection matrix multiplied by the view matrix this.mode = MeshCamera.MODE_ORBIT; }, @@ -88,30 +89,20 @@ var MeshCamera = new Class({ // To have different modes of movements, this function handles the view matrix update for the transform object. updateViewMatrix: function () { - var d = Math.PI / 180; - var matView = this.matView; - var rotation = this.rotation; + var matView = this.matrix; - matView.identity(); - - // Optimize camera transform update, no need for scale nor rotateZ if (this.mode === MeshCamera.MODE_FREE) { - matView.translate(this.position); - matView.rotateX(rotation.x * d); - matView.rotateY(rotation.y * d); + matView.fromRotationXYTranslation(this.rotation, this.position, true); } else { - matView.rotateX(rotation.x * d); - matView.rotateY(rotation.y * d); - matView.translate(this.position); + matView.fromRotationXYTranslation(this.rotation, this.position, false); } this.updateDirection(); - this.viewMatrix.copy(matView); - this.viewMatrix.invert(); + this.viewMatrix.copy(matView).invert(); this.dirty = true; }, @@ -120,28 +111,21 @@ var MeshCamera = new Class({ { this.aspectRatio = width / height; - this.updateViewMatrix(); - + // TODO - Only needs changing when the fov/etc does this.projectionMatrix.perspective(DegToRad(this._fov), this.aspectRatio, this._near, this._far); + + // TODO - Only needs calculating when this camera is dirty + this.projectionMatrix.multiplyToMat4(this.viewMatrix, this.viewProjectionMatrix); }, updateDirection: function () { - var matView = this.matView; + var matView = this.matrix; this.forward.set(0, 0, 1, 0).transformMat4(matView); this.up.set(0, 1, 0, 0).transformMat4(matView); this.right.set(1, 0, 0, 0).transformMat4(matView); }, - - reset: function () - { - this.position.set(); - this.rotation.set(); - - this.updateViewMatrix(); - }, - fov: { get: function () diff --git a/src/gameobjects/mesh/MeshLight.js b/src/gameobjects/mesh/MeshLight.js new file mode 100644 index 000000000..7e83054a9 --- /dev/null +++ b/src/gameobjects/mesh/MeshLight.js @@ -0,0 +1,103 @@ +/** + * @author Richard Davey + * @copyright 2020 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +var Class = require('../../utils/Class'); +var Vector3 = require('../../math/Vector3'); + +/** + * @classdesc + * The Mesh Light. + * + * @class MeshLight + * @memberof Phaser.GameObjects + * @constructor + * @since 3.50.0 + */ +var MeshLight = new Class({ + + initialize: + + function MeshLight (x, y, z) + { + this.position = new Vector3(x, y, z); + this.ambient = new Vector3(1, 1, 1); + this.diffuse = new Vector3(1, 1, 1); + this.specular = new Vector3(1, 1, 1); + }, + + setPosition: function (x, y, z) + { + this.position.set(x, y, z); + + return this; + }, + + setAmbient: function (r, g, b) + { + this.ambient.set(r, g, b); + + return this; + }, + + setDiffuse: function (r, g, b) + { + this.diffuse.set(r, g, b); + + return this; + }, + + setSpecular: function (r, g, b) + { + this.specular.set(r, g, b); + + return this; + }, + + x: { + + get: function () + { + return this.position.x; + }, + + set: function (value) + { + this.position.x = value; + } + + }, + + y: { + + get: function () + { + return this.position.y; + }, + + set: function (value) + { + this.position.y = value; + } + + }, + + z: { + + get: function () + { + return this.position.z; + }, + + set: function (value) + { + this.position.z = value; + } + + } + +}); + +module.exports = MeshLight; diff --git a/src/gameobjects/mesh/MeshWebGLRenderer.js b/src/gameobjects/mesh/MeshWebGLRenderer.js index 660eab25d..db0bf2629 100644 --- a/src/gameobjects/mesh/MeshWebGLRenderer.js +++ b/src/gameobjects/mesh/MeshWebGLRenderer.js @@ -4,8 +4,6 @@ * @license {@link https://opensource.org/licenses/MIT|MIT License} */ -var GetCalcMatrix = require('../GetCalcMatrix'); - /** * Renders this Game Object with the WebGL Renderer to the given Camera. * The object will not render if any of its renderFlags are set or it is being actively filtered out by the Camera. @@ -20,7 +18,7 @@ var GetCalcMatrix = require('../GetCalcMatrix'); * @param {Phaser.Cameras.Scene2D.Camera} camera - The Camera that is rendering the Game Object. * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - This transform matrix is defined if the game object is nested */ -var MeshWebGLRenderer = function (renderer, src, camera, parentMatrix) +var MeshWebGLRenderer = function (renderer, src) { var models = src.models; var totalModels = models.length; @@ -30,88 +28,18 @@ var MeshWebGLRenderer = function (renderer, src, camera, parentMatrix) return; } + renderer.pipelines.clear(); + var pipeline = renderer.pipelines.set(src.pipeline, src); - var calcMatrix = GetCalcMatrix(src, camera, parentMatrix).calc; - - var vertexOffset = (pipeline.vertexCount * pipeline.vertexComponentCount) - 1; - - var debugVerts; - var debugCallback = src.debugCallback; - - var a = calcMatrix.a; - var b = calcMatrix.b; - var c = calcMatrix.c; - var d = calcMatrix.d; - var e = calcMatrix.e; - var f = calcMatrix.f; - - var F32 = pipeline.vertexViewF32; - var U32 = pipeline.vertexViewU32; - - var globalAlpha = camera.alpha * src.alpha; - for (var m = 0; m < totalModels; m++) { var model = models[m]; - var faces = model.faces; - var totalFaces = faces.length; - var alpha = globalAlpha * model.alpha; - - if (totalFaces === 0 || !model.visible || alpha <= 0) - { - continue; - } - - if (debugCallback) - { - debugVerts = []; - } - - var tintEffect = model.tintFill; - var textureUnit = pipeline.setGameObject(model); - - for (var i = 0; i < totalFaces; i++) - { - var face = faces[i]; - - if (model.hideCCW && !face.isCounterClockwise()) - { - continue; - } - - if (pipeline.shouldFlush(3)) - { - pipeline.flush(); - - vertexOffset = 0; - } - - vertexOffset = face.vertex1.load(F32, U32, vertexOffset, textureUnit, tintEffect, alpha, a, b, c, d, e, f); - vertexOffset = face.vertex2.load(F32, U32, vertexOffset, textureUnit, tintEffect, alpha, a, b, c, d, e, f); - vertexOffset = face.vertex3.load(F32, U32, vertexOffset, textureUnit, tintEffect, alpha, a, b, c, d, e, f); - - pipeline.vertexCount += 3; - - if (debugCallback && model.drawDebug) - { - debugVerts.push( - F32[vertexOffset - 20], - F32[vertexOffset - 19], - F32[vertexOffset - 13], - F32[vertexOffset - 12], - F32[vertexOffset - 6], - F32[vertexOffset - 5] - ); - } - } - - if (debugCallback && model.drawDebug) - { - debugCallback.call(src, src, debugVerts); - } + pipeline.drawModel(src, model); } + + renderer.pipelines.rebind(); }; module.exports = MeshWebGLRenderer; diff --git a/src/geom/mesh/Model.js b/src/geom/mesh/Model.js index 3bbe14404..db5bc9290 100644 --- a/src/geom/mesh/Model.js +++ b/src/geom/mesh/Model.js @@ -8,9 +8,8 @@ var AnimationState = require('../../animations/AnimationState'); var Class = require('../../utils/Class'); var Components = require('../../gameobjects/components'); var Face = require('./Face'); -var GetCalcMatrix = require('../../gameobjects/GetCalcMatrix'); var Matrix4 = require('../../math/Matrix4'); -var StableSort = require('../../utils/array/StableSort'); +var Quaternion = require('../../math/Quaternion'); var Vector3 = require('../../math/Vector3'); var Vertex = require('./Vertex'); var WrapAngle = require('../../math/angle/Wrap'); @@ -59,7 +58,7 @@ var Model = new Class({ * * A Face consists of 3 Vertex objects. * - * This array is populated during the `setVertices` method. + * This array is populated during the `addVertices` method. * * @name Phaser.Geom.Mesh.Model#faces * @type {Phaser.Geom.Mesh.Face[]} @@ -78,39 +77,18 @@ var Model = new Class({ */ this.vertices = []; - /** - * The tint fill mode. - * - * `false` = An additive tint (the default), where vertices colors are blended with the texture. - * `true` = A fill tint, where the vertices colors replace the texture, but respects texture alpha. - * - * @name Phaser.Geom.Mesh.Model#tintFill - * @type {boolean} - * @default false - * @since 3.50.0 - */ - this.tintFill = false; - - /** - * When rendering, skip any Face that isn't counter clockwise? - * - * Enable this to hide backward-facing Faces during rendering. - * Disable it to render all Faces. - * - * @name Phaser.Geom.Mesh.Model#hideCCW - * @type {boolean} - * @since 3.50.0 - */ - this.hideCCW = true; - - this.drawDebug = true; - this.position = new Vector3(x, y, z); - this.rotation = new Vector3(); this.scale = new Vector3(1, 1, 1); + this.rotation = new Quaternion(); - this.dirtyCache = [ x, y, z, 0, 0, 0, 1, 1, 1, 0 ]; + this.dirtyCache = [ x, y, z, 0, 0, 0, 0, 1, 1, 1, 0 ]; + this.ambient = new Vector3(1, 1, 1); + this.diffuse = new Vector3(1, 1, 1); + this.specular = new Vector3(1, 1, 1); + this.shine = 0.25; + + this.normalMatrix = new Matrix4(); this.transformMatrix = new Matrix4(); if (!texture) @@ -165,6 +143,7 @@ var Model = new Class({ var rx = rotation.x; var ry = rotation.y; var rz = rotation.z; + var rw = rotation.w; var sx = scale.x; var sy = scale.y; @@ -179,12 +158,13 @@ var Model = new Class({ var rxCached = dirtyCache[3]; var ryCached = dirtyCache[4]; var rzCached = dirtyCache[5]; + var rwCached = dirtyCache[6]; - var sxCached = dirtyCache[6]; - var syCached = dirtyCache[7]; - var szCached = dirtyCache[8]; + var sxCached = dirtyCache[7]; + var syCached = dirtyCache[8]; + var szCached = dirtyCache[9]; - var fCached = dirtyCache[9]; + var fCached = dirtyCache[10]; dirtyCache[0] = px; dirtyCache[1] = py; @@ -193,43 +173,40 @@ var Model = new Class({ dirtyCache[3] = rx; dirtyCache[4] = ry; dirtyCache[5] = rz; + dirtyCache[6] = rw; - dirtyCache[6] = sx; - dirtyCache[7] = sy; - dirtyCache[8] = sz; + dirtyCache[7] = sx; + dirtyCache[8] = sy; + dirtyCache[9] = sz; - dirtyCache[9] = faces; + dirtyCache[10] = faces; return ( pxCached !== px || pyCached !== py || pzCached !== pz || - rxCached !== rx || ryCached !== ry || rzCached !== rz || + rxCached !== rx || ryCached !== ry || rzCached !== rz || rwCached !== rw || sxCached !== sx || syCached !== sy || szCached !== sz || fCached !== faces ); }, - preUpdate: function (time, delta, camera, width, height) + preUpdate: function (time, delta) { this.anims.update(time, delta); - if (!camera.dirty && !this.isDirty()) + // If the model isn't dirty we can bail out and save lots of math + if (this.isDirty()) { - // If neither the camera or the model is dirty, we can bail out and save lots of math - return; + var normalMatrix = this.normalMatrix; + var transformMatrix = this.transformMatrix; + + // TODO - No scale + transformMatrix.fromRotationTranslation(this.rotation, this.position); + transformMatrix.scale(this.scale); + + normalMatrix.copy(transformMatrix); + normalMatrix.invert(); + normalMatrix.transpose(); } - - var transformMatrix = this.transformMatrix; - - transformMatrix.setWorldMatrix(this.rotation, this.position, this.scale, camera.viewMatrix, camera.projectionMatrix); - - var vertices = this.vertices; - - for (var i = 0; i < vertices.length; i++) - { - vertices[i].transformCoordinatesLocal(transformMatrix, width, height); - } - - this.depthSort(); }, /** @@ -273,79 +250,6 @@ var Model = new Class({ return this.faces[index]; }, - /** - * Return an array of Face objects from this Mesh that intersect with the given coordinates. - * - * The given position is translated through the matrix of this Mesh and the given Camera, - * before being compared against the vertices. - * - * If more than one Face intersects, they will all be returned in the array, but the array will - * be depth sorted first, so the first element will always be that closest to the camera. - * - * @method Phaser.Geom.Mesh.Model#getFaceAt - * @since 3.50.0 - * - * @param {number} x - The x position to check against. - * @param {number} y - The y position to check against. - * @param {Phaser.Cameras.Scene2D.Camera} [camera] - The camera to pass the coordinates through. If not given, the default Scene Camera is used. - * - * @return {Phaser.Geom.Mesh.Face[]} An array of Face objects that intersect with the given point, ordered by depth. - */ - getFaceAt: function (x, y, camera, calcMatrix) - { - if (camera === undefined) { camera = this.scene.sys.cameras.main; } - if (calcMatrix === undefined) { calcMatrix = GetCalcMatrix(this.mesh, camera).calc; } - - var faces = this.faces; - var results = []; - - for (var i = 0; i < faces.length; i++) - { - var face = faces[i]; - - if (face.contains(x, y, calcMatrix)) - { - results.push(face); - } - } - - return StableSort(results, this.sortByDepth); - }, - - /** - * Runs a depth sort across all Faces in this Mesh, comparing their averaged depth. - * - * This is called automatically if you use any of the `rotate` methods, but you can - * also invoke it to sort the Faces should you manually position them. - * - * @method Phaser.Geom.Mesh.Model#depthSort - * @since 3.50.0 - * - * @return {this} This Mesh Game Object. - */ - depthSort: function () - { - StableSort(this.faces, this.sortByDepth); - - return this; - }, - - /** - * Compare the depth of two Faces. - * - * @method Phaser.Geom.Mesh.Model#sortByDepth - * @since 3.50.0 - * - * @param {Phaser.Geom.Mesh.Face} faceA - The first Face. - * @param {Phaser.Geom.Mesh.Face} faceB - The second Face. - * - * @return {integer} The difference between the depths of each Face. - */ - sortByDepth: function (faceA, faceB) - { - return faceA.depth - faceB.depth; - }, - /** * Adds a new Vertex into the vertices array of this Mesh. * @@ -513,102 +417,6 @@ var Model = new Class({ return this; }, - /** - * Rotates all vertices of this Mesh around the X axis by the amount given. - * - * It then runs a depth sort on the faces before returning. - * - * @method Phaser.Geom.Mesh.Model#rotateVerticesX - * @since 3.50.0 - * - * @param {number} theta - The amount to rotate by in radians. - * - * @return {this} This Mesh Game Object. - */ - rotateVerticesX: function (theta) - { - var ts = Math.sin(theta); - var tc = Math.cos(theta); - - var verts = this.vertices; - - for (var n = 0; n < verts.length; n++) - { - var vert = verts[n]; - var y = vert.y; - var z = vert.z; - - vert.y = y * tc - z * ts; - vert.z = z * tc + y * ts; - } - - return this.depthSort(); - }, - - /** - * Rotates all vertices of this Mesh around the Y axis by the amount given. - * - * It then runs a depth sort on the faces before returning. - * - * @method Phaser.Geom.Mesh.Model#rotateVerticesY - * @since 3.50.0 - * - * @param {number} theta - The amount to rotate by in radians. - * - * @return {this} This Mesh Game Object. - */ - rotateVerticesY: function (theta) - { - var ts = Math.sin(theta); - var tc = Math.cos(theta); - - var verts = this.vertices; - - for (var n = 0; n < verts.length; n++) - { - var vert = verts[n]; - var x = vert.x; - var z = vert.z; - - vert.x = x * tc - z * ts; - vert.z = z * tc + x * ts; - } - - return this.depthSort(); - }, - - /** - * Rotates all vertices of this Mesh around the Z axis by the amount given. - * - * It then runs a depth sort on the faces before returning. - * - * @method Phaser.Geom.Mesh.Model#rotateVerticesZ - * @since 3.50.0 - * - * @param {number} theta - The amount to rotate by in radians. - * - * @return {this} This Mesh Game Object. - */ - rotateVerticesZ: function (theta) - { - var ts = Math.sin(theta); - var tc = Math.cos(theta); - - var verts = this.vertices; - - for (var n = 0; n < verts.length; n++) - { - var vert = verts[n]; - var x = vert.x; - var y = vert.y; - - vert.x = x * tc - y * ts; - vert.y = y * tc + x * ts; - } - - return this.depthSort(); - }, - x: { get: function () diff --git a/src/geom/mesh/Vertex.js b/src/geom/mesh/Vertex.js index 3351b4c19..8f300a65c 100644 --- a/src/geom/mesh/Vertex.js +++ b/src/geom/mesh/Vertex.js @@ -5,8 +5,6 @@ */ var Class = require('../../utils/Class'); -var Vector3 = require('../../math/Vector3'); -var Utils = require('../../renderer/webgl/Utils'); /** * @classdesc @@ -17,7 +15,6 @@ var Utils = require('../../renderer/webgl/Utils'); * * @class Vertex * @memberof Phaser.Geom.Mesh - * @extends Phaser.Math.Vector3 * @constructor * @since 3.50.0 * @@ -31,43 +28,69 @@ var Utils = require('../../renderer/webgl/Utils'); */ var Vertex = new Class({ - Extends: Vector3, - initialize: - function Vertex (x, y, z, u, v, color, alpha) + function Vertex (x, y, z, u, v, normalX, normalY, normalZ, color, alpha) { + if (normalX === undefined) { normalX = 0; } + if (normalY === undefined) { normalY = 0; } + if (normalZ === undefined) { normalZ = 0; } if (color === undefined) { color = 0xffffff; } if (alpha === undefined) { alpha = 1; } - Vector3.call(this, x, y, z); - /** - * The projected x coordinate of this vertex. + * The x coordinate of this vertex. * * @name Phaser.Geom.Mesh.Vertex#vx * @type {number} * @since 3.50.0 */ - this.vx = 0; + this.x = x; /** - * The projected y coordinate of this vertex. + * The y coordinate of this vertex. * * @name Phaser.Geom.Mesh.Vertex#vy * @type {number} * @since 3.50.0 */ - this.vy = 0; + this.y = y; /** - * The projected z coordinate of this vertex. + * The z coordinate of this vertex. * * @name Phaser.Geom.Mesh.Vertex#vz * @type {number} * @since 3.50.0 */ - this.vz = 0; + this.z = z; + + /** + * The x normal of this vertex. + * + * @name Phaser.Geom.Mesh.Vertex#nx + * @type {number} + * @since 3.50.0 + */ + this.nx = normalX; + + /** + * The y normal of this vertex. + * + * @name Phaser.Geom.Mesh.Vertex#ny + * @type {number} + * @since 3.50.0 + */ + this.ny = normalY; + + /** + * The z normal of this vertex. + * + * @name Phaser.Geom.Mesh.Vertex#nz + * @type {number} + * @since 3.50.0 + */ + this.nz = normalZ; /** * UV u coordinate of this vertex. @@ -115,7 +138,6 @@ var Vertex = new Class({ * @param {Phaser.Math.Matrix4} transformMatrix - The transform matrix to apply to this vertex. * @param {number} width - The width of the parent Mesh. * @param {number} height - The height of the parent Mesh. - */ transformCoordinatesLocal: function (transformMatrix, width, height) { var x = this.x; @@ -133,39 +155,7 @@ var Vertex = new Class({ this.vy = -(ty / tw) * height; this.vz = (tz / tw); }, - - /** - * Loads this vertex into the given Typed Arrays. - * - * @method Phaser.Geom.Mesh.Model#load - * @since 3.50.0 - * - * @param {Float32Array} F32 - The Float32 Array to put the position data in. - * @param {Uint32Array} U32 - The Uint32 Array to put the color data in. - * @param {number} offset - The vertex offset to place the data at. - * @param {number} textureUnit - The currently bound texture unit. - * @param {number} alpha - The alpha value. - * @param {number} a - The transform matrix a value. - * @param {number} b - The transform matrix b value. - * @param {number} c - The transform matrix c value. - * @param {number} d - The transform matrix d value. - * @param {number} e - The transform matrix e value. - * @param {number} f - The transform matrix f value. - * - * @return {number} The new vertex offset. */ - load: function (F32, U32, offset, textureUnit, tintEffect, alpha, a, b, c, d, e, f) - { - F32[++offset] = this.vx * a + this.vy * c + e; - F32[++offset] = this.vx * b + this.vy * d + f; - F32[++offset] = this.u; - F32[++offset] = this.v; - F32[++offset] = textureUnit; - F32[++offset] = tintEffect; - U32[++offset] = Utils.getTintAppendFloatAlpha(this.color, alpha * this.alpha); - - return offset; - } }); diff --git a/src/math/Matrix4.js b/src/math/Matrix4.js index 79494eb49..f4a465b35 100644 --- a/src/math/Matrix4.js +++ b/src/math/Matrix4.js @@ -79,6 +79,90 @@ var Matrix4 = new Class({ return this.copy(src); }, + // TODO - Docs + fromRotationXYTranslation: function (rotation, position, translateFirst) + { + var x = position.x; + var y = position.y; + var z = position.z; + + var sx = Math.sin(rotation.x); + var cx = Math.cos(rotation.x); + + var sy = Math.sin(rotation.y); + var cy = Math.cos(rotation.y); + + var a30 = x; + var a31 = y; + var a32 = z; + + // Rotate X + + var b21 = -sx; + + // Rotate Y + + var c01 = 0 - b21 * sy; + + var c02 = 0 - cx * sy; + + var c21 = b21 * cy; + + var c22 = cx * cy; + + // Translate + if (!translateFirst) + { + // a30 = cy * x + 0 * y + sy * z; + a30 = cy * x + sy * z; + a31 = c01 * x + cx * y + c21 * z; + a32 = c02 * x + sx * y + c22 * z; + } + + return this.setValues( + cy, + c01, + c02, + 0, + 0, + cx, + sx, + 0, + sy, + c21, + c22, + 0, + a30, + a31, + a32, + 1 + ); + }, + + setValues: function (m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) + { + var out = this.val; + + out[0] = m00; + out[1] = m01; + out[2] = m02; + out[3] = m03; + out[4] = m10; + out[5] = m11; + out[6] = m12; + out[7] = m13; + out[8] = m20; + out[9] = m21; + out[10] = m22; + out[11] = m23; + out[12] = m30; + out[13] = m31; + out[14] = m32; + out[15] = m33; + + return this; + }, + /** * Copy the values of a given Matrix into this Matrix. * @@ -478,6 +562,69 @@ var Matrix4 = new Class({ return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; }, + // TODO - Docs + multiplyToMat4: function (src, out) + { + var a = this.val; + var b = src.val; + + var a00 = a[0]; + var a01 = a[1]; + var a02 = a[2]; + var a03 = a[3]; + var a10 = a[4]; + var a11 = a[5]; + var a12 = a[6]; + var a13 = a[7]; + var a20 = a[8]; + var a21 = a[9]; + var a22 = a[10]; + var a23 = a[11]; + var a30 = a[12]; + var a31 = a[13]; + var a32 = a[14]; + var a33 = a[15]; + + var b00 = b[0]; + var b01 = b[1]; + var b02 = b[2]; + var b03 = b[3]; + var b10 = b[4]; + var b11 = b[5]; + var b12 = b[6]; + var b13 = b[7]; + var b20 = b[8]; + var b21 = b[9]; + var b22 = b[10]; + var b23 = b[11]; + var b30 = b[12]; + var b31 = b[13]; + var b32 = b[14]; + var b33 = b[15]; + + return out.setValues( + b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, + b01 * a01 + b01 * a11 + b02 * a21 + b03 * a31, + b02 * a02 + b01 * a12 + b02 * a22 + b03 * a32, + b03 * a03 + b01 * a13 + b02 * a23 + b03 * a33, + + b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, + b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, + b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, + b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, + + b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, + b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, + b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, + b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, + + b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, + b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, + b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, + b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33 + ); + }, + /** * Multiply this Matrix by the given Matrix. * diff --git a/src/renderer/webgl/PipelineManager.js b/src/renderer/webgl/PipelineManager.js index 5a9bf71ec..0e23106b6 100644 --- a/src/renderer/webgl/PipelineManager.js +++ b/src/renderer/webgl/PipelineManager.js @@ -11,6 +11,7 @@ var CONST = require('./pipelines/const'); // Default Phaser 3 Pipelines var BitmapMaskPipeline = require('./pipelines/BitmapMaskPipeline'); var LightPipeline = require('./pipelines/LightPipeline'); +var MeshPipeline = require('./pipelines/MeshPipeline'); var MultiPipeline = require('./pipelines/MultiPipeline'); var RopePipeline = require('./pipelines/RopePipeline'); var SinglePipeline = require('./pipelines/SinglePipeline'); @@ -131,6 +132,7 @@ var PipelineManager = new Class({ this.add(CONST.SINGLE_PIPELINE, new SinglePipeline({ game: game })); this.add(CONST.ROPE_PIPELINE, new RopePipeline({ game: game })); this.add(CONST.LIGHT_PIPELINE, new LightPipeline({ game: game })); + this.add(CONST.MESH_PIPELINE, new MeshPipeline({ game: game })); this.set(this.MULTI_PIPELINE); }, diff --git a/src/renderer/webgl/WebGLPipeline.js b/src/renderer/webgl/WebGLPipeline.js index bc979460c..888eb897e 100644 --- a/src/renderer/webgl/WebGLPipeline.js +++ b/src/renderer/webgl/WebGLPipeline.js @@ -126,13 +126,19 @@ var WebGLPipeline = new Class({ this.vertexCapacity = GetFastValue(config, 'vertexCapacity', renderer.config.batchSize * 6); /** - * The size in bytes of a vertex. + * The size, in bytes, of a single vertex. * - * Derived by adding together all of the vertex attributes. + * This is derived by adding together all of the vertex attributes. * - * For example, the Texture Tint Pipeline has 2 + 2 + 1 + 1 + 4 for the attributes - * `inPosition` (size 2), `inTexCoord` (size 2), `inTexId` (size 1), `inTintEffect` (size 1) - * and `inTint` (size 4), for a total of 28, which is the default for this property. + * For example, the Multi Pipeline has the following attributes: + * + * inPosition - (size 2 x gl.FLOAT) = 8 + * inTexCoord - (size 2 x gl.FLOAT) = 8 + * inTexId - (size 1 x gl.FLOAT) = 4 + * inTintEffect - (size 1 x gl.FLOAT) = 4 + * inTint - (size 4 x gl.UNSIGNED_BYTE) = 4 + * + * The total is 8 + 8 + 4 + 4 + 4 = 28, which is the default for this property. * * @name Phaser.Renderer.WebGL.WebGLPipeline#vertexSize * @type {integer} @@ -459,13 +465,16 @@ var WebGLPipeline = new Class({ }, /** - * Set whenever this WebGL Pipeline is bound to a WebGL Renderer. + * This method is called every time a Game Object asks the Pipeline Manager to use this pipeline. * - * This method is called every time the WebGL Pipeline is attempted to be bound, even if it already is the current pipeline. + * Unlike the `bind` method, which is only called once per frame, this is called for every object + * that requests it, allowing you to perform per-object GL set-up. * * @method Phaser.Renderer.WebGL.WebGLPipeline#onBind * @since 3.0.0 * + * @param {Phaser.GameObjects.GameObject} [gameObject] - The Game Object that invoked this pipeline, if any. + * * @return {this} This WebGLPipeline instance. */ onBind: function () diff --git a/src/renderer/webgl/pipelines/MeshPipeline.js b/src/renderer/webgl/pipelines/MeshPipeline.js new file mode 100644 index 000000000..5181fcb6c --- /dev/null +++ b/src/renderer/webgl/pipelines/MeshPipeline.js @@ -0,0 +1,190 @@ +/** + * @author Richard Davey + * @copyright 2020 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +var Class = require('../../../utils/Class'); +var GetFastValue = require('../../../utils/object/GetFastValue'); +var ShaderSourceFS = require('../shaders/Mesh-frag.js'); +var ShaderSourceVS = require('../shaders/Mesh-vert.js'); +var WebGLPipeline = require('../WebGLPipeline'); + +/** + * @classdesc + * TODO + * + * @class MeshPipeline + * @extends Phaser.Renderer.WebGL.WebGLPipeline + * @memberof Phaser.Renderer.WebGL.Pipelines + * @constructor + * @since 3.50.0 + * + * @param {Phaser.Types.Renderer.WebGL.WebGLPipelineConfig} config - The configuration options for this pipeline. + */ +var MeshPipeline = new Class({ + + Extends: WebGLPipeline, + + initialize: + + function MeshPipeline (config) + { + var gl = config.game.renderer.gl; + + config.fragShader = GetFastValue(config, 'fragShader', ShaderSourceFS), + config.vertShader = GetFastValue(config, 'vertShader', ShaderSourceVS), + config.vertexCapacity = GetFastValue(config, 'vertexCapacity', 2048 * 8), + config.vertexSize = GetFastValue(config, 'vertexSize', 32), + config.attributes = GetFastValue(config, 'attributes', [ + { + name: 'aVertexPosition', + size: 3, + type: gl.FLOAT, + normalized: false, + offset: 0, + enabled: false, + location: -1 + }, + { + name: 'aVertexNormal', + size: 3, + type: gl.FLOAT, + normalized: false, + offset: 12, + enabled: false, + location: -1 + }, + { + name: 'aTextureCoord', + size: 2, + type: gl.FLOAT, + normalized: false, + offset: 24, + enabled: false, + location: -1 + } + ]); + + WebGLPipeline.call(this, config); + + /** + * Float32 view of the array buffer containing the pipeline's vertices. + * + * @name Phaser.Renderer.WebGL.Pipelines.MeshPipeline#vertexViewF32 + * @type {Float32Array} + * @since 3.0.0 + */ + this.vertexViewF32 = new Float32Array(this.vertexData); + + this.forceZero = true; + }, + + /** + * Called every time the pipeline is bound by the renderer. + * Sets the shader program, vertex buffer and other resources. + * Should only be called when changing pipeline. + * + * @method Phaser.Renderer.WebGL.Pipelines.MeshPipeline#bind + * @since 3.50.0 + * + * @param {boolean} [reset=false] - Should the pipeline be fully re-bound after a renderer pipeline clear? + * + * @return {this} This WebGLPipeline instance. + */ + bind: function (reset) + { + if (reset === undefined) { reset = false; } + + WebGLPipeline.prototype.bind.call(this, reset); + + var gl = this.gl; + + gl.enable(gl.DEPTH_TEST); + gl.enable(gl.CULL_FACE); + gl.cullFace(gl.BACK); + + return this; + }, + + /** + * This method is called every time a Game Object asks the Pipeline Manager to use this pipeline. + * + * Unlike the `bind` method, which is only called once per frame, this is called for every object + * that requests it, allowing you to perform per-object GL set-up. + * + * @method Phaser.Renderer.WebGL.Pipelines.MeshPipeline#onBind + * @since 3.50.0 + * + * @param {Phaser.GameObjects.Mesh} mesh - The Mesh that requested this pipeline. + * + * @return {this} This WebGLPipeline instance. + */ + onBind: function (mesh) + { + var program = this.program; + var renderer = this.renderer; + + var camera = mesh.camera; + var light = mesh.light; + + renderer.setMatrix4(program, 'uViewProjectionMatrix', false, camera.viewProjectionMatrix.val); + + renderer.setFloat3(program, 'uLightPosition', light.x, light.y, light.z); + renderer.setFloat3(program, 'uLightAmbient', light.ambient.x, light.ambient.y, light.ambient.z); + renderer.setFloat3(program, 'uLightDiffuse', light.diffuse.x, light.diffuse.y, light.diffuse.z); + renderer.setFloat3(program, 'uLightSpecular', light.specular.x, light.specular.y, light.specular.z); + renderer.setFloat3(program, 'uCameraPosition', camera.x, camera.y, camera.z); + }, + + drawModel: function (mesh, model) + { + var program = this.program; + var renderer = this.renderer; + + renderer.setMatrix4(program, 'uModelMatrix', false, model.transformMatrix.val); + renderer.setMatrix4(program, 'uNormalMatrix', false, model.normalMatrix.val); + + renderer.setFloat3(program, 'uMaterialAmbient', model.ambient.x, model.ambient.y, model.ambient.z); + renderer.setFloat3(program, 'uMaterialDiffuse', model.diffuse.x, model.diffuse.y, model.diffuse.z); + renderer.setFloat3(program, 'uMaterialSpecular', model.specular.x, model.specular.y, model.specular.z); + renderer.setFloat1(program, 'uMaterialShine', model.shine); + + renderer.setTextureZero(model.frame.glTexture); + renderer.setInt1(program, 'uTexture', 0); + + // All the uniforms are finally bound, so now lets draw our faces! + + var vertexViewF32 = this.vertexViewF32; + + var vertices = model.vertices; + + for (var i = 0; i < vertices.length; i++) + { + if (this.shouldFlush(1)) + { + this.flush(); + } + + var vertexOffset = (this.vertexCount * this.vertexComponentCount) - 1; + + var vert = vertices[i]; + + vertexViewF32[++vertexOffset] = vert.x; + vertexViewF32[++vertexOffset] = vert.y; + vertexViewF32[++vertexOffset] = vert.z; + vertexViewF32[++vertexOffset] = vert.nx; + vertexViewF32[++vertexOffset] = vert.ny; + vertexViewF32[++vertexOffset] = vert.nz; + vertexViewF32[++vertexOffset] = vert.u; + vertexViewF32[++vertexOffset] = vert.v; + + this.vertexCount++; + } + + this.flush(); + } + +}); + +module.exports = MeshPipeline; diff --git a/src/renderer/webgl/pipelines/SinglePipeline.js b/src/renderer/webgl/pipelines/SinglePipeline.js index 8ad5d396e..9d45bf086 100644 --- a/src/renderer/webgl/pipelines/SinglePipeline.js +++ b/src/renderer/webgl/pipelines/SinglePipeline.js @@ -156,7 +156,7 @@ var SinglePipeline = new Class({ var hasFlushed = false; - if (this.vertexCount + 6 > this.vertexCapacity) + if (this.shouldFlush(6)) { this.flush(); @@ -260,7 +260,7 @@ var SinglePipeline = new Class({ var hasFlushed = false; - if (this.vertexCount + 3 > this.vertexCapacity) + if (this.shouldFlush(3)) { this.flush(); diff --git a/src/renderer/webgl/pipelines/const.js b/src/renderer/webgl/pipelines/const.js index fa8171bf6..ae0a20c56 100644 --- a/src/renderer/webgl/pipelines/const.js +++ b/src/renderer/webgl/pipelines/const.js @@ -54,7 +54,17 @@ var PIPELINE_CONST = { * @const * @since 3.50.0 */ - ROPE_PIPELINE: 'RopePipeline' + ROPE_PIPELINE: 'RopePipeline', + + /** + * The Mesh Pipeline. + * + * @name Phaser.Renderer.WebGL.Pipelines.MESH_PIPELINE + * @type {string} + * @const + * @since 3.50.0 + */ + MESH_PIPELINE: 'MeshPipeline' }; diff --git a/src/renderer/webgl/pipelines/index.js b/src/renderer/webgl/pipelines/index.js index 6b6e1449d..60dd4b404 100644 --- a/src/renderer/webgl/pipelines/index.js +++ b/src/renderer/webgl/pipelines/index.js @@ -15,10 +15,11 @@ var Pipelines = { BitmapMaskPipeline: require('./BitmapMaskPipeline'), LightPipeline: require('./LightPipeline'), - SinglePipeline: require('./SinglePipeline'), + MeshPipeline: require('./MeshPipeline'), + ModelViewProjection: require('./components/ModelViewProjection'), MultiPipeline: require('./MultiPipeline'), RopePipeline: require('./RopePipeline'), - ModelViewProjection: require('./components/ModelViewProjection') + SinglePipeline: require('./SinglePipeline') }; diff --git a/src/renderer/webgl/shaders/Mesh-frag.js b/src/renderer/webgl/shaders/Mesh-frag.js new file mode 100644 index 000000000..ddb20a5c0 --- /dev/null +++ b/src/renderer/webgl/shaders/Mesh-frag.js @@ -0,0 +1,45 @@ +module.exports = [ + '#define SHADER_NAME PHASER_MESH_FS', + '', + 'precision mediump float;', + '', + 'uniform vec3 uLightPosition;', + 'uniform vec3 uLightAmbient;', + 'uniform vec3 uLightDiffuse;', + 'uniform vec3 uLightSpecular;', + '', + 'uniform vec3 uMaterialAmbient;', + 'uniform vec3 uMaterialDiffuse;', + 'uniform vec3 uMaterialSpecular;', + 'uniform float uMaterialShine;', + '', + 'uniform vec3 uCameraPosition;', + '', + 'uniform sampler2D uTexture;', + '', + 'varying vec2 vTextureCoord;', + 'varying vec3 vNormal;', + 'varying vec3 vPosition;', + '', + 'void main (void)', + '{', + ' vec4 color = texture2D(uTexture, vTextureCoord);', + '', + ' vec3 ambient = uLightAmbient * uMaterialAmbient;', + '', + ' vec3 norm = normalize(vNormal);', + ' vec3 lightDir = normalize(uLightPosition - vPosition);', + ' float diff = max(dot(norm, lightDir), 0.0);', + ' vec3 diffuse = uLightDiffuse * (diff * uMaterialDiffuse);', + '', + ' vec3 viewDir = normalize(uCameraPosition - vPosition);', + ' vec3 reflectDir = reflect(-lightDir, norm);', + ' float spec = pow(max(dot(viewDir, reflectDir), 0.0), uMaterialShine);', + ' vec3 specular = uLightSpecular * (spec * uMaterialSpecular);', + '', + ' vec3 result = (ambient + diffuse + specular) * color.rgb;', + '', + ' gl_FragColor = vec4(result, color.a);', + '}', + '' +].join('\n'); diff --git a/src/renderer/webgl/shaders/Mesh-vert.js b/src/renderer/webgl/shaders/Mesh-vert.js new file mode 100644 index 000000000..eed7de6bf --- /dev/null +++ b/src/renderer/webgl/shaders/Mesh-vert.js @@ -0,0 +1,29 @@ +module.exports = [ + '#define SHADER_NAME PHASER_MESH_VS', + '', + 'precision mediump float;', + '', + 'attribute vec3 aVertexPosition;', + 'attribute vec3 aVertexNormal;', + 'attribute vec2 aTextureCoord;', + '', + 'uniform mat4 uViewProjectionMatrix;', + 'uniform mat4 uModelMatrix;', + 'uniform mat4 uNormalMatrix;', + '', + 'varying vec2 vTextureCoord;', + 'varying vec3 vNormal;', + 'varying vec3 vPosition;', + '', + 'void main ()', + '{', + ' vTextureCoord = aTextureCoord;', + '', + ' vPosition = vec3(uModelMatrix * vec4(aVertexPosition, 1.0));', + '', + ' vNormal = vec3(uNormalMatrix * vec4(aVertexNormal, 1.0));', + '', + ' gl_Position = uViewProjectionMatrix * uModelMatrix * vec4(aVertexPosition, 1.0);', + '}', + '' +].join('\n'); diff --git a/src/renderer/webgl/shaders/src/Mesh.frag b/src/renderer/webgl/shaders/src/Mesh.frag new file mode 100644 index 000000000..cd69378cc --- /dev/null +++ b/src/renderer/webgl/shaders/src/Mesh.frag @@ -0,0 +1,42 @@ +#define SHADER_NAME PHASER_MESH_FS + +precision mediump float; + +uniform vec3 uLightPosition; +uniform vec3 uLightAmbient; +uniform vec3 uLightDiffuse; +uniform vec3 uLightSpecular; + +uniform vec3 uMaterialAmbient; +uniform vec3 uMaterialDiffuse; +uniform vec3 uMaterialSpecular; +uniform float uMaterialShine; + +uniform vec3 uCameraPosition; + +uniform sampler2D uTexture; + +varying vec2 vTextureCoord; +varying vec3 vNormal; +varying vec3 vPosition; + +void main (void) +{ + vec4 color = texture2D(uTexture, vTextureCoord); + + vec3 ambient = uLightAmbient * uMaterialAmbient; + + vec3 norm = normalize(vNormal); + vec3 lightDir = normalize(uLightPosition - vPosition); + float diff = max(dot(norm, lightDir), 0.0); + vec3 diffuse = uLightDiffuse * (diff * uMaterialDiffuse); + + vec3 viewDir = normalize(uCameraPosition - vPosition); + vec3 reflectDir = reflect(-lightDir, norm); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), uMaterialShine); + vec3 specular = uLightSpecular * (spec * uMaterialSpecular); + + vec3 result = (ambient + diffuse + specular) * color.rgb; + + gl_FragColor = vec4(result, color.a); +} diff --git a/src/renderer/webgl/shaders/src/Mesh.vert b/src/renderer/webgl/shaders/src/Mesh.vert new file mode 100644 index 000000000..94f611766 --- /dev/null +++ b/src/renderer/webgl/shaders/src/Mesh.vert @@ -0,0 +1,26 @@ +#define SHADER_NAME PHASER_MESH_VS + +precision mediump float; + +attribute vec3 aVertexPosition; +attribute vec3 aVertexNormal; +attribute vec2 aTextureCoord; + +uniform mat4 uViewProjectionMatrix; +uniform mat4 uModelMatrix; +uniform mat4 uNormalMatrix; + +varying vec2 vTextureCoord; +varying vec3 vNormal; +varying vec3 vPosition; + +void main () +{ + vTextureCoord = aTextureCoord; + + vPosition = vec3(uModelMatrix * vec4(aVertexPosition, 1.0)); + + vNormal = vec3(uNormalMatrix * vec4(aVertexNormal, 1.0)); + + gl_Position = uViewProjectionMatrix * uModelMatrix * vec4(aVertexPosition, 1.0); +}