diff --git a/src/Drawable.js b/src/Drawable.js index eb5688a..e90f09c 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -51,6 +51,12 @@ class Drawable { this._scale = twgl.v3.create(100, 100); this._direction = 90; this._transformDirty = true; + this._rotationMatrix = twgl.m4.identity(); + this._rotationTransformDirty = true; + this._rotationAdjusted = twgl.v3.create(); + this._rotationCenterDirty = true; + this._skinScale = twgl.v3.create(0, 0, 0); + this._skinScaleDirty = true; this._inverseMatrix = twgl.m4.identity(); this._inverseTransformDirty = true; this._visible = true; @@ -156,6 +162,7 @@ class Drawable { } if ('direction' in properties && this._direction !== properties.direction) { this._direction = properties.direction; + this._rotationTransformDirty = true; dirty = true; } if ('scale' in properties && ( @@ -163,6 +170,8 @@ class Drawable { this._scale[1] !== properties.scale[1])) { this._scale[0] = properties.scale[0]; this._scale[1] = properties.scale[1]; + this._rotationCenterDirty = true; + this._skinScaleDirty = true; dirty = true; } if ('visible' in properties) { @@ -197,26 +206,141 @@ class Drawable { * @private */ _calculateTransform () { - const modelMatrix = this._uniforms.u_modelMatrix; + if (this._rotationTransformDirty) { + const rotation = (270 - this._direction) * Math.PI / 180; - twgl.m4.identity(modelMatrix); - twgl.m4.translate(modelMatrix, this._position, modelMatrix); + // Calling rotationZ sets the destination matrix to a rotation + // around the Z axis setting matrix components 0, 1, 4 and 5 with + // cosine and sine values of the rotation. + // twgl.m4.rotationZ(rotation, this._rotationMatrix); - const rotation = (270 - this._direction) * Math.PI / 180; - twgl.m4.rotateZ(modelMatrix, rotation, modelMatrix); + // twgl assumes the last value set to the matrix was anything. + // Drawable knows, it was another rotationZ matrix, so we can skip + // assigning the values that will never change. + const c = Math.cos(rotation); + const s = Math.sin(rotation); + this._rotationMatrix[0] = c; + this._rotationMatrix[1] = s; + // this._rotationMatrix[2] = 0; + // this._rotationMatrix[3] = 0; + this._rotationMatrix[4] = -s; + this._rotationMatrix[5] = c; + // this._rotationMatrix[6] = 0; + // this._rotationMatrix[7] = 0; + // this._rotationMatrix[8] = 0; + // this._rotationMatrix[9] = 0; + // this._rotationMatrix[10] = 1; + // this._rotationMatrix[11] = 0; + // this._rotationMatrix[12] = 0; + // this._rotationMatrix[13] = 0; + // this._rotationMatrix[14] = 0; + // this._rotationMatrix[15] = 1; + + this._rotationTransformDirty = false; + } // Adjust rotation center relative to the skin. - let rotationAdjusted = twgl.v3.subtract(this.skin.rotationCenter, twgl.v3.divScalar(this.skin.size, 2)); - rotationAdjusted = twgl.v3.multiply(rotationAdjusted, this.scale); - rotationAdjusted = twgl.v3.divScalar(rotationAdjusted, 100); - rotationAdjusted[1] *= -1; // Y flipped to Scratch coordinate. - rotationAdjusted[2] = 0; // Z coordinate is 0. + if (this._rotationCenterDirty && this.skin !== null) { + // twgl version of the following in function work. + // let rotationAdjusted = twgl.v3.subtract( + // this.skin.rotationCenter, + // twgl.v3.divScalar(this.skin.size, 2, this._rotationAdjusted), + // this._rotationAdjusted + // ); + // rotationAdjusted = twgl.v3.multiply( + // rotationAdjusted, this._scale, rotationAdjusted + // ); + // rotationAdjusted = twgl.v3.divScalar( + // rotationAdjusted, 100, rotationAdjusted + // ); + // rotationAdjusted[1] *= -1; // Y flipped to Scratch coordinate. + // rotationAdjusted[2] = 0; // Z coordinate is 0. - twgl.m4.translate(modelMatrix, rotationAdjusted, modelMatrix); + // Locally assign rotationCenter and skinSize to keep from having + // the Skin getter properties called twice while locally assigning + // their components for readability. + const rotationCenter = this.skin.rotationCenter; + const skinSize = this.skin.size; + const center0 = rotationCenter[0]; + const center1 = rotationCenter[1]; + const skinSize0 = skinSize[0]; + const skinSize1 = skinSize[1]; + const scale0 = this._scale[0]; + const scale1 = this._scale[1]; - const scaledSize = twgl.v3.divScalar(twgl.v3.multiply(this.skin.size, this._scale), 100); - scaledSize[2] = 0; // was NaN because the vectors have only 2 components. - twgl.m4.scale(modelMatrix, scaledSize, modelMatrix); + const rotationAdjusted = this._rotationAdjusted; + rotationAdjusted[0] = (center0 - (skinSize0 / 2)) * scale0 / 100; + rotationAdjusted[1] = ((center1 - (skinSize1 / 2)) * scale1 / 100) * -1; + // rotationAdjusted[2] = 0; + + this._rotationCenterDirty = false; + } + + if (this._skinScaleDirty && this.skin !== null) { + // twgl version of the following in function work. + // const scaledSize = twgl.v3.divScalar( + // twgl.v3.multiply(this.skin.size, this._scale), + // 100 + // ); + // // was NaN because the vectors have only 2 components. + // scaledSize[2] = 0; + + // Locally assign skinSize to keep from having the Skin getter + // properties called twice. + const skinSize = this.skin.size; + const scaledSize = this._skinScale; + scaledSize[0] = skinSize[0] * this._scale[0] / 100; + scaledSize[1] = skinSize[1] * this._scale[1] / 100; + // scaledSize[2] = 0; + + this._skinScaleDirty = false; + } + + const modelMatrix = this._uniforms.u_modelMatrix; + + // twgl version of the following in function work. + // twgl.m4.identity(modelMatrix); + // twgl.m4.translate(modelMatrix, this._position, modelMatrix); + // twgl.m4.multiply(modelMatrix, this._rotationMatrix, modelMatrix); + // twgl.m4.translate(modelMatrix, this._rotationAdjusted, modelMatrix); + // twgl.m4.scale(modelMatrix, scaledSize, modelMatrix); + + // Drawable configures a 3D matrix for drawing in WebGL, but most values + // will never be set because the inputs are on the X and Y position axis + // and the Z rotation axis. Drawable can bring the work inside + // _calculateTransform and greatly reduce the ammount of math and array + // assignments needed. + + const scale0 = this._skinScale[0]; + const scale1 = this._skinScale[1]; + const rotation00 = this._rotationMatrix[0]; + const rotation01 = this._rotationMatrix[1]; + const rotation10 = this._rotationMatrix[4]; + const rotation11 = this._rotationMatrix[5]; + const adjusted0 = this._rotationAdjusted[0]; + const adjusted1 = this._rotationAdjusted[1]; + const position0 = this._position[0]; + const position1 = this._position[1]; + + // Commented assignments show what the values are when the matrix was + // instantiated. Those values will never change so they do not need to + // be reassigned. + modelMatrix[0] = scale0 * rotation00; + modelMatrix[1] = scale0 * rotation01; + // modelMatrix[2] = 0; + // modelMatrix[3] = 0; + modelMatrix[4] = scale1 * rotation10; + modelMatrix[5] = scale1 * rotation11; + // modelMatrix[6] = 0; + // modelMatrix[7] = 0; + // modelMatrix[8] = 0; + // modelMatrix[9] = 0; + // modelMatrix[10] = 1; + // modelMatrix[11] = 0; + modelMatrix[12] = (rotation00 * adjusted0) + (rotation10 * adjusted1) + position0; + modelMatrix[13] = (rotation01 * adjusted0) + (rotation11 * adjusted1) + position1; + // modelMatrix[14] = 0; + // modelMatrix[15] = 1; this._transformDirty = false; } @@ -369,6 +493,8 @@ class Drawable { * @private */ _skinWasAltered () { + this._rotationCenterDirty = true; + this._skinScaleDirty = true; this.setConvexHullDirty(); this.setTransformDirty(); }