scaffold.libs_as.starling.rendering.IndexData.as Maven / Gradle / Ivy
// =================================================================================================
//
// Starling Framework
// Copyright 2011-2015 Gamua. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.rendering
{
import flash.display3D.Context3D;
import flash.display3D.IndexBuffer3D;
import flash.errors.EOFError;
import flash.utils.ByteArray;
import flash.utils.Endian;
import starling.core.Starling;
import starling.errors.MissingContextError;
import starling.utils.StringUtil;
/** The IndexData class manages a raw list of vertex indices, allowing direct upload
* to Stage3D index buffers. You only have to work with this class if you're writing
* your own rendering code (e.g. if you create custom display objects).
*
* To render objects with Stage3D, you have to organize vertices and indices in so-called
* vertex- and index-buffers. Vertex buffers store the coordinates of the vertices that make
* up an object; index buffers reference those vertices to determine which vertices spawn
* up triangles. Those buffers reside in graphics memory and can be accessed very
* efficiently by the GPU.
*
* Before you can move data into the buffers, you have to set it up in conventional
* memory — that is, in a Vector or a ByteArray. Since it's quite cumbersome to manually
* create and manipulate those data structures, the IndexData and VertexData classes provide
* a simple way to do just that. The data is stored in a ByteArray (one index or vertex after
* the other) that can easily be uploaded to a buffer.
*
* Basic Quad Layout
*
* In many cases, the indices we are working with will reference just quads, i.e.
* triangles composing rectangles. That means that many IndexData instances will contain
* similar or identical data — a great opportunity for optimization!
*
* If an IndexData instance follows a specific layout, it will be recognized
* automatically and many operations can be executed much faster. In Starling, that
* layout is called "basic quad layout". In order to recognize this specific sequence,
* the indices of each quad have to use the following order:
*
* n, n+1, n+2, n+1, n+3, n+2
*
* The subsequent quad has to use n+4
as starting value, the next one
* n+8
, etc. Here is an example with 3 quads / 6 triangles:
*
* 0, 1, 2, 1, 3, 2, 4, 5, 6, 5, 7, 6, 8, 9, 10, 9, 11, 10
*
* If you are describing quad-like meshes, make sure to always use this layout.
*
* @see VertexData
*/
public class IndexData
{
/** The number of bytes per index element. */
private static const INDEX_SIZE:int = 2;
private var _rawData:ByteArray;
private var _numIndices:int;
private var _initialCapacity:int;
private var _useQuadLayout:Boolean;
// helper objects
private static var sVector:Vector. = new [];
private static var sTrimData:ByteArray = new ByteArray();
private static var sQuadData:ByteArray = new ByteArray();
/** Creates an empty IndexData instance with the given capacity (in indices).
*
* @param initialCapacity
*
* The initial capacity affects just the way the internal ByteArray is allocated, not the
* numIndices
value, which will always be zero when the constructor returns.
* The reason for this behavior is the peculiar way in which ByteArrays organize their
* memory:
*
* The first time you set the length of a ByteArray, it will adhere to that:
* a ByteArray with length 20 will take up 20 bytes (plus some overhead). When you change
* it to a smaller length, it will stick to the original value, e.g. with a length of 10
* it will still take up 20 bytes. However, now comes the weird part: change it to
* anything above the original length, and it will allocate 4096 bytes!
*
* Thus, be sure to always make a generous educated guess, depending on the planned
* usage of your IndexData instances.
*/
public function IndexData(initialCapacity:int=48)
{
_numIndices = 0;
_initialCapacity = initialCapacity;
_useQuadLayout = true;
}
/** Explicitly frees up the memory used by the ByteArray, thus removing all indices.
* Quad layout will be restored (until adding data violating that layout). */
public function clear():void
{
if (_rawData)
_rawData.clear();
_numIndices = 0;
_useQuadLayout = true;
}
/** Creates a duplicate of the IndexData object. */
public function clone():IndexData
{
var clone:IndexData = new IndexData(_numIndices);
if (!_useQuadLayout)
{
clone.switchToGenericData();
clone._rawData.writeBytes(_rawData);
}
clone._numIndices = _numIndices;
return clone;
}
/** Copies the index data (or a range of it, defined by 'indexID' and 'numIndices')
* of this instance to another IndexData object, starting at a certain target index.
* If the target is not big enough, it will grow to fit all the new indices.
*
* By passing a non-zero offset
, you can raise all copied indices
* by that value in the target object.
*/
public function copyTo(target:IndexData, targetIndexID:int=0, offset:int=0,
indexID:int=0, numIndices:int=-1):void
{
if (numIndices < 0 || indexID + numIndices > _numIndices)
numIndices = _numIndices - indexID;
var sourceData:ByteArray, targetData:ByteArray;
var newNumIndices:int = targetIndexID + numIndices;
if (target._numIndices < newNumIndices)
{
target._numIndices = newNumIndices;
ensureQuadDataCapacity(newNumIndices);
}
if (_useQuadLayout)
{
if (target._useQuadLayout)
{
var keepsQuadLayout:Boolean = true;
var distance:int = targetIndexID - indexID;
var distanceInQuads:int = distance / 6;
var offsetInQuads:int = offset / 4;
// This code is executed very often. If it turns out that both IndexData
// instances use a quad layout, we don't need to do anything here.
//
// When "distance / 6 == offset / 4 && distance % 6 == 0 && offset % 4 == 0",
// the copy operation preserves the quad layout. In that case, we can exit
// right away. The code below is a little more complex, though, to avoid the
// (surprisingly costly) mod-operations.
if (distanceInQuads == offsetInQuads && (offset & 3) == 0 &&
distanceInQuads * 6 == distance)
{
keepsQuadLayout = true;
}
else if (numIndices > 2)
{
keepsQuadLayout = false;
}
else
{
for (var i:int=0; i offset % 4 == 0
{
indexID += 6 * offset / 4;
offset = 0;
ensureQuadDataCapacity(indexID + numIndices);
}
}
else
{
if (target._useQuadLayout)
target.switchToGenericData();
sourceData = _rawData;
targetData = target._rawData;
}
targetData.position = targetIndexID * INDEX_SIZE;
if (offset == 0)
targetData.writeBytes(sourceData, indexID * INDEX_SIZE, numIndices * INDEX_SIZE);
else
{
sourceData.position = indexID * INDEX_SIZE;
// by reading junks of 32 instead of 16 bits, we can spare half the time
while (numIndices > 1)
{
var indexAB:uint = sourceData.readUnsignedInt();
var indexA:uint = ((indexAB & 0xffff0000) >> 16) + offset;
var indexB:uint = ((indexAB & 0x0000ffff) ) + offset;
targetData.writeUnsignedInt(indexA << 16 | indexB);
numIndices -= 2;
}
if (numIndices)
targetData.writeShort(sourceData.readUnsignedShort() + offset);
}
}
/** Sets an index at the specified position. */
public function setIndex(indexID:int, index:uint):void
{
if (_numIndices < indexID + 1)
numIndices = indexID + 1;
if (_useQuadLayout)
{
if (getBasicQuadIndexAt(indexID) == index) return;
else switchToGenericData();
}
_rawData.position = indexID * INDEX_SIZE;
_rawData.writeShort(index);
}
/** Reads the index from the specified position. */
public function getIndex(indexID:int):int
{
if (_useQuadLayout)
{
if (indexID < _numIndices)
return getBasicQuadIndexAt(indexID);
else
throw new EOFError();
}
else
{
_rawData.position = indexID * INDEX_SIZE;
return _rawData.readUnsignedShort();
}
}
/** Adds an offset to all indices in the specified range. */
public function offsetIndices(offset:int, indexID:int=0, numIndices:int=-1):void
{
if (numIndices < 0 || indexID + numIndices > _numIndices)
numIndices = _numIndices - indexID;
var endIndex:int = indexID + numIndices;
for (var i:int=indexID; i
* a - b
* | / |
* c - d
*
*
* To make sure the indices will follow the basic quad layout, make sure each
* parameter increments the one before it (e.g. 0, 1, 2, 3
).
numIndices
indices. The array might grow, but it will never be
* made smaller. */
private function ensureQuadDataCapacity(numIndices:int):void
{
if (sQuadData.length >= numIndices * INDEX_SIZE) return;
var i:int;
var oldNumQuads:int = sQuadData.length / 12;
var newNumQuads:int = Math.ceil(numIndices / 6);
sQuadData.endian = Endian.LITTLE_ENDIAN;
sQuadData.position = sQuadData.length;
for (i = oldNumQuads; i < newNumQuads; ++i)
{
sQuadData.writeShort(4 * i);
sQuadData.writeShort(4 * i + 1);
sQuadData.writeShort(4 * i + 2);
sQuadData.writeShort(4 * i + 1);
sQuadData.writeShort(4 * i + 3);
sQuadData.writeShort(4 * i + 2);
}
}
/** Returns the index that's expected at this position if following basic quad layout. */
private static function getBasicQuadIndexAt(indexID:int):int
{
var quadID:int = indexID / 6;
var posInQuad:int = indexID - quadID * 6; // => indexID % 6
var offset:int;
if (posInQuad == 0) offset = 0;
else if (posInQuad == 1 || posInQuad == 3) offset = 1;
else if (posInQuad == 2 || posInQuad == 5) offset = 2;
else offset = 3;
return quadID * 4 + offset;
}
// IndexBuffer helpers
/** Creates an index buffer object with the right size to fit the complete data.
* Optionally, the current data is uploaded right away. */
public function createIndexBuffer(upload:Boolean=false,
bufferUsage:String="staticDraw"):IndexBuffer3D
{
var context:Context3D = Starling.context;
if (context == null) throw new MissingContextError();
if (_numIndices == 0) return null;
var buffer:IndexBuffer3D = context.createIndexBuffer(_numIndices, bufferUsage);
if (upload) uploadToIndexBuffer(buffer);
return buffer;
}
/** Uploads the complete data (or a section of it) to the given index buffer. */
public function uploadToIndexBuffer(buffer:IndexBuffer3D, indexID:int=0, numIndices:int=-1):void
{
if (numIndices < 0 || indexID + numIndices > _numIndices)
numIndices = _numIndices - indexID;
if (numIndices > 0)
buffer.uploadFromByteArray(rawData, 0, indexID, numIndices);
}
/** Optimizes the ByteArray so that it has exactly the required capacity, without
* wasting any memory. If your IndexData object grows larger than the initial capacity
* you passed to the constructor, call this method to avoid the 4k memory problem. */
public function trim():void
{
if (_useQuadLayout) return;
sTrimData.length = _rawData.length;
sTrimData.position = 0;
sTrimData.writeBytes(_rawData);
_rawData.clear();
_rawData.length = sTrimData.length;
_rawData.writeBytes(sTrimData);
sTrimData.clear();
}
// properties
/** The total number of indices.
*
* If this instance contains only standardized, basic quad indices, resizing * will automatically fill up with appropriate quad indices. Otherwise, it will fill * up with zeroes.
* *If you set the number of indices to zero, quad layout will be restored.
*/ public function get numIndices():int { return _numIndices; } public function set numIndices(value:int):void { if (value != _numIndices) { if (_useQuadLayout) ensureQuadDataCapacity(value); else _rawData.length = value * INDEX_SIZE; if (value == 0) _useQuadLayout = true; _numIndices = value; } } /** The number of triangles that can be spawned up with the contained indices. * (In other words: the number of indices divided by three.) */ public function get numTriangles():int { return _numIndices / 3; } public function set numTriangles(value:int):void { numIndices = value * 3; } /** The number of quads that can be spawned up with the contained indices. * (In other words: the number of triangles divided by two.) */ public function get numQuads():int { return _numIndices / 6; } public function set numQuads(value:int):void { numIndices = value * 6; } /** The number of bytes required for each index value. */ public function get indexSizeInBytes():int { return INDEX_SIZE; } /** Indicates if all indices are following the basic quad layout. * *This property is automatically updated if an index is set to a value that violates
* basic quad layout. Once the layout was violated, the instance will always stay that
* way, even if you fix that violating value later. Only calling clear
or
* manually enabling the property will restore quad layout.
If you enable this property on an instance, all indices will immediately be * replaced with indices following standard quad layout.
* *Please look at the class documentation for more information about that kind * of layout, and why it is important.
* * @default true */ public function get useQuadLayout():Boolean { return _useQuadLayout; } public function set useQuadLayout(value:Boolean):void { if (value != _useQuadLayout) { if (value) { ensureQuadDataCapacity(_numIndices); _rawData.length = 0; _useQuadLayout = true; } else switchToGenericData(); } } /** The raw index data; not a copy! Beware: the referenced ByteArray may change any time. * Never store a reference to it, and never modify its contents manually. */ public function get rawData():ByteArray { if (_useQuadLayout) return sQuadData; else return _rawData; } } }