UNPKG

@awayjs/scene

Version:
312 lines (257 loc) 9.94 kB
import { AttributesBuffer, AttributesView } from '@awayjs/stage'; import { IMaterial, TriangleElements } from '@awayjs/renderer'; import { Graphics, Shape } from '@awayjs/graphics'; import { DisplayObjectContainer } from '../display/DisplayObjectContainer'; import { Sprite } from '../display/Sprite'; /** * Class Merge merges two or more static sprites into one.<code>Merge</code> */ export class Merge { //private const LIMIT:uint = 196605; private _objectSpace: boolean; private _keepMaterial: boolean; private _disposeSources: boolean; private _shapeVOs: Array<ShapeVO>; private _toDispose: Array<Sprite>; /** * @param keepMaterial [optional] * Determines if the merged object uses the recevier sprite material information or keeps its source material(s). * Defaults to false. * If false and receiver object has multiple materials, * the last material found in receiver subsprites is applied to the merged subsprite(es). * @param disposeSources [optional] * Determines if the sprite and geometry source(s) used for the merging are disposed. Defaults to false. * If true, only receiver geometry and resulting sprite are kept in memory. * @param objectSpace [optional] * Determines if source sprite(es) is/are merged using objectSpace or worldspace. Defaults to false. */ constructor(keepMaterial: boolean = false, disposeSources: boolean = false, objectSpace: boolean = false) { this._keepMaterial = keepMaterial; this._disposeSources = disposeSources; this._objectSpace = objectSpace; } /** * Determines if the sprite and geometry source(s) used for the merging are disposed. Defaults to false. */ public set disposeSources(b: boolean) { this._disposeSources = b; } public get disposeSources(): boolean { return this._disposeSources; } /** * Determines if the material source(s) used for the merging are disposed. Defaults to false. */ public set keepMaterial(b: boolean) { this._keepMaterial = b; } public get keepMaterial(): boolean { return this._keepMaterial; } /** * Determines if source sprite(es) is/are merged using objectSpace or worldspace. Defaults to false. */ public set objectSpace(b: boolean) { this._objectSpace = b; } public get objectSpace(): boolean { return this._objectSpace; } /** * Merges all the children of a container into a single Sprite. * If no Sprite object is found, method returns the receiver without modification. * * @param receiver The Sprite to receive the merged contents of the container. * @param objectContainer The DisplayObjectContainer holding the sprites to be mergd. * * @return The merged Sprite instance. */ public applyToContainer(receiver: Sprite, objectContainer: DisplayObjectContainer): void { this.reset(); //collect container sprites this.parseContainer(receiver, objectContainer); //collect receiver this.collect(receiver, false); //merge to receiver this.merge(receiver, this._disposeSources); } /** * Merges all the sprites found in the Array&lt;Sprite&gt; into a single Sprite. * * @param receiver The Sprite to receive the merged contents of the sprites. * @param sprites A series of Sprites to be merged with the reciever sprite. */ public applyToSprites(receiver: Sprite, sprites: Array<Sprite>): void { this.reset(); if (!sprites.length) return; //collect sprites in vector for (let i: number = 0; i < sprites.length; i++) if (sprites[i] != receiver) this.collect(sprites[i], this._disposeSources); //collect receiver this.collect(receiver, false); //merge to receiver this.merge(receiver, this._disposeSources); } /** * Merges 2 sprites into one. * It is recommand to use apply when 2 sprites are to be merged. * If more need to be merged, use either applyToSprites or applyToContainer methods. * * @param receiver The Sprite to receive the merged contents of both sprites. * @param sprite The Sprite to be merged with the receiver sprite */ public apply(receiver: Sprite, sprite: Sprite): void { this.reset(); //collect sprite this.collect(sprite, this._disposeSources); //collect receiver this.collect(receiver, false); //merge to receiver this.merge(receiver, this._disposeSources); } private reset(): void { this._toDispose = new Array<Sprite>(); this._shapeVOs = new Array<ShapeVO>(); } private merge(destSprite: Sprite, dispose: boolean): void { let i: number; const destGraphics: Graphics = destSprite.graphics; const useSubMaterials: boolean = (this._shapeVOs.length > 1); for (i = 0; i < this._shapeVOs.length; i++) { const elements: TriangleElements = new TriangleElements(new AttributesBuffer()); elements.autoDeriveNormals = false; elements.autoDeriveTangents = false; const data: ShapeVO = this._shapeVOs[i]; elements.setIndices(data.indices); elements.setPositions(data.vertices); elements.setNormals(data.normals); elements.setTangents(data.tangents); elements.setUVs(data.uvs); if (this._keepMaterial && useSubMaterials) destGraphics.addShape(Shape.getShape(elements, data.material)); else destGraphics.addShape(Shape.getShape(elements)); } if (this._keepMaterial && !useSubMaterials && this._shapeVOs.length) destSprite.material = this._shapeVOs[0].material; if (dispose) { const len: number = this._toDispose.length; for (i; i < len; i++) this._toDispose[i].dispose(); } this._toDispose = null; } private collect(sprite: Sprite, dispose: boolean): void { let subIdx: number; let calc: number; for (subIdx = 0; subIdx < sprite.graphics.count; subIdx++) { let i: number; let len: number; let iIdx: number, vIdx: number, nIdx: number, tIdx: number; const elements: TriangleElements = <TriangleElements> sprite.graphics.getShapeAt(subIdx).elements; // Get (or create) a VO for this material const vo: ShapeVO = this.getShapeData(sprite.graphics.getShapeAt(subIdx).material); // Vertices and normals are copied to temporary vectors, to be transformed // before concatenated onto those of the data. This is unnecessary if no // transformation will be performed, i.e. for object space merging. const vertices: number[] = (this._objectSpace) ? vo.vertices : []; const normals: number[] = (this._objectSpace) ? vo.normals : []; const tangents: number[] = (this._objectSpace) ? vo.tangents : []; // Copy over vertex attributes vIdx = vertices.length; nIdx = normals.length; tIdx = tangents.length; const uIdx = vo.uvs.length; this.copyAttributes(elements.positions, vertices, elements.numVertices, vIdx); this.copyAttributes(elements.normals, normals, elements.numVertices, nIdx); this.copyAttributes(elements.tangents, tangents, elements.numVertices, tIdx); this.copyAttributes(elements.uvs, vo.uvs, elements.numVertices, uIdx); // Copy over triangle indices const indexOffset: number = (!this._objectSpace) ? vo.vertices.length / 3 : 0; iIdx = vo.indices.length; len = elements.numElements; const ind: Uint16Array = elements.indices.get(len); for (i = 0; i < len; i++) { calc = i * 3; vo.indices[iIdx++] = ind[calc] + indexOffset; vo.indices[iIdx++] = ind[calc + 1] + indexOffset; vo.indices[iIdx++] = ind[calc + 2] + indexOffset; } if (!this._objectSpace) { sprite.transform.matrix3D.transformVectors(vertices, vertices); sprite.transform.matrix3D.deltaTransformVectors(normals, normals); sprite.transform.matrix3D.deltaTransformVectors(tangents, tangents); // Copy vertex data from temporary (transformed) vectors vIdx = vo.vertices.length; nIdx = vo.normals.length; tIdx = vo.tangents.length; len = vertices.length; for (i = 0; i < len; i++) { vo.vertices[vIdx++] = vertices[i]; vo.normals[nIdx++] = normals[i]; vo.tangents[tIdx++] = tangents[i]; } } } if (dispose) this._toDispose.push(sprite); } private copyAttributes(attributes: AttributesView, array: Array<number>, count: number, startIndex: number) { const vertices: ArrayBufferView = attributes.get(count); const dim: number = attributes.dimensions; const stride: number = attributes.stride; const len: number = count * stride; for (let i: number = 0; i < len; i += stride) for (let j: number = 0; j < dim; j++) array[startIndex++] = vertices[i + j]; } private getShapeData(material: IMaterial): ShapeVO { let data: ShapeVO; if (this._keepMaterial) { let i: number; const len = this._shapeVOs.length; for (i = 0; i < len; i++) { if (this._shapeVOs[i].material == material) { data = this._shapeVOs[i]; break; } } } else if (this._shapeVOs.length) { // If materials are not to be kept, all data can be // put into a single VO, so return that one. data = this._shapeVOs[0]; } // No data (for this material) found, create new. if (!data) { data = new ShapeVO(); data.vertices = new Array<number>(); data.normals = new Array<number>(); data.tangents = new Array<number>(); data.uvs = new Array<number>(); data.indices = new Array<number>(); data.material = material; this._shapeVOs.push(data); } return data; } private parseContainer(receiver: Sprite, object: DisplayObjectContainer): void { let child: DisplayObjectContainer; let i: number; if (object instanceof Sprite && object != (<DisplayObjectContainer> receiver)) this.collect(<Sprite> object, this._disposeSources); for (i = 0; i < object.numChildren; ++i) { child = <DisplayObjectContainer> object.getChildAt(i); this.parseContainer(receiver, child); } } } export class ShapeVO { public uvs: Array<number>; public vertices: Array<number>; public normals: Array<number>; public tangents: Array<number>; public indices: Array<number>; public material: IMaterial; }