/** * A mesh of a {@link Region}. * @extends {PIXI.Container} */ export default class RegionMesh extends PIXI.Container { /** * Create a RegionMesh. * @param {Region} region The Region to create the RegionMesh from. * @param {AbstractBaseShader} [shaderClass] The shader class to use. */ constructor(region, shaderClass=RegionShader) { super(); this.#region = region; this.region.geometry.refCount++; if ( !AbstractBaseShader.isPrototypeOf(shaderClass) ) { throw new Error("RegionMesh shader class must inherit from AbstractBaseShader."); } this.#shader = shaderClass.create(); } /* ---------------------------------------- */ /** * Shared point instance. * @type {PIXI.Point} */ static #SHARED_POINT = new PIXI.Point(); /* ---------------------------------------- */ /** * The Region of this RegionMesh. * @type {RegionMesh} */ get region() { return this.#region; } #region; /* ---------------------------------------- */ /** * The shader bound to this RegionMesh. * @type {AbstractBaseShader} */ get shader() { return this.#shader; } #shader; /* ---------------------------------------- */ /** * The blend mode assigned to this RegionMesh. * @type {PIXI.BLEND_MODES} */ get blendMode() { return this.#state.blendMode; } set blendMode(value) { if ( this.#state.blendMode === value ) return; this.#state.blendMode = value; this._tintAlphaDirty = true; } #state = PIXI.State.for2d(); /* ---------------------------------------- */ /** * The tint applied to the mesh. This is a hex value. * * A value of 0xFFFFFF will remove any tint effect. * @type {number} * @defaultValue 0xFFFFFF */ get tint() { return this._tintColor.value; } set tint(tint) { const currentTint = this._tintColor.value; this._tintColor.setValue(tint); if ( currentTint === this._tintColor.value ) return; this._tintAlphaDirty = true; } /* ---------------------------------------- */ /** * The tint applied to the mesh. This is a hex value. A value of 0xFFFFFF will remove any tint effect. * @type {PIXI.Color} * @protected */ _tintColor = new PIXI.Color(0xFFFFFF); /* ---------------------------------------- */ /** * Cached tint value for the shader uniforms. * @type {[red: number, green: number, blue: number, alpha: number]} * @protected * @internal */ _cachedTint = [1, 1, 1, 1]; /* ---------------------------------------- */ /** * Used to track a tint or alpha change to execute a recomputation of _cachedTint. * @type {boolean} * @protected */ _tintAlphaDirty = true; /* ---------------------------------------- */ /** * Initialize shader based on the shader class type. * @param {type AbstractBaseShader} shaderClass The shader class, which must inherit from {@link AbstractBaseShader}. */ setShaderClass(shaderClass) { if ( !AbstractBaseShader.isPrototypeOf(shaderClass) ) { throw new Error("RegionMesh shader class must inherit from AbstractBaseShader."); } if ( this.#shader.constructor === shaderClass ) return; // Create shader program this.#shader = shaderClass.create(); } /* ---------------------------------------- */ /** @override */ updateTransform() { super.updateTransform(); // We set tintAlphaDirty to true if the worldAlpha has changed // It is needed to recompute the _cachedTint vec4 which is a combination of tint and alpha if ( this.#worldAlpha !== this.worldAlpha ) { this.#worldAlpha = this.worldAlpha; this._tintAlphaDirty = true; } } #worldAlpha; /* ---------------------------------------- */ /** @override */ _render(renderer) { if ( this._tintAlphaDirty ) { const premultiply = PIXI.utils.premultiplyBlendMode[1][this.blendMode] === this.blendMode; PIXI.Color.shared.setValue(this._tintColor) .premultiply(this.worldAlpha, premultiply) .toArray(this._cachedTint); this._tintAlphaDirty = false; } this.#shader._preRender(this, renderer); this.#shader.uniforms.translationMatrix = this.transform.worldTransform.toArray(true); // Flush batch renderer renderer.batch.flush(); // Set state renderer.state.set(this.#state); // Bind shader and geometry renderer.shader.bind(this.#shader); const geometry = this.region.geometry; geometry._updateBuffers(); renderer.geometry.bind(geometry, this.#shader); // Draw the geometry renderer.geometry.draw(PIXI.DRAW_MODES.TRIANGLES); } /* ---------------------------------------- */ /** @override */ _calculateBounds() { const {left, top, right, bottom} = this.region.bounds; this._bounds.addFrame(this.transform, left, top, right, bottom); } /* ---------------------------------------- */ /** * Tests if a point is inside this RegionMesh. * @param {PIXI.IPointData} point * @returns {boolean} */ containsPoint(point) { return this.region.polygonTree.testPoint(this.worldTransform.applyInverse(point, RegionMesh.#SHARED_POINT)); } /* ---------------------------------------- */ /** @override */ destroy(options) { super.destroy(options); const geometry = this.region.geometry; geometry.refCount--; if ( geometry.refCount === 0 ) geometry.dispose(); this.#shader = null; this.#state = null; } }